Source code for G2pwd_CIF

# -*- coding: utf-8 -*-
########### SVN repository information ###################
# $Date: 2023-05-11 18:08:12 -0500 (Thu, 11 May 2023) $
# $Author: toby $
# $Revision: 5577 $
# $URL: https://subversion.xray.aps.anl.gov/pyGSAS/trunk/imports/G2pwd_CIF.py $
# $Id: G2pwd_CIF.py 5577 2023-05-11 23:08:12Z toby $
########### SVN repository information ###################
'''
'''
from __future__ import division, print_function
import numpy as np
import os.path
import GSASIIobj as G2obj
import CifFile as cif # PyCifRW from James Hester
import GSASIIpath
try:
    import GSASIIctrlGUI as G2G
except ImportError:
    pass
asind = lambda x: 180.*np.arcsin(x)/np.pi
GSASIIpath.SetVersionNumber("$Revision: 5577 $")

[docs] class CIFpwdReader(G2obj.ImportPowderData): 'Routines to import powder data from a CIF file' def __init__(self): super(self.__class__,self).__init__( # fancy way to self-reference extensionlist=('.CIF','.cif'), strictExtension=False, formatName = 'CIF', longFormatName = 'Powder data from CIF' ) # Validate the contents
[docs] def ContentsValidator(self, filename): 'Use standard CIF validator' fp = open(filename,'r') return self.CIFValidator(fp) fp.close()
[docs] def Reader(self,filename, ParentFrame=None, **kwarg): '''Read powder data from a CIF. If multiple datasets are requested, use self.repeat and buffer caching. ''' # Define lists of data names used for holding powder diffraction data # entries of a type that are not implemented are commented out in case # we will want them later. xDataItems = ( # "x-axis" data names" ('_pd_meas_2theta_range_min', '_pd_meas_2theta_range_max', '_pd_meas_2theta_range_inc'), ('_pd_proc_2theta_range_min', '_pd_proc_2theta_range_max', '_pd_proc_2theta_range_inc'), '_pd_meas_2theta_scan', '_pd_meas_time_of_flight', '_pd_proc_2theta_corrected', #'_pd_proc_d_spacing', #'_pd_proc_energy_incident', #'_pd_proc_energy_detection', '_pd_proc_recip_len_q', #'_pd_proc_wavelength', ) intDataItems = ( # intensity axis data names '_pd_meas_counts_total', #'_pd_meas_counts_background', #'_pd_meas_counts_container', '_pd_meas_intensity_total', #'_pd_meas_intensity_background', #'_pd_meas_intensity_container', '_pd_proc_intensity_net', '_pd_proc_intensity_total', #'_pd_proc_intensity_bkg_calc', #'_pd_proc_intensity_bkg_fix', '_pd_calc_intensity_net', # allow computed patterns as input data? '_pd_calc_intensity_total', ) ESDDataItems = ( # items that can be used to compute uncertainties for obs data '_pd_proc_ls_weight', '_pd_meas_counts_total' ) ModDataItems = ( # items that modify the use of the data '_pd_meas_step_count_time', '_pd_meas_counts_monitor', '_pd_meas_intensity_monitor', '_pd_proc_intensity_norm', '_pd_proc_intensity_incident', ) rdbuffer = kwarg.get('buffer') cf = None choicelist = None selections = None # reload previously saved values if self.repeat and rdbuffer is not None: cf = rdbuffer.get('lastcif') choicelist = rdbuffer.get('choicelist') print ('debug: Reuse previously parsed CIF') selections = rdbuffer.get('selections') if cf is None: if GSASIIpath.GetConfigValue('debug'): print("Starting parse of {} as CIF".format(filename)) cf = G2obj.ReadCIF(filename) if GSASIIpath.GetConfigValue('debug'): print ("CIF file parsed") # scan all blocks for sets of data if choicelist is None: choicelist = [] for blk in cf.keys(): blkkeys = [k.lower() for k in cf[blk].keys()] # save a list of the data items, since we will use it often # scan through block for x items xldict = {} for x in xDataItems: if type(x) is tuple: # check for the presence of all three items that define a range of data if not all([i in blkkeys for i in x]): continue try: items = [float(cf[blk][xi]) for xi in x] l = 1 + int(0.5 + (items[1]-items[0])/items[2]) except: continue else: if x not in blkkeys: continue l = len(cf[blk][x]) if xldict.get(l) is None: xldict[l] = [x] else: xldict[l].append(x) # now look for matching intensity items yldict = {} suldict = {} for y in intDataItems: if y in blkkeys: l = len(cf[blk][y]) if yldict.get(l) is None: yldict[l] = [y] else: yldict[l].append(y) # now check if the first item has an uncertainty if cif.get_number_with_esd(cf[blk][y][0])[1] is None: continue if suldict.get(l) is None: suldict[l] = [y] else: suldict[l].append(y) for y in ESDDataItems: if y in blkkeys: l = len(cf[blk][y]) if suldict.get(l) is None: suldict[l] = [y] else: suldict[l].append(y) modldict = {} for y in ModDataItems: if y in blkkeys: l = len(cf[blk][y]) if modldict.get(l) is None: modldict[l] = [y] else: modldict[l].append(y) for l in xldict: if yldict.get(l) is None: continue choicelist.append([blk,l,xldict[l],yldict[l],suldict.get(l,[]),modldict.get(l,[])]) #print blk,l,xldict[l],yldict[l],suldict.get(l,[]),modldict.get(l,[]) print ("CIF file scanned for blocks with data") if not choicelist: selblk = None # no block to choose self.errors = "No powder diffraction blocks found" return False elif len(choicelist) == 1: # only one choice selblk = 0 elif self.repeat and selections is not None: # we were called to repeat the read print ('debug: repeat #',self.repeatcount,'selection',selections[self.repeatcount]) selblk = selections[self.repeatcount] self.repeatcount += 1 if self.repeatcount >= len(selections): self.repeat = False else: # choose from options # compile a list of choices for the user choices = [] for blk,l,x,y,su,mod in choicelist: sx = x[0] if len(x) > 1: sx += '...' sy = y[0] if len(y) > 1: sy += '...' choices.append( 'Block '+str(blk)+', '+str(l)+' points. X='+sx+' & Y='+sy ) selections = G2G.MultipleBlockSelector( choices, ParentFrame=ParentFrame, title='Select dataset(s) to read from the list below', size=(600,100), header='Dataset Selector') if len(selections) == 0: self.errors = "Abort: block not selected" return False selblk = selections[0] # select first in list if len(selections) > 1: # prepare to loop through again self.repeat = True self.repeatcount = 1 if rdbuffer is not None: rdbuffer['selections'] = selections rdbuffer['lastcif'] = cf # save the parsed cif for the next loop rdbuffer['choicelist'] = choicelist # save the parsed choices for the future # got a selection, now read it # do we need to ask which fields to read? blk,l,xch,ych,such,modch = choicelist[selblk] xi,yi,sui,modi = 0,0,0,0 if len(xch) > 1 or len(ych) > 1 or len(such) > 1 or len(modch) > 0: choices = [] chlbls = [] chlbls.append('Select the scanned data item') xchlst = [] for x in xch: if type(x) is tuple: xchlst.append(x[0]) else: xchlst.append(x) choices.append(xchlst) chlbls.append('Select the intensity data item') choices.append(ych) chlbls.append('Select the data item to be used for weighting') choices.append(such) chlbls.append('Divide intensities by data item') choices.append(['none']+modch) res = G2G.MultipleChoicesSelector(choices,chlbls) if not res: self.errors = "Abort: data items not selected" return False xi,yi,sui,modi = res # now read in the values # x-values self.powderentry[0] = filename #self.powderentry[1] = pos # bank offset (N/A here) #self.powderentry[2] = 1 # xye file only has one bank self.idstring = os.path.basename(filename) + ': ' + blk if cf[blk].get('_diffrn_radiation_probe'): if cf[blk]['_diffrn_radiation_probe'] == 'neutron': self.instdict['type'] = 'PNC' #if cf[blk].get('_pd_meas_time_of_flight'): self.instdict['type'] = 'PNT' # not supported yet else: self.instdict['type'] = 'PXC' if cf[blk].get('_diffrn_radiation_wavelength'): val = cf[blk]['_diffrn_radiation_wavelength'] wl = [] if type(val) is list: for v in val: w,e = cif.get_number_with_esd(v) if w: wl.append(w) else: w,e = cif.get_number_with_esd(val) if w: wl.append(w) if wl: if len(wl) > 1: self.instdict['wave'] = wl else: self.instdict['wave'] = wl[0] if cf[blk].get('_diffrn_ambient_temperature'): val = cf[blk]['_diffrn_ambient_temperature'] w,e = cif.get_number_with_esd(val) if w: self.Sample['Temperature'] = w xcf = xch[xi] if type(xcf) is tuple: vals = [float(cf[blk][ixi]) for ixi in xcf] x = np.array([(i*vals[2] + vals[0]) for i in range(1 + int(0.5 + (vals[1]-vals[0])/vals[2]))]) else: vl = [] for val in cf[blk].get(xcf,'?'): v,e = cif.get_number_with_esd(val) if v is None: # not parsed vl.append(np.NaN) else: vl.append(v) x = np.array(vl) if 'recip_len_q' in xcf and 'wave' in self.instdict: wl = self.instdict['wave'] x = 2.*asind(wl*x/(4.*np.pi)) # y-values ycf = ych[yi] vl = [] v2 = [] for val in cf[blk].get(ycf,'?'): v,e = cif.get_number_with_esd(val) if v is None: # not parsed vl.append(np.NaN) v2.append(np.NaN) else: vl.append(v) if e is None: v2.append(np.sqrt(v)) else: v2.append(max(e,1.0)) y = np.array(vl) w = 1./np.array(v2)**2 # weights if sui == -1: # no weights vl = np.zeros(len(x)) + 1. else: vl = [] sucf = such[sui] if sucf == '_pd_proc_ls_weight': for val in cf[blk].get(sucf,'?'): v,e = cif.get_number_with_esd(val) if v is None: # not parsed vl.append(0.) else: vl.append(v) elif sucf == '_pd_proc_intensity_total': for val in cf[blk].get(sucf,'?'): v,e = cif.get_number_with_esd(val) if v is None: # not parsed vl.append(0.) elif v <= 0: vl.append(1.) else: vl.append(1./v) elif sucf == '_pd_meas_counts_total': for val in cf[blk].get(sucf,'?'): v,e = cif.get_number_with_esd(val) if v is None: # not parsed vl.append(0.) elif v <= 0: vl.append(1.) else: vl.append(1./v) else: for val in cf[blk].get(sucf,'?'): v,e = cif.get_number_with_esd(val) if v is None or e is None: # not parsed or no ESD vl.append(np.NaN) elif e <= 0: vl.append(1.) else: vl.append(1./(e*e)) # w = np.array(vl) # intensity modification factor if modi >= 1: ycf = modch[modi-1] vl = [] for val in cf[blk].get(ycf,'?'): v,e = cif.get_number_with_esd(val) if v is None: # not parsed vl.append(np.NaN) else: vl.append(v) y /= np.array(vl) w /= np.array(vl) N = len(x) print ("CIF file, read from selected block") self.errors = "Error while storing read values" self.powderdata = [ np.array(x), # x-axis values np.array(y), # powder pattern intensities np.array(w), # 1/sig(intensity)^2 values (weights) np.zeros(N), # calc. intensities (zero) np.zeros(N), # calc. background (zero) np.zeros(N), # obs-calc profiles ] return True