diffpy.srfit 3.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- diffpy/__init__.py +15 -0
- diffpy/srfit/__init__.py +41 -0
- diffpy/srfit/equation/__init__.py +31 -0
- diffpy/srfit/equation/builder.py +702 -0
- diffpy/srfit/equation/equationmod.py +213 -0
- diffpy/srfit/equation/literals/__init__.py +71 -0
- diffpy/srfit/equation/literals/abcs.py +81 -0
- diffpy/srfit/equation/literals/argument.py +76 -0
- diffpy/srfit/equation/literals/literal.py +69 -0
- diffpy/srfit/equation/literals/operators.py +380 -0
- diffpy/srfit/equation/visitors/__init__.py +94 -0
- diffpy/srfit/equation/visitors/argfinder.py +62 -0
- diffpy/srfit/equation/visitors/printer.py +120 -0
- diffpy/srfit/equation/visitors/swapper.py +142 -0
- diffpy/srfit/equation/visitors/validator.py +113 -0
- diffpy/srfit/equation/visitors/visitor.py +62 -0
- diffpy/srfit/exceptions.py +29 -0
- diffpy/srfit/fitbase/__init__.py +53 -0
- diffpy/srfit/fitbase/calculator.py +110 -0
- diffpy/srfit/fitbase/configurable.py +57 -0
- diffpy/srfit/fitbase/constraint.py +117 -0
- diffpy/srfit/fitbase/fitcontribution.py +351 -0
- diffpy/srfit/fitbase/fithook.py +227 -0
- diffpy/srfit/fitbase/fitrecipe.py +826 -0
- diffpy/srfit/fitbase/fitresults.py +674 -0
- diffpy/srfit/fitbase/parameter.py +314 -0
- diffpy/srfit/fitbase/parameterset.py +110 -0
- diffpy/srfit/fitbase/profile.py +405 -0
- diffpy/srfit/fitbase/profilegenerator.py +168 -0
- diffpy/srfit/fitbase/profileparser.py +179 -0
- diffpy/srfit/fitbase/recipeorganizer.py +1074 -0
- diffpy/srfit/fitbase/restraint.py +117 -0
- diffpy/srfit/fitbase/simplerecipe.py +230 -0
- diffpy/srfit/fitbase/validatable.py +59 -0
- diffpy/srfit/interface/__init__.py +33 -0
- diffpy/srfit/interface/interface.py +180 -0
- diffpy/srfit/pdf/__init__.py +24 -0
- diffpy/srfit/pdf/basepdfgenerator.py +296 -0
- diffpy/srfit/pdf/characteristicfunctions.py +432 -0
- diffpy/srfit/pdf/debyepdfgenerator.py +115 -0
- diffpy/srfit/pdf/pdfcontribution.py +311 -0
- diffpy/srfit/pdf/pdfgenerator.py +77 -0
- diffpy/srfit/pdf/pdfparser.py +226 -0
- diffpy/srfit/sas/__init__.py +30 -0
- diffpy/srfit/sas/prcalculator.py +139 -0
- diffpy/srfit/sas/sasgenerator.py +70 -0
- diffpy/srfit/sas/sasimport.py +66 -0
- diffpy/srfit/sas/sasparameter.py +70 -0
- diffpy/srfit/sas/sasparser.py +133 -0
- diffpy/srfit/sas/sasprofile.py +94 -0
- diffpy/srfit/structure/__init__.py +60 -0
- diffpy/srfit/structure/basestructureparset.py +62 -0
- diffpy/srfit/structure/bvsrestraint.py +101 -0
- diffpy/srfit/structure/cctbxparset.py +292 -0
- diffpy/srfit/structure/diffpyparset.py +303 -0
- diffpy/srfit/structure/objcrystparset.py +1552 -0
- diffpy/srfit/structure/sgconstraints.py +761 -0
- diffpy/srfit/structure/srrealparset.py +92 -0
- diffpy/srfit/util/__init__.py +50 -0
- diffpy/srfit/util/argbinders.py +29 -0
- diffpy/srfit/util/inpututils.py +50 -0
- diffpy/srfit/util/nameutils.py +45 -0
- diffpy/srfit/util/observable.py +93 -0
- diffpy/srfit/util/tagmanager.py +150 -0
- diffpy/srfit/util/weakrefcallable.py +170 -0
- diffpy/srfit/version.py +26 -0
- diffpy_srfit-3.1.0.dist-info/METADATA +216 -0
- diffpy_srfit-3.1.0.dist-info/RECORD +73 -0
- diffpy_srfit-3.1.0.dist-info/WHEEL +5 -0
- diffpy_srfit-3.1.0.dist-info/licenses/AUTHORS.rst +10 -0
- diffpy_srfit-3.1.0.dist-info/licenses/LICENSE.rst +138 -0
- diffpy_srfit-3.1.0.dist-info/licenses/LICENSE_DANSE.rst +34 -0
- diffpy_srfit-3.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,405 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
##############################################################################
|
|
3
|
+
#
|
|
4
|
+
# diffpy.srfit by DANSE Diffraction group
|
|
5
|
+
# Simon J. L. Billinge
|
|
6
|
+
# (c) 2008 The Trustees of Columbia University
|
|
7
|
+
# in the City of New York. All rights reserved.
|
|
8
|
+
#
|
|
9
|
+
# File coded by: Chris Farrow
|
|
10
|
+
#
|
|
11
|
+
# See AUTHORS.txt for a list of people who contributed.
|
|
12
|
+
# See LICENSE_DANSE.txt for license information.
|
|
13
|
+
#
|
|
14
|
+
##############################################################################
|
|
15
|
+
"""The Profile class containing the physical and calculated data.
|
|
16
|
+
|
|
17
|
+
Profile holds the arrays representing an observed profile, a selected
|
|
18
|
+
subset of the observed profile and a calculated profile. Profiles are
|
|
19
|
+
used by Calculators to store a calculated signal, and by
|
|
20
|
+
FitContributions to help calculate a residual equation.
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
__all__ = ["Parameter", "Profile"]
|
|
24
|
+
|
|
25
|
+
import numpy
|
|
26
|
+
import six
|
|
27
|
+
|
|
28
|
+
from diffpy.srfit.exceptions import SrFitError
|
|
29
|
+
from diffpy.srfit.fitbase.parameter import Parameter
|
|
30
|
+
from diffpy.srfit.fitbase.validatable import Validatable
|
|
31
|
+
from diffpy.srfit.util.observable import Observable
|
|
32
|
+
|
|
33
|
+
# This is the roundoff tolerance for selecting bounds on arrays.
|
|
34
|
+
epsilon = 1e-8
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class Profile(Observable, Validatable):
|
|
38
|
+
"""Observed and calculated profile container.
|
|
39
|
+
|
|
40
|
+
Profile is an Observable. The xpar, ypar and dypar attributes are observed
|
|
41
|
+
by the Profile, which can in turn be observed by some other object.
|
|
42
|
+
|
|
43
|
+
Attributes
|
|
44
|
+
|
|
45
|
+
_xobs -- A numpy array of the observed independent variable (default
|
|
46
|
+
None)
|
|
47
|
+
xobs -- Read-only property of _xobs.
|
|
48
|
+
_yobs -- A numpy array of the observed signal (default None)
|
|
49
|
+
yobs -- Read-only property of _yobs.
|
|
50
|
+
_dyobs -- A numpy array of the uncertainty of the observed signal
|
|
51
|
+
(default None, optional).
|
|
52
|
+
dyobs -- Read-only property of _dyobs.
|
|
53
|
+
x -- A numpy array of the calculated independent variable (default
|
|
54
|
+
None, property for xpar accessors).
|
|
55
|
+
y -- The profile over the calculation range (default None, property
|
|
56
|
+
for ypar accessors).
|
|
57
|
+
dy -- The uncertainty in the profile over the calculation range
|
|
58
|
+
(default None, property for dypar accessors).
|
|
59
|
+
ycalc -- A numpy array of the calculated signal (default None).
|
|
60
|
+
xpar -- A Parameter that stores x (named "x").
|
|
61
|
+
ypar -- A Parameter that stores y (named "y").
|
|
62
|
+
dypar -- A Parameter that stores dy (named "dy").
|
|
63
|
+
ycpar -- A Parameter that stores ycalc (named "ycalc"). This is
|
|
64
|
+
not observed by the profile, but it is present so it can be
|
|
65
|
+
constrained to.
|
|
66
|
+
meta -- A dictionary of metadata. This is only set if provided by a
|
|
67
|
+
parser.
|
|
68
|
+
"""
|
|
69
|
+
|
|
70
|
+
def __init__(self):
|
|
71
|
+
"""Initialize the attributes."""
|
|
72
|
+
Observable.__init__(self)
|
|
73
|
+
self._xobs = None
|
|
74
|
+
self._yobs = None
|
|
75
|
+
self._dyobs = None
|
|
76
|
+
self.xpar = Parameter("x")
|
|
77
|
+
self.ypar = Parameter("y")
|
|
78
|
+
self.dypar = Parameter("dy")
|
|
79
|
+
self.ycpar = Parameter("ycalc")
|
|
80
|
+
self.meta = {}
|
|
81
|
+
|
|
82
|
+
# Observable
|
|
83
|
+
self.xpar.addObserver(self._flush)
|
|
84
|
+
self.ypar.addObserver(self._flush)
|
|
85
|
+
self.dypar.addObserver(self._flush)
|
|
86
|
+
return
|
|
87
|
+
|
|
88
|
+
# We want x, y, ycalc and dy to stay in-sync with xpar, ypar and dypar
|
|
89
|
+
x = property(
|
|
90
|
+
lambda self: self.xpar.getValue(),
|
|
91
|
+
lambda self, val: self.xpar.setValue(val),
|
|
92
|
+
)
|
|
93
|
+
y = property(
|
|
94
|
+
lambda self: self.ypar.getValue(),
|
|
95
|
+
lambda self, val: self.ypar.setValue(val),
|
|
96
|
+
)
|
|
97
|
+
dy = property(
|
|
98
|
+
lambda self: self.dypar.getValue(),
|
|
99
|
+
lambda self, val: self.dypar.setValue(val),
|
|
100
|
+
)
|
|
101
|
+
ycalc = property(
|
|
102
|
+
lambda self: self.ycpar.getValue(),
|
|
103
|
+
lambda self, val: self.ycpar.setValue(val),
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
# We want xobs, yobs and dyobs to be read-only
|
|
107
|
+
xobs = property(lambda self: self._xobs)
|
|
108
|
+
yobs = property(lambda self: self._yobs)
|
|
109
|
+
dyobs = property(lambda self: self._dyobs)
|
|
110
|
+
|
|
111
|
+
def loadParsedData(self, parser):
|
|
112
|
+
"""Load parsed data from a ProfileParser.
|
|
113
|
+
|
|
114
|
+
This sets the xobs, yobs, dyobs arrays as well as the metadata.
|
|
115
|
+
"""
|
|
116
|
+
x, y, junk, dy = parser.getData()
|
|
117
|
+
self.meta = dict(parser.getMetaData())
|
|
118
|
+
self.setObservedProfile(x, y, dy)
|
|
119
|
+
return
|
|
120
|
+
|
|
121
|
+
def setObservedProfile(self, xobs, yobs, dyobs=None):
|
|
122
|
+
"""Set the observed profile.
|
|
123
|
+
|
|
124
|
+
Arguments
|
|
125
|
+
xobs -- Numpy array of the independent variable
|
|
126
|
+
yobs -- Numpy array of the observed signal.
|
|
127
|
+
dyobs -- Numpy array of the uncertainty in the observed signal. If
|
|
128
|
+
dyobs is None (default), it will be set to 1 at each
|
|
129
|
+
observed xobs.
|
|
130
|
+
|
|
131
|
+
Raises ValueError if len(yobs) != len(xobs)
|
|
132
|
+
Raises ValueError if dyobs != None and len(dyobs) != len(xobs)
|
|
133
|
+
"""
|
|
134
|
+
if len(yobs) != len(xobs):
|
|
135
|
+
raise ValueError("xobs and yobs are different lengths")
|
|
136
|
+
if dyobs is not None and len(dyobs) != len(xobs):
|
|
137
|
+
raise ValueError("xobs and dyobs are different lengths")
|
|
138
|
+
|
|
139
|
+
self._xobs = numpy.asarray(xobs, dtype=float)
|
|
140
|
+
self._yobs = numpy.asarray(yobs, dtype=float)
|
|
141
|
+
|
|
142
|
+
if dyobs is None:
|
|
143
|
+
self._dyobs = numpy.ones_like(xobs)
|
|
144
|
+
else:
|
|
145
|
+
self._dyobs = numpy.asarray(dyobs, dtype=float)
|
|
146
|
+
|
|
147
|
+
# Set the default calculation points
|
|
148
|
+
if self.x is None:
|
|
149
|
+
self.setCalculationPoints(self._xobs)
|
|
150
|
+
else:
|
|
151
|
+
self.setCalculationPoints(self.x)
|
|
152
|
+
|
|
153
|
+
return
|
|
154
|
+
|
|
155
|
+
def setCalculationRange(self, xmin=None, xmax=None, dx=None):
|
|
156
|
+
"""Set epsilon-inclusive calculation range.
|
|
157
|
+
|
|
158
|
+
Adhere to the observed ``xobs`` points when ``dx`` is the same
|
|
159
|
+
as in the data. ``xmin`` and ``xmax`` are clipped at the bounds
|
|
160
|
+
of the observed data.
|
|
161
|
+
|
|
162
|
+
Parameters
|
|
163
|
+
----------
|
|
164
|
+
|
|
165
|
+
xmin : float or "obs", optional
|
|
166
|
+
The minimum value of the independent variable. Keep the
|
|
167
|
+
current minimum when not specified. If specified as "obs"
|
|
168
|
+
reset to the minimum observed value.
|
|
169
|
+
xmax : float or "obs", optional
|
|
170
|
+
The maximum value of the independent variable. Keep the
|
|
171
|
+
current maximum when not specified. If specified as "obs"
|
|
172
|
+
reset to the maximum observed value.
|
|
173
|
+
dx : float or "obs", optional
|
|
174
|
+
The sample spacing in the independent variable. When different
|
|
175
|
+
from the data, resample the ``x`` as anchored at ``xmin``.
|
|
176
|
+
|
|
177
|
+
Note that xmin is always inclusive (unless clipped). xmax is inclusive
|
|
178
|
+
if it is within the bounds of the observed data.
|
|
179
|
+
|
|
180
|
+
Raises
|
|
181
|
+
------
|
|
182
|
+
AttributeError
|
|
183
|
+
If there is no observed data.
|
|
184
|
+
ValueError
|
|
185
|
+
When xmin > xmax or if dx <= 0. Also if dx > xmax - xmin.
|
|
186
|
+
"""
|
|
187
|
+
if self.xobs is None:
|
|
188
|
+
raise AttributeError("No observed profile")
|
|
189
|
+
|
|
190
|
+
# local helper function
|
|
191
|
+
def _isobs(a):
|
|
192
|
+
if not isinstance(a, six.string_types):
|
|
193
|
+
return False
|
|
194
|
+
if a != "obs":
|
|
195
|
+
raise ValueError('Must be either float or "obs".')
|
|
196
|
+
return True
|
|
197
|
+
|
|
198
|
+
# resolve new low and high bounds for x
|
|
199
|
+
lo = (
|
|
200
|
+
self.x[0]
|
|
201
|
+
if xmin is None
|
|
202
|
+
else self.xobs[0] if _isobs(xmin) else float(xmin)
|
|
203
|
+
)
|
|
204
|
+
lo = max(lo, self.xobs[0])
|
|
205
|
+
hi = (
|
|
206
|
+
self.x[-1]
|
|
207
|
+
if xmax is None
|
|
208
|
+
else self.xobs[-1] if _isobs(xmax) else float(xmax)
|
|
209
|
+
)
|
|
210
|
+
hi = min(hi, self.xobs[-1])
|
|
211
|
+
# determine if we need to clip the original grid
|
|
212
|
+
clip = True
|
|
213
|
+
step = None
|
|
214
|
+
ncur = len(self.x)
|
|
215
|
+
stepcur = 1 if ncur < 2 else (self.x[-1] - self.x[0]) / (ncur - 1.0)
|
|
216
|
+
nobs = len(self.xobs)
|
|
217
|
+
stepobs = (
|
|
218
|
+
1 if nobs < 2 else (self.xobs[-1] - self.xobs[0]) / (nobs - 1.0)
|
|
219
|
+
)
|
|
220
|
+
if dx is None:
|
|
221
|
+
# check if xobs overlaps with x
|
|
222
|
+
i0 = numpy.fabs(self.xobs - self.x[0]).argmin()
|
|
223
|
+
n0 = min(len(self.x), len(self.xobs) - i0)
|
|
224
|
+
if not numpy.allclose(self.xobs[i0 : i0 + n0], self.x[:n0]):
|
|
225
|
+
clip = False
|
|
226
|
+
step = stepcur if ncur > 1 else stepobs
|
|
227
|
+
elif _isobs(dx):
|
|
228
|
+
assert clip and step is None
|
|
229
|
+
elif numpy.allclose(stepobs, dx):
|
|
230
|
+
assert clip and step is None
|
|
231
|
+
else:
|
|
232
|
+
clip = False
|
|
233
|
+
step = float(dx)
|
|
234
|
+
# verify that we either clip or have the step defined.
|
|
235
|
+
assert clip or step is not None
|
|
236
|
+
# hi, lo, step, clip all resolved here.
|
|
237
|
+
# validate arguments
|
|
238
|
+
if lo > hi:
|
|
239
|
+
raise ValueError("xmax must be greater than xmin.")
|
|
240
|
+
if not clip:
|
|
241
|
+
if step > hi - lo:
|
|
242
|
+
raise ValueError("dx must be less than (xmax - xmin).")
|
|
243
|
+
if step <= 0:
|
|
244
|
+
raise ValueError("dx must be positive.")
|
|
245
|
+
# determine epsilon extensions to the lower and upper bounds.
|
|
246
|
+
epslo = abs(lo) * epsilon + epsilon
|
|
247
|
+
epshi = abs(hi) * epsilon + epsilon
|
|
248
|
+
# process the new grid.
|
|
249
|
+
if clip:
|
|
250
|
+
indices = (lo - epslo <= self.xobs) & (self.xobs <= hi + epshi)
|
|
251
|
+
self.x = self.xobs[indices]
|
|
252
|
+
self.y = self.yobs[indices]
|
|
253
|
+
self.dy = self.dyobs[indices]
|
|
254
|
+
else:
|
|
255
|
+
x1 = numpy.arange(lo, hi + epshi, step)
|
|
256
|
+
self.setCalculationPoints(x1)
|
|
257
|
+
return
|
|
258
|
+
|
|
259
|
+
def setCalculationPoints(self, x):
|
|
260
|
+
"""Set the calculation points.
|
|
261
|
+
|
|
262
|
+
Arguments
|
|
263
|
+
x -- A non-empty numpy array containing the calculation points. If
|
|
264
|
+
xobs exists, the bounds of x will be limited to its bounds.
|
|
265
|
+
|
|
266
|
+
This will create y and dy on the specified grid if xobs, yobs and
|
|
267
|
+
dyobs exist.
|
|
268
|
+
"""
|
|
269
|
+
x = numpy.asarray(x)
|
|
270
|
+
if self.xobs is not None:
|
|
271
|
+
x = x[x >= self.xobs[0] - epsilon]
|
|
272
|
+
x = x[x <= self.xobs[-1] + epsilon]
|
|
273
|
+
self.x = x
|
|
274
|
+
if self.yobs is not None:
|
|
275
|
+
self.y = rebinArray(self.yobs, self.xobs, self.x)
|
|
276
|
+
if self.dyobs is not None:
|
|
277
|
+
# work around for interpolation issue making some of these non-1
|
|
278
|
+
if (self.dyobs == 1).all():
|
|
279
|
+
self.dy = numpy.ones_like(self.x)
|
|
280
|
+
else:
|
|
281
|
+
# FIXME - This does not follow error propagation rules and it
|
|
282
|
+
# introduces (more) correlation between the data points.
|
|
283
|
+
self.dy = rebinArray(self.dyobs, self.xobs, self.x)
|
|
284
|
+
|
|
285
|
+
return
|
|
286
|
+
|
|
287
|
+
def loadtxt(self, *args, **kw):
|
|
288
|
+
"""Use numpy.loadtxt to load data.
|
|
289
|
+
|
|
290
|
+
Arguments are passed to numpy.loadtxt. unpack = True is
|
|
291
|
+
enforced. The first two arrays returned by numpy.loadtxt are
|
|
292
|
+
assumed to be x and y. If there is a third array, it is assumed
|
|
293
|
+
to by dy. Any other arrays are ignored. These are passed to
|
|
294
|
+
setObservedProfile.
|
|
295
|
+
|
|
296
|
+
Raises ValueError if the call to numpy.loadtxt returns fewer
|
|
297
|
+
than 2 arrays.
|
|
298
|
+
|
|
299
|
+
Returns the x, y and dy arrays loaded from the file
|
|
300
|
+
"""
|
|
301
|
+
if len(args) == 8 and not args[-1]:
|
|
302
|
+
args = list(args)
|
|
303
|
+
args[-1] = True
|
|
304
|
+
else:
|
|
305
|
+
kw["unpack"] = True
|
|
306
|
+
cols = numpy.loadtxt(*args, **kw)
|
|
307
|
+
|
|
308
|
+
x = y = dy = None
|
|
309
|
+
# Due to using 'unpack', a single column will come out as a single
|
|
310
|
+
# array, thus the second check.
|
|
311
|
+
if len(cols) < 2 or not isinstance(cols[0], numpy.ndarray):
|
|
312
|
+
raise ValueError("numpy.loadtxt returned fewer than 2 arrays")
|
|
313
|
+
x = cols[0]
|
|
314
|
+
y = cols[1]
|
|
315
|
+
if len(cols) > 2:
|
|
316
|
+
dy = cols[2]
|
|
317
|
+
|
|
318
|
+
self.setObservedProfile(x, y, dy)
|
|
319
|
+
return x, y, dy
|
|
320
|
+
|
|
321
|
+
def savetxt(self, fname, **kwargs):
|
|
322
|
+
"""Call `numpy.savetxt` with x, ycalc, y, dy.
|
|
323
|
+
|
|
324
|
+
Parameters
|
|
325
|
+
----------
|
|
326
|
+
fname : filename or file handle
|
|
327
|
+
This is passed to `numpy.savetxt`.
|
|
328
|
+
**kwargs
|
|
329
|
+
The keyword arguments that are passed to `numpy.savetxt`.
|
|
330
|
+
We preset file header "x ycalc y dy". Use ``header=''``
|
|
331
|
+
to save data without any header.
|
|
332
|
+
|
|
333
|
+
Raises
|
|
334
|
+
------
|
|
335
|
+
SrFitError
|
|
336
|
+
When `self.ycalc` has not been set.
|
|
337
|
+
|
|
338
|
+
See also
|
|
339
|
+
--------
|
|
340
|
+
numpy.savetxt
|
|
341
|
+
"""
|
|
342
|
+
x = self.x
|
|
343
|
+
ycalc = self.ycalc
|
|
344
|
+
if ycalc is None:
|
|
345
|
+
raise SrFitError("ycalc is None")
|
|
346
|
+
y = self.y
|
|
347
|
+
dy = self.dy
|
|
348
|
+
kwargs.setdefault("header", "x ycalc y dy")
|
|
349
|
+
data = numpy.transpose([x, ycalc, y, dy])
|
|
350
|
+
numpy.savetxt(fname, data, **kwargs)
|
|
351
|
+
return
|
|
352
|
+
|
|
353
|
+
def _flush(self, other):
|
|
354
|
+
"""Invalidate cached state.
|
|
355
|
+
|
|
356
|
+
This will force any observer to invalidate its state.
|
|
357
|
+
"""
|
|
358
|
+
self.ycalc = None
|
|
359
|
+
self.notify(other)
|
|
360
|
+
return
|
|
361
|
+
|
|
362
|
+
def _validate(self):
|
|
363
|
+
"""Validate my state.
|
|
364
|
+
|
|
365
|
+
This validates that x, y, dy, xobx, yobs and dyobs are not None.
|
|
366
|
+
This validates that x, y, and dy are the same length.
|
|
367
|
+
|
|
368
|
+
Raises SrFitError if validation fails.
|
|
369
|
+
"""
|
|
370
|
+
datanotset = any(
|
|
371
|
+
v is None
|
|
372
|
+
for v in [
|
|
373
|
+
self.x,
|
|
374
|
+
self.y,
|
|
375
|
+
self.dy,
|
|
376
|
+
self.xobs,
|
|
377
|
+
self.yobs,
|
|
378
|
+
self.dyobs,
|
|
379
|
+
]
|
|
380
|
+
)
|
|
381
|
+
if datanotset:
|
|
382
|
+
raise SrFitError("Missing data")
|
|
383
|
+
if len(self.x) != len(self.y) or len(self.x) != len(self.dy):
|
|
384
|
+
raise SrFitError("Data are different lengths")
|
|
385
|
+
return
|
|
386
|
+
|
|
387
|
+
|
|
388
|
+
# End class Profile
|
|
389
|
+
|
|
390
|
+
|
|
391
|
+
def rebinArray(A, xold, xnew):
|
|
392
|
+
"""Rebin the an array by interpolating over the new x range.
|
|
393
|
+
|
|
394
|
+
Arguments:
|
|
395
|
+
A -- Array to interpolate
|
|
396
|
+
xold -- Old sampling array
|
|
397
|
+
xnew -- New sampling array
|
|
398
|
+
|
|
399
|
+
This uses cubic spline interpolation.
|
|
400
|
+
|
|
401
|
+
Returns: A new array over the new sampling array.
|
|
402
|
+
"""
|
|
403
|
+
if numpy.array_equal(xold, xnew):
|
|
404
|
+
return A
|
|
405
|
+
return numpy.interp(xnew, xold, A)
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
##############################################################################
|
|
3
|
+
#
|
|
4
|
+
# diffpy.srfit by DANSE Diffraction group
|
|
5
|
+
# Simon J. L. Billinge
|
|
6
|
+
# (c) 2008 The Trustees of Columbia University
|
|
7
|
+
# in the City of New York. All rights reserved.
|
|
8
|
+
#
|
|
9
|
+
# File coded by: Chris Farrow
|
|
10
|
+
#
|
|
11
|
+
# See AUTHORS.txt for a list of people who contributed.
|
|
12
|
+
# See LICENSE_DANSE.txt for license information.
|
|
13
|
+
#
|
|
14
|
+
##############################################################################
|
|
15
|
+
"""The ProfileGenerator class for generating a profile.
|
|
16
|
+
|
|
17
|
+
ProfileGenerators encapsulate the evaluation and required Parameters and
|
|
18
|
+
ParameterSets of a profile calculator. The ProfileGenerator class can
|
|
19
|
+
be associated with a FitContribution to help calculate a profile.
|
|
20
|
+
|
|
21
|
+
To define a ProfileGenerator, one must implement the required Parameters
|
|
22
|
+
and ParameterSets as well as overload the __call__ method with the
|
|
23
|
+
calculation. A very simple example is > class
|
|
24
|
+
Gaussian(ProfileGenerator): > > def __init__(self): > #
|
|
25
|
+
Initialize and give this a name > ProfileGenerator.__init__(self,
|
|
26
|
+
"g") > # Add amplitude, center and width parameters >
|
|
27
|
+
self.newParameter("amp", 0) > self.newParameter("center", 0) >
|
|
28
|
+
self.newParameter("width", 0) > > def __call__(self, x): > a =
|
|
29
|
+
self.amp.getValue() > x0 = self.center.getValue() > w =
|
|
30
|
+
self.width.getValue() > return a * exp(-0.5*((x-x0)/w)**2)
|
|
31
|
+
|
|
32
|
+
More examples can be found in the example directory of the
|
|
33
|
+
documentation.
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
__all__ = ["ProfileGenerator"]
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
from diffpy.srfit.equation.literals.operators import Operator
|
|
40
|
+
from diffpy.srfit.exceptions import SrFitError
|
|
41
|
+
from diffpy.srfit.fitbase.parameterset import ParameterSet
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class ProfileGenerator(Operator, ParameterSet):
|
|
45
|
+
"""Base class for profile generators.
|
|
46
|
+
|
|
47
|
+
A ProfileGenerator organizes Parameters and has a __call__ method that can
|
|
48
|
+
generate a profile. ProfileGenerator is also an Operator
|
|
49
|
+
(diffpy.srfit.equation.literals.operators), so it can be used directly in
|
|
50
|
+
an evaluation network.
|
|
51
|
+
|
|
52
|
+
Attributes
|
|
53
|
+
name -- A name for this organizer.
|
|
54
|
+
profile -- A Profile instance that contains the calculation range
|
|
55
|
+
and will contain the generated profile.
|
|
56
|
+
meta -- A dictionary of metadata needed by the generator.
|
|
57
|
+
eq -- The Equation object used to wrap this ProfileGenerator.
|
|
58
|
+
This is set when the ProfileGenerator is added to a
|
|
59
|
+
FitContribution.
|
|
60
|
+
_calculators -- A managed dictionary of Calculators, indexed by name.
|
|
61
|
+
_constraints -- A set of constrained Parameters. Constraints can be
|
|
62
|
+
added using the 'constrain' methods.
|
|
63
|
+
_parameters -- A managed OrderedDict of contained Parameters.
|
|
64
|
+
_parsets -- A managed dictionary of ParameterSets.
|
|
65
|
+
_restraints -- A set of Restraints. Restraints can be added using the
|
|
66
|
+
'restrain' or 'confine' methods.
|
|
67
|
+
_eqfactory -- A diffpy.srfit.equation.builder.EquationFactory
|
|
68
|
+
instance that is used create Equations from string.
|
|
69
|
+
|
|
70
|
+
Operator Attributes
|
|
71
|
+
args -- List of Literal arguments, set with 'addLiteral'
|
|
72
|
+
name -- A name for this operator. e.g. "add" or "sin"
|
|
73
|
+
nin -- Number of inputs (<1 means this is variable)
|
|
74
|
+
nout -- Number of outputs
|
|
75
|
+
operation -- Function that performs the operation. e.g. numpy.add. In
|
|
76
|
+
this case, operation is an instance method.
|
|
77
|
+
symbol -- The symbolic representation. e.g. "+" or "sin"
|
|
78
|
+
_value -- The value of the Operator.
|
|
79
|
+
value -- Property for 'getValue'.
|
|
80
|
+
|
|
81
|
+
Properties
|
|
82
|
+
names -- Variable names (read only). See getNames.
|
|
83
|
+
values -- Variable values (read only). See getValues.
|
|
84
|
+
"""
|
|
85
|
+
|
|
86
|
+
# define abstract attributes from the Operator base.
|
|
87
|
+
nin = 0
|
|
88
|
+
nout = 1
|
|
89
|
+
|
|
90
|
+
def __init__(self, name):
|
|
91
|
+
"""Initialize the attributes."""
|
|
92
|
+
Operator.__init__(self)
|
|
93
|
+
ParameterSet.__init__(self, name)
|
|
94
|
+
self.profile = None
|
|
95
|
+
self.meta = {}
|
|
96
|
+
return
|
|
97
|
+
|
|
98
|
+
@property
|
|
99
|
+
def symbol(self):
|
|
100
|
+
return self.name
|
|
101
|
+
|
|
102
|
+
# Overload me!
|
|
103
|
+
|
|
104
|
+
def __call__(self, x):
|
|
105
|
+
"""Evaluate the profile.
|
|
106
|
+
|
|
107
|
+
This method must be overloaded.
|
|
108
|
+
|
|
109
|
+
This method only takes the independent variables to calculate
|
|
110
|
+
over.
|
|
111
|
+
"""
|
|
112
|
+
return x
|
|
113
|
+
|
|
114
|
+
# No need to overload anything below here
|
|
115
|
+
|
|
116
|
+
def operation(self):
|
|
117
|
+
"""Evaluate the profile.
|
|
118
|
+
|
|
119
|
+
Return the result of __call__(profile.x).
|
|
120
|
+
"""
|
|
121
|
+
y = self.__call__(self.profile.x)
|
|
122
|
+
return y
|
|
123
|
+
|
|
124
|
+
def setProfile(self, profile):
|
|
125
|
+
"""Assign the profile.
|
|
126
|
+
|
|
127
|
+
profile -- A Profile that specifies the calculation points and which
|
|
128
|
+
will store the calculated signal.
|
|
129
|
+
"""
|
|
130
|
+
if self.profile is not None:
|
|
131
|
+
self.profile.removeObserver(self._flush)
|
|
132
|
+
|
|
133
|
+
self.profile = profile
|
|
134
|
+
self.profile.addObserver(self._flush)
|
|
135
|
+
self._flush(other=(self,))
|
|
136
|
+
|
|
137
|
+
# Merge the profiles metadata with our own
|
|
138
|
+
self.meta.update(self.profile.meta)
|
|
139
|
+
self.processMetaData()
|
|
140
|
+
return
|
|
141
|
+
|
|
142
|
+
def processMetaData(self):
|
|
143
|
+
"""Process the metadata.
|
|
144
|
+
|
|
145
|
+
This can be used to configure a ProfileGenerator upon a change
|
|
146
|
+
in the metadata. This method gets called whenever the Profile is
|
|
147
|
+
set.
|
|
148
|
+
"""
|
|
149
|
+
return
|
|
150
|
+
|
|
151
|
+
def _validate(self):
|
|
152
|
+
"""Validate my state.
|
|
153
|
+
|
|
154
|
+
This performs profile validations. This performs ParameterSet
|
|
155
|
+
validations. This does not validate the operation, since this
|
|
156
|
+
could be costly. The operation should be validated with a
|
|
157
|
+
containing equation.
|
|
158
|
+
|
|
159
|
+
Raises SrFitError if validation fails.
|
|
160
|
+
"""
|
|
161
|
+
if self.profile is None:
|
|
162
|
+
raise SrFitError("profile is None")
|
|
163
|
+
self.profile._validate()
|
|
164
|
+
ParameterSet._validate(self)
|
|
165
|
+
return
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
# End class ProfileGenerator
|