forked from diffpy/diffpy.srfit
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathfitcontribution.py
More file actions
507 lines (417 loc) · 16.2 KB
/
fitcontribution.py
File metadata and controls
507 lines (417 loc) · 16.2 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
#!/usr/bin/env python
##############################################################################
#
# diffpy.srfit by DANSE Diffraction group
# Simon J. L. Billinge
# (c) 2008 The Trustees of Columbia University
# in the City of New York. All rights reserved.
#
# File coded by: Chris Farrow
#
# See AUTHORS.txt for a list of people who contributed.
# See LICENSE_DANSE.txt for license information.
#
##############################################################################
"""FitContribution class.
FitContributions generate a residual function for a FitRecipe. A
FitContribution associates an Equation for generating a signal,
optionally one or more ProfileGenerators or Calculators that help in
this, and a Profile that holds the observed and calculated signals.
See the examples in the documentation for how to use a FitContribution.
"""
__all__ = ["FitContribution"]
from diffpy.srfit.exceptions import SrFitError
from diffpy.srfit.fitbase.parameter import ParameterProxy
from diffpy.srfit.fitbase.parameterset import ParameterSet
from diffpy.srfit.fitbase.profile import Profile
from diffpy.srfit.fitbase.recipeorganizer import get_equation_from_string
from diffpy.utils._deprecator import build_deprecation_message, deprecated
base = "diffpy.srfit.fitbase.FitContribution"
removal_version = "4.0.0"
setequation_dep_msg = build_deprecation_message(
base,
"setEquation",
"set_equation",
removal_version,
)
setprofile_dep_msg = build_deprecation_message(
base,
"setProfile",
"set_profile",
removal_version,
)
addprofilegenerator_dep_msg = build_deprecation_message(
base,
"addProfileGenerator",
"add_profile_generator",
removal_version,
)
getequation_dep_msg = build_deprecation_message(
base,
"getEquation",
"get_equation",
removal_version,
)
setresidualequation_dep_msg = build_deprecation_message(
base,
"setResidualEquation",
"set_residual_equation",
removal_version,
)
getresidualequation_dep_msg = build_deprecation_message(
base,
"getResidualEquation",
"get_residual_equation",
removal_version,
)
class FitContribution(ParameterSet):
"""FitContribution class.
FitContributions organize an Equation that calculates the signal, and a
Profile that holds the signal. ProfileGenerators and Calculators can be
used as well. Constraints and Restraints can be created as part of a
FitContribution.
Attributes
----------
name
A name for this FitContribution.
profile
A Profile that holds the measured (and calculated)
signal.
_calculators
A managed dictionary of Calculators, indexed by name.
_constraints
A set of constrained Parameters. Constraints can be
added using the 'constrain' methods.
_generators
A managed dictionary of ProfileGenerators.
_parameters
A managed OrderedDict of parameters.
_restraints
A set of Restraints. Restraints can be added using the
'restrain' method.
_parsets
A managed dictionary of ParameterSets.
_eqfactory
A diffpy.srfit.equation.builder.EquationFactory
instance that is used to create constraints and
restraints from string
_eq
The FitContribution equation that will be optimized.
_reseq
The residual equation.
_xname
Name of the x-variable
_yname
Name of the y-variable
_dyname
Name of the dy-variable
Properties
----------
names
Variable names (read only). See get_names.
values
Variable values (read only). See get_values.
"""
def __init__(self, name):
"""Initialization."""
ParameterSet.__init__(self, name)
self._eq = None
self._reseq = None
self.profile = None
self._xname = None
self._yname = None
self._dyname = None
self._generators = {}
self._manage(self._generators)
return
def set_profile(self, profile, xname=None, yname=None, dyname=None):
"""Assign the Profile for this FitContribution.
Parameters
----------
profile
A Profile that specifies the calculation points and that
will store the calculated signal.
xname
The name of the independent variable from the Profile. If
this is None (default), then the name specified by the
Profile for this parameter will be used. This variable is
usable within string equations with the specified name.
yname
The name of the observed Profile. If this is None
(default), then the name specified by the Profile for this
parameter will be used. This variable is usable within
string equations with the specified name.
dyname
The name of the uncertainty in the observed Profile. If
this is None (default), then the name specified by the
Profile for this parameter will be used. This variable is
usable within string equations with the specified name.
"""
# Enforce type of the profile argument
if not isinstance(profile, Profile):
emsg = "Argument must be an instance of the Profile class."
raise TypeError(emsg)
# Set the Profile and add its parameters to this organizer.
self.profile = profile
if xname is None:
xname = self.profile.xpar.name
if yname is None:
yname = self.profile.ypar.name
if dyname is None:
dyname = self.profile.dypar.name
self._xname = xname
self._yname = yname
self._dyname = dyname
xpar = ParameterProxy(xname, self.profile.xpar)
ypar = ParameterProxy(yname, self.profile.ypar)
dypar = ParameterProxy(dyname, self.profile.dypar)
self.addParameter(xpar, check=False)
self.addParameter(ypar, check=False)
self.addParameter(dypar, check=False)
# If we have ProfileGenerators, set their Profiles.
for gen in self._generators.values():
gen.set_profile(profile)
# If we have _eq, but not _reseq, set the residual
if self._eq is not None and self._reseq is None:
self.set_residual_equation("chiv")
return
@deprecated(setprofile_dep_msg)
def setProfile(self, profile, xname=None, yname=None, dyname=None):
"""This function has been deprecated and will be removed in version
4.0.0.
Please use diffpy.srfit.fitbase.FitContribution.set_profile instead.
"""
return self.set_profile(
profile, xname=xname, yname=yname, dyname=dyname
)
def add_profile_generator(self, gen, name=None):
"""Add a ProfileGenerator to be used by this FitContribution.
The ProfileGenerator is given a name so that it can be used as part of
the profile equation (see setEquation). This can be different from the
name of the ProfileGenerator used for attribute access.
FitContributions should not share ProfileGenerator instances. Different
ProfileGenerators can share Parameters and ParameterSets, however.
Calling addProfileGenerator sets the profile equation to call the
calculator and if there is not a profile equation already.
Parameters
----------
gen
A ProfileGenerator instance
name
A name for the calculator. If name is None (default), then
the ProfileGenerator's name attribute will be used.
Raises ValueError if the ProfileGenerator has no name.
Raises ValueError if the ProfileGenerator has the same name as some
other managed object.
"""
if name is None:
name = gen.name
# Register the generator with the equation factory and add it as a
# managed object.
self._eqfactory.registerOperator(name, gen)
self._add_object(gen, self._generators, True)
# If we have a profile, set the profile of the generator.
if self.profile is not None:
gen.set_profile(self.profile)
# Make this our equation if we don't have one. This will set the
# residual equation if necessary.
if self._eq is None:
self.set_equation(name)
return
@deprecated(addprofilegenerator_dep_msg)
def addProfileGenerator(self, gen, name=None):
"""This function has been deprecated and will be removed in version
4.0.0.
Please use
diffpy.srfit.fitbase.FitContribution.add_profile_generator
instead.
"""
self.add_profile_generator(gen, name=name)
return
def set_equation(self, eqstr, ns={}):
"""Set the profile equation for the FitContribution.
This sets the equation that will be used when generating the residual
for this FitContribution. The equation will be usable within
set_residual_equation as "eq", and it takes no arguments.
Parameters
----------
eqstr
A string representation of the equation. Any Parameter
registered by addParameter or setProfile, or function
registered by setCalculator, register_function or
register_string_function can be can be used in the equation
by name. Other names will be turned into Parameters of this
FitContribution.
ns
A dictionary of Parameters, indexed by name, that are used
in the eqstr, but not registered (default {}).
Raises ValueError if ns uses a name that is already used for a
variable.
"""
# Build the equation instance.
eq = get_equation_from_string(
eqstr, self._eqfactory, buildargs=True, ns=ns
)
eq.name = "eq"
# Register any new Parameters.
for par in self._eqfactory.newargs:
self._add_parameter(par)
# Register eq as an operator
self._eqfactory.registerOperator("eq", eq)
self._eqfactory.wipeout(self._eq)
self._eq = eq
# Set the residual if we need to
if self.profile is not None and self._reseq is None:
self.set_residual_equation("chiv")
return
@deprecated(setequation_dep_msg)
def setEquation(self, eqstr, ns={}):
"""This function has been deprecated and will be removed in version
4.0.0.
Please use diffpy.srfit.fitbase.FitContribution.set_equation
instead.
"""
self.set_equation(eqstr, ns=ns)
return
def get_equation(self):
"""Get math expression string for the active profile equation.
Return normalized math expression or an empty string if profile
equation has not been set yet.
"""
from diffpy.srfit.equation.visitors import getExpression
rv = ""
if self._eq is not None:
rv = getExpression(self._eq)
return rv
@deprecated(getequation_dep_msg)
def getEquation(self):
"""This function has been deprecated and will be removed in version
4.0.0.
Please use diffpy.srfit.fitbase.FitContribution.get_equation
instead.
"""
return self.get_equation()
def set_residual_equation(self, eqstr):
"""Set the residual equation for the FitContribution.
Parameters
----------
eqstr
A string representation of the residual. If eqstr is None
(default), then the previous residual equation will be
used, or the chi2 residual will be used if that does not
exist.
Two residuals are preset for convenience, "chiv" and "resv".
chiv is defined such that dot(chiv, chiv) = chi^2.
resv is defined such that dot(resv, resv) = Rw^2.
You can call on these in your residual equation. Note that the quantity
that will be optimized is the summed square of the residual equation.
Keep that in mind when defining a new residual or using the built-in
ones.
Raises SrFitError if the Profile is not yet defined.
Raises ValueError if eqstr depends on a Parameter that is not part of
the FitContribution.
"""
if self.profile is None:
raise SrFitError("Assign the Profile first")
if self._eq is None:
raise SrFitError("Assign the Equation first")
chivstr = "(eq - %s)/%s" % (self._yname, self._dyname)
resvstr = "(eq - %s)/sum(%s**2)**0.5" % (self._yname, self._yname)
# Get the equation string if it is not defined
if eqstr == "chiv":
eqstr = chivstr
elif eqstr == "resv":
eqstr = resvstr
reseq = get_equation_from_string(eqstr, self._eqfactory)
self._eqfactory.wipeout(self._reseq)
self._reseq = reseq
return
@deprecated(setresidualequation_dep_msg)
def setResidualEquation(self, eqstr):
"""This function has been deprecated and will be removed in version
4.0.0.
Please use
diffpy.srfit.fitbase.FitContribution.set_residual_equation
instead.
"""
self.set_residual_equation(eqstr)
return
def get_residual_equation(self):
"""Get math expression string for the active residual equation.
Return normalized math formula or an empty string if residual
equation has not been configured yet.
"""
from diffpy.srfit.equation.visitors import getExpression
rv = ""
if self._reseq is not None:
rv = getExpression(self._reseq, eqskip="eq$")
return rv
@deprecated(getresidualequation_dep_msg)
def getResidualEquation(self):
"""This function has been deprecated and will be removed in version
4.0.0.
Please use
diffpy.srfit.fitbase.FitContribution.get_residual_equation
instead.
"""
return self.get_residual_equation()
def residual(self):
"""Calculate the residual for this fitcontribution.
When this method is called, it is assumed that all parameters have been
assigned their most current values by the FitRecipe. This will be the
case when being called as part of a FitRecipe refinement.
The residual is by default an array chiv:
chiv = (eq() - self.profile.y) / self.profile.dy
The value that is optimized is dot(chiv, chiv).
The residual equation can be changed with the set_residual_equation
method.
"""
# Assign the calculated profile
self.profile.ycalc = self._eq()
# Note that equations only recompute when their inputs are modified, so
# the following will not recompute the equation.
return self._reseq()
def evaluate(self):
"""Evaluate the contribution equation and update
profile.ycalc."""
yc = self._eq()
if self.profile is not None:
self.profile.ycalc = yc
return yc
def _validate(self):
"""Validate my state.
This performs profile validations. This performs
ProfileGenerator validations. This validates _eq. This validates
_reseq and residual.
Raises SrFitError if validation fails.
"""
self.profile._validate()
ParameterSet._validate(self)
# Try to get the value of eq.
from diffpy.srfit.equation.visitors import validate
try:
validate(self._eq)
except ValueError as e:
raise SrFitError(e)
if self._eq is None:
raise SrFitError("_eq is None")
val = None
try:
val = self._eq()
except TypeError as e:
raise SrFitError("_eq cannot be evaluated: %s" % e)
if val is None:
raise SrFitError("_eq evaluates to None")
# Try to get the value for residual
try:
validate(self._reseq)
except ValueError as e:
raise SrFitError(e)
try:
val = self.residual()
except TypeError:
raise SrFitError("residual cannot be evaluated")
if val is None:
raise SrFitError("residual evaluates to None")
return
# End of file