Пример #1
0
    def CIFopen(self, ciffile=None, cifblkname=None):
        from CifFile import ReadCif # part of the PycifRW module
        try:
            # the following is a trick to avoid that urllib.URLopen.open
            # used by ReadCif misinterprets the url when a drive:\ is 
            # present (Win)
            ciffile = ciffile.replace(':','|')
            cf = ReadCif(ciffile)
        except:
            logging.error('File %s could not be accessed' %ciffile)        

        if cifblkname == None:   
            #Try to guess blockname                                                     
            blocks = cf.keys()
            if len(blocks) > 1:
                if len(blocks) == 2 and 'global' in blocks:
                    cifblkname = blocks[abs(blocks.index('global') - 1)]
                else:
                    logging.error('More than one possible data set:')
                    logging.error('The following data block names are in the file:')
                    for block in blocks:
                        logging.error(block)
                    raise Exception
            else:
                # Only one available
                cifblkname = blocks[0]
        #Extract block
        try:
            self.cifblk = cf[cifblkname]
        except:
            logging.error('Block - %s - not found in %s' % (blockname, ciffile))
            raise IOError
        return self.cifblk
Пример #2
0
    def CIFopen(self, ciffile=None, cifblkname=None):
        from CifFile import ReadCif  # part of the PycifRW module
        try:
            # the following is a trick to avoid that urllib.URLopen.open
            # used by ReadCif misinterprets the url when a drive:\ is
            # present (Win)
            ciffile = ciffile.replace(':', '|')
            cf = ReadCif(ciffile)
        except:
            logging.error('File %s could not be accessed' % ciffile)

        if cifblkname == None:
            #Try to guess blockname
            blocks = list(cf.keys())
            if len(blocks) > 1:
                if len(blocks) == 2 and 'global' in blocks:
                    cifblkname = blocks[abs(blocks.index('global') - 1)]
                else:
                    logging.error('More than one possible data set:')
                    logging.error(
                        'The following data block names are in the file:')
                    for block in blocks:
                        logging.error(block)
                    raise Exception
            else:
                # Only one available
                cifblkname = blocks[0]
        #Extract block
        try:
            self.cifblk = cf[cifblkname]
        except:
            logging.error('Block - %s - not found in %s' %
                          (blockname, ciffile))
            raise IOError
        return self.cifblk
Пример #3
0
    def test_calculating_xrd_pattern_from_cif_file(self):
        fcc_cif = ReadCif(get_cif_url('fcc.cif'))
        cif_phase = CifPhase(fcc_cif[fcc_cif.keys()[0]])
        cif_converter = CifConverter(0.31)
        jcpds_phase = cif_converter.convert_cif_phase_to_jcpds(cif_phase)

        self.assertAlmostEqual(jcpds_phase.reflections[0].intensity, 100, places=4)
        self.assertAlmostEqual(jcpds_phase.reflections[0].d0, 2.814, places=4)
        self.assertAlmostEqual(jcpds_phase.reflections[1].d0, 2.437, places=4)
Пример #4
0
    def test_calculating_xrd_pattern_from_cif_file(self):
        fcc_cif = ReadCif(get_cif_url('fcc.cif'))
        cif_phase = CifPhase(fcc_cif[fcc_cif.keys()[0]])
        cif_converter = CifConverter(0.31)
        jcpds_phase = cif_converter.convert_cif_phase_to_jcpds(cif_phase)

        self.assertAlmostEqual(jcpds_phase.reflections[0].intensity, 100, places=4)
        self.assertAlmostEqual(jcpds_phase.reflections[0].d0, 2.814, places=4)
        self.assertAlmostEqual(jcpds_phase.reflections[1].d0, 2.437, places=4)
Пример #5
0
def import_cif(file_path, occupancy_tolerance=100):
    """
    Import cif with pymatgen. This is the current default.
    
    Aruments
    --------
    occupancy_tolerance: int
        Occupancy tolerance for pymatgen.io.cif.CifParser. Files from the 
        CSD can contain multiple atoms which sit on the same site when 
        PBC are taken into account. Pymatgen correctly recognizes these as the
        same atom, however, it has a tolerance for the number of atoms which it
        will assume are the same. This is the occupancy tolerance. I don't 
        believe this value should be limited and thus it's set at 100.
    """
    try:
        pycif = CifParser(file_path, occupancy_tolerance=occupancy_tolerance)
        pstruct_list = pycif.get_structures(primitive=False)
        struct = Structure.from_pymatgen(pstruct_list[0])
    except:
        struct = import_cif_ase(file_path)

    file_name = os.path.basename(file_path)
    struct.struct_id = file_name.replace('.cif', '')

    #### Read in data that may be stored as a CIF property
    if read_cif_data:
        ### Wrap in try except in case of PyCIFRW errors
        try:
            cif = ReadCif(file_path)
        except:
            print("Error in reading CIF data for {}".format(file_path))
            return struct

        if len(cif) > 1:
            raise Exception("Must only have one structure per CIF file.")

        cif_struct = [x for x in cif.keys()]
        cif_data = cif[cif_struct[0]]
        for key, value in cif_data.items():
            ## Skip over geometry information
            if "_atom_" == key[0:len("_atom_")]:
                continue
            elif "_cell_" == key[0:len("_cell_")]:
                continue
            elif "_symmetry_equiv" == key[0:len("_symmetry_equiv")]:
                continue
            else:
                pass

            ## Otherwise add info to structure property information
            struct.properties[key] = value

    return struct
Пример #6
0
def read_reference(cif_file):
    """
    Read a cif file and return a :mod:`~qmpy.Reference`.
    """
    cf = ReadCif(cif_file, grammar='1.1')
    cif_block = cf[cf.keys()[0]]
    reference = rx.Reference()
    reference.authors = _get_authors(cif_block)
    reference.journal = _get_journal(cif_block)
    reference.volume = _get_volume(cif_block)
    reference.year = _get_year(cif_block)
    reference.first_page = _get_first_page(cif_block)
    reference.last_page = _get_last_page(cif_block)
    reference.title = _get_title(cif_block)
    return reference
Пример #7
0
def read_reference(cif_file):
    """
    Read a cif file and return a :mod:`~qmpy.Reference`.
    """
    cf = ReadCif(cif_file, grammar='1.1')
    cif_block = cf[cf.keys()[0]]
    reference = rx.Reference()
    reference.authors = _get_authors(cif_block)
    reference.journal = _get_journal(cif_block)
    reference.volume = _get_volume(cif_block)
    reference.year = _get_year(cif_block)
    reference.first_page = _get_first_page(cif_block)
    reference.last_page = _get_last_page(cif_block)
    reference.title = _get_title(cif_block)
    return reference
Пример #8
0
    def convert_cif_to_jcpds(self, filename):
        """
        Reads a cif file and returns a jcpds with correct reflection and intensities
        :param filename:  cif filename
        :return: converted jcpds object
        :rtype: jcpds
        """
        file_url = 'file:' + urllib.pathname2url(filename)
        cif_file = ReadCif(file_url)
        cif_phase = CifPhase(cif_file[cif_file.keys()[0]])
        jcpds_phase = self.convert_cif_phase_to_jcpds(cif_phase)
        jcpds_phase.filename = filename
        jcpds_phase.name = os.path.splitext(os.path.basename(filename))[0]
        jcpds_phase.modified = False

        return jcpds_phase
Пример #9
0
    def convert_cif_to_jcpds(self, filename):
        """
        Reads a cif file and returns a jcpds with correct reflection and intensities
        :param filename:  cif filename
        :return: converted jcpds object
        :rtype: jcpds
        """
        file_url = 'file:' + pathname2url(filename)
        cif_file = ReadCif(file_url)
        cif_phase = CifPhase(cif_file[cif_file.keys()[0]])
        jcpds_phase = self.convert_cif_phase_to_jcpds(cif_phase)
        jcpds_phase.filename = filename
        jcpds_phase.name = os.path.splitext(os.path.basename(filename))[0]
        jcpds_phase.modified = False

        return jcpds_phase
Пример #10
0
    def test_reading_phase(self):
        fcc_cif = ReadCif(get_cif_url('fcc.cif'))

        cif_phase = CifPhase(fcc_cif[fcc_cif.keys()[0]])

        self.assertEqual(cif_phase.a, 4.874)
        self.assertEqual(cif_phase.b, 4.874)
        self.assertEqual(cif_phase.c, 4.874)

        self.assertEqual(cif_phase.alpha, 90)
        self.assertEqual(cif_phase.beta, 90)
        self.assertEqual(cif_phase.gamma, 90)

        self.assertEqual(cif_phase.volume, 115.79)
        self.assertEqual(cif_phase.space_group_number, 225)

        self.assertEqual(len(cif_phase.atoms), 8)
        self.assertEqual(cif_phase.comments, 'HoN, Fm-3m - NaCl structure type, ICSD 44776')
Пример #11
0
    def test_reading_phase(self):
        fcc_cif = ReadCif(get_cif_url('fcc.cif'))

        cif_phase = CifPhase(fcc_cif[fcc_cif.keys()[0]])

        self.assertEqual(cif_phase.a, 4.874)
        self.assertEqual(cif_phase.b, 4.874)
        self.assertEqual(cif_phase.c, 4.874)

        self.assertEqual(cif_phase.alpha, 90)
        self.assertEqual(cif_phase.beta, 90)
        self.assertEqual(cif_phase.gamma, 90)

        self.assertEqual(cif_phase.volume, 115.79)
        self.assertEqual(cif_phase.space_group_number, 225)

        self.assertEqual(len(cif_phase.atoms), 8)
        self.assertEqual(cif_phase.comments, 'HoN, Fm-3m - NaCl structure type, ICSD 44776')
Пример #12
0
def read(cif_file, grammar=None):
    """
    Takes a CIF format file, and returns a Structure object. Applies all
    symmetry operations in the CIF to the atoms supplied with the structure. If
    these are not correct, the structure will not be either. If the CIF
    contains more than one file, the return will be a list. If not, the return
    will be a single structure (not in a len-1 list).

    Examples::

        >>> s = io.cif.read(INSTALL_PATH+'io/files/fe3o4.cif')

    """
    if grammar:
        cf = ReadCif(cif_file, grammar=grammar)
    else:
        cf = ReadCif(cif_file)
    structures = []
    for key in cf.keys():
        structures.append(_read_cif_block(cf[key]))
    if len(structures) == 1:
        return structures[0]
    else:
        return structures
Пример #13
0
def read(cif_file, grammar=None):
    """
    Takes a CIF format file, and returns a Structure object. Applies all
    symmetry operations in the CIF to the atoms supplied with the structure. If
    these are not correct, the structure will not be either. If the CIF
    contains more than one file, the return will be a list. If not, the return
    will be a single structure (not in a len-1 list).

    Examples::

        >>> s = io.cif.read(INSTALL_PATH+'io/files/fe3o4.cif')

    """
    if grammar:
        cf = ReadCif(cif_file, grammar=grammar)
    else:
        cf = ReadCif(cif_file)
    structures = []
    for key in cf.keys():
        structures.append(_read_cif_block(cf[key]))
    if len(structures) == 1:
        return structures[0]
    else:
        return structures
Пример #14
0
    def run(config, input, output):

        dir = config['xcDir']

        # Copy cif2cell related input to cif2cellInput here. Later we will be overriding some settings,
        # so we want to keep the original input intact.
        cif2cellInput = {
            key: input[key]
            for key in input if key.startswith('cif2cell.')
        }

        # Generate any data that is needed from generic input and populate cif2cellInput with
        # global data (needed for all feff runs.)
        cif2cellInput['cif2cell.cif_input'] = input['cif_input']

        # Set executable directory for this exchange
        cif2celldir = config['cif2cell']

        # Set input file - cif2cell takes input from command line, so no input file, however, maybe we can take commands from input file?
        #inpf = os.path.join(dir, 'cif2cell.in')

        # Loop over targets in output. Not sure if there will ever be more than one output target here.
        if set(output.keys()).issubset(
                set([
                    'cell_vectors', 'cell_struct_xyz_red', 'cell_scaling_iso',
                    'cell_scaling_abc', 'number_density', 'supercell'
                ])):
            # Set output and error files
            with open(os.path.join(dir, 'corvus.CIF2CELL.stdout'),
                      'wb') as out, open(
                          os.path.join(dir, 'corvus.CIF2CELL.stderr'),
                          'wb') as err:
                # Copy necessary files to dir
                cif_file = cif2cellInput['cif2cell.cif_input'][0][0]
                shutil.copy(cif_file, dir)

                # Loop over executables: This is specific to feff. Other codes
                # will more likely have only one executable.
                executables = ['cif2cell']

                args = [cif_file, '-p', 'cif', '-o', 'out.cif']
                iExec = 0
                for executable in executables:
                    runExecutable(cif2celldir, dir, executable, args, out, err)

                # For now, I am only passing the directory.
                print('Setting output')
                outCif = os.path.join(dir, 'out.cif')
                cifFile = ReadCif(outCif)
                # cifFile now contains an object that acts like a dictionary
                # of dictionaries, with the outer keys the data for each
                # structure listed in the cif, next the normal cif data, like
                # '_cell_angle_alpha', and '_atom_site_fract_[xyz]'.
                # Note that as indicated above, the cif can hold multiple
                # structures. For now I will assume that there is only one.
                cif_dict = cifFile[list(cifFile.keys())[0]]

                # Now lets make all of the data structures that we want out of
                # this. We are using the CellData structure from cif2cell's
                # uctools package. Right now, I have cif2cell write another
                # cif file first, then we read it in and analyze it. This is
                # because we don't want to reproduce all of the sanity check
                # that cif2cell already has in it.
                cell_data = CellData()
                # Fill the cell data object from the cif file.
                cell_data.getFromCIF(cif_dict)

                # Calculate cell structure for the primitive cell for now. Supercells are
                # represented as P1, so this will not harm that.
                cell_data.primitive()

                # The cell_data structure now has all cell data that we need
                # in it, or a method to get that data.
                if 'cell_vectors' in output:
                    output['cell_vectors'] = cell_data.latticevectors

                if 'cell_struct_xyz_red' in output:
                    xred = []
                    for atom in cell_data.atomdata:
                        if atom:
                            xred = xred + [atom[0].position]

                    output['cell_struct_xyz_red'] = xred

                if 'cell_scaling_iso' in output:
                    output['cell_scaling_iso'] = [[cell_data.lengthscale]]

                if 'cell_scaling_abc' in output:
                    # For now I will output cell_data_abc as 1, since cif2cell seems to put things into lengthscale
                    # and a,b,c are for convenience.
                    output['cell_scaling_abc'] = [[1.0, 1.0, 1.0]]

        elif 'cell_structure' in output:
            # Return path to cif file.
            output['cell_structure'] = [['Working']]
Пример #15
0
    def _readCif(self, fcif=DFLT_NAME + '.cif'):
        """
        >> @AUTHOR:     Saransh Singh, Lawrence Livermore National Lab, [email protected]
        >> @DATE:       10/16/2019 SS 1.0 original
        >> @DETAILS:    hexrd3 will have real structure factors and will require the overhaul
                        of the crystallography. In this effort, we will have a cif reader and
                        also the HDF5 format reader in the material class. We will be using
                        pycifrw for i/o
        """

        # make sure file exists etc.
        if (fcif == Material.DFLT_NAME + '.cif'):
            try:
                cif = ReadCif(fcif)
            except (OSError):
                raise RuntimeError(
                    'OS Error: No file name supplied and default file name not found.'
                )
        else:
            try:
                cif = ReadCif(fcif)
            except (OSError):
                raise RuntimeError('OS Error: File not found')

        # read the file
        for k in cif.keys():
            if ('_cell_length_a' in cif[k]):
                m = k
                break
        cifdata = cif[m]
        # cifdata = cif[cif.keys()[0]]

        # make sure the space group is present in the cif file, either as
        # international table number, hermann-maguain or hall symbol
        sgkey = [
            '_space_group_IT_number', '_symmetry_space_group_name_h-m',
            '_symmetry_space_group_name_hall', '_symmetry_Int_Tables_number'
        ]

        sgdata = False
        for key in sgkey:
            sgdata = sgdata or (key in cifdata)
            if (sgdata):
                skey = key
                break

        if (not (sgdata)):
            raise RuntimeError(' No space group information in CIF file! ')

        sgnum = 0
        if skey is sgkey[0]:
            sgnum = int(cifdata[sgkey[0]])
        elif (skey is sgkey[1]):
            HM = cifdata[sgkey[1]]
            HM = HM.replace(" ", "")
            sgnum = HM_to_sgnum[HM]
        elif (skey is sgkey[2]):
            hall = cifdata[sgkey[2]]
            hall = hall.replace(" ", "")
            sgnum = Hall_to_sgnum[HM]
        elif (skey is sgkey[3]):
            sgnum = int(cifdata[sgkey[3]])

        # lattice parameters
        lparms = []
        lpkey = ['_cell_length_a', '_cell_length_b', \
                 '_cell_length_c', '_cell_angle_alpha', \
                 '_cell_angle_beta', '_cell_angle_gamma']

        for key in lpkey:
            n = cifdata[key].find('(')
            if (n != -1):
                lparms.append(float(cifdata[key][:n]))
            else:
                lparms.append(float(cifdata[key]))

        for i in range(6):
            if (i < 3):
                lparms[i] = _angstroms(lparms[i])
            else:
                lparms[i] = _degrees(lparms[i])

        self._lparms = lparms
        self.sgnum = sgnum

        # fractional atomic site, occ and vibration amplitude
        fracsitekey = ['_atom_site_fract_x', '_atom_site_fract_y',\
                        '_atom_site_fract_z',]

        occ_U       = ['_atom_site_occupancy',\
                        '_atom_site_u_iso_or_equiv','_atom_site_U_iso_or_equiv']

        sitedata = True
        for key in fracsitekey:
            sitedata = sitedata and (key in cifdata)

        if (not (sitedata)):
            raise RuntimeError(
                ' fractional site position is not present or incomplete in the CIF file! '
            )

        atompos = []
        for key in fracsitekey:
            slist = cifdata[key]
            pos = []

            for p in slist:
                n = p.find('(')

                if (n != -1):
                    pos.append(p[:n])
                else:
                    pos.append(p)
            '''
            sometimes cif files have negative values so need to
            bring them back to fractional coordinates between 0-1
            '''
            pos = numpy.asarray(pos).astype(numpy.float64)
            pos, _ = numpy.modf(pos + 100.0)
            atompos.append(pos)
        """note that the vibration amplitude, U is just the amplitude (in A)
            to convert to the typical B which occurs in the debye-waller factor,
            we will use the following formula
            B = 8 * pi ^2 * < U_av^2 >
            this will be done here so we dont have to worry about it later
        """

        pocc = (occ_U[0] in cifdata.keys())
        pU = (occ_U[1] in cifdata.keys()) or (occ_U[2] in cifdata.keys())

        if (not pocc):
            warn('occupation fraction not present. setting it to 1')
            occ = numpy.ones(atompos[0].shape)
            atompos.append(occ)
        else:
            slist = cifdata[occ_U[0]]
            occ = []
            for p in slist:
                n = p.find('(')

                if (n != -1):
                    occ.append(p[:n])
                else:
                    occ.append(p)

            atompos.append(numpy.asarray(occ).astype(numpy.float64))

        if (not pU):
            warn(
                'Debye-Waller factors not present. setting to same values for all atoms.'
            )
            U = 1.0 / numpy.pi / 2. / numpy.sqrt(2.) * numpy.ones(
                atompos[0].shape)
            self._U = U
        else:
            if (occ_U[1] in cifdata.keys()):
                k = occ_U[1]
            else:
                k = occ_U[2]

            slist = cifdata[k]
            U = []
            for p in slist:
                n = p.find('(')

                if (n != -1):
                    U.append(p[:n])
                else:
                    U.append(p)

            self._U = numpy.asarray(U).astype(numpy.float64)
        '''
        format everything in the right shape etc.
        '''
        self._atominfo = numpy.asarray(atompos).T
        '''
        get atome types here i.e. the atomic number of atoms at each site
        '''
        atype = '_atom_site_type_symbol'
        patype = (atype in cifdata)
        if (not patype):
            raise RuntimeError('atom types not defined in cif file.')

        satype = cifdata[atype]
        atomtype = []

        for s in satype:
            atomtype.append(ptable[s])

        self._atomtype = numpy.asarray(atomtype).astype(numpy.int32)
        self._sgsetting = 0
Пример #16
0
#
# Author:      Andrezio
#
# Created:     23/07/2017
# Copyright:   (c) Andrezio 2017
# Licence:     <your licence>
#-------------------------------------------------------------------------------

from CifFile import ReadCif
<<<<<<< HEAD
cf = ReadCif('UO2_STAR_246851.cif')
lambida=1.54
=======
cf = ReadCif('u308.cif')

>>>>>>> f26a11b75e998002824160efa1c337672e8cb21b
chave = cf.keys()


##for k,v in cf[chave[0]].items():
##    print k

<<<<<<< HEAD
value= cf[chave[0]]['_cell_length_a']

value=value.split('(')[0]

=======
print cf[chave[0]]['_symmetry_equiv_pos_as_xyz']
>>>>>>> f26a11b75e998002824160efa1c337672e8cb21b
Пример #17
0
class CIFParser(AbstractStructureParser):
    """
    Collection of methods that parses CIF files based on cif2cell. The preferred method
    of using this object is as a context manager.

    Parameters
    ----------
    filename : str or path-like
        Location of the CIF file.
    """
    def __init__(self, filename, **kwargs):
        # ReadCIF would get confused between local files and URLs
        # Therefore, more clear to pass an open file
        self._handle = open(filename, mode="r")
        self.file = ReadCif(self._handle, **kwargs)

    def __exit__(self, *args, **kwargs):
        self._handle.close()

    @property
    def filename(self):
        return self._handle.name

    @staticmethod
    def sym_ops_from_equiv(equiv_site):
        """ Parse a symmetry operator from an equivalent-site representation 
        
        Parameters
        ----------
        equiv_site : str or iterable of strings
            Either comma-separated string e.g. "+y, +x, -z + 1/2" or an
            iterable of the comma-separated values, e.g. ["+y", "+x", "-z + 1/2"] 
        
        Returns
        -------
        sym_ops : ndarray, shape (4,4)
            Symmetry operator as a 4x4 affine transformation on the FRACTIONAL
            coordinates.
        """
        symmetry = np.zeros((3, 3))
        translation = np.zeros((3, ))

        if isinstance(equiv_site, str):
            equiv_site = equiv_site.split(",")

        equiv_site = tuple(map(lambda s: s.strip().lower(), equiv_site))
        for j in range(3):
            xyz = equiv_site[j].replace("+", " +").replace("-", " -").split()
            for i in xyz:
                if i.strip("+-") == "x":
                    symmetry[0, j] = float(i.strip("x") + "1")
                elif i.strip("+-") == "y":
                    symmetry[1, j] = float(i.strip("y") + "1")
                elif i.strip("+-") == "z":
                    symmetry[2, j] = float(i.strip("z") + "1")

                if i.strip("+-xyz") != "":
                    translation[j] = eval(i)

        symmetry[:] = np.transpose(symmetry)

        # Combination of transform and translation into a single matrix
        # is done in a 4x4 affine transform
        symmetry_operation = affine_map(symmetry)
        symmetry_operation[:3, 3] = translation
        return symmetry_operation

    @property
    def structure_block(self):
        """ Retrieve which CIF block has the appropriate structural information """
        blocks = (self.file[key] for key in self.file.keys())
        for block in blocks:
            try:
                _, _ = get_number_with_esd(block["_cell_length_a"])
            except KeyError:
                continue
            else:
                return block

    @lru_cache(maxsize=1)
    def hall_symbol(self):
        """ Returns the Hall symbol """
        block = self.structure_block

        hall_symbol = block.get("_symmetry_space_group_name_Hall"
                                ) or block.get("_space_group_name_Hall")

        # In some rare cases, the given hall symbol in the file isn't standard,
        # otherwise it would be a key in SymOpsHall
        # Then, it is preferable to infer the conventional hall symbol from other info
        if (hall_symbol is None) or (hall_symbol not in SymOpsHall):
            h_m_symbol = block.get("_symmetry_space_group_name_H-M"
                                   ) or block.get("_space_group_name_H-M_alt")

            if h_m_symbol is not None:
                h_m_symbol = re.sub(r"\s+", "", h_m_symbol)
                with suppress(
                        KeyError
                ):  # Symbol could be meaningless, e.g. h_m_symbol = '?' (True story)
                    hall_symbol = HM2Hall[h_m_symbol]

        # Again, if hall_symbol is still missing OR invalid
        if (hall_symbol is None) or (hall_symbol not in SymOpsHall):
            table_number = block.get("_symmetry_Int_Tables_number"
                                     ) or block.get("_space_group_IT_number")

            if table_number is not None:
                hall_symbol = Number2Hall[int(table_number)]

        if hall_symbol is None:
            raise ParseError("Hall symbol could not be inferred")

        if hall_symbol[0] == "-":
            hall_symbol = "-" + hall_symbol[1].upper() + hall_symbol[2:].lower(
            )
        else:
            hall_symbol = hall_symbol[0].upper() + hall_symbol[1:].lower()

        return hall_symbol

    @lru_cache(maxsize=1)
    def lattice_parameters(self):
        """ 
        Returns the lattice parameters associated to a CIF structure.

        Returns
        ----------
        a, b, c : float
            Lengths of lattice vectors [Angstroms]
        alpha, beta, gamma : float
            Angles of lattice vectors [degrees]. 
        """
        block = self.structure_block

        try:
            a_with_err = block["_cell_length_a"]
        except KeyError:
            raise ParseError(
                f"No lattice information is present in {self.filename}")

        # In case where b and c are not listed, we use the value of a
        a, _ = get_number_with_esd(a_with_err)
        b, _ = get_number_with_esd(block.get("_cell_length_b", a_with_err))
        c, _ = get_number_with_esd(block.get("_cell_length_c", a_with_err))

        alpha, _ = get_number_with_esd(block["_cell_angle_alpha"])
        beta, _ = get_number_with_esd(block["_cell_angle_beta"])
        gamma, _ = get_number_with_esd(block["_cell_angle_gamma"])

        return a, b, c, alpha, beta, gamma

    @lru_cache(maxsize=1)
    def lattice_vectors(self):
        """ 
        Returns the lattice vectors associated to a CIF structure.
        
        Returns
        -------
        lv : list of ndarrays, shape (3,)
        """
        return Lattice.from_parameters(
            *self.lattice_parameters()).lattice_vectors

    def symmetry_operators(self):
        """
        Returns the symmetry operators that map the fractional atomic positions in a
        CIF file to the crystal *conventional* unit cell.

        Returns
        -------
        sym_ops : iterable of ndarray, shape (4,4)
            Transformation matrices. Since translations and rotation are combined,
            the transformation matrices are 4x4.
        """
        block = self.structure_block

        equivalent_sites_str = None
        for tag in [
                "_symmetry_equiv_pos_as_xyz",
                "_space_group_symop_operation_xyz"
        ]:
            with suppress(KeyError):
                equivalent_sites_str = block.GetLoop(tag).get(tag)

        # P1 space group only has a single equivalent site
        if isinstance(equivalent_sites_str, str):
            equivalent_sites_str = [equivalent_sites_str]

        with suppress(ParseError):
            if not equivalent_sites_str:
                equivalent_sites_str = SymOpsHall[self.hall_symbol()]
            elif len(equivalent_sites_str) != len(
                    SymOpsHall[self.hall_symbol()]):
                warnings.warn(
                    "The number of equivalent sites is not in line with the database. The file might be incomplete"
                )

        return list(map(self.sym_ops_from_equiv, equivalent_sites_str))

    def atoms(self):
        """
        Asymmetric unit cell. Combine with CIFParser.symmetry_operators() for a full unit cell.

        Returns
        -------
        atoms : iterable of Atom instance
        """
        block = self.structure_block

        try:
            tmpdata = block.GetLoop("_atom_site_fract_x")
            cartesian = False
        except:
            try:
                tmpdata = block.GetLoop("_atom_site_Cartn_x")
                cartesian = True
            except:
                raise ParseError(
                    "Atomic positions could not be found or inferred.")

            t11 = block.get("_atom_sites_Cartn_tran_matrix_11")
            t12 = block.get("_atom_sites_Cartn_tran_matrix_12")
            t13 = block.get("_atom_sites_Cartn_tran_matrix_13")
            t21 = block.get("_atom_sites_Cartn_tran_matrix_21")
            t22 = block.get("_atom_sites_Cartn_tran_matrix_22")
            t23 = block.get("_atom_sites_Cartn_tran_matrix_23")
            t31 = block.get("_atom_sites_Cartn_tran_matrix_13")
            t32 = block.get("_atom_sites_Cartn_tran_matrix_23")
            t33 = block.get("_atom_sites_Cartn_tran_matrix_33")
            cart_trans_matrix_inv = np.array([
                [float(t11), float(t12), float(t13)],
                [float(t21), float(t22), float(t23)],
                [float(t31), float(t32), float(t33)],
            ])
            cart_trans_matrix = inv(cart_trans_matrix_inv)

            if not all([t11, t12, t13, t21, t22, t23, t31, t32, t33]):
                raise ParseError(
                    "Cartesian coordinates in CIF but no transformation matrix given"
                )

        if cartesian:
            xs = tmpdata.get("_atom_site_Cartn_x")
            ys = tmpdata.get("_atom_site_Cartn_y")
            zs = tmpdata.get("_atom_site_Cartn_z")
        else:
            xs = tmpdata.get("_atom_site_fract_x")
            ys = tmpdata.get("_atom_site_fract_y")
            zs = tmpdata.get("_atom_site_fract_z")
        # TODO: handle wildcards like '?', '.' in xs, ys, zs

        elements = tmpdata.get("_atom_site_type_symbol")
        if not elements:
            elements = tmpdata.get("_atom_site_label")
            if not elements:
                raise ParseError(
                    "Atom symbols could not be found or inferred.")
        elements = map(lambda s: s.strip(punctuation + digits).title(),
                       elements)

        atoms = list()
        for e, x, y, z in zip(elements, xs, ys, zs):
            coords = np.array([
                get_number_with_esd(x)[0],
                get_number_with_esd(y)[0],
                get_number_with_esd(z)[0],
            ])

            # We normalize atom position to be within the unit cell
            # Therefore we need the fractional coordinates
            if cartesian:
                coords = transform(cart_trans_matrix, coords)
                coords[:] = frac_coords(coords, self.lattice_vectors())

            atoms.append(Atom(element=e, coords=np.mod(coords, 1)))

        return atoms
Пример #18
0
def read_cif(filename: str_PathLike, pps: muti_Dict = [{}], orbitals: muti_Dict = [{}], masses: muti_Dict = [{}], magmoms: muti_Dict = [{}], move: muti_Dict = [{}], abfs: muti_Dict = [{}]) -> List[Stru]:
    """Read CIF(Crystallographic Information Framework) file and return `Stru` object

    :params filename: name of cif file
    """


    raise UserWarning("Can only read direct positions, generating equivalent atoms based on site symmetry or Wackoff positions are not supported now")
    from CifFile import ReadCif
    strulist = []

    def list_elem2split(l):
        def list_split(s):
            return s.split('(')[0]
        return list(map(list_split, l))

    def get_elements(string):
        return re.search(r'([A-Z][a-z]?)', string).group(0)

    cf = ReadCif(filename)
    for index, name in enumerate(cf.keys()):
        data = cf[name]
        fract_x = list_elem2split(data["_atom_site_fract_x"])
        fract_y = list_elem2split(data["_atom_site_fract_y"])
        fract_z = list_elem2split(data["_atom_site_fract_z"])
        if "_atom_site_type_symbol" in data.keys():
            elements = list(map(get_elements, data["_atom_site_type_symbol"]))
        else:
            elements = list(map(get_elements, data["_atom_site_label"]))
        scaled_positions = defaultdict(list)
        for i, elem in enumerate(elements):
            scaled_positions[elem].append([np.array(fract_x[i], dtype=float), np.array(
                fract_y[i], dtype=float), np.array(fract_z[i], dtype=float)])

        la = float(data["_cell_length_a"].split('(')[0])
        lb = float(data["_cell_length_b"].split('(')[0])
        lc = float(data["_cell_length_c"].split('(')[0])
        alpha = float(data["_cell_angle_alpha"].split('(')[0])
        beta = float(data["_cell_angle_beta"].split('(')[0])
        gamma = float(data["_cell_angle_gamma"].split('(')[0])

        if "_symmetry_cell_setting" in data.keys():
            crystal_system = data["_symmetry_cell_setting"]
        elif "_symmetry_Int_Tables_number" in data.keys():
            crystal_system = Spacegroup().get_crystal_system(
                data["_symmetry_Int_Tables_number"])
        elif "_space_group_name_Hall" in data.keys():
            crystal_system = Spacegroup().get_crystal_system(
                Hall2Number[data["_space_group_name_Hall"]])
        elif "_symmetry_space_group_name_H-M" in data.keys():
            crystal_system = Spacegroup().get_crystal_system(
                Hall2Number[HM2Hall[data["_symmetry_space_group_name_H-M "]]])
        else:
            crystal_system = 'unknown'

        cell = conventional_cell(
            la, lb, lc, alpha, beta, gamma, crystal_system)

        strulist.append(Stru(lat0=1/BOHR_TO_A, cell=cell, pps=pps[index], scaled_positions=scaled_positions,
                             orbitals=orbitals[index], masses=masses[index], magmoms=magmoms[index], move=move[index], abfs=abfs[index]))

    return strulist