Skip to content

Commit 2d99c09

Browse files
committed
Whoops, include functionality sourced from PDFgui.
1 parent 3f46a5c commit 2d99c09

File tree

1 file changed

+399
-0
lines changed

1 file changed

+399
-0
lines changed

diffpy/srmise/mise/pdfdataset.py

Lines changed: 399 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,399 @@
1+
#!/usr/bin/env python
2+
##############################################################################
3+
#
4+
# SrMise by Luke Granlund
5+
# (c) 2015 trustees of the Michigan State University.
6+
# All rights reserved.
7+
#
8+
# File coded by: Luke Granlund
9+
#
10+
# See LICENSE.txt for license information.
11+
#
12+
# This file uses source code from the PDFgui files pdfdataset.py and
13+
# pdfcomponent.py, (c) 2006 trustees of the Michigan State University. See
14+
# LICENSE_PDFgui.txt for the full PDFgui license.
15+
#
16+
##############################################################################
17+
18+
19+
"""class PDFDataSet for experimental PDF data.
20+
"""
21+
22+
23+
import os.path
24+
import re
25+
import copy
26+
import time
27+
from getpass import getuser
28+
29+
from diffpy.srmise.mise.miseerrors import \
30+
MisePDFKeyError, MiseFileError
31+
32+
33+
class PDFComponent(object):
34+
"""Common base class."""
35+
def __init__(self, name):
36+
"""initialize
37+
38+
name -- object name
39+
"""
40+
self.name = name
41+
42+
def close ( self, force = False ):
43+
"""close myself
44+
45+
force -- if forcibly (no wait)
46+
"""
47+
pass
48+
49+
class PDFDataSet(PDFComponent):
50+
"""PDFDataSet is a class for experimental PDF data.
51+
52+
Data members:
53+
robs -- list of observed r points
54+
Gobs -- list of observed G values
55+
drobs -- list of standard deviations of robs
56+
dGobs -- list of standard deviations of Gobs
57+
stype -- scattering type, 'X' or 'N'
58+
qmax -- maximum value of Q in inverse Angstroms. Termination
59+
ripples are neglected for qmax=0.
60+
qdamp -- specifies width of Gaussian damping factor in pdf_obs due
61+
to imperfect Q resolution
62+
qbroad -- quadratic peak broadening factor related to dataset
63+
spdiameter -- particle diameter for shape damping function
64+
Note: This attribute was moved to PDFStructure.
65+
It is kept for backward compatibility when reading
66+
PDFgui project files.
67+
dscale -- scale factor of this dataset
68+
rmin -- same as robs[0]
69+
rmax -- same as robs[-1]
70+
filename -- set to absolute path after reading from file
71+
metadata -- dictionary for other experimental conditions, such as
72+
temperature or doping
73+
74+
Global member:
75+
persistentItems -- list of attributes saved in project file
76+
refinableVars -- set (dict) of refinable variable names.
77+
"""
78+
79+
persistentItems = [ 'robs', 'Gobs', 'drobs', 'dGobs', 'stype', 'qmax',
80+
'qdamp', 'qbroad', 'dscale', 'rmin', 'rmax', 'metadata' ]
81+
refinableVars = dict.fromkeys(('qdamp', 'qbroad', 'dscale'))
82+
83+
def __init__(self, name):
84+
"""Initialize.
85+
86+
name -- name of the data set. It must be a unique identifier.
87+
"""
88+
PDFComponent.__init__(self, name)
89+
self.clear()
90+
return
91+
92+
def clear(self):
93+
"""reset all data members to initial empty values"""
94+
self.robs = []
95+
self.Gobs = []
96+
self.drobs = []
97+
self.dGobs = []
98+
self.stype = 'X'
99+
# user must specify qmax to get termination ripples
100+
self.qmax = 0.0
101+
self.qdamp = 0.001
102+
self.qbroad = 0.0
103+
self.spdiameter = None
104+
self.dscale = 1.0
105+
self.rmin = None
106+
self.rmax = None
107+
self.filename = None
108+
self.metadata = {}
109+
return
110+
111+
def setvar(self, var, value):
112+
"""Assign data member using PdfFit-style variable.
113+
Used by applyParameters().
114+
115+
var -- string representation of dataset PdfFit variable.
116+
Possible values: qdamp, qbroad, dscale
117+
value -- new value of the variable
118+
"""
119+
barevar = var.strip()
120+
fvalue = float(value)
121+
if barevar in PDFDataSet.refinableVars:
122+
setattr(self, barevar, fvalue)
123+
else:
124+
emsg = "Invalid PdfFit dataset variable %r" % barevar
125+
raise MisePDFKeyError(emsg)
126+
return
127+
128+
def getvar(self, var):
129+
"""Obtain value corresponding to PdfFit dataset variable.
130+
Used by findParameters().
131+
132+
var -- string representation of dataset PdfFit variable.
133+
Possible values: qdamp, qbroad, dscale
134+
135+
returns value of var
136+
"""
137+
barevar = var.strip()
138+
if barevar in PDFDataSet.refinableVars:
139+
value = getattr(self, barevar)
140+
else:
141+
emsg = "Invalid PdfFit dataset variable %r" % barevar
142+
raise MisePDFKeyError(emsg)
143+
return value
144+
145+
def read(self, filename):
146+
"""load data from PDFGetX2 or PDFGetN gr file
147+
148+
filename -- file to read from
149+
150+
returns self
151+
"""
152+
try:
153+
self.readStr(open(filename,'rb').read())
154+
except PDFDataFormatError, err:
155+
basename = os.path.basename(filename)
156+
emsg = ("Could not open '%s' due to unsupported file format " +
157+
"or corrupted data. [%s]") % (basename, err)
158+
raise MiseFileError(emsg)
159+
self.filename = os.path.abspath(filename)
160+
return self
161+
162+
163+
def readStr(self, datastring):
164+
"""read experimental PDF data from a string
165+
166+
datastring -- string of raw data
167+
168+
returns self
169+
"""
170+
self.clear()
171+
# useful regex patterns:
172+
rx = { 'f' : r'[-+]?(\d+(\.\d*)?|\d*\.\d+)([eE][-+]?\d+)?' }
173+
# find where does the data start
174+
res = re.search(r'^#+ start data\s*(?:#.*\s+)*', datastring, re.M)
175+
# start_data is position where the first data line starts
176+
if res:
177+
start_data = res.end()
178+
else:
179+
# find line that starts with a floating point number
180+
regexp = r'^\s*%(f)s' % rx
181+
res = re.search(regexp, datastring, re.M)
182+
if res:
183+
start_data = res.start()
184+
else:
185+
start_data = 0
186+
header = datastring[:start_data]
187+
databody = datastring[start_data:].strip()
188+
189+
# find where the metadata starts
190+
metadata = ''
191+
res = re.search(r'^#+\ +metadata\b\n', header, re.M)
192+
if res:
193+
metadata = header[res.end():]
194+
header = header[:res.start()]
195+
196+
# parse header
197+
# stype
198+
if re.search('(x-?ray|PDFgetX)', header, re.I):
199+
self.stype = 'X'
200+
elif re.search('(neutron|PDFgetN)', header, re.I):
201+
self.stype = 'N'
202+
# qmax
203+
regexp = r"\bqmax *= *(%(f)s)\b" % rx
204+
res = re.search(regexp, header, re.I)
205+
if res:
206+
self.qmax = float(res.groups()[0])
207+
# qdamp
208+
regexp = r"\b(?:qdamp|qsig) *= *(%(f)s)\b" % rx
209+
res = re.search(regexp, header, re.I)
210+
if res:
211+
self.qdamp = float(res.groups()[0])
212+
# qbroad
213+
regexp = r"\b(?:qbroad|qalp) *= *(%(f)s)\b" % rx
214+
res = re.search(regexp, header, re.I)
215+
if res:
216+
self.qbroad = float(res.groups()[0])
217+
# spdiameter
218+
regexp = r"\bspdiameter *= *(%(f)s)\b" % rx
219+
res = re.search(regexp, header, re.I)
220+
if res:
221+
self.spdiameter = float(res.groups()[0])
222+
# dscale
223+
regexp = r"\bdscale *= *(%(f)s)\b" % rx
224+
res = re.search(regexp, header, re.I)
225+
if res:
226+
self.dscale = float(res.groups()[0])
227+
# temperature
228+
regexp = r"\b(?:temp|temperature|T)\ *=\ *(%(f)s)\b" % rx
229+
res = re.search(regexp, header)
230+
if res:
231+
self.metadata['temperature'] = float(res.groups()[0])
232+
# doping
233+
regexp = r"\b(?:x|doping)\ *=\ *(%(f)s)\b" % rx
234+
res = re.search(regexp, header)
235+
if res:
236+
self.metadata['doping'] = float(res.groups()[0])
237+
238+
# parsing gerneral metadata
239+
if metadata:
240+
regexp = r"\b(\w+)\ *=\ *(%(f)s)\b" % rx
241+
while True:
242+
res = re.search(regexp, metadata, re.M)
243+
if res:
244+
self.metadata[res.groups()[0]] = float(res.groups()[1])
245+
metadata = metadata[res.end():]
246+
else:
247+
break
248+
249+
# read actual data - robs, Gobs, drobs, dGobs
250+
inf_or_nan = re.compile('(?i)^[+-]?(NaN|Inf)\\b')
251+
has_drobs = True
252+
has_dGobs = True
253+
# raise PDFDataFormatError if something goes wrong
254+
try:
255+
for line in databody.split("\n"):
256+
v = line.split()
257+
# there should be at least 2 value in the line
258+
self.robs.append(float(v[0]))
259+
self.Gobs.append(float(v[1]))
260+
# drobs is valid if all values are defined and positive
261+
has_drobs = (has_drobs and
262+
len(v) > 2 and not inf_or_nan.match(v[2]))
263+
if has_drobs:
264+
v2 = float(v[2])
265+
has_drobs = v2 > 0.0
266+
self.drobs.append(v2)
267+
# dGobs is valid if all values are defined and positive
268+
has_dGobs = (has_dGobs and
269+
len(v) > 3 and not inf_or_nan.match(v[3]))
270+
if has_dGobs:
271+
v3 = float(v[3])
272+
has_dGobs = v3 > 0.0
273+
self.dGobs.append(v3)
274+
if not has_drobs:
275+
self.drobs = len(self.robs) * [0.0]
276+
if not has_dGobs:
277+
self.dGobs = len(self.robs) * [0.0]
278+
except (ValueError, IndexError), err:
279+
raise PDFDataFormatError(err)
280+
self.rmin = self.robs[0]
281+
self.rmax = self.robs[-1]
282+
if not has_drobs: self.drobs = len(self.robs) * [0.0]
283+
if not has_dGobs: self.dGobs = len(self.robs) * [0.0]
284+
return self
285+
286+
287+
def write(self, filename):
288+
"""Write experimental PDF data to a file.
289+
290+
filename -- name of file to write to
291+
292+
No return value.
293+
"""
294+
bytes = self.writeStr()
295+
f = open(filename, 'w')
296+
f.write(bytes)
297+
f.close()
298+
return
299+
300+
def writeStr(self):
301+
"""String representation of experimental PDF data.
302+
303+
Return data string.
304+
"""
305+
lines = []
306+
# write metadata
307+
lines.extend([
308+
'History written: ' + time.ctime(),
309+
'produced by ' + getuser(),
310+
'##### PDFgui' ])
311+
# stype
312+
if self.stype == 'X':
313+
lines.append('stype=X x-ray scattering')
314+
elif self.stype == 'N':
315+
lines.append('stype=N neutron scattering')
316+
# qmax
317+
if self.qmax == 0:
318+
qmax_line = 'qmax=0 correction not applied'
319+
else:
320+
qmax_line = 'qmax=%.2f' % self.qmax
321+
lines.append(qmax_line)
322+
# qdamp
323+
lines.append('qdamp=%g' % self.qdamp)
324+
# qbroad
325+
lines.append('qbroad=%g' % self.qbroad)
326+
# dscale
327+
lines.append('dscale=%g' % self.dscale)
328+
# metadata
329+
if len(self.metadata) > 0:
330+
lines.append('# metadata')
331+
for k, v in self.metadata.iteritems():
332+
lines.append( "%s=%s" % (k,v) )
333+
# write data:
334+
lines.append('##### start data')
335+
lines.append('#L r(A) G(r) d_r d_Gr')
336+
for i in range(len(self.robs)):
337+
lines.append('%g %g %g %g' % \
338+
(self.robs[i], self.Gobs[i], self.drobs[i], self.dGobs[i]) )
339+
# that should be it
340+
datastring = "\n".join(lines) + "\n"
341+
return datastring
342+
343+
def copy(self, other=None):
344+
"""copy self to other. if other is None, create new instance
345+
346+
other -- ref to other object
347+
returns reference to copied object
348+
"""
349+
if other is None:
350+
other = PDFDataSet(self.name)
351+
elif isinstance(other, PDFDataSet):
352+
other.clear()
353+
# some attributes can be assigned, e.g., robs, Gobs, drobs, dGobs are
354+
# constant so they can be shared between copies.
355+
assign_attributes = ( 'robs', 'Gobs', 'drobs', 'dGobs', 'stype',
356+
'qmax', 'qdamp', 'qbroad', 'dscale',
357+
'rmin', 'rmax', 'filename' )
358+
# for others we will assign a copy
359+
copy_attributes = ( 'metadata', )
360+
for a in assign_attributes:
361+
setattr(other, a, getattr(self, a))
362+
import copy
363+
for a in copy_attributes:
364+
setattr(other, a, copy.deepcopy(getattr(self, a)))
365+
return other
366+
367+
# End of class PDFDataSet
368+
369+
370+
class PDFDataFormatError(Exception):
371+
"""Exception class marking failure to proccess PDF data string.
372+
"""
373+
pass
374+
375+
376+
# simple test code
377+
if __name__ == '__main__':
378+
import sys
379+
filename = sys.argv[1]
380+
dataset = PDFDataSet("test")
381+
dataset.read(filename)
382+
print "== metadata =="
383+
for k, v in dataset.metadata.iteritems():
384+
print k, "=", v
385+
print "== data members =="
386+
for k, v in dataset.__dict__.iteritems():
387+
if k in ('metadata', 'robs', 'Gobs', 'drobs', 'dGobs') or k[0] == "_":
388+
continue
389+
print k, "=", v
390+
print "== robs Gobs drobs dGobs =="
391+
for i in range(len(dataset.robs)):
392+
print dataset.robs[i], dataset.Gobs[i], dataset.drobs[i], dataset.dGobs[i]
393+
print "== writeStr() =="
394+
print dataset.writeStr()
395+
print "== datasetcopy.writeStr() =="
396+
datasetcopy = dataset.copy()
397+
print datasetcopy.writeStr()
398+
399+
# End of file

0 commit comments

Comments
 (0)