Example #1
0
 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() )
Example #2
0
    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
Example #3
0
 def __init__(self, filename=None, config=None, setting=None, parse=True):
     PWInput.__init__(self,
                      filename,
                      config,
                      type='cp',
                      setting=setting,
                      parse=parse)
Example #4
0
    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
Example #5
0
    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
Example #6
0
 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()
Example #7
0
    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
Example #8
0
 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()
Example #9
0
    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
Example #10
0
 def __init__(self, filename=None, config=None, setting=None, parse=True):
     PWInput.__init__(self, filename, config, type="cp", setting=setting, parse=parse)
Example #11
0
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
Example #12
0
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
Example #13
0
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]
Example #14
0
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