def __init__(self, ibrav = 1,a = 1. ,b = 1.,c = 1., cBC = 0.,cAC = 0. ,cAB = 0., base = None, lattice = None, qeInput = None ): self.formatString = '%# .8f %# .8f %# .8f' self._qeInput = qeInput #self._qeInput = None self._type = 'celldm' # Lattice container class, used for lattice operations self.__primitiveLattice = Lattice() # Lattice vectors in bohr or angstrom: self._base = None # initialize the lattice if there is enough information if ibrav > 0 and base != None: self.setLatticeFromQEVectors(ibrav, base) else: self.setLattice(ibrav ,a ,b , c, cBC ,cAC ,cAB, base) # copy constructor: if isinstance(ibrav, QELattice) or lattice != None: if lattice.ibrav > 0: self.setLattice( ibrav = lattice.ibrav, a = lattice.a, \ b = lattice.b, c = lattice.c, cBC = lattice.cBC, cAC = lattice.cAC,\ cAB = lattice.cAB) else: self.setLattice( ibrav = lattice.ibrav, a = lattice.a, \ base = lattice.base ) # copy input: from pwinput import PWInput self._qeInput = PWInput() self._qeInput.readString( lattice._qeInput.toString() )
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 __init__(self, filename=None, config=None, setting=None, parse=True): PWInput.__init__(self, filename, config, type='cp', setting=setting, parse=parse)
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 __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 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 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 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 __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, filename=None, config=None, setting=None, parse=True): PWInput.__init__(self, filename, config, type="cp", setting=setting, parse=parse)
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( 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 QELattice(object): """Class QELattice for working with crystal lattices in QE notation Uses matter.Lattice class for storage Following properties are dynamically linked to other properties (E.g. lattice vectors) and QEInput(if QEInput.autoUpdate = True(default)): ibrav -- lattice type, setting it into a different value will automatically update lattice vectors, QE parsing object and structure if ibrav = 0, only 'a' parameter is relevant a, b, c, cBC, cAC ,cAB -- lattice parameters, setting any of them will dynamically update the QE parsing object (e.g. pw.input) and structure(if relevant). They are also ibrav sensitive. E.g. if ibrav = 1 (simple cubic). Setting 'a' to a \ different value will also modify b and c. type -- lattice type to save into PWSCF cfg file 'celldm' (default) 'traditional' - using a,b,c,cosAC, cosAB, cosBC; 'generic cubic', 'generic hexagonal' - assume that section 'CELL_PARAMETERS' exists 'generic' types also assume/set ibrav = 0 setLattice() -- will set everything at once """ def __init__(self, ibrav = 1,a = 1. ,b = 1.,c = 1., cBC = 0.,cAC = 0. ,cAB = 0., base = None, lattice = None, qeInput = None ): self.formatString = '%# .8f %# .8f %# .8f' self._qeInput = qeInput #self._qeInput = None self._type = 'celldm' # Lattice container class, used for lattice operations self.__primitiveLattice = Lattice() # Lattice vectors in bohr or angstrom: self._base = None # initialize the lattice if there is enough information if ibrav > 0 and base != None: self.setLatticeFromQEVectors(ibrav, base) else: self.setLattice(ibrav ,a ,b , c, cBC ,cAC ,cAB, base) # copy constructor: if isinstance(ibrav, QELattice) or lattice != None: if lattice.ibrav > 0: self.setLattice( ibrav = lattice.ibrav, a = lattice.a, \ b = lattice.b, c = lattice.c, cBC = lattice.cBC, cAC = lattice.cAC,\ cAB = lattice.cAB) else: self.setLattice( ibrav = lattice.ibrav, a = lattice.a, \ base = lattice.base ) # copy input: from pwinput import PWInput self._qeInput = PWInput() self._qeInput.readString( lattice._qeInput.toString() ) def __str__(self): """simple string representation""" st = '' if self._ibrav == 0: st = st + '"generic" cell:\n' else: qeBaseTuple = self._getQEBaseFromParCos(self._ibrav, self._a, self._b, \ self._c, self._cBC, self._cAC, self._cAB) qeBase = numpy.array(qeBaseTuple[1], dtype = float)*qeBaseTuple[0] st = st + '"' + qeBaseTuple[2] + '" cell:\n' for i in range(3): v = self.__primitiveLattice.base[i,:] st = st + self.formatString%(v[0], v[1], v[2]) st = st + '\n' return st def cartesian(self, u): """return cartesian coordinates of a lattice vector""" return self.__primitiveLattice.cartesian(u) def fractional(self, rc): """return fractional coordinates of a cartesian vector""" return self.__primitiveLattice.fractional(rc) def dot(self, u, v): """return dot product of 2 lattice vectors""" return self.__primitiveLattice.dot(u, v) def norm(self, u): """return norm of a lattice vector""" return self.__primitiveLattice.norm(u) def dist(self, u, v): """Return distance of 2 points in lattice coordinates. """ return self.__primitiveLattice.dist(u, v) def angle(self, u, v): """Return angle(u, v) --> angle of 2 lattice vectors in degrees. """ return self.__primitiveLattice.angle(u, v) def recipCartesian(self, kPoint): """Conversts a vector in fractional coordinates in reciprocal space into a vector in cartesian coordinates""" recip_base = self.matter().reciprocal().base*self._a return numpy.dot( kPoint, recip_base) def reciprocalBase(self): """ Get reciprocal lattice vectors in units of 2*pi/a """ return self.matter().reciprocal().base*self._a def latticeParams(self): """Returns tuple of six lattice parameters: a, b, c, cos(BC), cos(AC), cos(AB) """ return [self._a, self._b,self._c, self._cBC, self._cAC, self._cAB] def setLattice(self, ibrav, a = None, b = None, c = None, cBC = None, cAC = None, cAB = None, base = None, updateInput = True): """ 'base', numpy array of lattice vectors, and 'a' will only be used if ibrav == 0. Otherwise, ibrav + lattice parameters will be used""" from math import acos # if [ibrav, a,b,c,cBC,cAC,cAB, base] == 8*[None]: # return None if ibrav == None: raise NotImplementedError('ibrav should be specified') self._ibrav = ibrav if self._ibrav == 0: # print 'Found "generic" cell:' if base == None: raise NotImplementedError('base must be specified') if a == None: raise # a and the base must be provided for ibrav = 0 qeBase = numpy.array(base, dtype = float)#*self._a/a self._a = a # print qeBase #self._a = 1.0 if 'generic' not in self._type: self._type = 'generic cubic' self.__primitiveLattice.setLatBase(qeBase) #self._standardLattice.setLatBase(qeBase) else: # Make sure all lattice parameters are mutually consistent # according to ibrav. base array is not used: if ibrav < 4 or ibrav == 5: if a is not None: self._a = self._b = self._c = a else: if b is not None: self._a = self._b = self._c = b else: if c is not None: self._a = self._b = self._c = c else: self._b = self._c = self._a if ibrav < 4: self._cBC = self._cAC = self._cAB = 0.0 if ibrav == 4 or ibrav == 6 or ibrav == 7: if a is not None: self._a = self._b = a else: if b is not None: self._a = self._b = b else: self._b = self._a if c is not None: self._c = c if ibrav == 4: self._cAB = cosd(120.0) self._cBC = self._cAC = 0.0 if ibrav == 5: if cBC is not None: self._cBC = self._cAC = self._cAB = cBC else: if cAC is not None: self._cBC = self._cAC = self._cAB = cAC else: if cAB is not None: self._cBC = self._cAC = self._cAB = cAB else: self._cAC = self._cAB = self._cBC if ibrav == 6 or ibrav == 7: self._cBC = self._cAC = self._cAB = 0.0 if ibrav > 7 and ibrav <= 14: if a is not None: self._a = a if b is not None: self._b = b if c is not None: self._c = c if ibrav > 7 and ibrav < 12: self._cBC = self._cAC = self._cAB = 0.0 if ibrav == 12 or ibrav == 13: if cAB is not None: self._cAB = cAB else: if cBC is not None or cAC is not None: raise Exception("Should specify cos(AB) only for" + \ " ibrav = 12 or ibrav = 13" ) self._cBC = self._cAC = 0.0 if ibrav == 14: if cBC is not None: self._cBC = cBC if cAC is not None: self._cAC = cAC if cAB is not None: self._cAB = cAB qeBaseTuple = self._getQEBaseFromParCos(self._ibrav, self._a, self._b, self._c, self._cBC, self._cAC, self._cAB) qeBase = numpy.array(qeBaseTuple[1], dtype = float)*qeBaseTuple[0] self.__primitiveLattice.setLatBase(qeBase) alpha = degrees(acos(self._cBC)) beta = degrees(acos(self._cAC)) gamma = degrees(acos(self._cAB)) #self._standardLattice.setLatPar(self._a,self._b,self._c,alpha,beta,gamma) #self._a0 = a self._base = qeBase if self._qeInput != None and updateInput == True: #self.updatePWInput() self._qeInput.update( forceUpdate = True ) def matter(self): '''Returns matter.Lattice object. Do not use it for reading QE (standard cell) lattice parameters. Use latticeParams, or a, b, c , ... instead''' return self.__primitiveLattice def updatePWInput(self, qeInput = None): """ Deprecated """ self._qeInput.update( forceUpdate = True ) def setLatticeFromQEVectors(self, ibrav, vectors): """ Will extract conventional lattice parameters from primitive vectors. 'vectors' - is a list with primitive vectors (in QE notation), including lattice parameters. For example from PWSCF output""" from numpy import dot # default values: a = b = c = 1.0 cBC = cAC = cAB = 0.0 v = numpy.array(vectors, dtype = float) if ibrav == 0: self.setLattice(ibrav = 0, a = 1.0, base = vectors ) return #raise NotImplementedError # sc simple cubic: if ibrav == 1: a = v[0,0] if ibrav == 2: a = b = c = sqrt( 2.0*dot(v[0,:],v[0,:]) ) if ibrav == 3: a = b = c = 2.0*sqrt( dot(v[0,:],v[0,:])/3.0) if ibrav == 4: a = b = sqrt( dot(v[0,:],v[0,:])) c = sqrt( dot(v[2,:],v[2,:])) cAB = cosd(120.0) if ibrav == 5: a = b = c = sqrt( dot(v[0,:],v[0,:])) cBC = cAC = cAB = dot(v[0,:],v[2,:])/a**2 if ibrav == 6: a = b = sqrt( dot(v[0,:],v[0,:])) c = sqrt( dot(v[2,:],v[2,:])) if ibrav == 7: a = b = v[1,0] - v[2,0] c = v[1,2] + v[2,2] if ibrav == 8: a = v[0,0] b = v[1,1] c = v[2,2] if ibrav == 9: a = v[0,0] - v[1,0] b = v[0,1] + v[1,1] c = v[2,2] if ibrav == 10: a = v[2,0] - v[0,0] - v[1,0] b = v[2,1] - v[0,1] + v[1,1] c = v[0,2] - v[1,2] + v[2,2] if ibrav == 11: a = v[0,0] - v[1,0] b = v[1,1] - v[2,1] c = v[0,2] - v[2,2] if ibrav == 12: a = v[0,0] b = sqrt( dot(v[1,:],v[1,:])) cAB = v[1,0]/b c = v[2,2] if ibrav == 13: a = v[0,0] + v[2,0] b = sqrt( dot(v[1,:],v[1,:])) c = v[2,2] - v[0,2] cAB = v[1,0]/b if ibrav == 14: a = v[0,0] b = sqrt( dot(v[1,:],v[1,:])) cAB = v[1,0]/b c = sqrt( dot(v[2,:],v[2,:])) cAC = v[2,0]/c cBC = v[2,1]*sqrt(1.0 - cAB**2)/c + cAC*cAB self.setLattice(ibrav, a, b, c, cBC, cAC, cAB) #################################################################### # property handlers #################################################################### # lattice parameters # def _get_a0(self): # if self._a0 != None: # return self._a0 # else: # return self._a # a0 = property(_get_a0, doc ="old lattice parameter a0") def _get_a(self): return self._a def _set_a(self, value): #self._a = value self.setLattice(ibrav = self._ibrav, a = float(value), base = self._base/self._a*value) a = property(_get_a, _set_a, doc ="lattice parameter a") def _get_b(self): return self._b def _set_b(self, value): #self._b = value self.setLattice(ibrav = self._ibrav, b = float(value)) b = property(_get_b, _set_b, doc ="""lattice parameter b""") def _get_c(self): return self._c def _set_c(self, value): #self._c = value self.setLattice(ibrav = self._ibrav, c = float(value)) c = property(_get_c, _set_c, doc ="""lattice parameter c""") def _get_cBC(self): return self._cBC def _set_cBC(self, value): #self._cBC = value self.setLattice(ibrav = self._ibrav, cBC = float(value)) cBC = property(_get_cBC, _set_cBC, doc ="""lattice parameter cBC""") def _get_cAC(self): return self._cAC def _set_cAC(self, value): #self._cAC = value self.setLattice(ibrav = self._ibrav, cAC = float(value)) cAC = property(_get_cAC, _set_cAC, doc ="""lattice parameter cAC""") def _get_cAB(self): return self._cAB def _set_cAB(self, value): #self._cAB = value self.setLattice(ibrav = self._ibrav, cAB = float(value)) cAB = property(_get_cAB, _set_cAB, doc ="""lattice parameter cAB""") def _get_ibrav(self): return self._ibrav def _set_ibrav(self, value): if value < 0: value = 0 ibravOld = self._ibrav self._ibrav = value if value == 0: base = self._base#/self._a if ibravOld != 4: self._type = 'generic cubic' else: self._type = 'generic hexagonal' self.setLattice(ibrav = self._ibrav, a = self._a, base = base) else: if 'generic' in self._type: self._type = 'celldm' base = self._getQEBaseFromParCos( ibrav = self._ibrav, \ a = self._a, b = self._b, c = self._c, cBC = self._cBC, \ cAC = self._cAC, cAB = self._cAB ) qebase = base[0]*numpy.array(base[1]) self.setLatticeFromQEVectors(self._ibrav, qebase) # self.setLattice(self._ibrav) ibrav = property(_get_ibrav, _set_ibrav, doc ="""Lattice symmetry parameter ibrav. Changing it will update the lattice, but leave atomic fractional coordinates unchanged""") def _get_type(self): return self._type def _set_type(self, value): if 'generic' in value: self._type = value self._ibrav = 0 base = self._base #/self._a self.setLattice(ibrav = self._ibrav, a = self._a, base = base) else: if self._ibrav == 0: pass else: self._type = value type = property(_get_type, _set_type, doc ="""QE lattice type: 'celldm', 'traditional' or 'generic cubic', 'generic hexagonal'(implies ibrav = 0""") def _get_base(self): return self._base base = property(_get_base, doc ="""QE base array""") def _getQEBaseFromParCos( self, ibrav = 1, a = 1., b = 1., c = 1., cBC = 0.,cAC = 0. ,cAB = 0.): c_a = float(c)/a # description dictionary of QE base vectors: QEBase = { # sc simple cubic: 1 : (a, [[1, 0, 0], [0, 1, 0], [0, 0, 1]], 'Simple Cubic'), # fcc face centered cubic: 2 : (a/2., [[-1, 0, 1], [0, 1, 1], [-1, 1, 0]], 'Face Centered Cubic'), # bcc body entered cubic: 3 : (a/2., [[1, 1, 1], [-1, 1, 1], [-1, -1, 1]], 'Body Centered Cubic'), # simple hexagonal and trigonal(p): 4 : (a, [[1, 0, 0], [-0.5, sqrt(3.0)/2.0, 0.], [0, 0, c_a]], 'Simple Hexagonal or Trigonal(P)'), # trigonal(r): 5 : (a, [[sqrt((1.-cAB)/2.),-sqrt((1.-cAB)/6.), sqrt((1.+2.*cAB)/3.)], [0, 2.*sqrt((1.-cAB)/6.), sqrt((1.+2.*cAB)/3.)], [-sqrt((1.-cAB)/2.), -sqrt((1.-cAB)/6.), sqrt((1.+2.*cAB)/3.)]], 'Trigonal(R)'), # simple tetragonal (p): 6 : (a, [[1, 0, 0], [0, 1, 0.], [0, 0, c_a]], 'Simple Tetragonal(P)'), # body centered tetragonal (i): 7 : (a/2., [[1, -1, c_a], [1, 1, c_a], [-1, -1, c_a]], 'Body Centered Tetragonal (I)'), # simple orthorhombic (p): 8 : (1.0, [[a, 0., 0.], [0., b, 0.], [0., 0., c]], 'Simple Orthorhombic (P)'), # bco base centered orthorhombic: 9: (1.0, [[a/2., b/2., 0.], [-a/2., b/2., 0.], [0., 0., c]], 'Base Centered Orthorhombic'), # face centered orthorhombic: 10: (1.0, [[a/2., 0., c/2.], [a/2., b/2., 0.], [0., b/2., c/2.]], 'Face Centered Orthorhombic' ), # body centered orthorhombic: 11: (1.0, [[a/2., b/2., c/2.], [-a/2., b/2., c/2.], [-a/2., -b/2., c/2.]], 'Body Centered Orthorhombic'), # monoclinic (p): 12: (1.0, [[a, 0, 0], [b*cAB, b*sqrt(1.0 - cAB**2), 0], [0, 0, c]], 'Monoclinic (P)'), # base centered monoclinic: 13: (1.0, [[a/2., 0, -c/2.], [b*cAB, b*sqrt(1.0 - cAB**2), 0], [a/2., 0, c/2.]], 'Base Centered Monoclinic'), # triclinic: 14: (1.0, [[a, 0, 0], [b*cAB, b*sqrt(1.0 - cAB**2), 0], [c*cAC, c*( cBC-cAC*cAB )/sqrt(1.-cAB**2), c*sqrt( 1. + 2.*cBC*cAC*cAB - cBC**2 - cAC**2 - cAB**2)/sqrt(1.-cAB**2)]], 'Triclinic') } return QEBase[ibrav]
def make_fd_supercell(pw: PWInput, q_cry, ucart, skip_minkowski=False): import copy from math import floor, ceil from itertools import product """ Generate nondiagonal supercell with finite displacements qe_input: a Pwscf() instance of an input file q_cry: perturbation crystal momentum, (m1/n1, m2/n2, m3/n3). ucart: displacement of atoms in the unit cell in cartesian alat units """ # find fractional representation of q tol = 1E-10 mlist = [] nlist = [] for i in range(3): found = False for n in range(1, 101): m = round(abs(q_cry[i]) * n) if abs(abs(q_cry[i]) - m / n) < tol: mlist.append(m) nlist.append(n) found = True break if not found: print(f"Unable to find fractional representation of q = {q[i]}") return None s_new = find_nondiagonal(mlist, nlist) multiplier = s_new[0, 0] * s_new[1, 1] * s_new[2, 2] cell_new = np.einsum('ij,xj->xi', s_new, pw.cell) nat_new = pw.nat * multiplier # Minkowski reduction of lattice vector: linear combination to make them # as short as possible if skip_minkowski: cell_red = np.copy(cell_new) else: cell_red = minkowski_reduce(cell_new) cell_cell = np.einsum('xi,xj->ij', pw.cell, pw.cell) redcell_cell = np.einsum('xi,xj->ij', cell_red, pw.cell) s_red = redcell_cell @ np.linalg.inv(cell_cell) s_red = np.rint(s_red) atoms_name_new, atoms_cart_new = atom_supercell(pw, s_new) # move atoms to [0, 1)^3 unit cell of cell_red atoms_cry_new = np.linalg.inv(cell_red) @ atoms_cart_new for iat in range(nat_new): for idir in range(3): atoms_cart_new[:, iat] -= floor( atoms_cry_new[idir, iat]) * cell_red[:, idir] # apply displacements s_diag = np.diag(s_new) for nz, ny, nx in product(range(s_diag[2]), range(s_diag[1]), range(s_diag[0])): icell = nx + ny * s_diag[0] + nz * s_diag[0] * s_diag[1] phase = np.exp(2j * np.pi * sum([x * y for x, y in zip(q_cry, (nx, ny, nz))])) for iat in range(pw.nat): atoms_cart_new[:, icell * pw.nat + iat] += (ucart[:, iat] * phase).real pw_super = PWInput(nat=nat_new, cell=cell_red, atoms_name=atoms_name_new, atoms_cart=atoms_cart_new, input_cards=copy.deepcopy(pw.input_cards), input_namelists=copy.deepcopy(pw.input_namelists)) if 'ATOMIC_POSITIONS' in pw_super.input_cards.keys(): input_atmpos = ['ATOMIC_POSITIONS alat \n'] for iat in range(pw_super.nat): line = ( f" {atoms_name_new[iat]:4s} {atoms_cart_new[0, iat]:18.12f}" f" {atoms_cart_new[1, iat]:18.12f} {atoms_cart_new[2, iat]:18.12f}\n" ) input_atmpos.append(line) pw_super.input_cards['ATOMIC_POSITIONS'] = input_atmpos if 'CELL_PARAMETERS' in pw_super.input_cards.keys(): input_cell = ['CELL_PARAMETERS alat\n'] for i in range(3): line = (f" {pw_super.cell[0,i]:18.12f} {pw_super.cell[1,i]:18.12f}" f" {pw_super.cell[2,i]:18.12f}\n") input_cell.append(line) pw_super.input_cards['CELL_PARAMETERS'] = input_cell if 'K_POINTS' in pw_super.input_cards.keys(): nks = np.array( [int(x) for x in pw.input_cards['K_POINTS'][1].split()[:3]]) dks = np.linalg.norm(lat_to_reclat(pw.cell), axis=0) / nks nks_new = np.linalg.norm(lat_to_reclat(pw_super.cell), axis=0) / min(dks) nks_new = [ceil(x - 1E-4) for x in nks_new] # minus 1E-3 to avoid floating point truncation error (of the input file) # If original nk is 8, nks_new can become 8.00001 due to small digits in # the input file. If we ceil this, it becomes 9. We want it to be 8, so # we subtract a small value, 1E-4, from nks_new. input_kpt = ['K_POINTS automatic\n'] input_kpt.append( f" {nks_new[0]:3d}{nks_new[1]:3d}{nks_new[2]:3d} 0 0 0") pw_super.input_cards['K_POINTS'] = input_kpt # update namelists try: pw_super.input_namelists['SYSTEM']['ibrav'] = "0" pw_super.input_namelists['SYSTEM']['nat'] = f"{pw_super.nat}" except KeyError as e: print("KeyError in pw_super.input_namelists, keyword {e.args[0]}") return pw_super