def write(structures, filename=None, wrap=False, **kwargs): """ Write a structure or list of structures to a cif file. Arguments: structures: A :mod:`~qmpy.Structure` object or list of objects. If supplied with several Structures, they will all be written to the same cif. Keyword Arguments: filename: If supplied with a file name, will write to the file. If no filename is given, will return a string. wrap: If True, will include atoms at fractional coordinates 0 and 1. Useful only for visualizing structures. Examples:: >>> io.cif.write(structure, "test.cif") >>> io.cif.write([structure1, structure2], "test.cif") """ cif = CifFile() if not isinstance(structures, list): structures = [structures] for i, structure in enumerate(structures): cif['structure_%d' % i] = _make_cif_block(structure, wrap=wrap) if filename: open(filename, 'w').write(str(cif)) else: return str(cif)
def main(): #read filename f = 'CHA.cif' #input("Filename: ") # open and parse our cif cf = CifFile(f) F = f[:3] cb = cf[F] Crystal = Crysdata(F, cb) Crystal.printout()
def read(self, stream, default_type='A'): """Read text stream and return a trajectory instance. :param stream: The stream, which contains the ciffile. :type stream: A file-like textstream. :param default_type: The default particle type for ciffile dialects without type definition. :type default_type: str """ parsed_file = CifFile(stream) keys = list(sorted(parsed_file.keys())) # Index the stream frames = list(self._scan(parsed_file, keys, default_type)) if len(frames) == 0: raise ParserError("Did not read a single complete frame.") logger.info("Read {} frames.".format(len(frames))) return Trajectory(frames)
def drawCrystal(file): # Check if file is file: S = time.time() global user_feedback ext = file[len(file)-4:] if(ext.lower() != ".cif"): print("Only cif files can be visualised") user_feedback = "Not a cif file" return # Check OpenBabel installation try: # Convert the cif file to its P1 symmetry notation as a temporary cif file print('Converting %s to P1' %file) obabel_fill_unit_cell(file, "temp.CIF") cf = CifFile("temp.CIF") except: print("No OpenBabel installation found, install it from http://openbabel.org/wiki/Category:Installation") user_feedback = "OpenBabel not installed" #cf = CifFile(file) CifFile apparently can't read in long filepaths return # Open and parse our cif f = file.rsplit(dir_sep, 1)[-1] F = f[:3] print(f) cb = cf.first_block() Crystal = Crysdata(F,cb) # Print crystal data in terminal if checked if(print_data): Crystal.printout() print("Crystal data read after "+ str(time.time() - S) + " seconds") # Draw crystal if in Blender environment if(Blender_env): clearWS() Crystal.drawCrystal() bpy.ops.object.select_all(action='DESELECT') if(add_camera): addCamera(Crystal.cell.alen,Crystal.cell.blen,Crystal.cell.clen)
def _parseCifDataSource(self, datasource): """\ Open and process CIF data from the specified `datasource`. Parameters ---------- datasource : str or a file-like object This is used as an argument to the CifFile class. The CifFile instance is stored in `ciffile` attribute of this Parser. Returns ------- Structure The Structure object loaded from the specified data source. Raises ------ StructureFormatError When the data do not constitute a valid CIF format. """ from CifFile import CifFile, StarError self.stru = None try: with _suppressCifParserOutput(): # Use `grammar` option to digest values with curly-brackets. # Ref: https://bitbucket.org/jamesrhester/pycifrw/issues/19 self.ciffile = CifFile(datasource, grammar='auto') for blockname in self.ciffile.keys(): self._parseCifBlock(blockname) # stop after reading the first structure if self.stru is not None: break except (StarError, ValueError, IndexError) as err: exc_type, exc_value, exc_traceback = sys.exc_info() emsg = str(err).strip() e = StructureFormatError(emsg) six.reraise(StructureFormatError, e, exc_traceback) return self.stru
def _parseCifDataSource(self, datasource): """\ Open and process CIF data from the specified `datasource`. Parameters ---------- datasource : str or a file-like object This is used as an argument to the CifFile class. The CifFile instance is stored in `ciffile` attribute of this Parser. Returns ------- Structure The Structure object loaded from the specified data source. Raises ------ StructureFormatError When the data do not constitute a valid CIF format. """ from CifFile import CifFile, StarError self.stru = None try: with _suppressCifParserOutput(): self.ciffile = CifFile(datasource) for blockname in self.ciffile.keys(): self._parseCifBlock(blockname) # stop after reading the first structure if self.stru is not None: break except (StarError, ValueError, IndexError) as err: exc_type, exc_value, exc_traceback = sys.exc_info() emsg = str(err).strip() e = StructureFormatError(emsg) six.reraise(StructureFormatError, e, exc_traceback) return self.stru
def prepareGulpInput(cifFile, gulpFile, jobName, verbose=False, auto_fractions=False): """ Example file: mgh2 cell 4.5168 4.5168 3.0205 90.0 90.0 90.0 frac Mg 0.0 0.0 0.0 H 0.306 0.306 0.0 space 136 output xyz mgh2 """ from CifFile import CifFile if not os.path.exists(cifFile): raise IOError("CIF file '%s' was not found!" % (cifFile)) cf = CifFile(cifFile) if verbose: print( "------------------------------------------------------------------" ) if len(cf) != 1: raise StandardError( "The cif file contains %i data blocks, while one was expected") # A cif file can contain several "datablocks" that each start # with "data_". if verbose: print("Reading data block '%s'..." % (cf.keys()[0])) cb = cf[cf.keys()[0]] # open the first block AA = float(re.match('([0-9.e]*)', cb['_cell_length_a']).group(0)) BB = float(re.match('([0-9.e]*)', cb['_cell_length_b']).group(0)) CC = float(re.match('([0-9.e]*)', cb['_cell_length_c']).group(0)) alpha = float(cb['_cell_angle_alpha']) beta = float(cb['_cell_angle_beta']) gamma = float(cb['_cell_angle_gamma']) # Spacegroup number (1-230) # '_symmetry_Int_Tables_number' has been superseded by '_space_group_IT_number' if '_space_group_IT_number' in cb.keys(): SG = int(cb['_space_group_IT_number']) elif '_symmetry_Int_Tables_number' in cb.keys(): SG = int(cb['_symmetry_Int_Tables_number']) else: #print "WARNING: No space group specified. Assuming P1." SG = 1 # CCTBX: #unit_cell = uctbx.unit_cell([AA,BB,CC,alpha,beta,gamma]) #space_group_info = sgtbx.space_group_info(symbol=cb['_symmetry_space_group_name_H-M']) #crystal_symmetry = crystal.symmetry(unit_cell=unit_cell,space_group_info=space_group_info) ##print "CIF file read successfully:" #crystal_symmetry.show_summary() if verbose: print(" Space group:", SG) print(" a=%s, b=%s, c=%s, alpha=%s, beta=%s, gamma=%s" % (AA, BB, CC, alpha, beta, gamma)) atomTypes = [] atoms = '' fracOccFound = False firstAtom = True atoms = "" # The coordinates of the atom (_atom_site_fract_x/y/z) may have # a last digit in parenthesis, like "-0.6636(7)". Therefore we # extract the part consisting of only digits and a decimal separator: coordsMatch = re.compile('[0-9.e-]*') for atom in cb.GetLoop('_atom_site_label'): atomKeys = dir(atom) if '_atom_site_type_symbol' in atomKeys: m = re.match('[a-z]*', atom._atom_site_type_symbol, re.I) atomType = m.group(0) else: m = re.match('[a-z]*', atom._atom_site_label, re.I) atomType = m.group(0) if '_atom_site_occupancy' in atomKeys: occ = float(coordsMatch.match(atom._atom_site_occupancy).group()) if not occ == 1.0: if not fracOccFound: #print " " #print " WARNING: Fractional occupancy (" + str(occ) +") " \ + "found for atom of type " + atomType + "." fracOccFound = True else: occ = 1.0 # Some crystal structures obtained by neutron diffraction use D for H: if atomType == 'D': atomType = 'H' if '_atom_site_symmetry_multiplicity' in atomKeys and '_atom_site_Wyckoff_symbol' in atomKeys: atomTypes.append(atomType + ' at ' + atom._atom_site_symmetry_multiplicity + atom._atom_site_Wyckoff_symbol) else: atomTypes.append(atomType) atomX = coordsMatch.match(atom._atom_site_fract_x).group() atomY = coordsMatch.match(atom._atom_site_fract_y).group() atomZ = coordsMatch.match(atom._atom_site_fract_z).group() atomPos = [atomX, atomY, atomZ] for i in range(3): pp = atomPos[i].split(".") if len(pp) is 2: decimals = pp[1] if len(decimals) > 3 and len(decimals) < 6 and decimals[ 0] == decimals[1] and decimals[0] == decimals[ 2] and decimals[-1] != "0": if auto_fractions: oldPos = atomPos[i] atomPos[i] = "%.6f" % (float( eval('1.*' + float2fraction(atomPos[i])))) #print " Notice: Converted %s into %s" %(oldPos,atomPos[i]) else: #print "\n"\ + " ! Warning: The coordinate "+atomPos[i]+" looks similar to the fraction %s, but\n" % float2fraction(atomPos[i]) \ + " ! has insufficient decimals to be recognized as so by GULP. If you want\n" \ + " ! this coordinate to be recognized as a special high-symmetry position,\n" \ + " ! you need to specify at least six digits. If you run cif2vasp with the \n" \ + " ! -f switch, cif2vasp will try to add the necessary decimals automaticly." atoms += "%s %s %s %s %f %f\n" % (atomType, atomPos[0], atomPos[1], atomPos[2], 0.0, occ) firstAtom = False if fracOccFound: #print " " #print "ERROR: Fractional occupancies are not currently supported.\n" exit() if verbose: print(" Atom types: " + ', '.join(atomTypes)) gulpFile = open(gulpFile, 'w') #Create and write the GULP gulpFile.writelines([ jobName + '\n', 'cell\n', '%s %s %s %s %s %s\n' % (AA, BB, CC, alpha, beta, gamma), 'frac\n', atoms, 'space\n', str(SG) + '\n', 'output xyz ' + jobName + '\n' ])
def readCifFile(cifFile): from CifFile import CifFile if not os.path.exists(cifFile): raise IOError("CIF file '%s' was not found!" % (cifFile)) cf = CifFile(cifFile) #print "------------------------------------------------------------------" if len(cf) != 1: print("xx") # raise(tandardError("The cif file contains %i data blocks, while one was expected")) # A cif file can contain several "datablocks" that each start # with "data_". cb = cf[cf.keys()[0]] # open the first block AA = float(re.match('([0-9.]*)', cb['_cell_length_a']).group(0)) BB = float(re.match('([0-9.]*)', cb['_cell_length_b']).group(0)) CC = float(re.match('([0-9.]*)', cb['_cell_length_c']).group(0)) alpha = float(cb['_cell_angle_alpha']) beta = float(cb['_cell_angle_beta']) gamma = float(cb['_cell_angle_gamma']) SG = int(cb['_symmetry_Int_Tables_number']) # spacegroup atomTypes = [] atoms = '' fracOccFound = False firstAtom = True atoms = [] for atom in cb.GetLoop('_atom_site_label'): atomKeys = dir(atom) if '_atom_site_type_symbol' in atomKeys: m = re.match('[a-z]*', atom._atom_site_type_symbol, re.I) atomType = m.group(0) else: m = re.match('[a-z]*', atom._atom_site_label, re.I) atomType = m.group(0) atomLabel = atom._atom_site_label if '_atom_site_occupancy' in atomKeys: occ = float(atom._atom_site_occupancy) if not occ == 1.0: if not fracOccFound: #print " " #print " WARNING: Fractional occupancy (" + str(occ) +") " \ + "found for atom of type " + atomType + "." fracOccFound = True else: occ = 1.0 # Some crystal structures obtained by neutron diffraction use D for H: if atomType == 'D': atomType = 'H' atomLabel.replace('H', 'D') if '_atom_site_symmetry_multiplicity' in atomKeys and '_atom_site_Wyckoff_symbol' in atomKeys: atomTypes.append(atomType + ' at ' + atom._atom_site_symmetry_multiplicity + atom._atom_site_Wyckoff_symbol) else: atomTypes.append(atomType) atomPos = [ atom._atom_site_fract_x, atom._atom_site_fract_y, atom._atom_site_fract_z ] for p in atomPos: pp = p.split(".") if len(pp) is 2: decimals = p.split(".")[1] # if len(decimals) > 3 and len(decimals) < 6 and decimals[0] == decimals[1] and decimals[-1] != "0": #print "\n ---------------------\n"\ # + " Warning: If the fractional coordinate "+p+" is a recurring decimal, such as 1/3,\n" \ # + " then it is necessary to specify this value to six decimal places to be sure of \n" \ # + " it being recognised correctly as a spcecial position.\n ------------------" # The coordinates of the atom (_atom_site_fract_x/y/z) may have # a last digit in parenthesis, like "0.6636(7)". Therefore we # extract the part consisting of only digits and a decimal separator: p = re.compile('[0-9.]*') atomX = float(p.match(atom._atom_site_fract_x).group()) atomY = float(p.match(atom._atom_site_fract_y).group()) atomZ = float(p.match(atom._atom_site_fract_z).group()) #atoms += "%s %f %f %f %f %f\n" % (atomType, atomX, atomY, atomZ, 0.0, occ) atoms.append({ 'label': atomLabel, 'type': atomType, 'pos': (atomX, atomY, atomZ) }) firstAtom = False if fracOccFound: #print " " #print "ERROR: Fractional occupancies are not currently supported.\n" exit() #print " Atom types: " + ', '.join(atomTypes) return { 'spacegroup': SG, 'unit_cell': [AA, BB, CC, alpha, beta, gamma], 'scatterers': atoms }
class P_cif(StructureParser): """Simple parser for CIF structure format. Reads Structure from the first block containing _atom_site_label key. Following blocks, if any are ignored. Data members: format -- structure format name ciffile -- instance of CifFile from PyCifRW stru -- Structure instance used for cif input or output Data members used for input only: spacegroup -- instance of SpaceGroup used for symmetry expansion eps -- resolution in fractional coordinates for non-equal positions. Use for expansion of asymmetric unit. eau -- instance of ExpandAsymmetricUnit from SymmetryUtilities asymmetric_unit -- list of atom instances for the original asymmetric unit in the CIF file labelindex -- dictionary mapping unique atom label to index of atom in self.asymmetric_unit cif_sgname -- space group name obtained by looking up the value of _space_group_name_Hall, _symmetry_space_group_name_Hall, _space_group_name_H-M_alt, _symmetry_space_group_name_H-M items. None when neither is defined. """ # static data and methods ------------------------------------------------ # dictionary set of class methods for translating CIF values # to Atom attributes _atom_setters = dict.fromkeys(( '_tr_ignore', '_tr_atom_site_label', '_tr_atom_site_type_symbol', '_tr_atom_site_fract_x', '_tr_atom_site_fract_y', '_tr_atom_site_fract_z', '_tr_atom_site_cartn_x', '_tr_atom_site_cartn_y', '_tr_atom_site_cartn_z', '_tr_atom_site_U_iso_or_equiv', '_tr_atom_site_B_iso_or_equiv', '_tr_atom_site_adp_type', '_tr_atom_site_thermal_displace_type', '_tr_atom_site_occupancy', '_tr_atom_site_aniso_U_11', '_tr_atom_site_aniso_U_22', '_tr_atom_site_aniso_U_33', '_tr_atom_site_aniso_U_12', '_tr_atom_site_aniso_U_13', '_tr_atom_site_aniso_U_23', '_tr_atom_site_aniso_B_11', '_tr_atom_site_aniso_B_22', '_tr_atom_site_aniso_B_33', '_tr_atom_site_aniso_B_12', '_tr_atom_site_aniso_B_13', '_tr_atom_site_aniso_B_23', )) # make _atom_setters case insensitive for k in list(_atom_setters.keys()): _atom_setters[k] = _atom_setters[k.lower()] = k del k BtoU = 1.0/(8 * numpy.pi**2) def _tr_ignore(a, value): return _tr_ignore = staticmethod(_tr_ignore) def _tr_atom_site_label(a, value): a.label = str(value) # set element when not specified by _atom_site_type_symbol if not a.element: P_cif._tr_atom_site_type_symbol(a, value) _tr_atom_site_label = staticmethod(_tr_atom_site_label) # 3 regexp groups for nucleon number, atom symbol, and oxidation state _psymb = re.compile(r'(\d+-)?([a-zA-Z]+)(\d[+-])?') def _tr_atom_site_type_symbol(a, value): rx = P_cif._psymb.match(value) smbl = rx and rx.group(0) or value smbl = str(smbl) a.element = smbl[:1].upper() + smbl[1:].lower() _tr_atom_site_type_symbol = staticmethod(_tr_atom_site_type_symbol) def _tr_atom_site_fract_x(a, value): a.xyz[0] = leading_float(value) _tr_atom_site_fract_x = staticmethod(_tr_atom_site_fract_x) def _tr_atom_site_fract_y(a, value): a.xyz[1] = leading_float(value) _tr_atom_site_fract_y = staticmethod(_tr_atom_site_fract_y) def _tr_atom_site_fract_z(a, value): a.xyz[2] = leading_float(value) _tr_atom_site_fract_z = staticmethod(_tr_atom_site_fract_z) def _tr_atom_site_cartn_x(a, value): a.xyz_cartn[0] = leading_float(value) _tr_atom_site_cartn_x = staticmethod(_tr_atom_site_cartn_x) def _tr_atom_site_cartn_y(a, value): a.xyz_cartn[1] = leading_float(value) _tr_atom_site_cartn_y = staticmethod(_tr_atom_site_cartn_y) def _tr_atom_site_cartn_z(a, value): a.xyz_cartn[2] = leading_float(value) _tr_atom_site_cartn_z = staticmethod(_tr_atom_site_cartn_z) def _tr_atom_site_U_iso_or_equiv(a, value): a.Uisoequiv = leading_float(value) _tr_atom_site_U_iso_or_equiv = staticmethod(_tr_atom_site_U_iso_or_equiv) def _tr_atom_site_B_iso_or_equiv(a, value): a.Uisoequiv = P_cif.BtoU * leading_float(value) _tr_atom_site_B_iso_or_equiv = staticmethod(_tr_atom_site_B_iso_or_equiv) def _tr_atom_site_adp_type(a, value): a.anisotropy = value not in ("Uiso", "Biso") _tr_atom_site_adp_type = staticmethod(_tr_atom_site_adp_type) _tr_atom_site_thermal_displace_type = _tr_atom_site_adp_type def _tr_atom_site_occupancy(a, value): a.occupancy = leading_float(value, 1.0) _tr_atom_site_occupancy = staticmethod(_tr_atom_site_occupancy) def _tr_atom_site_aniso_U_11(a, value): a.U11 = leading_float(value) _tr_atom_site_aniso_U_11 = staticmethod(_tr_atom_site_aniso_U_11) def _tr_atom_site_aniso_U_22(a, value): a.U22 = leading_float(value) _tr_atom_site_aniso_U_22 = staticmethod(_tr_atom_site_aniso_U_22) def _tr_atom_site_aniso_U_33(a, value): a.U33 = leading_float(value) _tr_atom_site_aniso_U_33 = staticmethod(_tr_atom_site_aniso_U_33) def _tr_atom_site_aniso_U_12(a, value): a.U12 = leading_float(value) _tr_atom_site_aniso_U_12 = staticmethod(_tr_atom_site_aniso_U_12) def _tr_atom_site_aniso_U_13(a, value): a.U13 = leading_float(value) _tr_atom_site_aniso_U_13 = staticmethod(_tr_atom_site_aniso_U_13) def _tr_atom_site_aniso_U_23(a, value): a.U23 = leading_float(value) _tr_atom_site_aniso_U_23 = staticmethod(_tr_atom_site_aniso_U_23) def _tr_atom_site_aniso_B_11(a, value): a.U11 = P_cif.BtoU * leading_float(value) _tr_atom_site_aniso_B_11 = staticmethod(_tr_atom_site_aniso_B_11) def _tr_atom_site_aniso_B_22(a, value): a.U22 = P_cif.BtoU * leading_float(value) _tr_atom_site_aniso_B_22 = staticmethod(_tr_atom_site_aniso_B_22) def _tr_atom_site_aniso_B_33(a, value): a.U33 = P_cif.BtoU * leading_float(value) _tr_atom_site_aniso_B_33 = staticmethod(_tr_atom_site_aniso_B_33) def _tr_atom_site_aniso_B_12(a, value): a.U12 = P_cif.BtoU * leading_float(value) _tr_atom_site_aniso_B_12 = staticmethod(_tr_atom_site_aniso_B_12) def _tr_atom_site_aniso_B_13(a, value): a.U13 = P_cif.BtoU * leading_float(value) _tr_atom_site_aniso_B_13 = staticmethod(_tr_atom_site_aniso_B_13) def _tr_atom_site_aniso_B_23(a, value): a.U23 = P_cif.BtoU * leading_float(value) _tr_atom_site_aniso_B_23 = staticmethod(_tr_atom_site_aniso_B_23) def _get_atom_setters(cifloop): """Find translators of CifLoop items to data in Atom instance. Static method. cifloop -- instance of CifLoop Return a list of setter functions in the order of cifloop.keys(). """ rv = [] for p in cifloop.keys(): lcname = "_tr" + p.lower() fncname = P_cif._atom_setters.get(lcname, '_tr_ignore') f = getattr(P_cif, fncname) rv.append(f) return rv _get_atom_setters = staticmethod(_get_atom_setters) # normal methods --------------------------------------------------------- def __init__(self, eps=None): """Initialize the parser for CIF structure files. eps -- fractional coordinates cutoff for duplicate positions. When None use the default for ExpandAsymmetricUnit. """ StructureParser.__init__(self) self.format = "cif" self.ciffile = None self.stru = None self.spacegroup = None self.eps = eps self.eau = None self.asymmetric_unit = None self.labelindex = {} self.cif_sgname = None pass def parse(self, s): """Create Structure instance from a string in CIF format. Return Structure instance or raise StructureFormatError. """ self.ciffile = None self.filename = '' fp = six.StringIO(s) rv = self._parseCifDataSource(fp) return rv def parseLines(self, lines): """Parse list of lines in CIF format. lines -- list of strings stripped of line terminator Return Structure instance or raise StructureFormatError. """ s = "\n".join(lines) + '\n' return self.parse(s) def parseFile(self, filename): """Create Structure from an existing CIF file. filename -- path to structure file Return Structure object. Raise StructureFormatError or IOError. """ self.ciffile = None self.filename = filename fileurl = _fixIfWindowsPath(filename) rv = self._parseCifDataSource(fileurl) # all good here return rv def _parseCifDataSource(self, datasource): """\ Open and process CIF data from the specified `datasource`. Parameters ---------- datasource : str or a file-like object This is used as an argument to the CifFile class. The CifFile instance is stored in `ciffile` attribute of this Parser. Returns ------- Structure The Structure object loaded from the specified data source. Raises ------ StructureFormatError When the data do not constitute a valid CIF format. """ from CifFile import CifFile, StarError self.stru = None try: with _suppressCifParserOutput(): self.ciffile = CifFile(datasource) for blockname in self.ciffile.keys(): self._parseCifBlock(blockname) # stop after reading the first structure if self.stru is not None: break except (StarError, ValueError, IndexError) as err: exc_type, exc_value, exc_traceback = sys.exc_info() emsg = str(err).strip() e = StructureFormatError(emsg) six.reraise(StructureFormatError, e, exc_traceback) return self.stru def _parseCifBlock(self, blockname): """Translate CIF file block, skip blocks without _atom_site_label. Updates data members stru, eau. blockname -- name of top level block in self.ciffile No return value. """ block = self.ciffile[blockname] if '_atom_site_label' not in block: return # here block contains structure, initialize output data self.stru = Structure() self.labelindex.clear() # execute specialized block parsers self._parse_lattice(block) self._parse_atom_site_label(block) self._parse_atom_site_aniso_label(block) self._parse_space_group_symop_operation_xyz(block) return def _parse_lattice(self, block): """Obtain lattice parameters from a CifBlock. This method updates self.stru.lattice. block -- instance of CifBlock No return value. """ if '_cell_length_a' not in block: return # obtain lattice parameters try: latpars = ( leading_float(block['_cell_length_a']), leading_float(block['_cell_length_b']), leading_float(block['_cell_length_c']), leading_float(block['_cell_angle_alpha']), leading_float(block['_cell_angle_beta']), leading_float(block['_cell_angle_gamma']), ) except KeyError as err: exc_type, exc_value, exc_traceback = sys.exc_info() emsg = str(err) e = StructureFormatError(emsg) six.reraise(StructureFormatError, e, exc_traceback) self.stru.lattice = Lattice(*latpars) return def _parse_atom_site_label(self, block): """Obtain atoms in asymmetric unit from a CifBlock. This method inserts Atom instances to self.stru and updates labelindex dictionary. block -- instance of CifBlock No return value. """ # process _atom_site_label atom_site_loop = block.GetLoop('_atom_site_label') # get a list of setters for atom_site values prop_setters = P_cif._get_atom_setters(atom_site_loop) # index of the _atom_site_label item for the labelindex dictionary ilb = atom_site_loop.keys().index('_atom_site_label') # loop through the values and pass them to the setters sitedatalist = zip(*atom_site_loop.values()) for values in sitedatalist: curlabel = values[ilb] # skip entries that have invalid label if curlabel == '?': continue self.labelindex[curlabel] = len(self.stru) self.stru.addNewAtom() a = self.stru.getLastAtom() for fset, val in zip(prop_setters, values): fset(a, val) return def _parse_atom_site_aniso_label(self, block): """Obtain value of anisotropic thermal displacements from a CifBlock. This method updates U members of Atom instances in self.stru. The labelindex dictionary has to be defined beforehand. block -- instance of CifBlock No return value. """ if '_atom_site_aniso_label' not in block: return # was anisotropy processed in the _atom_site_label loop? isotropy_done = _hasAtomSiteADPType(block) # something to do here: adp_loop = block.GetLoop('_atom_site_aniso_label') # index of the _atom_site_label column ilb = adp_loop.keys().index('_atom_site_aniso_label') # get a list of setters for this loop prop_setters = P_cif._get_atom_setters(adp_loop) sitedatalist = zip(*adp_loop.values()) for values in sitedatalist: idx = self.labelindex[values[ilb]] a = self.stru[idx] if not isotropy_done: a.anisotropy = True for fset, val in zip(prop_setters, values): fset(a, val) return def _parse_space_group_symop_operation_xyz(self, block): """Process symmetry operations from a CifBlock. The method updates spacegroup and eau data according to symmetry operations defined in _space_group_symop_operation_xyz or _symmetry_equiv_pos_as_xyz items in CifBlock. block -- instance of CifBlock No return value. """ from diffpy.structure.spacegroups import IsSpaceGroupIdentifier from diffpy.structure.spacegroups import SpaceGroup, GetSpaceGroup from diffpy.structure.spacegroups import FindSpaceGroup self.asymmetric_unit = list(self.stru) sym_synonyms = ('_space_group_symop_operation_xyz', '_symmetry_equiv_pos_as_xyz') sym_loop_name = [n for n in sym_synonyms if n in block] # recover explicit list of symmetry operations symop_list = [] if sym_loop_name: # sym_loop exists here and we know its cif name sym_loop_name = sym_loop_name[0] sym_loop = block.GetLoop(sym_loop_name) for eqxyz in sym_loop[sym_loop_name]: opcif = getSymOp(eqxyz) symop_list.append(opcif) # determine space group number sg_nameHall = (block.get('_space_group_name_Hall', '') or block.get('_symmetry_space_group_name_Hall', '')) sg_nameHM = (block.get('_space_group_name_H-M_alt', '') or block.get('_symmetry_space_group_name_H-M', '')) self.cif_sgname = (sg_nameHall or sg_nameHM or None) sgid = (int(block.get('_space_group_IT_number', '0')) or int(block.get('_symmetry_Int_Tables_number', '0')) or sg_nameHM) self.spacegroup = None # try to reuse existing space group from symmetry operations if symop_list: try: self.spacegroup = FindSpaceGroup(symop_list) except ValueError: pass # otherwise lookup the space group from its identifier if self.spacegroup is None and sgid and IsSpaceGroupIdentifier(sgid): self.spacegroup = GetSpaceGroup(sgid) # define new spacegroup when symmetry operations were listed, but # there is no match to an existing definition if symop_list and self.spacegroup is None: new_short_name = "CIF " + (sg_nameHall or 'data') new_crystal_system = ( block.get('_space_group_crystal_system') or block.get('_symmetry_cell_setting') or 'TRICLINIC' ).upper() self.spacegroup = SpaceGroup( short_name=new_short_name, crystal_system=new_crystal_system, symop_list=symop_list) if self.spacegroup is None: emsg = "CIF file has unknown space group identifier {!r}." raise StructureFormatError(emsg.format(sgid)) self._expandAsymmetricUnit(block) return def _expandAsymmetricUnit(self, block): """Perform symmetry expansion of self.stru using self.spacegroup. This method updates data in stru and eau. Parameters ---------- block : CifBlock The top-level block containing crystal structure data. """ from diffpy.structure.symmetryutilities import ExpandAsymmetricUnit corepos = [a.xyz for a in self.stru] coreUijs = [a.U for a in self.stru] self.eau = ExpandAsymmetricUnit(self.spacegroup, corepos, coreUijs, eps=self.eps) # setup anisotropy according to symmetry requirements # was isotropy flag already processed isotropy_done = (_hasAtomSiteADPType(block) or '_atom_site_aniso_label' in block) if not isotropy_done: for ca, uisotropy in zip(self.stru, self.eau.Uisotropy): ca.anisotropy = not uisotropy # build a nested list of new atoms: newatoms = [] for i, ca in enumerate(self.stru): eca = [] # expanded core atom for j in range(self.eau.multiplicity[i]): a = Atom(ca) a.xyz = self.eau.expandedpos[i][j] if j > 0: a.label += '_' + str(j + 1) if a.anisotropy: a.U = self.eau.expandedUijs[i][j] eca.append(a) newatoms.append(eca) # insert new atoms where they belong self.stru[:] = sum(newatoms, []) return # conversion to CIF ------------------------------------------------------ def toLines(self, stru): """Convert Structure stru to a list of lines in basic CIF format. Return list of strings. """ import time lines = [] # may be replaced with filtered Structure.title # for now, we can add the title as a comment if stru.title.strip() != "": title_lines = stru.title.split('\n') lines.extend([ "# " + line.strip() for line in title_lines ]) lines.append("") lines.append("data_3D") iso_date = "%04i-%02i-%02i" % time.gmtime()[:3] lines.extend([ "%-31s %s" % ("_audit_creation_date", iso_date), "%-31s %s" % ("_audit_creation_method", "P_cif.py"), "", "%-31s %s" % ("_symmetry_space_group_name_H-M", "'P1'"), "%-31s %s" % ("_symmetry_Int_Tables_number", "1"), "%-31s %s" % ("_symmetry_cell_setting", "triclinic"), "" ]) # there should be no need to specify equivalent positions for P1 # _symmetry_equiv_posi_as_xyz x,y,z lines.extend([ "%-31s %.6g" % ("_cell_length_a", stru.lattice.a), "%-31s %.6g" % ("_cell_length_b", stru.lattice.b), "%-31s %.6g" % ("_cell_length_c", stru.lattice.c), "%-31s %.6g" % ("_cell_angle_alpha", stru.lattice.alpha), "%-31s %.6g" % ("_cell_angle_beta", stru.lattice.beta), "%-31s %.6g" % ("_cell_angle_gamma", stru.lattice.gamma), "" ]) # build a list of site labels and adp (displacement factor) types element_count = {} a_site_label = [] a_adp_type = [] for a in stru: cnt = element_count[a.element] = element_count.get(a.element,0)+1 a_site_label.append( "%s%i" % (a.element, cnt) ) if numpy.all(a.U == a.U[0,0]*numpy.identity(3)): a_adp_type.append("Uiso") else: a_adp_type.append("Uani") # list all atoms lines.extend([ "loop_", " _atom_site_label", " _atom_site_type_symbol", " _atom_site_fract_x", " _atom_site_fract_y", " _atom_site_fract_z", " _atom_site_U_iso_or_equiv", " _atom_site_adp_type", " _atom_site_occupancy" ]) for i in range(len(stru)): a = stru[i] line = " %-5s %-3s %11.6f %11.6f %11.6f %11.6f %-5s %.4f" % ( a_site_label[i], a.element, a.xyz[0], a.xyz[1], a.xyz[2], a.Uisoequiv, a_adp_type[i], a.occupancy ) lines.append(line) # find anisotropic atoms idx_aniso = [ i for i in range(len(stru)) if a_adp_type[i] != "Uiso" ] if idx_aniso != []: lines.extend([ "loop_", " _atom_site_aniso_label", " _atom_site_aniso_U_11", " _atom_site_aniso_U_22", " _atom_site_aniso_U_33", " _atom_site_aniso_U_12", " _atom_site_aniso_U_13", " _atom_site_aniso_U_23" ]) for i in idx_aniso: a = stru[i] line = " %-5s %9.6f %9.6f %9.6f %9.6f %9.6f %9.6f" % ( a_site_label[i], a.U[0,0], a.U[1,1], a.U[2,2], a.U[0,1], a.U[0,2], a.U[1,2] ) lines.append(line) return lines
def write_cif(crystal, fname): """ Generate an atomic coordinates .cif file from a crystal structure. .. versionadded:: 1.2.0 Parameters ---------- crystal : crystals.Crystal Crystal to be converted. fname : path-like The CIF file will be written to this file. If the file already exists, it will be overwritten. comment : str or None, optional Comment to include at the second line of ``fname``. """ cf = CifFile(strict=False) a, b, c, alpha, beta, gamma = crystal.lattice_parameters lattice_items = { "_cell_length_a": a, "_cell_length_b": b, "_cell_length_c": c, "_cell_angle_alpha": alpha, "_cell_angle_beta": beta, "_cell_angle_gamma": gamma, } sym = crystal.symmetry() symmetry_items = { "_symmetry_Int_Tables_number": sym["international_number"], "_symmetry_space_group_name_Hall": sym["hall_symbol"], } block = CifBlock() for key, val in lattice_items.items(): block[key] = val for key, val in symmetry_items.items(): block[key] = val # Note that we are using all atoms in the unit-cell, # and not the asymmetric unit cell + symmetry operators # This is valid CIF! And it is much simpler to implement # TODO: how to determine asymmetric cell + symmetry operations? atoms = list(crystal.primitive().unitcell) symbols = [atm.symbol for atm in atoms] xf = [atm.coords_fractional[0] for atm in atoms] yf = [atm.coords_fractional[1] for atm in atoms] zf = [atm.coords_fractional[2] for atm in atoms] block.CreateLoop( datanames=[ "_atom_site_type_symbol", "_atom_site_fract_x", "_atom_site_fract_y", "_atom_site_fract_z", ], length_check=False, ) block["_atom_site_type_symbol"] = symbols block["_atom_site_fract_x"] = xf block["_atom_site_fract_y"] = yf block["_atom_site_fract_z"] = zf # Name of the block cannot be empty! block_name = crystal.chemical_formula.replace(" ", "_") cf[block_name] = block # Converting to string writes to stdout for some reason with redirect_stdout(StringIO()): lines = str(cf).splitlines() with open(fname, "w", encoding="utf-8") as f: f.write(CIF_HEADER) f.write("\n".join(lines[13::])) # Skip the fixed header
def get_cif(self, find_symmetry: bool = False): """ Get CIF format string :params find_symmetry: if use Spglib to find symmetry. Default: False """ lattice = np.array(self.cell) * self.lat0 * BOHR_TO_A # in Cartesian positions = [] numbers = [] magmoms = [] label = [] type_symbol = [] for index, elem in enumerate(self.elements): num = self.numbers[elem] for n in range(num): numbers.append(index) label.append(f"{elem}{n+1}") type_symbol.append(f"{elem}") # fractional atomic positions positions.append(self.scaled_positions[elem][n]) magmoms.append(self.magmoms[elem]) if sum(magmoms): spgcell = (lattice, positions, numbers, magmoms) else: spgcell = (lattice, positions, numbers) from CifFile import CifBlock, CifFile cf = CifFile() cb = CifBlock() cb['_cell_length_a'], cb['_cell_length_b'], cb['_cell_length_c'], cb[ '_cell_angle_alpha'], cb['_cell_angle_beta'], cb[ '_cell_angle_gamma'] = self.cellpar from abacuskit.postprocess.symmetry import Spacegroup if find_symmetry: from spglib import get_symmetry_dataset dataset = get_symmetry_dataset(spgcell) if dataset: cb['_atom_site_label'] = [ self.elements[i] for i in dataset['std_types'] ] cb['_atom_site_type_symbol'] = [ f"{self.elements[i]}{i}" for i in dataset['std_types'] ] number = dataset['number'] cb['_symmetry_cell_setting'] = Spacegroup().get_crystal_system( number) cb['_space_group_IT_number'] = number cb['_space_group_name_H-M_alt'] = dataset['international'] cb['_space_group_name_Hall'] = dataset['hall'] x, y, z = np.split(dataset["std_positions"], 3, axis=1) else: cb['_atom_site_label'] = label cb['_atom_site_type_symbol'] = type_symbol cb['_symmetry_cell_setting'] = Spacegroup().get_crystal_system( 1) cb['_space_group_IT_number'] = 1 cb['_space_group_name_H-M_alt'] = "P1" cb['_space_group_name_Hall'] = "P 1" x, y, z = np.split(np.array(positions), 3, axis=1) else: cb['_atom_site_label'] = label cb['_atom_site_type_symbol'] = type_symbol cb['_symmetry_cell_setting'] = Spacegroup().get_crystal_system(1) cb['_space_group_IT_number'] = 1 cb['_space_group_name_H-M_alt'] = "P1" cb['_space_group_name_Hall'] = "P 1" x, y, z = np.split(np.array(positions), 3, axis=1) cb['_atom_site_fract_x'], cb['_atom_site_fract_y'], cb[ '_atom_site_fract_z'] = x.flatten(), y.flatten(), z.flatten() cb.CreateLoop([ '_atom_site_label', '_atom_site_type_symbol', '_atom_site_fract_x', '_atom_site_fract_y', '_atom_site_fract_z' ]) cf['stru_to_cif'] = cb return str(cf)
def read_cif(fNameIn): logger = logging.getLogger() data = {} # Open the CIF file and read all the lines into a list of strings. try: f = open(fNameIn, 'r') lines = [] oko_fix = False for line in f: stripped = line.strip() if (len(stripped) > 0): #fix for OKO #http://stackoverflow.com/questions/79968/split-a-string-by-spaces-preserving-quoted-substrings-in-python import shlex if stripped[0] == '_': cols = shlex.split(stripped) #shlex protects the quotation if len(cols) > 2: stripped = "{0} '{1}'".format(cols[0]," ".join(cols[1:])) logger.debug("Line (Fixed): {0}".format(stripped)) oko_fix = True lines.append(stripped) if oko_fix: fixedfilename = fNameIn + ".fix" fixedfile = open(fixedfilename,"w") fixedfile.write("\n".join(lines)+"\n") fixedfile.close() fNameIn = fixedfilename except: logger.error("Failed to open CIF file '{0}'".format(fNameIn)) sys.exit() # Use the CifFile parser to extract the data. Although there might be # multiple data blocks, we'll only use the first one. cif_file = CifFile(fNameIn) for db in cif_file: data_block = db break try: # Extract some parameters, and convert them to floats. data['_cell_length_a'] = float_with_error(data_block['_cell_length_a']) data['_cell_length_b'] = float_with_error(data_block['_cell_length_b']) data['_cell_length_c'] = float_with_error(data_block['_cell_length_c']) data['_cell_angle_alpha'] = float_with_error(data_block['_cell_angle_alpha']) data['_cell_angle_beta'] = float_with_error(data_block['_cell_angle_beta']) data['_cell_angle_gamma'] = float_with_error(data_block['_cell_angle_gamma']) if data_block.has_key('_cell_volume'): data['_cell_volume'] = float_with_error(data_block['_cell_volume']) # Get the symbolic operations that define the space group. In a CIF file # that's the part that looks like: # # loop_ # _symmetry_equiv_pos_as_xyz # 'x,y,z' # 'y,x,2/3-z' # '-y,x-y,2/3+z' # '-x,-x+y,1/3-z' # '-x+y,-x,1/3+z' # 'x-y,-y,-z' # # In some cases it's called "_space_group_symop_operation_xyz" apparently?!?! data['_symmetry_equiv_pos_as_xyz'] = [] try: xyz = data_block["_symmetry_equiv_pos_as_xyz"] except KeyError: try: xyz = data_block["_space_group_symop_operation_xyz"] except KeyError: logger.error("Missing item in CIF file: need either '_symmetry_equiv_pos_as_xyz' or '_space_group_symop_operation_xyz'.") sys.exit() # Copy the x,y,z symmetry group operations. Remove the quotes if there # are any. for op_xyz in xyz: if (op_xyz[0] == '\''): data['_symmetry_equiv_pos_as_xyz'].append(op_xyz[1:-1]) else: data['_symmetry_equiv_pos_as_xyz'].append(op_xyz) # Add x,y,z of the atoms to "data", but make sure to convert # e.g. "0.1549(8)" to "0.1549". data['_atom_site_label'] = data_block['_atom_site_label'] data['_atom_site_fract_x'] = [] for str_x in data_block['_atom_site_fract_x']: data['_atom_site_fract_x'].append( float_with_error(str_x)) data['_atom_site_fract_y'] = [] for str_y in data_block['_atom_site_fract_y']: data['_atom_site_fract_y'].append( float_with_error(str_y)) data['_atom_site_fract_z'] = [] for str_z in data_block['_atom_site_fract_z']: data['_atom_site_fract_z'].append( float_with_error(str_z)) except KeyError as e: logger.error("Error! Missing item in file.") logger.error(e) sys.exit() #print "ALL DATA:" #print data #print # Return the extracted data. return data
def prepareGulpInput(cifFile, gulpFile, jobName, verbose = False, auto_fractions = False): """ Example file: mgh2 cell 4.5168 4.5168 3.0205 90.0 90.0 90.0 frac Mg 0.0 0.0 0.0 H 0.306 0.306 0.0 space 136 output xyz mgh2 """ from CifFile import CifFile if not os.path.exists(cifFile): raise IOError("CIF file '%s' was not found!" % (cifFile)) cf = CifFile(cifFile) if verbose: print "------------------------------------------------------------------" if len(cf) != 1: raise StandardError("The cif file contains %i data blocks, while one was expected") # A cif file can contain several "datablocks" that each start # with "data_". if verbose: print "Reading data block '%s'..." % (cf.keys()[0]) cb = cf[cf.keys()[0]] # open the first block AA = float(re.match('([0-9.e]*)',cb['_cell_length_a']).group(0)) BB = float(re.match('([0-9.e]*)',cb['_cell_length_b']).group(0)) CC = float(re.match('([0-9.e]*)',cb['_cell_length_c']).group(0)) alpha = float(cb['_cell_angle_alpha']) beta = float(cb['_cell_angle_beta']) gamma = float(cb['_cell_angle_gamma']) # Spacegroup number (1-230) # '_symmetry_Int_Tables_number' has been superseded by '_space_group_IT_number' if '_space_group_IT_number' in cb.keys(): SG = int(cb['_space_group_IT_number']) elif '_symmetry_Int_Tables_number' in cb.keys(): SG = int(cb['_symmetry_Int_Tables_number']) else: print "WARNING: No space group specified. Assuming P1." SG = 1 # CCTBX: #unit_cell = uctbx.unit_cell([AA,BB,CC,alpha,beta,gamma]) #space_group_info = sgtbx.space_group_info(symbol=cb['_symmetry_space_group_name_H-M']) #crystal_symmetry = crystal.symmetry(unit_cell=unit_cell,space_group_info=space_group_info) #print "CIF file read successfully:" #crystal_symmetry.show_summary() if verbose: print " Space group:",SG print " a=%s, b=%s, c=%s, alpha=%s, beta=%s, gamma=%s" % (AA,BB,CC,alpha,beta,gamma) atomTypes = [] atoms = '' fracOccFound = False firstAtom = True atoms = "" # The coordinates of the atom (_atom_site_fract_x/y/z) may have # a last digit in parenthesis, like "-0.6636(7)". Therefore we # extract the part consisting of only digits and a decimal separator: coordsMatch = re.compile('[0-9.e-]*'); for atom in cb.GetLoop('_atom_site_label'): atomKeys = dir(atom) if '_atom_site_type_symbol' in atomKeys: m = re.match('[a-z]*',atom._atom_site_type_symbol,re.I) atomType = m.group(0) else: m = re.match('[a-z]*',atom._atom_site_label,re.I) atomType = m.group(0) if '_atom_site_occupancy' in atomKeys: occ = float(coordsMatch.match(atom._atom_site_occupancy).group()) if not occ == 1.0: if not fracOccFound: print " " print " WARNING: Fractional occupancy (" + str(occ) +") " \ + "found for atom of type " + atomType + "." fracOccFound = True else: occ = 1.0 # Some crystal structures obtained by neutron diffraction use D for H: if atomType == 'D': atomType = 'H' if '_atom_site_symmetry_multiplicity' in atomKeys and '_atom_site_Wyckoff_symbol' in atomKeys: atomTypes.append(atomType+' at '+atom._atom_site_symmetry_multiplicity+atom._atom_site_Wyckoff_symbol) else: atomTypes.append(atomType) atomX = coordsMatch.match(atom._atom_site_fract_x).group() atomY = coordsMatch.match(atom._atom_site_fract_y).group() atomZ = coordsMatch.match(atom._atom_site_fract_z).group() atomPos = [atomX, atomY, atomZ] for i in range(3): pp = atomPos[i].split(".") if len(pp) is 2: decimals = pp[1] if len(decimals) > 3 and len(decimals) < 6 and decimals[0] == decimals[1] and decimals[0] == decimals[2] and decimals[-1] != "0": if auto_fractions: oldPos = atomPos[i] atomPos[i] = "%.6f" % (float(eval('1.*'+float2fraction(atomPos[i])))) print " Notice: Converted %s into %s" %(oldPos,atomPos[i]) else: print "\n"\ + " ! Warning: The coordinate "+atomPos[i]+" looks similar to the fraction %s, but\n" % float2fraction(atomPos[i]) \ + " ! has insufficient decimals to be recognized as so by GULP. If you want\n" \ + " ! this coordinate to be recognized as a special high-symmetry position,\n" \ + " ! you need to specify at least six digits. If you run cif2vasp with the \n" \ + " ! -f switch, cif2vasp will try to add the necessary decimals automaticly." atoms += "%s %s %s %s %f %f\n" % (atomType, atomPos[0], atomPos[1], atomPos[2], 0.0, occ) firstAtom = False if fracOccFound: print " " print "ERROR: Fractional occupancies are not currently supported.\n" exit() if verbose: print " Atom types: " + ', '.join(atomTypes) gulpFile = open(gulpFile,'w') #Create and write the GULP gulpFile.writelines([jobName+'\n', 'cell\n', '%s %s %s %s %s %s\n' % (AA,BB,CC,alpha,beta,gamma), 'frac\n', atoms, 'space\n', str(SG)+'\n', 'output xyz '+jobName+'\n' ])
def readCifFile(cifFile): from CifFile import CifFile if not os.path.exists(cifFile): raise IOError("CIF file '%s' was not found!" % (cifFile)) cf = CifFile(cifFile) print "------------------------------------------------------------------" if len(cf) != 1: raise StandardError("The cif file contains %i data blocks, while one was expected") # A cif file can contain several "datablocks" that each start # with "data_". cb = cf[cf.keys()[0]] # open the first block AA = float(re.match('([0-9.]*)',cb['_cell_length_a']).group(0)) BB = float(re.match('([0-9.]*)',cb['_cell_length_b']).group(0)) CC = float(re.match('([0-9.]*)',cb['_cell_length_c']).group(0)) alpha = float(cb['_cell_angle_alpha']) beta = float(cb['_cell_angle_beta']) gamma = float(cb['_cell_angle_gamma']) SG = int(cb['_symmetry_Int_Tables_number']) # spacegroup atomTypes = [] atoms = '' fracOccFound = False firstAtom = True atoms = [] for atom in cb.GetLoop('_atom_site_label'): atomKeys = dir(atom) if '_atom_site_type_symbol' in atomKeys: m = re.match('[a-z]*',atom._atom_site_type_symbol,re.I) atomType = m.group(0) else: m = re.match('[a-z]*',atom._atom_site_label,re.I) atomType = m.group(0) atomLabel = atom._atom_site_label if '_atom_site_occupancy' in atomKeys: occ = float(atom._atom_site_occupancy) if not occ == 1.0: if not fracOccFound: print " " print " WARNING: Fractional occupancy (" + str(occ) +") " \ + "found for atom of type " + atomType + "." fracOccFound = True else: occ = 1.0 # Some crystal structures obtained by neutron diffraction use D for H: if atomType == 'D': atomType = 'H' atomLabel.replace('H','D') if '_atom_site_symmetry_multiplicity' in atomKeys and '_atom_site_Wyckoff_symbol' in atomKeys: atomTypes.append(atomType+' at '+atom._atom_site_symmetry_multiplicity+atom._atom_site_Wyckoff_symbol) else: atomTypes.append(atomType) atomPos = [atom._atom_site_fract_x, atom._atom_site_fract_y, atom._atom_site_fract_z] for p in atomPos: pp = p.split(".") if len(pp) is 2: decimals = p.split(".")[1] if len(decimals) > 3 and len(decimals) < 6 and decimals[0] == decimals[1] and decimals[-1] != "0": print "\n ---------------------\n"\ + " Warning: If the fractional coordinate "+p+" is a recurring decimal, such as 1/3,\n" \ + " then it is necessary to specify this value to six decimal places to be sure of \n" \ + " it being recognised correctly as a spcecial position.\n ------------------" # The coordinates of the atom (_atom_site_fract_x/y/z) may have # a last digit in parenthesis, like "0.6636(7)". Therefore we # extract the part consisting of only digits and a decimal separator: p = re.compile('[0-9.]*'); atomX = float(p.match(atom._atom_site_fract_x).group()) atomY = float(p.match(atom._atom_site_fract_y).group()) atomZ = float(p.match(atom._atom_site_fract_z).group()) #atoms += "%s %f %f %f %f %f\n" % (atomType, atomX, atomY, atomZ, 0.0, occ) atoms.append({'label': atomLabel, 'type': atomType, 'pos': (atomX,atomY,atomZ) }) firstAtom = False if fracOccFound: print " " print "ERROR: Fractional occupancies are not currently supported.\n" exit() print " Atom types: " + ', '.join(atomTypes) return {'spacegroup': SG, 'unit_cell': [AA,BB,CC,alpha,beta,gamma], 'scatterers': atoms}
# prepare a cursor object using cursor() method cursor = db.cursor() sql = "select file from data" cursor.execute(sql) allFiles = cursor.fetchall() for i in range(0, len(allFiles)): fileNum = str(allFiles[i][0]) fileName = "%s.cif" % fileNum dir_level1 = fileNum[0] dir_level2 = fileNum[1:3] dir_level3 = fileNum[3:5] cif_full_dir = os.path.join(cif_dir, dir_level1, dir_level2, dir_level3) cif_full_file = os.path.join(cif_full_dir, fileName) if (not os.path.exists(cif_full_file)): raise IOError("CIF file '%s' was not found!" % (fileName)) else: cf = CifFile(cif_full_file) cb = cf[cf.keys()[0]] cb['_symmetry_cell_setting'] cb['_symmetry_space_group_name_Hall'] cb['_symmetry_space_group_name_H-M'] cb['_atom_site_label'] cb['_atom_site_fract_x'] cb['_atom_site_fract_y'] cb['_atom_site_fract_z']
class P_cif(StructureParser): """Simple parser for CIF structure format. Reads Structure from the first block containing _atom_site_label key. Following blocks, if any are ignored. Data members: format -- structure format name ciffile -- instance of CifFile from PyCifRW stru -- Structure instance used for cif input or output Data members used for input only: spacegroup -- instance of SpaceGroup used for symmetry expansion eps -- resolution in fractional coordinates for non-equal positions. Use for expansion of asymmetric unit. eau -- instance of ExpandAsymmetricUnit from SymmetryUtilities asymmetric_unit -- list of atom instances for the original asymmetric unit in the CIF file labelindex -- dictionary mapping unique atom label to index of atom in self.asymmetric_unit cif_sgname -- space group name obtained by looking up the value of _space_group_name_Hall, _symmetry_space_group_name_Hall, _space_group_name_H-M_alt, _symmetry_space_group_name_H-M items. None when neither is defined. """ # static data and methods ------------------------------------------------ # dictionary set of class methods for translating CIF values # to Atom attributes _atom_setters = dict.fromkeys(( '_tr_ignore', '_tr_atom_site_label', '_tr_atom_site_type_symbol', '_tr_atom_site_fract_x', '_tr_atom_site_fract_y', '_tr_atom_site_fract_z', '_tr_atom_site_cartn_x', '_tr_atom_site_cartn_y', '_tr_atom_site_cartn_z', '_tr_atom_site_U_iso_or_equiv', '_tr_atom_site_B_iso_or_equiv', '_tr_atom_site_adp_type', '_tr_atom_site_thermal_displace_type', '_tr_atom_site_occupancy', '_tr_atom_site_aniso_U_11', '_tr_atom_site_aniso_U_22', '_tr_atom_site_aniso_U_33', '_tr_atom_site_aniso_U_12', '_tr_atom_site_aniso_U_13', '_tr_atom_site_aniso_U_23', '_tr_atom_site_aniso_B_11', '_tr_atom_site_aniso_B_22', '_tr_atom_site_aniso_B_33', '_tr_atom_site_aniso_B_12', '_tr_atom_site_aniso_B_13', '_tr_atom_site_aniso_B_23', )) # make _atom_setters case insensitive for k in list(_atom_setters.keys()): _atom_setters[k] = _atom_setters[k.lower()] = k del k BtoU = 1.0/(8 * numpy.pi**2) def _tr_ignore(a, value): return _tr_ignore = staticmethod(_tr_ignore) def _tr_atom_site_label(a, value): a.label = str(value) # set element when not specified by _atom_site_type_symbol if not a.element: P_cif._tr_atom_site_type_symbol(a, value) _tr_atom_site_label = staticmethod(_tr_atom_site_label) # 3 regexp groups for nucleon number, atom symbol, and oxidation state _psymb = re.compile(r'(\d+-)?([a-zA-Z]+)(\d[+-])?') def _tr_atom_site_type_symbol(a, value): rx = P_cif._psymb.match(value) smbl = rx and rx.group(0) or value smbl = str(smbl) a.element = smbl[:1].upper() + smbl[1:].lower() _tr_atom_site_type_symbol = staticmethod(_tr_atom_site_type_symbol) def _tr_atom_site_fract_x(a, value): a.xyz[0] = leading_float(value) _tr_atom_site_fract_x = staticmethod(_tr_atom_site_fract_x) def _tr_atom_site_fract_y(a, value): a.xyz[1] = leading_float(value) _tr_atom_site_fract_y = staticmethod(_tr_atom_site_fract_y) def _tr_atom_site_fract_z(a, value): a.xyz[2] = leading_float(value) _tr_atom_site_fract_z = staticmethod(_tr_atom_site_fract_z) def _tr_atom_site_cartn_x(a, value): a.xyz_cartn[0] = leading_float(value) _tr_atom_site_cartn_x = staticmethod(_tr_atom_site_cartn_x) def _tr_atom_site_cartn_y(a, value): a.xyz_cartn[1] = leading_float(value) _tr_atom_site_cartn_y = staticmethod(_tr_atom_site_cartn_y) def _tr_atom_site_cartn_z(a, value): a.xyz_cartn[2] = leading_float(value) _tr_atom_site_cartn_z = staticmethod(_tr_atom_site_cartn_z) def _tr_atom_site_U_iso_or_equiv(a, value): a.Uisoequiv = leading_float(value) _tr_atom_site_U_iso_or_equiv = staticmethod(_tr_atom_site_U_iso_or_equiv) def _tr_atom_site_B_iso_or_equiv(a, value): a.Uisoequiv = P_cif.BtoU * leading_float(value) _tr_atom_site_B_iso_or_equiv = staticmethod(_tr_atom_site_B_iso_or_equiv) def _tr_atom_site_adp_type(a, value): a.anisotropy = value not in ("Uiso", "Biso") _tr_atom_site_adp_type = staticmethod(_tr_atom_site_adp_type) _tr_atom_site_thermal_displace_type = _tr_atom_site_adp_type def _tr_atom_site_occupancy(a, value): a.occupancy = leading_float(value, 1.0) _tr_atom_site_occupancy = staticmethod(_tr_atom_site_occupancy) def _tr_atom_site_aniso_U_11(a, value): a.U11 = leading_float(value) _tr_atom_site_aniso_U_11 = staticmethod(_tr_atom_site_aniso_U_11) def _tr_atom_site_aniso_U_22(a, value): a.U22 = leading_float(value) _tr_atom_site_aniso_U_22 = staticmethod(_tr_atom_site_aniso_U_22) def _tr_atom_site_aniso_U_33(a, value): a.U33 = leading_float(value) _tr_atom_site_aniso_U_33 = staticmethod(_tr_atom_site_aniso_U_33) def _tr_atom_site_aniso_U_12(a, value): a.U12 = leading_float(value) _tr_atom_site_aniso_U_12 = staticmethod(_tr_atom_site_aniso_U_12) def _tr_atom_site_aniso_U_13(a, value): a.U13 = leading_float(value) _tr_atom_site_aniso_U_13 = staticmethod(_tr_atom_site_aniso_U_13) def _tr_atom_site_aniso_U_23(a, value): a.U23 = leading_float(value) _tr_atom_site_aniso_U_23 = staticmethod(_tr_atom_site_aniso_U_23) def _tr_atom_site_aniso_B_11(a, value): a.U11 = P_cif.BtoU * leading_float(value) _tr_atom_site_aniso_B_11 = staticmethod(_tr_atom_site_aniso_B_11) def _tr_atom_site_aniso_B_22(a, value): a.U22 = P_cif.BtoU * leading_float(value) _tr_atom_site_aniso_B_22 = staticmethod(_tr_atom_site_aniso_B_22) def _tr_atom_site_aniso_B_33(a, value): a.U33 = P_cif.BtoU * leading_float(value) _tr_atom_site_aniso_B_33 = staticmethod(_tr_atom_site_aniso_B_33) def _tr_atom_site_aniso_B_12(a, value): a.U12 = P_cif.BtoU * leading_float(value) _tr_atom_site_aniso_B_12 = staticmethod(_tr_atom_site_aniso_B_12) def _tr_atom_site_aniso_B_13(a, value): a.U13 = P_cif.BtoU * leading_float(value) _tr_atom_site_aniso_B_13 = staticmethod(_tr_atom_site_aniso_B_13) def _tr_atom_site_aniso_B_23(a, value): a.U23 = P_cif.BtoU * leading_float(value) _tr_atom_site_aniso_B_23 = staticmethod(_tr_atom_site_aniso_B_23) def _get_atom_setters(cifloop): """Find translators of CifLoop items to data in Atom instance. Static method. cifloop -- instance of CifLoop Return a list of setter functions in the order of cifloop.keys(). """ rv = [] for p in cifloop.keys(): lcname = "_tr" + p.lower() fncname = P_cif._atom_setters.get(lcname, '_tr_ignore') f = getattr(P_cif, fncname) rv.append(f) return rv _get_atom_setters = staticmethod(_get_atom_setters) # normal methods --------------------------------------------------------- def __init__(self, eps=None): """Initialize the parser for CIF structure files. eps -- fractional coordinates cutoff for duplicate positions. When None use the default for ExpandAsymmetricUnit. """ StructureParser.__init__(self) self.format = "cif" self.ciffile = None self.stru = None self.spacegroup = None self.eps = eps self.eau = None self.asymmetric_unit = None self.labelindex = {} self.cif_sgname = None pass def parse(self, s): """Create Structure instance from a string in CIF format. Return Structure instance or raise StructureFormatError. """ self.ciffile = None self.filename = '' fp = six.StringIO(s) rv = self._parseCifDataSource(fp) return rv def parseLines(self, lines): """Parse list of lines in CIF format. lines -- list of strings stripped of line terminator Return Structure instance or raise StructureFormatError. """ s = "\n".join(lines) + '\n' return self.parse(s) def parseFile(self, filename): """Create Structure from an existing CIF file. filename -- path to structure file Return Structure object. Raise StructureFormatError or IOError. """ self.ciffile = None self.filename = filename fileurl = _fixIfWindowsPath(filename) rv = self._parseCifDataSource(fileurl) # all good here return rv def _parseCifDataSource(self, datasource): """\ Open and process CIF data from the specified `datasource`. Parameters ---------- datasource : str or a file-like object This is used as an argument to the CifFile class. The CifFile instance is stored in `ciffile` attribute of this Parser. Returns ------- Structure The Structure object loaded from the specified data source. Raises ------ StructureFormatError When the data do not constitute a valid CIF format. """ from CifFile import CifFile, StarError self.stru = None try: with _suppressCifParserOutput(): self.ciffile = CifFile(datasource) for blockname in self.ciffile.keys(): self._parseCifBlock(blockname) # stop after reading the first structure if self.stru is not None: break except (StarError, ValueError, IndexError) as err: exc_type, exc_value, exc_traceback = sys.exc_info() emsg = str(err).strip() e = StructureFormatError(emsg) six.reraise(StructureFormatError, e, exc_traceback) return self.stru def _parseCifBlock(self, blockname): """Translate CIF file block, skip blocks without _atom_site_label. Updates data members stru, eau. blockname -- name of top level block in self.ciffile No return value. """ block = self.ciffile[blockname] if '_atom_site_label' not in block: return # here block contains structure, initialize output data self.stru = Structure() self.labelindex.clear() # execute specialized block parsers self._parse_lattice(block) self._parse_atom_site_label(block) self._parse_atom_site_aniso_label(block) self._parse_space_group_symop_operation_xyz(block) return def _parse_lattice(self, block): """Obtain lattice parameters from a CifBlock. This method updates self.stru.lattice. block -- instance of CifBlock No return value. """ if '_cell_length_a' not in block: return # obtain lattice parameters try: latpars = ( leading_float(block['_cell_length_a']), leading_float(block['_cell_length_b']), leading_float(block['_cell_length_c']), leading_float(block['_cell_angle_alpha']), leading_float(block['_cell_angle_beta']), leading_float(block['_cell_angle_gamma']), ) except KeyError as err: exc_type, exc_value, exc_traceback = sys.exc_info() emsg = str(err) e = StructureFormatError(emsg) six.reraise(StructureFormatError, e, exc_traceback) self.stru.lattice = Lattice(*latpars) return def _parse_atom_site_label(self, block): """Obtain atoms in asymmetric unit from a CifBlock. This method inserts Atom instances to self.stru and updates labelindex dictionary. block -- instance of CifBlock No return value. """ # process _atom_site_label atom_site_loop = block.GetLoop('_atom_site_label') # get a list of setters for atom_site values prop_setters = P_cif._get_atom_setters(atom_site_loop) # index of the _atom_site_label item for the labelindex dictionary ilb = atom_site_loop.keys().index('_atom_site_label') # loop through the values and pass them to the setters sitedatalist = zip(*atom_site_loop.values()) for values in sitedatalist: curlabel = values[ilb] # skip entries that have invalid label if curlabel == '?': continue self.labelindex[curlabel] = len(self.stru) self.stru.addNewAtom() a = self.stru.getLastAtom() for fset, val in zip(prop_setters, values): fset(a, val) return def _parse_atom_site_aniso_label(self, block): """Obtain value of anisotropic thermal displacements from a CifBlock. This method updates U members of Atom instances in self.stru. The labelindex dictionary has to be defined beforehand. block -- instance of CifBlock No return value. """ if '_atom_site_aniso_label' not in block: return # was anisotropy set in the _atom_site_label loop? atom_site_loop = block.GetLoop('_atom_site_label') anisotropy_already_set = ( '_atom_site_adp_type' in atom_site_loop or '_atom_site_thermal_displace_type' in atom_site_loop) # something to do here: adp_loop = block.GetLoop('_atom_site_aniso_label') # index of the _atom_site_label column ilb = adp_loop.keys().index('_atom_site_aniso_label') # get a list of setters for this loop prop_setters = P_cif._get_atom_setters(adp_loop) sitedatalist = zip(*adp_loop.values()) for values in sitedatalist: idx = self.labelindex[values[ilb]] a = self.stru[idx] if not anisotropy_already_set: a.anisotropy = True for fset, val in zip(prop_setters, values): fset(a, val) return def _parse_space_group_symop_operation_xyz(self, block): """Process symmetry operations from a CifBlock. The method updates spacegroup and eau data according to symmetry operations defined in _space_group_symop_operation_xyz or _symmetry_equiv_pos_as_xyz items in CifBlock. block -- instance of CifBlock No return value. """ from diffpy.structure.spacegroups import IsSpaceGroupIdentifier from diffpy.structure.spacegroups import SpaceGroup, GetSpaceGroup self.asymmetric_unit = list(self.stru) sym_synonyms = ('_space_group_symop_operation_xyz', '_symmetry_equiv_pos_as_xyz') sym_loop_name = [n for n in sym_synonyms if n in block] # recover explicit list of symmetry operations symop_list = [] if sym_loop_name: # sym_loop exists here and we know its cif name sym_loop_name = sym_loop_name[0] sym_loop = block.GetLoop(sym_loop_name) for eqxyz in sym_loop[sym_loop_name]: opcif = getSymOp(eqxyz) symop_list.append(opcif) # determine space group number sg_nameHall = (block.get('_space_group_name_Hall', '') or block.get('_symmetry_space_group_name_Hall', '')) sg_nameHM = (block.get('_space_group_name_H-M_alt', '') or block.get('_symmetry_space_group_name_H-M', '')) self.cif_sgname = (sg_nameHall or sg_nameHM or None) sgid = (int(block.get('_space_group_IT_number', '0')) or int(block.get('_symmetry_Int_Tables_number', '0')) or sg_nameHM) # try to reuse existing space group self.spacegroup = None if sgid and IsSpaceGroupIdentifier(sgid): sgstd = GetSpaceGroup(sgid) oprep_std = [str(op) for op in sgstd.iter_symops()] oprep_std.sort() oprep_cif = [str(op) for op in symop_list] oprep_cif.sort() # make sure symmetry operations have the same order if oprep_std == oprep_cif: self.spacegroup = copy.copy(sgstd) self.spacegroup.symop_list = symop_list # use standard definition when symmetry operations were not listed elif not symop_list: self.spacegroup = sgstd # define new spacegroup when symmetry operations were listed, but # there is no match to an existing definition if symop_list and self.spacegroup is None: new_short_name = "CIF " + (sg_nameHall or 'data') new_crystal_system = ( block.get('_space_group_crystal_system') or block.get('_symmetry_cell_setting') or 'TRICLINIC' ).upper() self.spacegroup = SpaceGroup( short_name=new_short_name, crystal_system=new_crystal_system, symop_list=symop_list) if self.spacegroup is None: emsg = "CIF file has unknown space group identifier {!r}." raise StructureFormatError(emsg.format(sgid)) self._expandAsymmetricUnit() return def _expandAsymmetricUnit(self): """Perform symmetry expansion of self.stru using self.spacegroup. This method updates data in stru and eau. No return value. """ from diffpy.structure.symmetryutilities import ExpandAsymmetricUnit # get reverse-ordered unique indices corepos = [a.xyz for a in self.stru] coreUijs = [a.U for a in self.stru] self.eau = ExpandAsymmetricUnit(self.spacegroup, corepos, coreUijs, eps=self.eps) # build a nested list of new atoms: newatoms = [] for i, ca in enumerate(self.stru): eca = [] # expanded core atom for j in range(self.eau.multiplicity[i]): a = Atom(ca) a.xyz = self.eau.expandedpos[i][j] if j > 0: a.label += '_' + str(j + 1) if a.anisotropy: a.U = self.eau.expandedUijs[i][j] eca.append(a) newatoms.append(eca) # insert new atoms where they belong self.stru[:] = sum(newatoms, []) return # conversion to CIF ------------------------------------------------------ def toLines(self, stru): """Convert Structure stru to a list of lines in basic CIF format. Return list of strings. """ import time lines = [] # may be replaced with filtered Structure.title # for now, we can add the title as a comment if stru.title.strip() != "": title_lines = stru.title.split('\n') lines.extend([ "# " + line.strip() for line in title_lines ]) lines.append("") lines.append("data_3D") iso_date = "%04i-%02i-%02i" % time.gmtime()[:3] lines.extend([ "%-31s %s" % ("_audit_creation_date", iso_date), "%-31s %s" % ("_audit_creation_method", "P_cif.py"), "", "%-31s %s" % ("_symmetry_space_group_name_H-M", "'P1'"), "%-31s %s" % ("_symmetry_Int_Tables_number", "1"), "%-31s %s" % ("_symmetry_cell_setting", "triclinic"), "" ]) # there should be no need to specify equivalent positions for P1 # _symmetry_equiv_posi_as_xyz x,y,z lines.extend([ "%-31s %.6g" % ("_cell_length_a", stru.lattice.a), "%-31s %.6g" % ("_cell_length_b", stru.lattice.b), "%-31s %.6g" % ("_cell_length_c", stru.lattice.c), "%-31s %.6g" % ("_cell_angle_alpha", stru.lattice.alpha), "%-31s %.6g" % ("_cell_angle_beta", stru.lattice.beta), "%-31s %.6g" % ("_cell_angle_gamma", stru.lattice.gamma), "" ]) # build a list of site labels and adp (displacement factor) types element_count = {} a_site_label = [] a_adp_type = [] for a in stru: cnt = element_count[a.element] = element_count.get(a.element,0)+1 a_site_label.append( "%s%i" % (a.element, cnt) ) if numpy.all(a.U == a.U[0,0]*numpy.identity(3)): a_adp_type.append("Uiso") else: a_adp_type.append("Uani") # list all atoms lines.extend([ "loop_", " _atom_site_label", " _atom_site_type_symbol", " _atom_site_fract_x", " _atom_site_fract_y", " _atom_site_fract_z", " _atom_site_U_iso_or_equiv", " _atom_site_adp_type", " _atom_site_occupancy" ]) for i in range(len(stru)): a = stru[i] line = " %-5s %-3s %11.6f %11.6f %11.6f %11.6f %-5s %.4f" % ( a_site_label[i], a.element, a.xyz[0], a.xyz[1], a.xyz[2], a.Uisoequiv, a_adp_type[i], a.occupancy ) lines.append(line) # find anisotropic atoms idx_aniso = [ i for i in range(len(stru)) if a_adp_type[i] != "Uiso" ] if idx_aniso != []: lines.extend([ "loop_", " _atom_site_aniso_label", " _atom_site_aniso_U_11", " _atom_site_aniso_U_22", " _atom_site_aniso_U_33", " _atom_site_aniso_U_12", " _atom_site_aniso_U_13", " _atom_site_aniso_U_23" ]) for i in idx_aniso: a = stru[i] line = " %-5s %9.6f %9.6f %9.6f %9.6f %9.6f %9.6f" % ( a_site_label[i], a.U[0,0], a.U[1,1], a.U[2,2], a.U[0,1], a.U[0,2], a.U[1,2] ) lines.append(line) return lines