def getLatticeParameters(self): """Extract lattice parameters after pwscf geometry optimization Returns a list of 6 parameters: A, B, C, cos(BC), cos(AC), cos(AB)""" from qelattice import QELattice # obtain lattice from PWSCF input file: lat = QELattice(fname=self.pwscfInput) pwscfOut = read_file(self.pwscfOutput) key_a_0 = find_key_from_string(pwscfOut, 'lattice parameter (a_0)') a_0 = float(string.split(pwscfOut[key_a_0])[4]) if lat.type == 'traditional': a_0 = a_0 * 0.529177249 # convert back to angstrom keyEnd = max( find_all_keys_from_marker_string(pwscfOut, '!', 'total energy')) keyCellPar = find_key_from_string_afterkey(pwscfOut, keyEnd, \ 'CELL_PARAMETERS (alat)') + 1 latticeVectors = [ [ float(valstr) * a_0 for valstr in string.split(pwscfOut[keyCellPar]) ], [ float(valstr) * a_0 for valstr in string.split(pwscfOut[keyCellPar + 1]) ], [ float(valstr) * a_0 for valstr in string.split(pwscfOut[keyCellPar + 2]) ] ] lat.setLatticeFromQEVectors(lat.ibrav, latticeVectors) return [lat.a, lat.b, lat.c, lat.cBC, lat.cAC, lat.cAB]
def reduce(self, ibrav = None): """ Reduces a structure instance with nonprimitive lattice ( i.e. solves for equivalent atomic positions) according to specified lattice type (ibrav). Each ibrav number corresponds to a specific primitive lattice (see QE documentation). """ ib = self.lattice.ibrav if ibrav != None: ib = ibrav a = self.lattice.matter().a b = self.lattice.matter().b c = self.lattice.matter().c cAB = cosd(self.lattice.matter().gamma) cBC = cosd(self.lattice.matter().alpha) cAC = cosd(self.lattice.matter().beta) if ib > 0: qeLattice = QELattice(ibrav = ib, a = a, b = b, c = c, cBC = cBC, \ cAC = cAC, cAB = cAB) else: qeLattice = QELattice(ibrav = ib, base = self.lattice.base) qeLattice._qeInput = self._qeInput reducedStructure = QEStructure(self) reducedStructure.placeInLattice( qeLattice ) # collect atoms that are at equivalent position to some previous atom duplicates = set([a1 for i0, a0 in enumerate(reducedStructure) for a1 in reducedStructure[i0+1:] if a0.symbol == a1.symbol and equalPositions(a0.xyz, a1.xyz, eps=1e-4)]) # Filter out duplicate atoms. Use slice assignment so that # reducedStructure is not replaced with a list. self.lattice = qeLattice self[:] = [a for a in reducedStructure if not a in duplicates] self._qeInput.structure = self return
def _setStructureFromMatterStructure(self, structure, massList = [], psList = [], ibrav = 0): """ structure - matter.Structure object ibrav - Lattice index psList - list of strings with potential names matterStructure object will be modified with reduced atomic positions """ matterLattice = structure.lattice qeLattice = QELattice(ibrav = 0, base = matterLattice.base) qeLattice.a = 1.889725989*qeLattice.a qeLattice._qeInput = self._qeInput self.lattice = qeLattice self.lattice.type = 'generic cubic' atomNames = [] for a in structure: if a.symbol not in atomNames: atomNames.append(a.symbol) atomicSpecies = {} for i, elem in enumerate(atomNames): if len(massList) - 1 < i: mass = 0 else: mass = massList[i] if len(psList) - 1 < i: ps = '' else: ps = psList[i] atomicSpecies[elem] = (mass, ps) self[:] = [] for atom in structure: self.addNewAtom(atype = atom.symbol, xyz = atom.xyz, \ mass = atomicSpecies[atom.symbol][0], \ potential = atomicSpecies[atom.symbol][1],\ lattice = self.lattice, optConstraint = [])
def __init__(self, atoms = [], lattice = None, filename = None, qeInput = None): """ atoms -- list of QEAtom atom instances or a QEStructure object lattice -- QELattice object filename -- filename QE input file qeInput -- pointer to a PWInput parsing object. If not None, its PWInput.structure and PWInput.structure.lattice will be reset to the current instance of the structure """ Structure.__init__(self) self.formatString = '%# .8f %# .8f %# .8f' self._atomicPositionsType = 'crystal' self._qeInput = qeInput self.lattice = QELattice() self.lattice._qeInput = self._qeInput if lattice != None: if self.lattice._qeInput != None: self._qeInput = self.lattice._qeInput else: self.lattice._qeInput = self._qeInput self.lattice = QELattice( lattice = lattice ) # CP and PW inputs are compatible from pwinput import PWInput from cpinput import CPInput if isinstance( atoms, PWInput) or isinstance( atoms, CPInput): qeInput = atoms elif isinstance( atoms, QEStructure): stru = atoms self.__dict__.update( stru.__dict__ ) # deep copy of the lattice will deep copy PWInput as well self.lattice = QELattice(lattice = stru.lattice) self._qeInput = self.lattice._qeInput self[:] = stru else: if self.lattice == None: raise "Lattice must be provided" self[:] = atoms if filename != None: qeInput = PWInput(filename = filename) qeInput.parse() self.parseInput(qeInput) if qeInput != None: self.lattice._qeInput = qeInput self._qeInput = qeInput if self.lattice._qeInput != None: self.lattice._qeInput.structure = self self.lattice._qeInput.structure.lattice = self.lattice return
def _setReducedStructureFromMatterStructure(self, structure, ibrav, massList = [], psList = []): """ structure - matter.Structure object ibrav - Lattice index psList - list of strings with potential names matterStructure object will be modified with reduced atomic positions """ import copy matterLattice = copy.deepcopy(structure.lattice) # there is a big problem with what Nikolay is doing because he is initializing QELattice with abcalbega which # leaves room for errors in how the basis vectors are chosen compared to the old lattice a = matterLattice.a b = matterLattice.b c = matterLattice.c cAB = cosd(matterLattice.gamma) cBC = cosd(matterLattice.alpha) cAC = cosd(matterLattice.beta) qeLattice = QELattice(ibrav = ibrav, a = a, b = b, c = c, cBC = cBC, \ cAC = cAC, cAB = cAB) #what he should do is initialize the vectors of QELattice with the vectors of matterLattice # qeLattice._qeInput = self._qeInput self.lattice = qeLattice # make a deep copy: reducedStructure = Structure(atoms = structure) reducedStructure.placeInLattice(Lattice(base=qeLattice.matter().base)) # collect atoms that are at equivalent position to some previous atom duplicates = set([a1 for i0, a0 in enumerate(reducedStructure) for a1 in reducedStructure[i0+1:] if a0.symbol==a1.symbol and equalPositions(a0.xyz, a1.xyz, eps=1e-4)]) # Filter out duplicate atoms. Use slice assignment so that # reducedStructure is not replaced with a list. reducedStructure[:] = [a for a in reducedStructure if not a in duplicates] atomNames = [] for a in reducedStructure: if a.symbol not in atomNames: atomNames.append(a.symbol) atomicSpecies = {} for i, elem in enumerate(atomNames): if len(massList) - 1 < i: mass = 0 else: mass = massList[i] if len(psList) - 1 < i: ps = '' else: ps = psList[i] atomicSpecies[elem] = (mass, ps) self[:] = [] # convert to bohr units self.lattice.setLattice(ibrav, self.lattice.a*1.889725989, \ self.lattice.b*1.889725989, self.lattice.c*1.889725989) for atom in reducedStructure: self.addNewAtom(atype = atom.symbol, xyz = atom.xyz, \ mass = atomicSpecies[atom.symbol][0], \ potential = atomicSpecies[atom.symbol][1],\ lattice = self.lattice, optConstraint = [])
def __init__(self, qeConf): """the structure is initialized from PWSCF config file 'lattice' and 'structure' are automatically updated""" self.filename = qeConf.filename self.atomicSpecies = OrderedDict() self.formatString = '%# .8f %# .8f %# .8f' # optConstraints three 1/0 for each coordinate of each atom self.optConstraints = [] self.qeConf = qeConf self.lattice = QELattice() self.structure = Structure(lattice=self.lattice.diffpy()) self.nat = None self.ntyp = None
def setStructureFromDiffpyStructure(self, structure, massList = [], psList = [], ibrav = 0): """ structure - diffpy.Structure object ibrav - Lattice index psList - list of strings with pseudopotential names diffpyStructure object will be modified with reduced atomic positions """ diffpyLattice = structure.lattice self.structure = structure #set lattice and convert to bohr units qeLattice = QELattice(ibrav = 0, a = 1.889725989, base = diffpyLattice.base) self.lattice = qeLattice self.lattice.type = 'generic cubic' atomNames = [] for a in structure: if self._element(a) not in atomNames: atomNames.append(self._element(a)) #print atomNames #print len(massList) for i, elem in enumerate(atomNames): if len(massList) - 1 < i: mass = 0 else: mass = massList[i] if len(psList) - 1 < i: ps = '' else: ps = psList[i] self.atomicSpecies[elem] = AtomicSpecies(elem, mass, ps) for atom in structure: self.optConstraints.append([]) # for i, atom in enumerate(structure): # elem = self._element(atom) # if len(massList) - 1 < i: # mass = 0 # else: # mass = massList[i] # if len(psList) - 1 < i: # ps = '' # else: # ps = psList[i] # self.atomicSpecies[elem] = AtomicSpecies(elem, mass, ps) # self.optConstraints.append([]) # for atom, mass, ps in zip(structure, massList, psList): # elem = self._element(atom) # self.atomicSpecies[elem] = AtomicSpecies(elem, mass, ps) # self.optConstraints.append([]) self.nat = len(structure) self.ntyp = len(self.atomicSpecies)
def getLatticeParameters(self): """Extract lattice parameters after pwscf geometry optimization Returns a list of 6 parameters: A, B, C, cos(BC), cos(AC), cos(AB)""" from qelattice import QELattice # obtain lattice from PWSCF input file: lat = QELattice(fname = self.pwscfInput) pwscfOut = read_file(self.pwscfOutput) key_a_0 = find_key_from_string(pwscfOut, 'lattice parameter (a_0)') a_0 = float( string.split( pwscfOut[key_a_0] )[4] ) if lat.type == 'traditional': a_0 = a_0*0.529177249 # convert back to angstrom keyEnd = max( find_all_keys_from_marker_string(pwscfOut, '!', 'total energy') ) keyCellPar = find_key_from_string_afterkey(pwscfOut, keyEnd, \ 'CELL_PARAMETERS (alat)') + 1 latticeVectors = [[float(valstr)*a_0 for valstr in string.split( pwscfOut[keyCellPar] ) ], [ float(valstr)*a_0 for valstr in string.split( pwscfOut[keyCellPar+1] ) ], [ float(valstr)*a_0 for valstr in string.split( pwscfOut[keyCellPar+2] ) ]] lat.setLatticeFromQEVectors(lat.ibrav, latticeVectors) return [lat.a, lat.b, lat.c, lat.cBC ,lat.cAC , lat.cAB]
def reduce(self, ibrav=None): """ Reduces a structure instance with nonprimitive lattice ( i.e. solves for equivalent atomic positions) according to specified lattice type (ibrav). Each ibrav number corresponds to a specific primitive lattice (see QE documentation). """ ib = self.lattice.ibrav if ibrav != None: ib = ibrav a = self.lattice.diffpy().a b = self.lattice.diffpy().b c = self.lattice.diffpy().c cAB = cosd(self.lattice.diffpy().gamma) cBC = cosd(self.lattice.diffpy().alpha) cAC = cosd(self.lattice.diffpy().beta) if ib > 0: qeLattice = QELattice(ibrav = ib, a = a, b = b, c = c, cBC = cBC, \ cAC = cAC, cAB = cAB) else: qeLattice = QELattice(ibrav=ib, base=self.lattice.base) qeLattice._qeInput = self._qeInput reducedStructure = QEStructure(self) reducedStructure.placeInLattice(qeLattice) # collect atoms that are at equivalent position to some previous atom duplicates = set([ a1 for i0, a0 in enumerate(reducedStructure) for a1 in reducedStructure[i0 + 1:] if a0.element == a1.element and equalPositions(a0.xyz, a1.xyz, eps=1e-4) ]) # Filter out duplicate atoms. Use slice assignment so that # reducedStructure is not replaced with a list. self.lattice = qeLattice self[:] = [a for a in reducedStructure if not a in duplicates] self._qeInput.structure = self return
def _setStructureFromDiffpyStructure(self, structure, massList=[], psList=[], ibrav=0): """ structure - diffpy.Structure object ibrav - Lattice index psList - list of strings with potential names diffpyStructure object will be modified with reduced atomic positions """ diffpyLattice = structure.lattice qeLattice = QELattice(ibrav=0, base=diffpyLattice.base) qeLattice.a = 1.889725989 * qeLattice.a qeLattice._qeInput = self._qeInput self.lattice = qeLattice self.lattice.type = 'generic cubic' atomNames = [] for a in structure: if self._element(a) not in atomNames: atomNames.append(self._element(a)) atomicSpecies = {} for i, elem in enumerate(atomNames): if len(massList) - 1 < i: mass = 0 else: mass = massList[i] if len(psList) - 1 < i: ps = '' else: ps = psList[i] atomicSpecies[elem] = (mass, ps) self[:] = [] for atom in structure: elem = self._element(atom) self.addNewAtom(atype = elem, xyz = atom.xyz, \ mass = atomicSpecies[elem][0], \ potential = atomicSpecies[elem][1],\ lattice = self.lattice, optConstraint = [])
def setStructureFromPWSCF(self): """ Loads structure from PWSCF config file""" self.lattice = QELattice(fname=self.filename) self.structure = Structure(lattice=self.lattice.diffpy()) self.nat = int(self.qeConf.namelist('system').param('nat')) self.ntyp = int(self.qeConf.namelist('system').param('ntyp')) atomicLines = self.qeConf.card('atomic_positions').getLines() self.atomicPositionsType = self.qeConf.card( 'atomic_positions').argument() if self.atomicPositionsType == 'bohr' or self.atomicPositionsType == 'angstrom': raise NotImplementedError if self.atomicPositionsType == None: self.atomicPositionsType = 'alat' for line in atomicLines: if '!' not in line: words = line.split() coords = [float(w) for w in words[1:4]] constraint = [] if len(words) > 4: constraint = [int(c) for c in words[4:7]] self.optConstraints.append(numpy.array(constraint, dtype=int)) atomSymbol = words[0] if self.atomicPositionsType == 'alat': coords = self.lattice.diffpy().fractional( numpy.array(coords[0:3]) * self.lattice.a0) if self.atomicPositionsType == 'crystal': coords = numpy.array(coords[0:3]) self.structure.addNewAtom(atomSymbol, xyz=numpy.array(coords[0:3])) # parse mass ATOMIC_SPECIES section: atomicSpeciesLines = self.qeConf.card( 'atomic_species').getLines() for line in atomicSpeciesLines: if '!' not in line: atomicSpeciesWords = line.split() element = atomicSpeciesWords[0] mass = float(atomicSpeciesWords[1]) ps = atomicSpeciesWords[2] self.atomicSpecies[element] = AtomicSpecies( element, mass, ps)
def __init__(self, atype=None, xyz=None, mass = None, \ potential = None, lattice=None, optConstraint = None, name=None): """Create atom of a specified type at given lattice coordinates. Atom(a) creates a copy of Atom instance a. atype -- element symbol string or Atom instance xyz -- fractional(crystal) coordinates name -- atom label mass -- atom mass potential -- pseudopotential file name lattice -- QE coordinate system for fractional coordinates optConstraint -- list of up to three constraints for each coordinate for QE geometry optimization (0 or 1) """ # declare data members self.element = None self._xyz = numpy.zeros(3, dtype=float) self.name = '' self._mass = 0 self._potential = '' self.lattice = QELattice() from pwinput import PWInput self.lattice._qeInput = PWInput() self._optConstraint = numpy.array([], dtype = int) # assign them as needed if isinstance(atype, QEAtom): atype_dup = atype.__copy__() self.__dict__.update(atype_dup.__dict__) else: self.element = atype # take care of remaining arguments if xyz is not None: self._xyz[:] = xyz if name is not None: self.name = name if mass is not None: self._mass = mass if potential is not None: self._potential = potential if lattice is not None: self.lattice = lattice if optConstraint is not None: self._optConstraint = \ numpy.array(optConstraint, dtype = int) return
def __init__(self, qeConf): """the structure is initialized from PWSCF config file 'lattice' and 'structure' are automatically updated""" self.filename = qeConf.filename self.atomicSpecies = OrderedDict() self.formatString = "%# .8f %# .8f %# .8f" # optConstraints three 1/0 for each coordinate of each atom self.optConstraints = [] self.qeConf = qeConf self.lattice = QELattice() self.structure = Structure(lattice=self.lattice.diffpy()) self.nat = None self.ntyp = None
def __init__(self, atoms=[], lattice=None, filename=None, qeInput=None): """ atoms -- list of QEAtom atom instances or a QEStructure object lattice -- QELattice object filename -- filename QE input file qeInput -- pointer to a PWInput parsing object. If not None, its PWInput.structure and PWInput.structure.lattice will be reset to the current instance of the structure """ Structure.__init__(self) self.formatString = '%# .8f %# .8f %# .8f' self._atomicPositionsType = 'crystal' self._qeInput = qeInput self.lattice = QELattice() self.lattice._qeInput = self._qeInput if lattice != None: if self.lattice._qeInput != None: self._qeInput = self.lattice._qeInput else: self.lattice._qeInput = self._qeInput self.lattice = QELattice(lattice=lattice) # CP and PW inputs are compatible from pwinput import PWInput from cpinput import CPInput if isinstance(atoms, PWInput) or isinstance(atoms, CPInput): qeInput = atoms elif isinstance(atoms, QEStructure): stru = atoms self.__dict__.update(stru.__dict__) # deep copy of the lattice will deep copy PWInput as well self.lattice = QELattice(lattice=stru.lattice) self._qeInput = self.lattice._qeInput self[:] = stru else: if self.lattice == None: raise "Lattice must be provided" self[:] = atoms if filename != None: qeInput = PWInput(filename=filename) qeInput.parse() self.parseInput(qeInput) if qeInput != None: self.lattice._qeInput = qeInput self._qeInput = qeInput if self.lattice._qeInput != None: self.lattice._qeInput.structure = self self.lattice._qeInput.structure.lattice = self.lattice return
def setStructureFromQEInput(self): """ Loads structure from PWSCF config file""" self.atomicSpecies = OrderedDict() self.lattice = QELattice(qeConf = self.qeConf) self.structure = Structure(lattice = self.lattice.matter()) self.nat = int(self.qeConf.namelist('system').param('nat')) self.ntyp = int(self.qeConf.namelist('system').param('ntyp')) atomicLines = self.qeConf.card('atomic_positions').lines() self.atomicPositionsType = self.qeConf.card('atomic_positions').arg() if self.atomicPositionsType == 'bohr' or self.atomicPositionsType == 'angstrom': raise NotImplementedError if self.atomicPositionsType == None: self.atomicPositionsType = 'alat' for line in atomicLines: if '!' not in line: words = line.split() coords = [float(w) for w in words[1:4]] constraint = [] if len(words) > 4: constraint = [int(c) for c in words[4:7]] self.optConstraints.append(numpy.array(constraint, dtype = int)) atomSymbol = words[0] if self.atomicPositionsType == 'alat': coords = self.lattice.matter().fractional(numpy.array(coords[0:3])*self.lattice.a0) if self.atomicPositionsType == 'crystal': coords = numpy.array(coords[0:3]) self.structure.addNewAtom(atomSymbol, xyz = numpy.array(coords[0:3])) # parse mass ATOMIC_SPECIES section: atomicSpeciesLines = self.qeConf.card('atomic_species').lines() for line in atomicSpeciesLines: if '!' not in line: atomicSpeciesWords = line.split() element = atomicSpeciesWords[0] mass = float(atomicSpeciesWords[1]) ps = atomicSpeciesWords[2] self.atomicSpecies[element] = AtomicSpecies(element, mass, ps)
def setStructureFromPWOutput(self, pwscfOutputFile): """ Loads structure from PWSCF output file. If there was geometry optimization (relax or vc-relax), the structure will be reinitialized from the last step of the optimization """ file = open(pwscfOutputFile) pwscfOut = file.readlines() pseudoList = [] atomList = [] massList = [] self.atomicSpecies = OrderedDict() self.atomicPositionsType = 'alat' # parse beginning: for i, line in enumerate(pwscfOut): if 'lattice parameter (a_0)' in line: a_0 = float(line.split()[4]) if 'bravais-lattice index' in line: ibrav = int(line.split('=')[1]) if 'number of atoms/cell' in line: self.nat = int(line.split('=')[1]) if 'number of atomic types' in line: self.ntyp = int(line.split('=')[1]) if 'PseudoPot.' in line: pseudoList.append(line.split('read from file')[1].strip()) if 'atomic species valence mass pseudopotential' in line: for j in range(self.ntyp): atomList.append(pwscfOut[i + j + 1].split()[0]) massList.append(float(pwscfOut[i + j + 1].split()[2])) if 'crystal axes: (cart. coord. in units of a_0)' in line: latticeVectors = [ [float(f) * a_0 for f in pwscfOut[i + 1].split()[3:6]], [float(f) * a_0 for f in pwscfOut[i + 2].split()[3:6]], [float(f) * a_0 for f in pwscfOut[i + 3].split()[3:6]] ] self.lattice = QELattice(ibrav=ibrav, base=latticeVectors) if 'site n. atom positions (a_0 units)' in line: self.structure = Structure(lattice=self.lattice.matter()) for n in range(self.nat): words = pwscfOut[i + n + 1].split() atomSymbol = words[1] coords = [float(w) for w in words[6:9]] constraint = [] self.optConstraints.append( numpy.array(constraint, dtype=int)) #print numpy.array(coords[0:3])*a_0 coords = self.lattice.matter().fractional( numpy.array(coords[0:3]) * a_0) self.structure.addNewAtom(atomSymbol, xyz=numpy.array(coords[0:3])) for a, m, p in zip(atomList, massList, pseudoList): self.atomicSpecies[a] = AtomicSpecies(a, m, p) #print 'Input structure from output file: ', self.toString() #Parse end: # Find all geometry optimization steps posList = [ i for i, line in enumerate(pwscfOut) if '! total energy' in line ] lastSection = pwscfOut[posList[-1]:] for i, line in enumerate(lastSection): if 'CELL_PARAMETERS (alat)' in line: latticeVectors = [ [float(f) * a_0 for f in lastSection[i + 1].split()], [float(f) * a_0 for f in lastSection[i + 2].split()], [float(f) * a_0 for f in lastSection[i + 3].split()] ] self.lattice = QELattice(ibrav=ibrav, base=latticeVectors) print self.lattice.matter().base if 'ATOMIC_POSITIONS (alat)' in line: self.structure = Structure(lattice=self.lattice.matter()) for n in range(self.nat): words = lastSection[i + n + 1].split() atomSymbol = words[0] coords = [float(w) for w in words[1:4]] constraint = [] if len(words) > 4: constraint = [int(c) for c in words[4:7]] self.optConstraints.append( numpy.array(constraint, dtype=int)) #print numpy.array(coords[0:3])*a_0 coords = self.lattice.matter().fractional( numpy.array(coords[0:3]) * a_0) self.structure.addNewAtom(atomSymbol, xyz=numpy.array(coords[0:3]))
def _setReducedStructureFromDiffpyStructure(self, structure, ibrav, massList=[], psList=[]): """ structure - diffpy.Structure object ibrav - Lattice index psList - list of strings with potential names diffpyStructure object will be modified with reduced atomic positions """ import copy diffpyLattice = copy.deepcopy(structure.lattice) a = diffpyLattice.a b = diffpyLattice.b c = diffpyLattice.c cAB = cosd(diffpyLattice.gamma) cBC = cosd(diffpyLattice.alpha) cAC = cosd(diffpyLattice.beta) qeLattice = QELattice(ibrav = ibrav, a = a, b = b, c = c, cBC = cBC, \ cAC = cAC, cAB = cAB) qeLattice._qeInput = self._qeInput self.lattice = qeLattice # make a deep copy: reducedStructure = Structure(atoms=structure) reducedStructure.placeInLattice(Lattice(base=qeLattice.diffpy().base)) # collect atoms that are at equivalent position to some previous atom duplicates = set([ a1 for i0, a0 in enumerate(reducedStructure) for a1 in reducedStructure[i0 + 1:] if self._element(a0) == self._element(a1) and equalPositions(a0.xyz, a1.xyz, eps=1e-4) ]) # Filter out duplicate atoms. Use slice assignment so that # reducedStructure is not replaced with a list. reducedStructure[:] = [ a for a in reducedStructure if not a in duplicates ] atomNames = [] for a in reducedStructure: if self._element(a) not in atomNames: atomNames.append(self._element(a)) atomicSpecies = {} for i, elem in enumerate(atomNames): if len(massList) - 1 < i: mass = 0 else: mass = massList[i] if len(psList) - 1 < i: ps = '' else: ps = psList[i] atomicSpecies[elem] = (mass, ps) self[:] = [] # convert to bohr units self.lattice.setLattice(ibrav, self.lattice.a*1.889725989, \ self.lattice.b*1.889725989, self.lattice.c*1.889725989) for atom in reducedStructure: elem = self._element(atom) self.addNewAtom(atype = elem, xyz = atom.xyz, \ mass = atomicSpecies[elem][0], \ potential = atomicSpecies[elem][1],\ lattice = self.lattice, optConstraint = [])
def _setReducedStructureFromMatterStructure(self, structure, ibrav, massList=[], psList=[]): """ structure - matter.Structure object ibrav - Lattice index psList - list of strings with potential names matterStructure object will be modified with reduced atomic positions """ import copy matterLattice = copy.deepcopy(structure.lattice) # there is a big problem with what Nikolay is doing because he is initializing QELattice with abcalbega which # leaves room for errors in how the basis vectors are chosen compared to the old lattice a = matterLattice.a b = matterLattice.b c = matterLattice.c cAB = cosd(matterLattice.gamma) cBC = cosd(matterLattice.alpha) cAC = cosd(matterLattice.beta) qeLattice = QELattice(ibrav = ibrav, a = a, b = b, c = c, cBC = cBC, \ cAC = cAC, cAB = cAB) #what he should do is initialize the vectors of QELattice with the vectors of matterLattice # qeLattice._qeInput = self._qeInput self.lattice = qeLattice # make a deep copy: reducedStructure = Structure(atoms=structure) reducedStructure.placeInLattice(Lattice(base=qeLattice.matter().base)) # collect atoms that are at equivalent position to some previous atom duplicates = set([ a1 for i0, a0 in enumerate(reducedStructure) for a1 in reducedStructure[i0 + 1:] if a0.symbol == a1.symbol and equalPositions(a0.xyz, a1.xyz, eps=1e-4) ]) # Filter out duplicate atoms. Use slice assignment so that # reducedStructure is not replaced with a list. reducedStructure[:] = [ a for a in reducedStructure if not a in duplicates ] atomNames = [] for a in reducedStructure: if a.symbol not in atomNames: atomNames.append(a.symbol) atomicSpecies = {} for i, elem in enumerate(atomNames): if len(massList) - 1 < i: mass = 0 else: mass = massList[i] if len(psList) - 1 < i: ps = '' else: ps = psList[i] atomicSpecies[elem] = (mass, ps) self[:] = [] # convert to bohr units self.lattice.setLattice(ibrav, self.lattice.a*1.889725989, \ self.lattice.b*1.889725989, self.lattice.c*1.889725989) for atom in reducedStructure: self.addNewAtom(atype = atom.symbol, xyz = atom.xyz, \ mass = atomicSpecies[atom.symbol][0], \ potential = atomicSpecies[atom.symbol][1],\ lattice = self.lattice, optConstraint = [])
def setReducedStructureFromDiffpyStructure(self, structure, ibrav, massList = [], psList = []): """ structure - diffpy.Structure object ibrav - Lattice index psList - list of strings with pseudopotential names diffpyStructure object will be modified with reduced atomic positions """ #self.atomicSpecies = OrderedDict() #self.optConstraints = [] #self.atomicPositionsType = 'crystal' diffpyLattice = structure.lattice a = diffpyLattice.a b = diffpyLattice.b c = diffpyLattice.c cAB = cosd(diffpyLattice.gamma) cBC = cosd(diffpyLattice.alpha) cAC = cosd(diffpyLattice.beta) qeLattice = QELattice(ibrav = ibrav, a = a, b = b, c = c, cBC = cBC, \ cAC = cAC, cAB = cAB) self.lattice = qeLattice # make a deep copy (does not wok now) #reducedStructure = Structure(diffpyStructure) reducedStructure = structure reducedStructure.placeInLattice(Lattice(base=qeLattice.diffpy().base)) # collect atoms that are at equivalent position to some previous atom duplicates = set([a1 for i0, a0 in enumerate(reducedStructure) for a1 in reducedStructure[i0+1:] if self._element(a0) == self._element(a1) and equalPositions(a0.xyz, a1.xyz, eps=1e-4)]) # Filter out duplicate atoms. Use slice assignment so that # reducedStructure is not replaced with a list. reducedStructure[:] = [a for a in reducedStructure if not a in duplicates] self.structure = reducedStructure atomNames = [] for a in reducedStructure: if self._element(a) not in atomNames: atomNames.append(self._element(a)) #print atomNames #print len(massList) for i, elem in enumerate(atomNames): if len(massList) - 1 < i: mass = 0 else: mass = massList[i] if len(psList) - 1 < i: ps = '' else: ps = psList[i] #print mass, ps # atomDict[a] = # for i, atom in enumerate(reducedStructure): # elem = self._element(atom) # if len(massList) - 1 < i: # mass = 0 # else: # mass = massList[i] # if len(psList) - 1 < i: # ps = '' # else: # ps = psList[i] self.atomicSpecies[elem] = AtomicSpecies(elem, mass, ps) for atom in reducedStructure: self.optConstraints.append([]) # convert to bohr units self.lattice.setLattice(ibrav, self.lattice.a*1.889725989, \ self.lattice.b*1.889725989, self.lattice.c*1.889725989) self.nat = len(reducedStructure) self.ntyp = len(self.atomicSpecies)
class QEStructure(Structure): """ Parses and handles Quantum Espresso structure information. QEStructure is inherited from a diffpy.Structure, which is in turn inherited from a Python list. QEStructure is a list of QEAtom object instances. All list functionality is preserved. setitem and setslice methods are overloaded so that the lattice attribute of atoms get set to lattice. QEStructure, QELattice and QEAtom objects contain a hidden QEInput pointer to current Quantum Espresso parsing object. if QEInput.autoUpdate = True (default),any change in a property from QEStructure, QELattice, or QEAtom will automatically invoke QEInput.update(). In that case, QEInput.save() or QEInput.toString() will immediately yield updated QE input file or a string All properties are mutually synchronized. E.g. change in a lattice parameter will also affect other lattice parameters as well as atomic positions according to the lattice type (ibrav) All relevant methods from diffpy.Structure are redefined. QEStructure read only properties, automatically synchronized with current list of Atoms: nat -- "number of atoms" (integer) ntyp -- "number of atomic types" (integer) atomicSpecies -- OrderedDic of AtomicSpecies class instances (see AtomicSpecies class definition) Other properties: atomicPositionsType -- 'crystal' (default), 'alat', 'bohr', and 'angstrom' Note: This version of QEStructure does not support multiple images from QE input files """ def __init__(self, atoms=[], lattice=None, filename=None, qeInput=None): """ atoms -- list of QEAtom atom instances or a QEStructure object lattice -- QELattice object filename -- filename QE input file qeInput -- pointer to a PWInput parsing object. If not None, its PWInput.structure and PWInput.structure.lattice will be reset to the current instance of the structure """ Structure.__init__(self) self.formatString = '%# .8f %# .8f %# .8f' self._atomicPositionsType = 'crystal' self._qeInput = qeInput self.lattice = QELattice() self.lattice._qeInput = self._qeInput if lattice != None: if self.lattice._qeInput != None: self._qeInput = self.lattice._qeInput else: self.lattice._qeInput = self._qeInput self.lattice = QELattice(lattice=lattice) # CP and PW inputs are compatible from pwinput import PWInput from cpinput import CPInput if isinstance(atoms, PWInput) or isinstance(atoms, CPInput): qeInput = atoms elif isinstance(atoms, QEStructure): stru = atoms self.__dict__.update(stru.__dict__) # deep copy of the lattice will deep copy PWInput as well self.lattice = QELattice(lattice=stru.lattice) self._qeInput = self.lattice._qeInput self[:] = stru else: if self.lattice == None: raise "Lattice must be provided" self[:] = atoms if filename != None: qeInput = PWInput(filename=filename) qeInput.parse() self.parseInput(qeInput) if qeInput != None: self.lattice._qeInput = qeInput self._qeInput = qeInput if self.lattice._qeInput != None: self.lattice._qeInput.structure = self self.lattice._qeInput.structure.lattice = self.lattice return def addNewAtom(self, *args, **kwargs): """Add new Atom instance to the end of this Structure. All arguments are forwarded to Atom constructor. No return value. """ kwargs['lattice'] = self.lattice a = QEAtom(*args, **kwargs) list.append(self, a) self._uncache('labels') return def placeInLattice(self, new_lattice): """place structure into new_lattice coordinate system sets lattice to new_lattice and recalculate fractional coordinates of all atoms so their absolute positionls remain the same return self """ Tx = numpy.dot(self.lattice.diffpy().base, new_lattice.diffpy().recbase) Tu = numpy.dot(self.lattice.diffpy().normbase, new_lattice.diffpy().recnormbase) for a in self: a.xyz = numpy.dot(a.xyz, Tx) tmpInput = self.lattice._qeInput self.lattice = new_lattice self.lattice._qeInput = tmpInput self.lattice._qeInput.structure = self return self ############################################################################## # overloaded list methods - taken from diffpy.Structure ############################################################################## def append(self, a, copy=True): """Append atom to a structure and update its lattice attribute. a -- instance of QEAtom copy -- flag for appending a copy of a. When False, append a and update a.owner. No return value. """ self._uncache('labels') adup = copy and QEAtom(a) or a adup.lattice = self.lattice list.append(self, adup) if hasattr(self, '_qeInput') and self._qeInput != None: self._qeInput.update() return def insert(self, idx, a, copy=True): """Insert atom a before position idx in this Structure. idx -- position in atom list a -- instance of QEAtom copy -- flag for inserting a copy of a. When False, append a and update a.lattice. No return value. """ self._uncache('labels') adup = copy and QEAtom(a) or a adup.lattice = self.lattice list.insert(self, idx, adup) if hasattr(self, '_qeInput') and self._qeInput != None: self._qeInput.update() return def extend(self, atoms, copy=True): """Extend Structure by appending copies from a list of atoms. atoms -- list of QEAtom instances copy -- flag for extending with copies of QEAtom instances. When False extend with atoms and update their lattice attributes. No return value. """ self._uncache('labels') if copy: adups = [QEAtom(a) for a in atoms] else: adups = atoms for a in adups: a.lattice = self.lattice list.extend(self, adups) if hasattr(self, '_qeInput') and self._qeInput != None: self._qeInput.update() return def __setitem__(self, idx, a, copy=True): """Set idx-th atom to a. idx -- index of atom in this Structure a -- instance of QEAtom copy -- flag for setting to a copy of a. When False, set to a and update a.lattice. No return value. """ self._uncache('labels') adup = copy and QEAtom(a) or a adup.lattice = self.lattice list.__setitem__(self, idx, adup) if hasattr(self, '_qeInput') and self._qeInput != None: self._qeInput.update() return def __setslice__(self, lo, hi, atoms, copy=True): """Set Structure slice from lo to hi-1 to the sequence of atoms. lo -- low index for the slice hi -- high index of the slice atoms -- sequence of Atom instances copy -- flag for using copies of Atom instances. When False, set to existing instances and update their lattice attributes. No return value. """ self._uncache('labels') if copy: adups = [QEAtom(a) for a in atoms] else: adups = atoms for a in adups: a.lattice = self.lattice list.__setslice__(self, lo, hi, adups) if hasattr(self, '_qeInput') and self._qeInput != None: self._qeInput.update() return def _get_nat(self): return len(self) nat = property(_get_nat, doc="number of atoms") def _get_ntyp(self): return len(self.atomicSpecies) ntyp = property(_get_ntyp, doc="number of types") def _get_atomicPositionsType(self): return self._atomicPositionsType def _set_atomicPositionsType(self, value): self._atomicPositionsType = value self.lattice._qeInput.update() atomicPositionsType = property(_get_atomicPositionsType, \ _set_atomicPositionsType, \ doc ="type of atomic positions (crystal, alat ...)") def _get_atomicSpecies(self): atomicSpecies = OrderedDict() for a in self: atomicSpecies[a.element] = AtomicSpecies(element = a.element, \ mass = a.mass, potential = a.potential) return atomicSpecies atomicSpecies = property(_get_atomicSpecies, \ doc ="returns an ordered dictionary with atomic species' objects") def __str__(self): """simple string representation""" s = str(self.lattice) + '\n' if self.atomicPositionsType == 'alat': s = s + 'Atomic positions in units of lattice parametr "a":\n' if self.atomicPositionsType == 'crystal': s = s + 'Atomic positions in crystal coordinates:\n' for atom in self: constraint = atom.optConstraint if self.atomicPositionsType == 'alat': coords = self.lattice.diffpy().cartesian( atom.xyz) / self.lattice.a coords = self.formatString % (coords[0], coords[1], coords[2]) else: if self.atomicPositionsType == 'crystal': coords = self.formatString % (atom.xyz[0], atom.xyz[1], atom.xyz[2]) else: raise NonImplementedError s = s + '%-3s'%self._element(atom) + ' ' + coords + ' ' \ + str(constraint)[1:-1] + '\n' s = s + '\n' for element, specie in self.atomicSpecies.items(): s = s + specie.toString() + '\n' return s def atomLabels(self): labels = [] for l in self.atomicSpecies: labels.append(l) return labels def parseInput(self, qeInput): from qecalc.qetask.qeparser.qestructureparser.qestructureparser import QEStructureParser new_structure = QEStructureParser(qeInput).parseqeInput() self.__Init(new_structure) def __Init(self, structure): QEStructure.__init__(self) if structure is not None: self.__dict__.update(structure.__dict__) self.lattice.__dict__.update(structure.lattice.__dict__) self[:] = structure def read(self, filename, format='pwinput'): """Load structure from a file, any original data become lost. l filename -- file to be loaded format -- structure formats 'pwinput' ( pw.x input, default), 'pwoutput' (pw.x output), 'bratoms', 'cif', 'discus', 'pdb', 'pdffit', 'rawxyz', 'xcfg', 'xyz' Return instance of data Parser used to process file. This can be inspected for information related to particular format. """ from qecalc.qetask.qeparser.qestructureparser import parser_index if self._qeInput == None: from qecalc.qetask.qeparser.pwinput import PWInput self._qeInput = PWInput() self._qeInput.structure = self #self._qeInput.parse() if format in parser_index: module = __import__("qestructureparser.P_" + format, globals(), \ locals(), ['P_' + format], -1) parser = module.getParser(self._qeInput) new_structure = parser.parse(filename) else: diffpyStruct = Structure() parser = diffpyStruct.read(filename, format=format) new_structure = QEStructure(qeInput=self._qeInput) new_structure._setStructureFromDiffpyStructure(diffpyStruct, \ massList = [], psList = [], ibrav = 0) new_structure.lattice._qeInput.update(forceUpdate=True) self.__Init(new_structure) return parser def readStr(self, s, format='pwinput'): """Load structure from a string, any original data become lost. filename -- file to be loaded format -- structure formats 'pwinput' ( pw.x input, default), 'pwoutput' (pw.x output), 'bratoms', 'cif', 'discus', 'pdb', 'pdffit', 'rawxyz', 'xcfg', 'xyz' Return instance of data Parser used to process file. This can be inspected for information related to particular format. """ from qecalc.qetask.qeparser.qestructureparser import parser_index #from qecalc.qetask.qeparser.pwinput import PWInput if self._qeInput == None: from qecalc.qetask.qeparser.pwinput import PWInput self._qeInput = PWInput() self._qeInput.structure = self #if self._qeInput == None: # self._qeInput = PWInput() #self._qeInput.parse() if format in parser_index: module = __import__("qestructureparser.P_" + format, globals(), \ locals(), ['P_' + format], -1) parser = module.getParser(self._qeInput) new_structure = parser.parseStr(s) else: diffpyStruct = Structure() parser = diffpyStruct.readStr(s, format=format) new_structure = QEStructure(qeInput=self._qeInput) new_structure._setStructureFromDiffpyStructure(diffpyStruct, \ massList = [], psList = [], ibrav = 0) new_structure.lattice._qeInput.update(forceUpdate=True) #new_structure._qeInput = new_structure.lattice._qeInput #print 'toString: ', new_structure._qeInput.toString() #self = QEStructure(new_structure) self.__Init(new_structure) return parser def write(self, filename=None, format="pwinput"): """Save structure to file in the specified format format -- structure formats 'pwinput' ( pw.x input, default), 'pwoutput' (pw.x output), 'bratoms', 'cif', 'discus', 'pdb', 'pdffit', 'rawxyz', 'xcfg', 'xyz' No return value. """ if format == "pwinput": self._qeInput.update(forceUpdate=True) if filename == None: filename = self._qeInput.filename input = QEInput(config=self._qeInput.toString(), type='pw') input.save(filename=filename) else: self.diffpy().write(filename=filename, format=format) return def writeStr(self, format='pwinput'): """return string representation of the structure in specified format format -- structure formats 'pwinput' ( pw.x input, default), 'pwoutput' (pw.x output), 'bratoms', 'cif', 'discus', 'pdb', 'pdffit', 'rawxyz', 'xcfg', 'xyz' """ if format == 'pwinput': self._qeInput.update(forceUpdate=True) return self.toString() else: return self.diffpy().writeStr(format=format) def toString(self, string=None): """Writes/updates structure into PW config string. If the string is None, one, based on current structure will be generated """ if string != None: stru = QEStructure() stru.readStr(string, format='pwinput') qeInput = stru._qeInput else: qeInput = self._qeInput self._qeInput.update(qeInput=qeInput, forceUpdate=True) return qeInput.toString() def save(self, filename=None): """Writes/updates structure into PW config file, if the file does not exist, new one will be created""" from os.path import exists from qecalc.qetask.qeparser.pwinput import PWInput if filename != None: if not exists(filename): f = open(filename, 'w') qeInput = PWInput() qeInput.readFile(filename) else: qeInput = self._qeInput self._qeInput.update(qeInput=qeInput, forceUpdate=True) qeInput.save() def reduce(self, ibrav=None): """ Reduces a structure instance with nonprimitive lattice ( i.e. solves for equivalent atomic positions) according to specified lattice type (ibrav). Each ibrav number corresponds to a specific primitive lattice (see QE documentation). """ ib = self.lattice.ibrav if ibrav != None: ib = ibrav a = self.lattice.diffpy().a b = self.lattice.diffpy().b c = self.lattice.diffpy().c cAB = cosd(self.lattice.diffpy().gamma) cBC = cosd(self.lattice.diffpy().alpha) cAC = cosd(self.lattice.diffpy().beta) if ib > 0: qeLattice = QELattice(ibrav = ib, a = a, b = b, c = c, cBC = cBC, \ cAC = cAC, cAB = cAB) else: qeLattice = QELattice(ibrav=ib, base=self.lattice.base) qeLattice._qeInput = self._qeInput reducedStructure = QEStructure(self) reducedStructure.placeInLattice(qeLattice) # collect atoms that are at equivalent position to some previous atom duplicates = set([ a1 for i0, a0 in enumerate(reducedStructure) for a1 in reducedStructure[i0 + 1:] if a0.element == a1.element and equalPositions(a0.xyz, a1.xyz, eps=1e-4) ]) # Filter out duplicate atoms. Use slice assignment so that # reducedStructure is not replaced with a list. self.lattice = qeLattice self[:] = [a for a in reducedStructure if not a in duplicates] self._qeInput.structure = self return def load(self, source, **args): task = { 'diffpy': self._setStructureFromDiffpyStructure, 'matter': self._setStructureFromMatter, } if source == 'matter': if 'ibrav' in args and args['ibrav'] != 0: task['matter'] = self._setReducedStructureFromMatter if source == 'diffpy': if 'ibrav' in args and args['ibrav'] != 0: task['diffpy'] = self._setReducedStructureFromDiffpyStructure task[source](**args) self.lattice._qeInput.update() def _matter_diffpy(self, structure): """ converts matter Structure object to diffpy.Structure returns diffpy.Structure object """ l = structure.lattice lat = Lattice( a=l.a, b=l.b, c=l.c, alpha=l.alpha, \ beta=l.beta, gamma=l.gamma) stru = Structure(lattice=lat) for a in structure: stru.addNewAtom(atype = a.symbol, xyz = a.xyz, name = a.symbol, \ anisotropy=a.anisotropy, U=a.U, Uisoequiv=a.Uisoequiv, \ lattice=lat) return stru def _setStructureFromMatter(self, structure, massList=[], psList=[], ibrav=0): stru = self._matter_diffpy(structure) self._setStructureFromDiffpyStructure(structure=stru,\ massList=massList, psList=psList,ibrav=ibrav) def _setReducedStructureFromMatter(self, structure, ibrav, massList=[], psList=[]): stru = self._matter_diffpy(structure) self._setReducedStructureFromDiffpyStructure(structure = stru, \ ibrav=ibrav, massList=massList,psList=psList) def _setStructureFromDiffpyStructure(self, structure, massList=[], psList=[], ibrav=0): """ structure - diffpy.Structure object ibrav - Lattice index psList - list of strings with potential names diffpyStructure object will be modified with reduced atomic positions """ diffpyLattice = structure.lattice qeLattice = QELattice(ibrav=0, base=diffpyLattice.base) qeLattice.a = 1.889725989 * qeLattice.a qeLattice._qeInput = self._qeInput self.lattice = qeLattice self.lattice.type = 'generic cubic' atomNames = [] for a in structure: if self._element(a) not in atomNames: atomNames.append(self._element(a)) atomicSpecies = {} for i, elem in enumerate(atomNames): if len(massList) - 1 < i: mass = 0 else: mass = massList[i] if len(psList) - 1 < i: ps = '' else: ps = psList[i] atomicSpecies[elem] = (mass, ps) self[:] = [] for atom in structure: elem = self._element(atom) self.addNewAtom(atype = elem, xyz = atom.xyz, \ mass = atomicSpecies[elem][0], \ potential = atomicSpecies[elem][1],\ lattice = self.lattice, optConstraint = []) def _setReducedStructureFromDiffpyStructure(self, structure, ibrav, massList=[], psList=[]): """ structure - diffpy.Structure object ibrav - Lattice index psList - list of strings with potential names diffpyStructure object will be modified with reduced atomic positions """ import copy diffpyLattice = copy.deepcopy(structure.lattice) a = diffpyLattice.a b = diffpyLattice.b c = diffpyLattice.c cAB = cosd(diffpyLattice.gamma) cBC = cosd(diffpyLattice.alpha) cAC = cosd(diffpyLattice.beta) qeLattice = QELattice(ibrav = ibrav, a = a, b = b, c = c, cBC = cBC, \ cAC = cAC, cAB = cAB) qeLattice._qeInput = self._qeInput self.lattice = qeLattice # make a deep copy: reducedStructure = Structure(atoms=structure) reducedStructure.placeInLattice(Lattice(base=qeLattice.diffpy().base)) # collect atoms that are at equivalent position to some previous atom duplicates = set([ a1 for i0, a0 in enumerate(reducedStructure) for a1 in reducedStructure[i0 + 1:] if self._element(a0) == self._element(a1) and equalPositions(a0.xyz, a1.xyz, eps=1e-4) ]) # Filter out duplicate atoms. Use slice assignment so that # reducedStructure is not replaced with a list. reducedStructure[:] = [ a for a in reducedStructure if not a in duplicates ] atomNames = [] for a in reducedStructure: if self._element(a) not in atomNames: atomNames.append(self._element(a)) atomicSpecies = {} for i, elem in enumerate(atomNames): if len(massList) - 1 < i: mass = 0 else: mass = massList[i] if len(psList) - 1 < i: ps = '' else: ps = psList[i] atomicSpecies[elem] = (mass, ps) self[:] = [] # convert to bohr units self.lattice.setLattice(ibrav, self.lattice.a*1.889725989, \ self.lattice.b*1.889725989, self.lattice.c*1.889725989) for atom in reducedStructure: elem = self._element(atom) self.addNewAtom(atype = elem, xyz = atom.xyz, \ mass = atomicSpecies[elem][0], \ potential = atomicSpecies[elem][1],\ lattice = self.lattice, optConstraint = []) def updatePWInput(self, qeInput=None): """ Deprecated """ self._qeInput.update() def diffpy(self): stru = Structure(lattice=self.lattice.diffpy()) for atom in self: stru.addNewAtom(atype = atom.element, xyz = atom.xyz, \ lattice = self.lattice.diffpy() ) return stru def _element(self, atom): """ Is needed for support both diffpy and matter classes """ if 'element' in dir(atom): return atom.element else: if 'symbol' in dir(atom): return atom.symbol else: raise
class QEStructure(): def __init__(self, qeConf): """the structure is initialized from PWSCF config file 'lattice' and 'structure' are automatically updated""" self.filename = qeConf.filename self.atomicSpecies = OrderedDict() self.formatString = '%# .8f %# .8f %# .8f' # optConstraints three 1/0 for each coordinate of each atom self.optConstraints = [] self.qeConf = qeConf self.lattice = QELattice() self.structure = Structure(lattice = self.lattice.diffpy()) self.nat = None self.ntyp = None self.atomicPositionsType = 'crystal' def atomLabels(self): labels = [] for l in self.atomicSpecies: labels.append(l) return labels def parseInput(self): self.setStructureFromQEInput() def parseOutput(self, pwscfOutputFile): self.setStructureFromPWOutput(pwscfOutputFile) def setStructureFromPWOutput(self, pwscfOutputFile): """ Loads structure from PWSCF output file. If there was geometry optimization (relax or vc-relax), the structure will be reinitialized from the last step of the optimization """ file = open(pwscfOutputFile) pwscfOut = file.readlines() pseudoList = [] atomList = [] massList = [] self.atomicSpecies = OrderedDict() self.atomicPositionsType = 'alat' # parse beginning: for i, line in enumerate(pwscfOut): if 'lattice parameter (a_0)' in line: a_0 = float(line.split()[4]) if 'bravais-lattice index' in line: ibrav = int(line.split('=')[1]) if 'number of atoms/cell' in line: self.nat = int(line.split('=')[1]) if 'number of atomic types' in line: self.ntyp = int(line.split('=')[1]) if 'PseudoPot.' in line: pseudoList.append(line.split('read from file')[1].strip()) if 'atomic species valence mass pseudopotential' in line: for j in range(self.ntyp): atomList.append(pwscfOut[i+j+1].split()[0]) massList.append(float(pwscfOut[i+j+1].split()[2])) if 'crystal axes: (cart. coord. in units of a_0)' in line: latticeVectors = [[float(f)*a_0 for f in pwscfOut[i + 1].split()[3:6] ], [float(f)*a_0 for f in pwscfOut[i + 2].split()[3:6] ], [float(f)*a_0 for f in pwscfOut[i + 3].split()[3:6] ]] self.lattice.setLatticeFromQEVectors(ibrav, latticeVectors) if 'site n. atom positions (a_0 units)' in line: self.structure = Structure(lattice = self.lattice.diffpy()) for n in range(self.nat): words = pwscfOut[i + n + 1].split() atomSymbol = words[1] coords = [float(w) for w in words[6:9]] constraint = [] self.optConstraints.append(numpy.array(constraint, dtype = int)) #print numpy.array(coords[0:3])*a_0 coords = self.lattice.diffpy().fractional(numpy.array(coords[0:3])*a_0) self.structure.addNewAtom(atomSymbol, xyz = numpy.array(coords[0:3])) for a, m, p in zip(atomList, massList, pseudoList): self.atomicSpecies[a] = AtomicSpecies(a, m, p) #print 'Input structure from output file: ', self.toString() #Parse end: # Find all geometry optimization steps posList = [i for i,line in enumerate(pwscfOut) if '! total energy' in line] lastSection = pwscfOut[posList[-1]:] for i, line in enumerate(lastSection): if 'CELL_PARAMETERS (alat)' in line: latticeVectors = [[float(f)*a_0 for f in lastSection[i + 1].split() ], [float(f)*a_0 for f in lastSection[i + 2].split() ], [float(f)*a_0 for f in lastSection[i + 3].split() ]] self.lattice.setLatticeFromQEVectors(ibrav, latticeVectors) #self.lattice = QELattice(ibrav = ibrav, base = latticeVectors) #print self.lattice.diffpy().base if 'ATOMIC_POSITIONS (alat)' in line: self.structure = Structure(lattice = self.lattice.diffpy()) for n in range(self.nat): words = lastSection[i + n + 1].split() atomSymbol = words[0] coords = [float(w) for w in words[1:4]] constraint = [] if len(words) > 4: constraint = [int(c) for c in words[4:7]] self.optConstraints.append(numpy.array(constraint, dtype = int)) coords = self.lattice.diffpy().fractional(numpy.array(coords[0:3])*a_0) self.structure.addNewAtom(atomSymbol, xyz = numpy.array(coords[0:3])) def setStructureFromQEInput(self): """ Loads structure from PWSCF config file""" self.atomicSpecies = OrderedDict() self.lattice.setLatticeFromPWInput(self.qeConf) #self.lattice = QELattice(qeConf = self.qeConf) self.structure = Structure(lattice = self.lattice.diffpy()) self.nat = self.ntyp = None self.filename = self.qeConf.filename self.optConstraints = [] if 'system' in self.qeConf.namelists: self.nat = int(self.qeConf.namelist('system').param('nat')) self.ntyp = int(self.qeConf.namelist('system').param('ntyp')) if 'atomic_positions' in self.qeConf.cards: atomicLines = self.qeConf.card('atomic_positions').lines() self.atomicPositionsType = self.qeConf.card('atomic_positions').arg() if self.atomicPositionsType == 'bohr' or self.atomicPositionsType == 'angstrom': raise NotImplementedError if self.atomicPositionsType == None: self.atomicPositionsType = 'alat' for line in atomicLines: if '!' not in line: words = line.split() coords = [float(w) for w in words[1:4]] constraint = [] if len(words) > 4: constraint = [int(c) for c in words[4:7]] self.optConstraints.append(numpy.array(constraint, dtype = int)) atomSymbol = words[0] if self.atomicPositionsType == 'alat': coords = self.lattice.diffpy().fractional(numpy.array(coords[0:3])*self.lattice.a0) if self.atomicPositionsType == 'crystal': coords = numpy.array(coords[0:3]) self.structure.addNewAtom(atomSymbol, xyz = numpy.array(coords[0:3])) # parse mass ATOMIC_SPECIES section: if 'atomic_species' in self.qeConf.cards: atomicSpeciesLines = self.qeConf.card('atomic_species').lines() for line in atomicSpeciesLines: if '!' not in line: if line.strip() != '': atomicSpeciesWords = line.split() element = atomicSpeciesWords[0] mass = 0 ps = '' if len(atomicSpeciesWords) > 1 : mass = float(atomicSpeciesWords[1]) if len(atomicSpeciesWords) > 2: ps = atomicSpeciesWords[2] self.atomicSpecies[element] = AtomicSpecies(element, mass, ps) def load(self, source, **args): task = { 'diffpy': self.setStructureFromDiffpyStructure } if source == 'diffpy': if 'ibrav' in args and args['ibrav'] != 0: task['diffpy'] = self.setReducedStructureFromDiffpyStructure task[source](**args) self.updatePWInput(qeConf = self.qeConf) def setStructureFromDiffpyStructure(self, structure, massList = [], psList = [], ibrav = 0): """ structure - diffpy.Structure object ibrav - Lattice index psList - list of strings with pseudopotential names diffpyStructure object will be modified with reduced atomic positions """ diffpyLattice = structure.lattice self.structure = structure #set lattice and convert to bohr units qeLattice = QELattice(ibrav = 0, a = 1.889725989, base = diffpyLattice.base) self.lattice = qeLattice self.lattice.type = 'generic cubic' atomNames = [] for a in structure: if self._element(a) not in atomNames: atomNames.append(self._element(a)) #print atomNames #print len(massList) for i, elem in enumerate(atomNames): if len(massList) - 1 < i: mass = 0 else: mass = massList[i] if len(psList) - 1 < i: ps = '' else: ps = psList[i] self.atomicSpecies[elem] = AtomicSpecies(elem, mass, ps) for atom in structure: self.optConstraints.append([]) # for i, atom in enumerate(structure): # elem = self._element(atom) # if len(massList) - 1 < i: # mass = 0 # else: # mass = massList[i] # if len(psList) - 1 < i: # ps = '' # else: # ps = psList[i] # self.atomicSpecies[elem] = AtomicSpecies(elem, mass, ps) # self.optConstraints.append([]) # for atom, mass, ps in zip(structure, massList, psList): # elem = self._element(atom) # self.atomicSpecies[elem] = AtomicSpecies(elem, mass, ps) # self.optConstraints.append([]) self.nat = len(structure) self.ntyp = len(self.atomicSpecies) def setReducedStructureFromDiffpyStructure(self, structure, ibrav, massList = [], psList = []): """ structure - diffpy.Structure object ibrav - Lattice index psList - list of strings with pseudopotential names diffpyStructure object will be modified with reduced atomic positions """ #self.atomicSpecies = OrderedDict() #self.optConstraints = [] #self.atomicPositionsType = 'crystal' diffpyLattice = structure.lattice a = diffpyLattice.a b = diffpyLattice.b c = diffpyLattice.c cAB = cosd(diffpyLattice.gamma) cBC = cosd(diffpyLattice.alpha) cAC = cosd(diffpyLattice.beta) qeLattice = QELattice(ibrav = ibrav, a = a, b = b, c = c, cBC = cBC, \ cAC = cAC, cAB = cAB) self.lattice = qeLattice # make a deep copy (does not wok now) #reducedStructure = Structure(diffpyStructure) reducedStructure = structure reducedStructure.placeInLattice(Lattice(base=qeLattice.diffpy().base)) # collect atoms that are at equivalent position to some previous atom duplicates = set([a1 for i0, a0 in enumerate(reducedStructure) for a1 in reducedStructure[i0+1:] if self._element(a0) == self._element(a1) and equalPositions(a0.xyz, a1.xyz, eps=1e-4)]) # Filter out duplicate atoms. Use slice assignment so that # reducedStructure is not replaced with a list. reducedStructure[:] = [a for a in reducedStructure if not a in duplicates] self.structure = reducedStructure atomNames = [] for a in reducedStructure: if self._element(a) not in atomNames: atomNames.append(self._element(a)) #print atomNames #print len(massList) for i, elem in enumerate(atomNames): if len(massList) - 1 < i: mass = 0 else: mass = massList[i] if len(psList) - 1 < i: ps = '' else: ps = psList[i] #print mass, ps # atomDict[a] = # for i, atom in enumerate(reducedStructure): # elem = self._element(atom) # if len(massList) - 1 < i: # mass = 0 # else: # mass = massList[i] # if len(psList) - 1 < i: # ps = '' # else: # ps = psList[i] self.atomicSpecies[elem] = AtomicSpecies(elem, mass, ps) for atom in reducedStructure: self.optConstraints.append([]) # convert to bohr units self.lattice.setLattice(ibrav, self.lattice.a*1.889725989, \ self.lattice.b*1.889725989, self.lattice.c*1.889725989) self.nat = len(reducedStructure) self.ntyp = len(self.atomicSpecies) # use rstrip to avoid duplicate line feed #print reducedStructure.writeStr(format='discus').rstrip() #print reducedStructure.writeStr(format='discus') #print reducedStructure.writeStr().rstrip() #print reducedStructure #self.lattice = setLatticeFromDiffpyLattice(structure.lattice, ibrav) # def toString(self): # s = self.lattice.toString() + '\n' # if self.atomicPositionsType == 'alat': # s = s + 'Atomic positions in units of lattice parametr "a":\n' # if self.atomicPositionsType == 'crystal': # s = s + 'Atomic positions in crystal coordinates:\n' # for atom, constraint in zip(self.structure, self.optConstraints): # if self.atomicPositionsType == 'alat': # coords = self.lattice.diffpy().cartesian(atom.xyz)/self.lattice.a # coords = self.formatString%(coords[0], coords[1], coords[2]) # else: # if self.atomicPositionsType == 'crystal': # coords = self.formatString%(atom.xyz[0], atom.xyz[1], atom.xyz[2]) # else: # raise NonImplementedError # s = s + '%-3s'%self._element(atom) + ' ' + coords + ' ' \ # + str(constraint)[1:-1] + '\n' # s = s + '\n' # for element, specie in self.atomicSpecies.items(): # s = s + specie.toString() + '\n' # return s def toString(self, string = None): if string != None: string = self.lattice.toString(string = string) qeConf = QEInput(config = string) qeConf.parse() else: if self.qeConf != None: qeConf = self.qeConf else: qeConf = QEInput(config = '') self.updatePWInput(qeConf) return qeConf.toString() def updatePWInput(self, qeConf = None): if qeConf == None: qeConf = self.qeConf self.lattice.updatePWInput(qeConf) if 'system' not in qeConf.namelists: qeConf.addNamelist('system') qeConf.namelist('system').remove('ntyp') qeConf.namelist('system').remove('nat') if self.ntyp != None: qeConf.namelist('system').add('ntyp', self.ntyp) if self.nat != None: qeConf.namelist('system').add('nat', self.nat) if len(qeConf.namelist('system').params) == 0: qeConf.removeNamelist('system') if 'atomic_positions' in qeConf.cards: qeConf.removeCard('atomic_positions') qeConf.createCard('atomic_positions') qeConf.card('atomic_positions').setArg(self.atomicPositionsType) for atom, constraint in zip(self.structure, self.optConstraints): if self.atomicPositionsType == 'alat': coords = self.lattice.diffpy().cartesian(atom.xyz)/self.lattice.a coords = self.formatString%(coords[0], coords[1], coords[2]) else: if self.atomicPositionsType == 'crystal': coords = self.formatString%(atom.xyz[0], atom.xyz[1], atom.xyz[2]) else: raise NonImplementedError line = '%-3s'%self._element(atom) + ' ' + coords + ' ' + str(constraint)[1:-1] qeConf.card('atomic_positions').addLine(line) if len(qeConf.card('atomic_positions').lines()) == 0: qeConf.removeCard('atomic_positions') # update ATOMIC_SPECIES card if 'atomic_species' in qeConf.cards: qeConf.removeCard('atomic_species') qeConf.createCard('atomic_species') for element, specie in self.atomicSpecies.items(): qeConf.card('atomic_species').addLine(specie.toString()) if len(qeConf.card('atomic_species').lines()) == 0: qeConf.removeCard('atomic_species') def save(self, fname = None): """Writes/updates structure into PW config file, if the file does not exist, new one will be created""" filename = fname if fname != None: self.lattice.save(filename) qeConf = QEInput(fname) qeConf.parse() else: filename = self.filename self.lattice.save(filename) qeConf = self.qeConf self.updatePWInput(qeConf) qeConf.save(filename) def diffpy(self): return self.structure def _element(self, atom): """ Is needed for suport both diffpy and matter classess """ if 'element' in dir(atom): return atom.element else: if 'symbol' in dir(atom): return atom.symbol else: raise
class QEStructure(): def __init__(self, qeConf): """the structure is initialized from PWSCF config file 'lattice' and 'structure' are automatically updated""" self.filename = qeConf.filename self.atomicSpecies = OrderedDict() self.formatString = '%# .8f %# .8f %# .8f' # optConstraints three 1/0 for each coordinate of each atom self.optConstraints = [] self.qeConf = qeConf self.lattice = QELattice() self.structure = Structure(lattice=self.lattice.diffpy()) self.nat = None self.ntyp = None def parseInput(self): self.setStructureFromQEInput() def parseOutput(self, pwscfOutputFile): self.setStructureFromPWOutput(pwscfOutputFile) def setStructureFromPWOutput(self, pwscfOutputFile): """ Loads structure from PWSCF output file. If there was geometry optimization (relax or vc-relax), the structure will be reinitialized from the last step of the optimization """ file = open(pwscfOutputFile) pwscfOut = file.readlines() pseudoList = [] atomList = [] massList = [] self.atomicSpecies = OrderedDict() self.atomicPositionsType = 'alat' # parse beginning: for i, line in enumerate(pwscfOut): if 'lattice parameter (a_0)' in line: a_0 = float(line.split()[4]) if 'bravais-lattice index' in line: ibrav = int(line.split('=')[1]) if 'number of atoms/cell' in line: self.nat = int(line.split('=')[1]) if 'number of atomic types' in line: self.ntyp = int(line.split('=')[1]) if 'PseudoPot.' in line: pseudoList.append(line.split('read from file')[1].strip()) if 'atomic species valence mass pseudopotential' in line: for j in range(self.ntyp): atomList.append(pwscfOut[i + j + 1].split()[0]) massList.append(float(pwscfOut[i + j + 1].split()[2])) if 'crystal axes: (cart. coord. in units of a_0)' in line: latticeVectors = [ [float(f) * a_0 for f in pwscfOut[i + 1].split()[3:6]], [float(f) * a_0 for f in pwscfOut[i + 2].split()[3:6]], [float(f) * a_0 for f in pwscfOut[i + 3].split()[3:6]] ] self.lattice.setLatticeFromQEVectors(ibrav, latticeVectors) if 'site n. atom positions (a_0 units)' in line: self.structure = Structure(lattice=self.lattice.diffpy()) for n in range(self.nat): words = pwscfOut[i + n + 1].split() atomSymbol = words[1] coords = [float(w) for w in words[6:9]] constraint = [] self.optConstraints.append( numpy.array(constraint, dtype=int)) #print numpy.array(coords[0:3])*a_0 coords = self.lattice.diffpy().fractional( numpy.array(coords[0:3]) * a_0) self.structure.addNewAtom(atomSymbol, xyz=numpy.array(coords[0:3])) for a, m, p in zip(atomList, massList, pseudoList): self.atomicSpecies[a] = AtomicSpecies(a, m, p) #print 'Input structure from output file: ', self.toString() #Parse end: # Find all geometry optimization steps posList = [ i for i, line in enumerate(pwscfOut) if '! total energy' in line ] lastSection = pwscfOut[posList[-1]:] for i, line in enumerate(lastSection): if 'CELL_PARAMETERS (alat)' in line: latticeVectors = [ [float(f) * a_0 for f in lastSection[i + 1].split()], [float(f) * a_0 for f in lastSection[i + 2].split()], [float(f) * a_0 for f in lastSection[i + 3].split()] ] self.lattice.setLatticeFromQEVectors(ibrav, latticeVectors) #self.lattice = QELattice(ibrav = ibrav, base = latticeVectors) print self.lattice.diffpy().base if 'ATOMIC_POSITIONS (alat)' in line: self.structure = Structure(lattice=self.lattice.diffpy()) for n in range(self.nat): words = lastSection[i + n + 1].split() atomSymbol = words[0] coords = [float(w) for w in words[1:4]] constraint = [] if len(words) > 4: constraint = [int(c) for c in words[4:7]] self.optConstraints.append( numpy.array(constraint, dtype=int)) coords = self.lattice.diffpy().fractional( numpy.array(coords[0:3]) * a_0) self.structure.addNewAtom(atomSymbol, xyz=numpy.array(coords[0:3])) def setStructureFromQEInput(self): """ Loads structure from PWSCF config file""" self.atomicSpecies = OrderedDict() self.lattice.setLatticeFromPWInput(self.qeConf) #self.lattice = QELattice(qeConf = self.qeConf) self.structure = Structure(lattice=self.lattice.diffpy()) self.nat = int(self.qeConf.namelist('system').param('nat')) self.ntyp = int(self.qeConf.namelist('system').param('ntyp')) atomicLines = self.qeConf.card('atomic_positions').lines() self.atomicPositionsType = self.qeConf.card('atomic_positions').arg() if self.atomicPositionsType == 'bohr' or self.atomicPositionsType == 'angstrom': raise NotImplementedError if self.atomicPositionsType == None: self.atomicPositionsType = 'alat' for line in atomicLines: if '!' not in line: words = line.split() coords = [float(w) for w in words[1:4]] constraint = [] if len(words) > 4: constraint = [int(c) for c in words[4:7]] self.optConstraints.append(numpy.array(constraint, dtype=int)) atomSymbol = words[0] if self.atomicPositionsType == 'alat': coords = self.lattice.diffpy().fractional( numpy.array(coords[0:3]) * self.lattice.a0) if self.atomicPositionsType == 'crystal': coords = numpy.array(coords[0:3]) self.structure.addNewAtom(atomSymbol, xyz=numpy.array(coords[0:3])) # parse mass ATOMIC_SPECIES section: atomicSpeciesLines = self.qeConf.card('atomic_species').lines() for line in atomicSpeciesLines: if '!' not in line: atomicSpeciesWords = line.split() element = atomicSpeciesWords[0] mass = float(atomicSpeciesWords[1]) ps = atomicSpeciesWords[2] self.atomicSpecies[element] = AtomicSpecies( element, mass, ps) def toString(self): s = self.lattice.toString() + '\n' if self.atomicPositionsType == 'alat': s = s + 'Atomic positions in units of lattice parametr "a":\n' if self.atomicPositionsType == 'crystal': s = s + 'Atomic positions in crystal coordinates:\n' for atom, constraint in zip(self.structure, self.optConstraints): if self.atomicPositionsType == 'alat': coords = self.lattice.diffpy().cartesian( atom.xyz) / self.lattice.a coords = self.formatString % (coords[0], coords[1], coords[2]) else: if self.atomicPositionsType == 'crystal': coords = self.formatString % (v[0], v[1], v[2]) % ( atom.xyz[0], atom.xyz[1], atom.xyz[2]) else: raise NonImplementedError s = s + '%-3s'%atom.element + ' ' + coords + ' ' \ + str(constraint)[1:-1] + '\n' s = s + '\n' for element, specie in self.atomicSpecies.items(): s = s + specie.toString() + '\n' return s def updatePWInput(self, qeConf=None): if qeConf == None: qeConf = self.qeConf self.lattice.updatePWInput(qeConf) qeConf.namelist('system').remove('ntyp') qeConf.namelist('system').remove('nat') qeConf.namelist('system').add('ntyp', self.ntyp) qeConf.namelist('system').add('nat', self.nat) if 'atomic_positions' in qeConf.cards: qeConf.removeCard('atomic_positions') qeConf.createCard('atomic_positions') qeConf.card('atomic_positions').setArg(self.atomicPositionsType) for atom, constraint in zip(self.structure, self.optConstraints): if self.atomicPositionsType == 'alat': coords = self.lattice.diffpy().cartesian( atom.xyz) / self.lattice.a coords = self.formatString % (coords[0], coords[1], coords[2]) else: if self.atomicPositionsType == 'crystal': coords = self.formatString % (atom.xyz[0], atom.xyz[1], atom.xyz[2]) else: raise NonImplementedError line = '%-3s' % atom.element + ' ' + coords + ' ' + str( constraint)[1:-1] qeConf.card('atomic_positions').addLine(line) # update ATOMIC_SPECIES card if 'atomic_species' in qeConf.cards: qeConf.removeCard('atomic_species') qeConf.createCard('atomic_species') for element, specie in self.atomicSpecies.items(): qeConf.card('atomic_species').addLine(specie.toString()) def save(self, fname=None): """Writes/updates structure into PW config file, if the file does not exist, new one will be created""" if fname != None: filename = fname self.lattice.save(filename) qeConf = QEInput(fname) qeConf.parse() else: filename = self.filename self.lattice.save(filename) qeConf = self.qeConf self.updatePWInput(qeConf) qeConf.save(filename) def diffpy(self): return self.structure
def setStructureFromPWOutput(self, pwscfOutputFile): """ Loads structure from PWSCF output file. If there was geometry optimization (relax or vc-relax), the structure will be reinitialized from the last step of the optimization """ file = open(pwscfOutputFile) pwscfOut = file.readlines() pseudoList = [] atomList = [] massList = [] self.atomicSpecies = OrderedDict() self.atomicPositionsType = 'alat' # parse beginning: for i, line in enumerate(pwscfOut): if 'lattice parameter (a_0)' in line: a_0 = float(line.split()[4]) if 'bravais-lattice index' in line: ibrav = int(line.split('=')[1]) if 'number of atoms/cell' in line: self.nat = int(line.split('=')[1]) if 'number of atomic types' in line: self.ntyp = int(line.split('=')[1]) if 'PseudoPot.' in line: pseudoList.append(line.split('read from file')[1].strip()) if 'atomic species valence mass pseudopotential' in line: for j in range(self.ntyp): atomList.append(pwscfOut[i+j+1].split()[0]) massList.append(float(pwscfOut[i+j+1].split()[2])) if 'crystal axes: (cart. coord. in units of a_0)' in line: latticeVectors = [[float(f)*a_0 for f in pwscfOut[i + 1].split()[3:6] ], [float(f)*a_0 for f in pwscfOut[i + 2].split()[3:6] ], [float(f)*a_0 for f in pwscfOut[i + 3].split()[3:6] ]] self.lattice = QELattice(ibrav = ibrav, base = latticeVectors) if 'site n. atom positions (a_0 units)' in line: self.structure = Structure(lattice = self.lattice.matter()) for n in range(self.nat): words = pwscfOut[i + n + 1].split() atomSymbol = words[1] coords = [float(w) for w in words[6:9]] constraint = [] self.optConstraints.append(numpy.array(constraint, dtype = int)) #print numpy.array(coords[0:3])*a_0 coords = self.lattice.matter().fractional(numpy.array(coords[0:3])*a_0) self.structure.addNewAtom(atomSymbol, xyz = numpy.array(coords[0:3])) for a, m, p in zip(atomList, massList, pseudoList): self.atomicSpecies[a] = AtomicSpecies(a, m, p) #print 'Input structure from output file: ', self.toString() #Parse end: # Find all geometry optimization steps posList = [i for i,line in enumerate(pwscfOut) if '! total energy' in line] lastSection = pwscfOut[posList[-1]:] for i, line in enumerate(lastSection): if 'CELL_PARAMETERS (alat)' in line: latticeVectors = [[float(f)*a_0 for f in lastSection[i + 1].split() ], [float(f)*a_0 for f in lastSection[i + 2].split() ], [float(f)*a_0 for f in lastSection[i + 3].split() ]] self.lattice = QELattice(ibrav = ibrav, base = latticeVectors) print self.lattice.matter().base if 'ATOMIC_POSITIONS (alat)' in line: self.structure = Structure(lattice = self.lattice.matter()) for n in range(self.nat): words = lastSection[i + n + 1].split() atomSymbol = words[0] coords = [float(w) for w in words[1:4]] constraint = [] if len(words) > 4: constraint = [int(c) for c in words[4:7]] self.optConstraints.append(numpy.array(constraint, dtype = int)) #print numpy.array(coords[0:3])*a_0 coords = self.lattice.matter().fractional(numpy.array(coords[0:3])*a_0) self.structure.addNewAtom(atomSymbol, xyz = numpy.array(coords[0:3]))
class QEStructure(): def __init__(self, qeConf): """the structure is initialized from PWSCF config file 'lattice' and 'structure' are automatically updated""" self.filename = qeConf.filename self.atomicSpecies = OrderedDict() self.formatString = '%# .8f %# .8f %# .8f' # optConstraints three 1/0 for each coordinate of each atom self.optConstraints = [] self.qeConf = qeConf #self.qeConf.parse() #self.setStructureFromQEInput() self.lattice = None self.structure = None self.nat = None self.ntyp = None def parseInput(self): self.setStructureFromQEInput() def parseOutput(self, pwscfOutputFile): self.setStructureFromPWOutput(pwscfOutputFile) # def _parseAtomicPositions(self, pwscfOut): # for n in range(self.nat): # words = pwscfOut[i + n + 1].split() # atomSymbol = words[0] # coords = [float(w) for w in words[1:4]] # constraint = [] # if len(words) > 4: # constraint = [int(c) for c in words[4:7]] # self.optConstraints.append(numpy.array(constraint, dtype = int)) # print numpy.array(coords[0:3])*a_0 # coords = self.lattice.matter().fractional(numpy.array(coords[0:3])*a_0) # self.structure.addNewAtom(atomSymbol, xyz = numpy.array(coords[0:3])) def setStructureFromPWOutput(self, pwscfOutputFile): """ Loads structure from PWSCF output file. If there was geometry optimization (relax or vc-relax), the structure will be reinitialized from the last step of the optimization """ file = open(pwscfOutputFile) pwscfOut = file.readlines() pseudoList = [] atomList = [] massList = [] self.atomicSpecies = OrderedDict() self.atomicPositionsType = 'alat' # parse beginning: for i, line in enumerate(pwscfOut): if 'lattice parameter (a_0)' in line: a_0 = float(line.split()[4]) if 'bravais-lattice index' in line: ibrav = int(line.split('=')[1]) if 'number of atoms/cell' in line: self.nat = int(line.split('=')[1]) if 'number of atomic types' in line: self.ntyp = int(line.split('=')[1]) if 'PseudoPot.' in line: pseudoList.append(line.split('read from file')[1].strip()) if 'atomic species valence mass pseudopotential' in line: for j in range(self.ntyp): atomList.append(pwscfOut[i+j+1].split()[0]) massList.append(float(pwscfOut[i+j+1].split()[2])) if 'crystal axes: (cart. coord. in units of a_0)' in line: latticeVectors = [[float(f)*a_0 for f in pwscfOut[i + 1].split()[3:6] ], [float(f)*a_0 for f in pwscfOut[i + 2].split()[3:6] ], [float(f)*a_0 for f in pwscfOut[i + 3].split()[3:6] ]] self.lattice = QELattice(ibrav = ibrav, base = latticeVectors) if 'site n. atom positions (a_0 units)' in line: self.structure = Structure(lattice = self.lattice.matter()) for n in range(self.nat): words = pwscfOut[i + n + 1].split() atomSymbol = words[1] coords = [float(w) for w in words[6:9]] constraint = [] self.optConstraints.append(numpy.array(constraint, dtype = int)) #print numpy.array(coords[0:3])*a_0 coords = self.lattice.matter().fractional(numpy.array(coords[0:3])*a_0) self.structure.addNewAtom(atomSymbol, xyz = numpy.array(coords[0:3])) for a, m, p in zip(atomList, massList, pseudoList): self.atomicSpecies[a] = AtomicSpecies(a, m, p) #print 'Input structure from output file: ', self.toString() #Parse end: # Find all geometry optimization steps posList = [i for i,line in enumerate(pwscfOut) if '! total energy' in line] lastSection = pwscfOut[posList[-1]:] for i, line in enumerate(lastSection): if 'CELL_PARAMETERS (alat)' in line: latticeVectors = [[float(f)*a_0 for f in lastSection[i + 1].split() ], [float(f)*a_0 for f in lastSection[i + 2].split() ], [float(f)*a_0 for f in lastSection[i + 3].split() ]] self.lattice = QELattice(ibrav = ibrav, base = latticeVectors) print self.lattice.matter().base if 'ATOMIC_POSITIONS (alat)' in line: self.structure = Structure(lattice = self.lattice.matter()) for n in range(self.nat): words = lastSection[i + n + 1].split() atomSymbol = words[0] coords = [float(w) for w in words[1:4]] constraint = [] if len(words) > 4: constraint = [int(c) for c in words[4:7]] self.optConstraints.append(numpy.array(constraint, dtype = int)) #print numpy.array(coords[0:3])*a_0 coords = self.lattice.matter().fractional(numpy.array(coords[0:3])*a_0) self.structure.addNewAtom(atomSymbol, xyz = numpy.array(coords[0:3])) #self.lattice.ibrav = ibrav #print 'Output structure from output file: ', self.toString() def setStructureFromQEInput(self): """ Loads structure from PWSCF config file""" self.atomicSpecies = OrderedDict() self.lattice = QELattice(qeConf = self.qeConf) self.structure = Structure(lattice = self.lattice.matter()) self.nat = int(self.qeConf.namelist('system').param('nat')) self.ntyp = int(self.qeConf.namelist('system').param('ntyp')) atomicLines = self.qeConf.card('atomic_positions').lines() self.atomicPositionsType = self.qeConf.card('atomic_positions').arg() if self.atomicPositionsType == 'bohr' or self.atomicPositionsType == 'angstrom': raise NotImplementedError if self.atomicPositionsType == None: self.atomicPositionsType = 'alat' for line in atomicLines: if '!' not in line: words = line.split() coords = [float(w) for w in words[1:4]] constraint = [] if len(words) > 4: constraint = [int(c) for c in words[4:7]] self.optConstraints.append(numpy.array(constraint, dtype = int)) atomSymbol = words[0] if self.atomicPositionsType == 'alat': coords = self.lattice.matter().fractional(numpy.array(coords[0:3])*self.lattice.a0) if self.atomicPositionsType == 'crystal': coords = numpy.array(coords[0:3]) self.structure.addNewAtom(atomSymbol, xyz = numpy.array(coords[0:3])) # parse mass ATOMIC_SPECIES section: atomicSpeciesLines = self.qeConf.card('atomic_species').lines() for line in atomicSpeciesLines: if '!' not in line: atomicSpeciesWords = line.split() element = atomicSpeciesWords[0] mass = float(atomicSpeciesWords[1]) ps = atomicSpeciesWords[2] self.atomicSpecies[element] = AtomicSpecies(element, mass, ps) def toString(self): s = self.lattice.toString() + '\n' if self.atomicPositionsType == 'alat': s = s + 'Atomic positions in units of lattice parametr "a":\n' if self.atomicPositionsType == 'crystal': s = s + 'Atomic positions in crystal coordinates:\n' for atom, constraint in zip(self.structure, self.optConstraints): if self.atomicPositionsType == 'alat': coords = self.lattice.matter().cartesian(atom.xyz)/self.lattice.a coords = self.formatString%(coords[0], coords[1], coords[2]) #coords = str(coords/self.lattice.a)[1:-1] else: if self.atomicPositionsType == 'crystal': #coords = str(atom.xyz)[1:-1] coords = self.formatString%(v[0], v[1], v[2])%(atom.xyz[0], atom.xyz[1], atom.xyz[2]) else: raise NonImplementedError s = s + '%-3s'%atom.element + ' ' + coords + ' ' \ + str(constraint)[1:-1] + '\n' s = s + '\n' for element, specie in self.atomicSpecies.items(): s = s + specie.toString() + '\n' return s def updatePWInput(self, qeConf = None): if qeConf == None: qeConf = self.qeConf self.lattice.updatePWInput(qeConf) qeConf.namelist('system').remove('ntyp') qeConf.namelist('system').remove('nat') qeConf.namelist('system').add('ntyp', self.ntyp) qeConf.namelist('system').add('nat', self.nat) if 'atomic_positions' in qeConf.cards: qeConf.removeCard('atomic_positions') qeConf.createCard('atomic_positions') qeConf.card('atomic_positions').setArg(self.atomicPositionsType) for atom, constraint in zip(self.structure, self.optConstraints): if self.atomicPositionsType == 'alat': coords = self.lattice.matter().cartesian(atom.xyz)/self.lattice.a coords = self.formatString%(coords[0], coords[1], coords[2]) else: if self.atomicPositionsType == 'crystal': #coords = str(atom.xyz)[1:-1] coords = self.formatString%(atom.xyz[0], atom.xyz[1], atom.xyz[2]) else: raise NonImplementedError line = '%-3s'%atom.element + ' ' + coords + ' ' + str(constraint)[1:-1] # line = atom.element + ' ' + coords + ' ' + str(constraint)[1:-1] qeConf.card('atomic_positions').addLine(line) # update ATOMIC_SPECIES card if 'atomic_species' in qeConf.cards: qeConf.removeCard('atomic_species') qeConf.createCard('atomic_species') for element, specie in self.atomicSpecies.items(): qeConf.card('atomic_species').addLine(specie.toString()) def save(self, fname = None): """Writes/updates structure into PW config file, if the file does not exist, new one will be created""" if fname != None: filename = fname self.lattice.save(filename) qeConf = QEInput(fname) qeConf.parse() else: filename = self.filename self.lattice.save(filename) qeConf = self.qeConf #qeConf.parse() self.updatePWInput(qeConf ) qeConf.save(filename) def diffpy(self): return self.structure
class QEAtom(object): """Atom --> class for storing atom information Data members: element -- type of the atom xyz -- fractional coordinates name -- atom label xyz_cartn -- absolute Cartesian coordinates, property synced with xyz lattice -- coordinate system for fractional coordinates, an instance of Lattice or None for Cartesian system """ def __init__(self, atype=None, xyz=None, mass = None, \ potential = None, lattice=None, optConstraint = None, name=None): """Create atom of a specified type at given lattice coordinates. Atom(a) creates a copy of Atom instance a. atype -- element symbol string or Atom instance xyz -- fractional(crystal) coordinates name -- atom label mass -- atom mass potential -- pseudopotential file name lattice -- QE coordinate system for fractional coordinates optConstraint -- list of up to three constraints for each coordinate for QE geometry optimization (0 or 1) """ # declare data members self.element = None self._xyz = numpy.zeros(3, dtype=float) self.name = '' self._mass = 0 self._potential = '' self.lattice = QELattice() from pwinput import PWInput self.lattice._qeInput = PWInput() self._optConstraint = numpy.array([], dtype = int) # assign them as needed if isinstance(atype, QEAtom): atype_dup = atype.__copy__() self.__dict__.update(atype_dup.__dict__) else: self.element = atype # take care of remaining arguments if xyz is not None: self._xyz[:] = xyz if name is not None: self.name = name if mass is not None: self._mass = mass if potential is not None: self._potential = potential if lattice is not None: self.lattice = lattice if optConstraint is not None: self._optConstraint = \ numpy.array(optConstraint, dtype = int) return def __repr__(self): """simple string representation""" xyz = self.xyz s = "%-4s %8.6f %8.6f %8.6f %8.6f %s" % \ (self.element, xyz[0], xyz[1], xyz[2], self.mass, self.potential) return s def __copy__(self): """Return a copy of this instance. """ adup = QEAtom(self.element) adup.__dict__.update(self.__dict__) # create copies for what should be copied adup.xyz = numpy.array(self.xyz) return adup #################################################################### # property handlers #################################################################### # xyz_cartn def _get_xyz_cartn(self): if not self.lattice: rv = self.xyz else: rv = CartesianCoordinatesArray(self.lattice, self.xyz) return rv def _set_xyz_cartn(self, value): if not self.lattice: self.xyz[:] = value else: self.xyz = self.lattice.fractional(value) self.lattice._qeInput.update() return xyz_cartn = property(_get_xyz_cartn, _set_xyz_cartn, doc = """absolute Cartesian coordinates of an atom """ ) def _get_xyz(self): return self._xyz def _set_xyz(self, value): self._xyz = value self.lattice._qeInput.update() xyz = property(_get_xyz, _set_xyz, doc = """fractional coordinates of an atom """ ) def _get_mass(self): return self._mass def _set_mass(self, value): self._mass = value self.lattice._qeInput.update() mass = property(_get_mass, _set_mass, doc = """mass of an atom """ ) def _get_potential(self): return self._potential def _set_potential(self, value): self._potential = value self.lattice._qeInput.update() potential = property(_get_potential, _set_potential, doc = """property. Name of paseudopotential of an atom """) def _get_optConstraint(self): return self._optConstraint def _set_optConstraint(self, value): self._optConstraints = value self.lattice._qeInput.update() optConstraint = property(_get_optConstraint, _set_optConstraint, doc = """optimization constraint, e.g. [1, 0, 1] of an atom for QE geometry optimization""")
def _setReducedStructureFromDiffpyStructure(self, structure, ibrav, massList = [], psList = []): """ structure - diffpy.Structure object ibrav - Lattice index psList - list of strings with potential names diffpyStructure object will be modified with reduced atomic positions """ import copy diffpyLattice = copy.deepcopy(structure.lattice) a = diffpyLattice.a b = diffpyLattice.b c = diffpyLattice.c cAB = cosd(diffpyLattice.gamma) cBC = cosd(diffpyLattice.alpha) cAC = cosd(diffpyLattice.beta) qeLattice = QELattice(ibrav = ibrav, a = a, b = b, c = c, cBC = cBC, \ cAC = cAC, cAB = cAB) qeLattice._qeInput = self._qeInput self.lattice = qeLattice # make a deep copy: reducedStructure = Structure(atoms = structure) reducedStructure.placeInLattice(Lattice(base=qeLattice.diffpy().base)) # collect atoms that are at equivalent position to some previous atom duplicates = set([a1 for i0, a0 in enumerate(reducedStructure) for a1 in reducedStructure[i0+1:] if self._element(a0) == self._element(a1) and equalPositions(a0.xyz, a1.xyz, eps=1e-4)]) # Filter out duplicate atoms. Use slice assignment so that # reducedStructure is not replaced with a list. reducedStructure[:] = [a for a in reducedStructure if not a in duplicates] atomNames = [] for a in reducedStructure: if self._element(a) not in atomNames: atomNames.append(self._element(a)) atomicSpecies = {} for i, elem in enumerate(atomNames): if len(massList) - 1 < i: mass = 0 else: mass = massList[i] if len(psList) - 1 < i: ps = '' else: ps = psList[i] atomicSpecies[elem] = (mass, ps) self[:] = [] # convert to bohr units self.lattice.setLattice(ibrav, self.lattice.a*1.889725989, \ self.lattice.b*1.889725989, self.lattice.c*1.889725989) for atom in reducedStructure: elem = self._element(atom) self.addNewAtom(atype = elem, xyz = atom.xyz, \ mass = atomicSpecies[elem][0], \ potential = atomicSpecies[elem][1],\ lattice = self.lattice, optConstraint = [])
class QEStructure( Structure ): """ Parses and handles Quantum Espresso structure information. QEStructure is inherited from a diffpy.Structure, which is in turn inherited from a Python list. QEStructure is a list of QEAtom object instances. All list functionality is preserved. setitem and setslice methods are overloaded so that the lattice attribute of atoms get set to lattice. QEStructure, QELattice and QEAtom objects contain a hidden QEInput pointer to current Quantum Espresso parsing object. if QEInput.autoUpdate = True (default),any change in a property from QEStructure, QELattice, or QEAtom will automatically invoke QEInput.update(). In that case, QEInput.save() or QEInput.toString() will immediately yield updated QE input file or a string All properties are mutually synchronized. E.g. change in a lattice parameter will also affect other lattice parameters as well as atomic positions according to the lattice type (ibrav) All relevant methods from diffpy.Structure are redefined. QEStructure read only properties, automatically synchronized with current list of Atoms: nat -- "number of atoms" (integer) ntyp -- "number of atomic types" (integer) atomicSpecies -- OrderedDic of AtomicSpecies class instances (see AtomicSpecies class definition) Other properties: atomicPositionsType -- 'crystal' (default), 'alat', 'bohr', and 'angstrom' Note: This version of QEStructure does not support multiple images from QE input files """ def __init__(self, atoms = [], lattice = None, filename = None, qeInput = None): """ atoms -- list of QEAtom atom instances or a QEStructure object lattice -- QELattice object filename -- filename QE input file qeInput -- pointer to a PWInput parsing object. If not None, its PWInput.structure and PWInput.structure.lattice will be reset to the current instance of the structure """ Structure.__init__(self) self.formatString = '%# .8f %# .8f %# .8f' self._atomicPositionsType = 'crystal' self._qeInput = qeInput self.lattice = QELattice() self.lattice._qeInput = self._qeInput if lattice != None: self.lattice = QELattice( lattice = lattice ) self._qeInput = self.lattice._qeInput # CP and PW inputs are compatible from pwinput import PWInput from cpinput import CPInput if isinstance( atoms, PWInput) or isinstance( atoms, CPInput): qeInput = atoms elif isinstance( atoms, QEStructure): stru = atoms self.__dict__.update( stru.__dict__ ) # deep copy of the lattice will deep copy PWInput as well self.lattice = QELattice(lattice = stru.lattice) self._qeInput = self.lattice._qeInput self[:] = stru else: if self.lattice == None: raise "Lattice must be provided" self[:] = atoms if filename != None: qeInput = PWInput(filename = filename) qeInput.parse() self.parseInput(qeInput) if qeInput != None: self.lattice._qeInput = qeInput self._qeInput = qeInput if self.lattice._qeInput != None: self.lattice._qeInput.structure = self self.lattice._qeInput.structure.lattice = self.lattice return def addNewAtom(self, *args, **kwargs): """Add new Atom instance to the end of this Structure. All arguments are forwarded to Atom constructor. No return value. """ kwargs['lattice'] = self.lattice a = QEAtom(*args, **kwargs) list.append(self, a) self._uncache('labels') return def placeInLattice(self, new_lattice): """place structure into new_lattice coordinate system sets lattice to new_lattice and recalculate fractional coordinates of all atoms so their absolute positionls remain the same return self """ Tx = numpy.dot(self.lattice.diffpy().base, new_lattice.diffpy().recbase) Tu = numpy.dot(self.lattice.diffpy().normbase, new_lattice.diffpy().recnormbase) for a in self: a.xyz = numpy.dot(a.xyz, Tx) tmpInput = self.lattice._qeInput self.lattice = new_lattice self.lattice._qeInput = tmpInput self.lattice._qeInput.structure = self return self ############################################################################## # overloaded list methods - taken from diffpy.Structure ############################################################################## def append(self, a, copy=True): """Append atom to a structure and update its lattice attribute. a -- instance of QEAtom copy -- flag for appending a copy of a. When False, append a and update a.owner. No return value. """ self._uncache('labels') adup = copy and QEAtom(a) or a adup.lattice = self.lattice list.append(self, adup) if hasattr(self, '_qeInput') and self._qeInput != None: self._qeInput.update() return def insert(self, idx, a, copy=True): """Insert atom a before position idx in this Structure. idx -- position in atom list a -- instance of QEAtom copy -- flag for inserting a copy of a. When False, append a and update a.lattice. No return value. """ self._uncache('labels') adup = copy and QEAtom(a) or a adup.lattice = self.lattice list.insert(self, idx, adup) if hasattr(self, '_qeInput') and self._qeInput != None: self._qeInput.update() return def extend(self, atoms, copy=True): """Extend Structure by appending copies from a list of atoms. atoms -- list of QEAtom instances copy -- flag for extending with copies of QEAtom instances. When False extend with atoms and update their lattice attributes. No return value. """ self._uncache('labels') if copy: adups = [QEAtom(a) for a in atoms] else: adups = atoms for a in adups: a.lattice = self.lattice list.extend(self, adups) if hasattr(self, '_qeInput') and self._qeInput != None: self._qeInput.update() return def __setitem__(self, idx, a, copy=True): """Set idx-th atom to a. idx -- index of atom in this Structure a -- instance of QEAtom copy -- flag for setting to a copy of a. When False, set to a and update a.lattice. No return value. """ self._uncache('labels') adup = copy and QEAtom(a) or a adup.lattice = self.lattice list.__setitem__(self, idx, adup) if hasattr(self, '_qeInput') and self._qeInput != None: self._qeInput.update() return def __setslice__(self, lo, hi, atoms, copy=True): """Set Structure slice from lo to hi-1 to the sequence of atoms. lo -- low index for the slice hi -- high index of the slice atoms -- sequence of Atom instances copy -- flag for using copies of Atom instances. When False, set to existing instances and update their lattice attributes. No return value. """ self._uncache('labels') if copy: adups = [QEAtom(a) for a in atoms] else: adups = atoms for a in adups: a.lattice = self.lattice list.__setslice__(self, lo, hi, adups) if hasattr(self, '_qeInput') and self._qeInput != None: self._qeInput.update() return def _get_nat(self): return len(self) nat = property(_get_nat, doc ="number of atoms") def _get_ntyp(self): return len(self.atomicSpecies) ntyp = property(_get_ntyp, doc ="number of types") def _get_atomicPositionsType(self): return self._atomicPositionsType def _set_atomicPositionsType(self, value): self._atomicPositionsType = value self.lattice._qeInput.update() atomicPositionsType = property(_get_atomicPositionsType, \ _set_atomicPositionsType, \ doc ="type of atomic positions (crystal, alat ...)") def _get_atomicSpecies(self): atomicSpecies = OrderedDict() for a in self: atomicSpecies[a.element] = AtomicSpecies(element = a.element, \ mass = a.mass, potential = a.potential) return atomicSpecies atomicSpecies = property(_get_atomicSpecies, \ doc ="returns an ordered dictionary with atomic species' objects") def __str__(self): """simple string representation""" s = str(self.lattice) + '\n' if self.atomicPositionsType == 'alat': s = s + 'Atomic positions in units of lattice parametr "a":\n' if self.atomicPositionsType == 'crystal': s = s + 'Atomic positions in crystal coordinates:\n' for atom in self: constraint = atom.optConstraint if self.atomicPositionsType == 'alat': coords = self.lattice.diffpy().cartesian(atom.xyz)/self.lattice.a coords = self.formatString%(coords[0], coords[1], coords[2]) else: if self.atomicPositionsType == 'crystal': coords = self.formatString%(atom.xyz[0], atom.xyz[1], atom.xyz[2]) else: raise NonImplementedError s = s + '%-3s'%self._element(atom) + ' ' + coords + ' ' \ + str(constraint)[1:-1] + '\n' s = s + '\n' for element, specie in self.atomicSpecies.items(): s = s + specie.toString() + '\n' return s def atomLabels(self): labels = [] for l in self.atomicSpecies: labels.append(l) return labels def parseInput(self, qeInput): from qecalc.qetask.qeparser.qestructureparser.qestructureparser import QEStructureParser new_structure = QEStructureParser(qeInput).parseqeInput() self.__Init(new_structure) def __Init(self, structure): QEStructure.__init__(self) if structure is not None: self.__dict__.update(structure.__dict__) self.lattice.__dict__.update(structure.lattice.__dict__) self[:] = structure def read(self, filename, format = 'pwinput'): """Load structure from a file, any original data become lost. l filename -- file to be loaded format -- structure formats 'pwinput' ( pw.x input, default), 'pwoutput' (pw.x output), 'bratoms', 'cif', 'discus', 'pdb', 'pdffit', 'rawxyz', 'xcfg', 'xyz' Return instance of data Parser used to process file. This can be inspected for information related to particular format. """ from qecalc.qetask.qeparser.qestructureparser import parser_index if self._qeInput == None: from qecalc.qetask.qeparser.pwinput import PWInput self._qeInput = PWInput() #self._qeInput.parse() if format in parser_index: module = __import__("qestructureparser.P_" + format, globals(), \ locals(), ['P_' + format], -1) parser = module.getParser(self._qeInput) new_structure = parser.parse(filename) else: diffpyStruct = Structure() parser = diffpyStruct.read(filename, format = format) new_structure = QEStructure(qeInput = self._qeInput) new_structure._setStructureFromDiffpyStructure(diffpyStruct, \ massList = [], psList = [], ibrav = 0) new_structure.lattice._qeInput.update( forceUpdate = True ) self.__Init(new_structure) return parser def readStr(self, s, format = 'pwinput'): """Load structure from a string, any original data become lost. filename -- file to be loaded format -- structure formats 'pwinput' ( pw.x input, default), 'pwoutput' (pw.x output), 'bratoms', 'cif', 'discus', 'pdb', 'pdffit', 'rawxyz', 'xcfg', 'xyz' Return instance of data Parser used to process file. This can be inspected for information related to particular format. """ from qecalc.qetask.qeparser.qestructureparser import parser_index from qecalc.qetask.qeparser.pwinput import PWInput #if self._qeInput == None: # self._qeInput = PWInput() #self._qeInput.parse() if format in parser_index: module = __import__("qestructureparser.P_" + format, globals(), \ locals(), ['P_' + format], -1) parser = module.getParser(self._qeInput) new_structure = parser.parseStr(s) else: diffpyStruct = Structure() parser = diffpyStruct.readStr(s, format = format) new_structure = QEStructure(qeInput = self._qeInput) new_structure._setStructureFromDiffpyStructure(diffpyStruct, \ massList = [], psList = [], ibrav = 0) new_structure.lattice._qeInput.update( forceUpdate = True ) #new_structure._qeInput = new_structure.lattice._qeInput #print 'toString: ', new_structure._qeInput.toString() #self = QEStructure(new_structure) self.__Init(new_structure) return parser def write(self, filename = None, format = "pwinput"): """Save structure to file in the specified format format -- structure formats 'pwinput' ( pw.x input, default), 'pwoutput' (pw.x output), 'bratoms', 'cif', 'discus', 'pdb', 'pdffit', 'rawxyz', 'xcfg', 'xyz' No return value. """ if format == "pwinput": self._qeInput.update( qeInput = qeInput, forceUpdate = True ) if filename == None: filename = self._qeInput.filename input = QEInput(config = self._qeInput.toString(), type='pw') input.save( filename = filename) else: self.diffpy().write( filename = filename, format = format ) return def writeStr(self, format = 'pwinput'): """return string representation of the structure in specified format format -- structure formats 'pwinput' ( pw.x input, default), 'pwoutput' (pw.x output), 'bratoms', 'cif', 'discus', 'pdb', 'pdffit', 'rawxyz', 'xcfg', 'xyz' """ if format == 'pwinput': return self.toString() else: return self.diffpy().writeStr(format = format) def toString(self, string = None): """Writes/updates structure into PW config string. If the string is None, one, based on current structure will be generated """ if string != None: stru = QEStructure() stru.readStr(string, format = 'pwinput') qeInput = stru._qeInput else: qeInput = self._qeInput self._qeInput.update( qeInput = qeInput, forceUpdate = True ) return qeInput.toString() def save(self, filename = None): """Writes/updates structure into PW config file, if the file does not exist, new one will be created""" from os.path import exists from qecalc.qetask.qeparser.pwinput import PWInput if filename != None: if not exists(filename): f = open(filename, 'w') qeInput = PWInput() qeInput.readFile(filename) else: qeInput = self._qeInput self._qeInput.update( qeInput = qeInput, forceUpdate = True ) qeInput.save() def reduce(self, ibrav = None): """ Reduces a structure instance with nonprimitive lattice ( i.e. solves for equivalent atomic positions) according to specified lattice type (ibrav). Each ibrav number corresponds to a specific primitive lattice (see QE documentation). """ ib = self.lattice.ibrav if ibrav != None: ib = ibrav a = self.lattice.diffpy().a b = self.lattice.diffpy().b c = self.lattice.diffpy().c cAB = cosd(self.lattice.diffpy().gamma) cBC = cosd(self.lattice.diffpy().alpha) cAC = cosd(self.lattice.diffpy().beta) if ib > 0: qeLattice = QELattice(ibrav = ib, a = a, b = b, c = c, cBC = cBC, \ cAC = cAC, cAB = cAB) else: qeLattice = QELattice(ibrav = ib, base = self.lattice.base) qeLattice._qeInput = self._qeInput reducedStructure = QEStructure(self) reducedStructure.placeInLattice( qeLattice ) # collect atoms that are at equivalent position to some previous atom duplicates = set([a1 for i0, a0 in enumerate(reducedStructure) for a1 in reducedStructure[i0+1:] if a0.element == a1.element and equalPositions(a0.xyz, a1.xyz, eps=1e-4)]) # Filter out duplicate atoms. Use slice assignment so that # reducedStructure is not replaced with a list. self.lattice = qeLattice self[:] = [a for a in reducedStructure if not a in duplicates] return def load(self, source, **args): task = { 'diffpy': self._setStructureFromDiffpyStructure, 'matter': self._setStructureFromMatter, } if source == 'matter': if 'ibrav' in args and args['ibrav'] != 0: task['matter'] = self._setReducedStructureFromMatter if source == 'diffpy': if 'ibrav' in args and args['ibrav'] != 0: task['diffpy'] = self._setReducedStructureFromDiffpyStructure task[source](**args) self.lattice._qeInput.update() def _matter_diffpy(self, structure): """ converts matter Structure object to diffpy.Structure returns diffpy.Structure object """ l = structure.lattice lat = Lattice( a=l.a, b=l.b, c=l.c, alpha=l.alpha, \ beta=l.beta, gamma=l.gamma) stru = Structure( lattice = lat) for a in structure: stru.addNewAtom(atype = a.symbol, xyz = a.xyz, name = a.symbol, \ anisotropy=a.anisotropy, U=a.U, Uisoequiv=a.Uisoequiv, \ lattice=lat) return stru def _setStructureFromMatter(self, structure, massList = [], psList = [], ibrav = 0): stru = self._matter_diffpy( structure ) self._setStructureFromDiffpyStructure(structure=stru,\ massList=massList, psList=psList,ibrav=ibrav) def _setReducedStructureFromMatter(self, structure, ibrav, massList = [], psList = []): stru = self._matter_diffpy( structure ) self._setReducedStructureFromDiffpyStructure(structure = stru, \ ibrav=ibrav, massList=massList,psList=psList) def _setStructureFromDiffpyStructure(self, structure, massList = [], psList = [], ibrav = 0): """ structure - diffpy.Structure object ibrav - Lattice index psList - list of strings with potential names diffpyStructure object will be modified with reduced atomic positions """ diffpyLattice = structure.lattice qeLattice = QELattice(ibrav = 0, base = diffpyLattice.base) qeLattice.a = 1.889725989*qeLattice.a qeLattice._qeInput = self._qeInput self.lattice = qeLattice self.lattice.type = 'generic cubic' atomNames = [] for a in structure: if self._element(a) not in atomNames: atomNames.append(self._element(a)) atomicSpecies = {} for i, elem in enumerate(atomNames): if len(massList) - 1 < i: mass = 0 else: mass = massList[i] if len(psList) - 1 < i: ps = '' else: ps = psList[i] atomicSpecies[elem] = (mass, ps) self[:] = [] for atom in structure: elem = self._element(atom) self.addNewAtom(atype = elem, xyz = atom.xyz, \ mass = atomicSpecies[elem][0], \ potential = atomicSpecies[elem][1],\ lattice = self.lattice, optConstraint = []) def _setReducedStructureFromDiffpyStructure(self, structure, ibrav, massList = [], psList = []): """ structure - diffpy.Structure object ibrav - Lattice index psList - list of strings with potential names diffpyStructure object will be modified with reduced atomic positions """ import copy diffpyLattice = copy.deepcopy(structure.lattice) a = diffpyLattice.a b = diffpyLattice.b c = diffpyLattice.c cAB = cosd(diffpyLattice.gamma) cBC = cosd(diffpyLattice.alpha) cAC = cosd(diffpyLattice.beta) qeLattice = QELattice(ibrav = ibrav, a = a, b = b, c = c, cBC = cBC, \ cAC = cAC, cAB = cAB) qeLattice._qeInput = self._qeInput self.lattice = qeLattice # make a deep copy: reducedStructure = Structure(atoms = structure) reducedStructure.placeInLattice(Lattice(base=qeLattice.diffpy().base)) # collect atoms that are at equivalent position to some previous atom duplicates = set([a1 for i0, a0 in enumerate(reducedStructure) for a1 in reducedStructure[i0+1:] if self._element(a0) == self._element(a1) and equalPositions(a0.xyz, a1.xyz, eps=1e-4)]) # Filter out duplicate atoms. Use slice assignment so that # reducedStructure is not replaced with a list. reducedStructure[:] = [a for a in reducedStructure if not a in duplicates] atomNames = [] for a in reducedStructure: if self._element(a) not in atomNames: atomNames.append(self._element(a)) atomicSpecies = {} for i, elem in enumerate(atomNames): if len(massList) - 1 < i: mass = 0 else: mass = massList[i] if len(psList) - 1 < i: ps = '' else: ps = psList[i] atomicSpecies[elem] = (mass, ps) self[:] = [] # convert to bohr units self.lattice.setLattice(ibrav, self.lattice.a*1.889725989, \ self.lattice.b*1.889725989, self.lattice.c*1.889725989) for atom in reducedStructure: elem = self._element(atom) self.addNewAtom(atype = elem, xyz = atom.xyz, \ mass = atomicSpecies[elem][0], \ potential = atomicSpecies[elem][1],\ lattice = self.lattice, optConstraint = []) def updatePWInput(self, qeInput = None): """ Deprecated """ self._qeInput.update() def diffpy(self): stru = Structure(lattice = self.lattice.diffpy()) for atom in self: stru.addNewAtom(atype = atom.element, xyz = atom.xyz, \ lattice = self.lattice.diffpy() ) return stru def _element(self, atom): """ Is needed for support both diffpy and matter classes """ if 'element' in dir(atom): return atom.element else: if 'symbol' in dir(atom): return atom.symbol else: raise
class QEStructure(): def __init__(self, fname): """the structure is initialized from PWSCF config file 'lattice' and 'structure' are automatically updated""" self.filename = fname self.atomicSpecies = OrderedDict() self.lattice = None # optConstraints three 1/0 for each coordinate of each atom self.optConstraints = [] self.qeConf = QEConfig(fname) self.qeConf.parse() self.setStructureFromPWSCF() def setStructureFromPWSCF(self): """ Loads structure from PWSCF config file""" self.lattice = QELattice(fname=self.filename) self.structure = Structure(lattice=self.lattice.diffpy()) self.nat = int(self.qeConf.namelist('system').param('nat')) self.ntyp = int(self.qeConf.namelist('system').param('ntyp')) atomicLines = self.qeConf.card('atomic_positions').getLines() self.atomicPositionsType = self.qeConf.card( 'atomic_positions').argument() if self.atomicPositionsType == 'bohr' or self.atomicPositionsType == 'angstrom': raise NotImplementedError if self.atomicPositionsType == None: self.atomicPositionsType = 'alat' for line in atomicLines: if '!' not in line: words = line.split() coords = [float(w) for w in words[1:4]] constraint = [] if len(words) > 4: constraint = [int(c) for c in words[4:7]] self.optConstraints.append(numpy.array(constraint, dtype=int)) atomSymbol = words[0] if self.atomicPositionsType == 'alat': coords = self.lattice.diffpy().fractional( numpy.array(coords[0:3]) * self.lattice.a0) if self.atomicPositionsType == 'crystal': coords = numpy.array(coords[0:3]) self.structure.addNewAtom(atomSymbol, xyz=numpy.array(coords[0:3])) # parse mass ATOMIC_SPECIES section: atomicSpeciesLines = self.qeConf.card( 'atomic_species').getLines() for line in atomicSpeciesLines: if '!' not in line: atomicSpeciesWords = line.split() element = atomicSpeciesWords[0] mass = float(atomicSpeciesWords[1]) ps = atomicSpeciesWords[2] self.atomicSpecies[element] = AtomicSpecies( element, mass, ps) def saveStructureToPWSCF(self, fname=None): """Writes/updates structure into PWSCF config file, if the file does not exist, new one will be created""" if fname != None: filename = fname self.lattice.saveLatticeToPWSCF(filename) qeConf = QEConfig(fname) qeConf.parse() else: filename = self.filename self.lattice.saveLatticeToPWSCF(filename) qeConf = self.qeConf qeConf.parse() qeConf.namelist('system').removeParam('ntyp') qeConf.namelist('system').removeParam('nat') qeConf.namelist('system').addParam('ntyp', self.ntyp) qeConf.namelist('system').addParam('nat', self.nat) if 'atomic_positions' in qeConf.cards: qeConf.removeCard('atomic_positions') qeConf.createCard('atomic_positions') qeConf.card('atomic_positions').setArgument(self.atomicPositionsType) for atom, constraint in zip(self.structure, self.optConstraints): if self.atomicPositionsType == 'alat': coords = self.lattice.diffpy().cartesian(atom.xyz) coords = str(coords / self.lattice.a)[1:-1] else: if self.atomicPositionsType == 'crystal': #coords = str(atom.xyz)[1:-1] coords = '%f.8 %f.8 %f.8' % (atom.xyz[0], atom.xyz[1], atom.xyz[2]) else: raise NonImplementedError line = atom.element + ' ' + coords + ' ' + str( constraint)[1:-1] # line = atom.element + ' ' + coords + ' ' + str(constraint)[1:-1] qeConf.card('atomic_positions').addLine(line) # update ATOMIC_SPECIES card if 'atomic_species' in qeConf.cards: qeConf.removeCard('atomic_species') qeConf.createCard('atomic_species') for element, specie in self.atomicSpecies.items(): qeConf.card('atomic_species').addLine(specie.toString()) qeConf.save(filename) def diffpy(self): return self.structure