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,674 @@
|
|
|
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, Pavol Juhas
|
|
10
|
+
#
|
|
11
|
+
# See AUTHORS.txt for a list of people who contributed.
|
|
12
|
+
# See LICENSE_DANSE.txt for license information.
|
|
13
|
+
#
|
|
14
|
+
##############################################################################
|
|
15
|
+
"""The FitResults and ContributionResults classes for storing results of a fit.
|
|
16
|
+
|
|
17
|
+
The FitResults class is used to display the current state of a
|
|
18
|
+
FitRecipe. It stores the state, and uses it to calculate useful
|
|
19
|
+
statistics, which can be displayed on screen or saved to file.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
from __future__ import print_function
|
|
23
|
+
|
|
24
|
+
__all__ = ["FitResults", "ContributionResults", "initializeRecipe"]
|
|
25
|
+
|
|
26
|
+
import re
|
|
27
|
+
from collections import OrderedDict
|
|
28
|
+
|
|
29
|
+
import numpy
|
|
30
|
+
|
|
31
|
+
from diffpy.srfit.util import _DASHEDLINE
|
|
32
|
+
from diffpy.srfit.util import sortKeyForNumericString as numstr
|
|
33
|
+
from diffpy.srfit.util.inpututils import inputToString
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class FitResults(object):
|
|
37
|
+
"""Class for processing, presenting and storing results of a fit.
|
|
38
|
+
|
|
39
|
+
Attributes
|
|
40
|
+
recipe -- The recipe containing the results.
|
|
41
|
+
cov -- The covariance matrix from the recipe.
|
|
42
|
+
conresults -- An ordered dictionary of ContributionResults for each
|
|
43
|
+
FitContribution, indexed by the FitContribution name.
|
|
44
|
+
derivstep -- The fractional step size for calculating numeric
|
|
45
|
+
derivatives. Default 1e-8.
|
|
46
|
+
varnames -- Names of the variables in the recipe.
|
|
47
|
+
varvals -- Values of the variables in the recipe.
|
|
48
|
+
varunc -- Uncertainties in the variable values.
|
|
49
|
+
showfixed -- Show fixed variables (default True).
|
|
50
|
+
fixednames -- Names of the fixed variables of the recipe.
|
|
51
|
+
fixedvals -- Values of the fixed variables of the recipe.
|
|
52
|
+
showcon -- Show constraint values in the output (default False).
|
|
53
|
+
connames -- Names of the constrained parameters.
|
|
54
|
+
convals -- Values of the constrained parameters.
|
|
55
|
+
conunc -- Uncertainties in the constraint values.
|
|
56
|
+
residual -- The scalar residual of the recipe.
|
|
57
|
+
penalty -- The penalty to residual from the restraints.
|
|
58
|
+
chi2 -- The chi2 of the recipe.
|
|
59
|
+
cumchi2 -- The cumulative chi2 of the recipe.
|
|
60
|
+
rchi2 -- The reduced chi2 of the recipe.
|
|
61
|
+
rw -- The Rw of the recipe.
|
|
62
|
+
cumrw -- The cumulative Rw of the recipe.
|
|
63
|
+
messages -- A list of messages about the results.
|
|
64
|
+
precision -- The precision of numeric output (default 8).
|
|
65
|
+
_dcon -- The derivatives of the constraint equations with respect to
|
|
66
|
+
the variables. This is used internally.
|
|
67
|
+
|
|
68
|
+
Each of these attributes, except the recipe, are created or updated when
|
|
69
|
+
the update method is called.
|
|
70
|
+
"""
|
|
71
|
+
|
|
72
|
+
def __init__(self, recipe, update=True, showfixed=True, showcon=False):
|
|
73
|
+
"""Initialize the attributes.
|
|
74
|
+
|
|
75
|
+
recipe -- The recipe containing the results
|
|
76
|
+
update -- Flag indicating whether to do an immediate update (default
|
|
77
|
+
True).
|
|
78
|
+
showcon -- Show fixed variables in the output (default True).
|
|
79
|
+
showcon -- Show constraint values in the output (default False).
|
|
80
|
+
"""
|
|
81
|
+
self.recipe = recipe
|
|
82
|
+
self.conresults = OrderedDict()
|
|
83
|
+
self.derivstep = 1e-8
|
|
84
|
+
self.varnames = []
|
|
85
|
+
self.varvals = []
|
|
86
|
+
self.varunc = []
|
|
87
|
+
self.fixednames = []
|
|
88
|
+
self.fixedvals = []
|
|
89
|
+
self.connames = []
|
|
90
|
+
self.convals = []
|
|
91
|
+
self.conunc = []
|
|
92
|
+
self.cov = None
|
|
93
|
+
self.residual = 0
|
|
94
|
+
self.penalty = 0
|
|
95
|
+
self.chi2 = 0
|
|
96
|
+
self.rchi2 = 0
|
|
97
|
+
self.rw = 0
|
|
98
|
+
self.precision = 8
|
|
99
|
+
self._dcon = []
|
|
100
|
+
self.messages = []
|
|
101
|
+
|
|
102
|
+
self.showfixed = bool(showfixed)
|
|
103
|
+
self.showcon = bool(showcon)
|
|
104
|
+
|
|
105
|
+
if update:
|
|
106
|
+
self.update()
|
|
107
|
+
return
|
|
108
|
+
|
|
109
|
+
def update(self):
|
|
110
|
+
"""Update the results according to the current state of the recipe."""
|
|
111
|
+
# Note that the order of these operations are chosen to reduce
|
|
112
|
+
# computation time.
|
|
113
|
+
|
|
114
|
+
recipe = self.recipe
|
|
115
|
+
|
|
116
|
+
if not recipe._contributions:
|
|
117
|
+
return
|
|
118
|
+
|
|
119
|
+
# Make sure everything is ready for calculation
|
|
120
|
+
recipe._prepare()
|
|
121
|
+
|
|
122
|
+
# Store the variable names and values
|
|
123
|
+
self.varnames = recipe.getNames()
|
|
124
|
+
self.varvals = recipe.getValues()
|
|
125
|
+
fixedpars = recipe._tagmanager.union(recipe._fixedtag)
|
|
126
|
+
fixedpars = [p for p in fixedpars if not p.constrained]
|
|
127
|
+
self.fixednames = [p.name for p in fixedpars]
|
|
128
|
+
self.fixedvals = [p.value for p in fixedpars]
|
|
129
|
+
|
|
130
|
+
# Store the constraint information
|
|
131
|
+
self.connames = [con.par.name for con in recipe._oconstraints]
|
|
132
|
+
self.convals = [con.par.getValue() for con in recipe._oconstraints]
|
|
133
|
+
|
|
134
|
+
if self.varnames:
|
|
135
|
+
# Calculate the covariance
|
|
136
|
+
self._calculateCovariance()
|
|
137
|
+
|
|
138
|
+
# Get the variable uncertainties
|
|
139
|
+
self.varunc = [
|
|
140
|
+
self.cov[i, i] ** 0.5 for i in range(len(self.varnames))
|
|
141
|
+
]
|
|
142
|
+
|
|
143
|
+
# Get the constraint uncertainties
|
|
144
|
+
self._calculateConstraintUncertainties()
|
|
145
|
+
|
|
146
|
+
# Store the fitting arrays and metrics for each FitContribution.
|
|
147
|
+
self.conresults = OrderedDict()
|
|
148
|
+
for con, weight in zip(
|
|
149
|
+
recipe._contributions.values(), recipe._weights
|
|
150
|
+
):
|
|
151
|
+
self.conresults[con.name] = ContributionResults(con, weight, self)
|
|
152
|
+
|
|
153
|
+
# Calculate the metrics
|
|
154
|
+
res = recipe.residual()
|
|
155
|
+
self.residual = numpy.dot(res, res)
|
|
156
|
+
self._calculateMetrics()
|
|
157
|
+
|
|
158
|
+
# Calculate the restraints penalty
|
|
159
|
+
w = self.chi2 / len(res)
|
|
160
|
+
self.penalty = sum([r.penalty(w) for r in recipe._restraintlist])
|
|
161
|
+
|
|
162
|
+
return
|
|
163
|
+
|
|
164
|
+
def _calculateCovariance(self):
|
|
165
|
+
"""Calculate the covariance matrix. This is called by update.
|
|
166
|
+
|
|
167
|
+
This code borrowed from PARK. It finds the pseudo-inverse of the
|
|
168
|
+
Jacobian using the singular value decomposition.
|
|
169
|
+
"""
|
|
170
|
+
try:
|
|
171
|
+
J = self._calculateJacobian()
|
|
172
|
+
u, s, vh = numpy.linalg.svd(J, 0)
|
|
173
|
+
self.cov = numpy.dot(vh.T.conj() / s**2, vh)
|
|
174
|
+
except numpy.linalg.LinAlgError:
|
|
175
|
+
self.messages.append("Cannot compute covariance matrix.")
|
|
176
|
+
lvarvals = len(self.varvals)
|
|
177
|
+
self.cov = numpy.zeros((lvarvals, lvarvals), dtype=float)
|
|
178
|
+
return
|
|
179
|
+
|
|
180
|
+
def _calculateJacobian(self):
|
|
181
|
+
"""Calculate the Jacobian for the fitting.
|
|
182
|
+
|
|
183
|
+
Adapted from PARK. Returns the derivative wrt the fit variables
|
|
184
|
+
at point p.
|
|
185
|
+
|
|
186
|
+
This also calculates the derivatives of the constrained
|
|
187
|
+
parameters while we're at it.
|
|
188
|
+
|
|
189
|
+
Numeric derivatives are calculated based on step, where step is
|
|
190
|
+
the portion of variable value. E.g. step = dv/v.
|
|
191
|
+
"""
|
|
192
|
+
recipe = self.recipe
|
|
193
|
+
step = self.derivstep
|
|
194
|
+
|
|
195
|
+
# Make sure the input vector is an array
|
|
196
|
+
pvals = numpy.asarray(self.varvals)
|
|
197
|
+
# Compute the numeric derivative using the center point formula.
|
|
198
|
+
delta = step * pvals
|
|
199
|
+
|
|
200
|
+
# Center point formula:
|
|
201
|
+
# df/dv = lim_{h->0} ( f(v+h)-f(v-h) ) / ( 2h )
|
|
202
|
+
#
|
|
203
|
+
|
|
204
|
+
r = []
|
|
205
|
+
# The list of constraint derivatives with respect to variables
|
|
206
|
+
# The forward difference would be faster, but perhaps not as accurate.
|
|
207
|
+
conr = []
|
|
208
|
+
for k, v in enumerate(pvals):
|
|
209
|
+
h = delta[k]
|
|
210
|
+
pvals[k] = v + h
|
|
211
|
+
rk = self.recipe.residual(pvals)
|
|
212
|
+
|
|
213
|
+
# The constraints derivatives
|
|
214
|
+
cond = []
|
|
215
|
+
for con in recipe._oconstraints:
|
|
216
|
+
con.update()
|
|
217
|
+
cond.append(con.par.getValue())
|
|
218
|
+
|
|
219
|
+
pvals[k] = v - h
|
|
220
|
+
rk -= self.recipe.residual(pvals)
|
|
221
|
+
|
|
222
|
+
# FIXME - constraints are used for vectors as well!
|
|
223
|
+
for i, con in enumerate(recipe._oconstraints):
|
|
224
|
+
con.update()
|
|
225
|
+
val = con.par.getValue()
|
|
226
|
+
if numpy.isscalar(val):
|
|
227
|
+
cond[i] -= con.par.getValue()
|
|
228
|
+
cond[i] /= 2 * h
|
|
229
|
+
else:
|
|
230
|
+
cond[i] = 0.0
|
|
231
|
+
|
|
232
|
+
conr.append(cond)
|
|
233
|
+
|
|
234
|
+
pvals[k] = v
|
|
235
|
+
r.append(rk / (2 * h))
|
|
236
|
+
|
|
237
|
+
# Reset the constrained parameters to their original values
|
|
238
|
+
for con in recipe._oconstraints:
|
|
239
|
+
con.update()
|
|
240
|
+
|
|
241
|
+
self._dcon = numpy.vstack(conr).T
|
|
242
|
+
|
|
243
|
+
# return the jacobian
|
|
244
|
+
jac = numpy.vstack(r).T
|
|
245
|
+
return jac
|
|
246
|
+
|
|
247
|
+
def _calculateMetrics(self):
|
|
248
|
+
"""Calculate chi2, cumchi2, rchi2, rw and cumrw for the recipe."""
|
|
249
|
+
cumchi2 = numpy.array([], dtype=float)
|
|
250
|
+
# total weighed denominator for the ratio in the Rw formula
|
|
251
|
+
yw2tot = 0.0
|
|
252
|
+
numpoints = 0
|
|
253
|
+
for con in self.conresults.values():
|
|
254
|
+
cc2w = con.weight * con.cumchi2
|
|
255
|
+
c2last = cumchi2[-1:].sum()
|
|
256
|
+
cumchi2 = numpy.concatenate([cumchi2, c2last + cc2w])
|
|
257
|
+
yw2tot += con.weight * (con.chi2 / con.rw**2)
|
|
258
|
+
numpoints += len(con.x)
|
|
259
|
+
|
|
260
|
+
chi2 = cumchi2[-1:].sum()
|
|
261
|
+
cumrw = numpy.sqrt(cumchi2 / yw2tot)
|
|
262
|
+
rw = cumrw[-1:].sum()
|
|
263
|
+
|
|
264
|
+
numpoints += len(self.recipe._restraintlist)
|
|
265
|
+
rchi2 = chi2 / (numpoints - len(self.varnames))
|
|
266
|
+
|
|
267
|
+
self.chi2 = chi2
|
|
268
|
+
self.rchi2 = rchi2
|
|
269
|
+
self.rw = rw
|
|
270
|
+
self.cumchi2 = cumchi2
|
|
271
|
+
self.cumrw = cumrw
|
|
272
|
+
return
|
|
273
|
+
|
|
274
|
+
def _calculateConstraintUncertainties(self):
|
|
275
|
+
"""Calculate the uncertainty on the constrained parameters."""
|
|
276
|
+
vu = self.varunc
|
|
277
|
+
|
|
278
|
+
# sig^2(c) = sum_i sum_j sig(v_i) sig(v_j) (dc/dv_i)(dc/dv_j)
|
|
279
|
+
# sig^2(c) = sum_i sum_j [sig(v_i)(dc/dv_i)][sig(v_j)(dc/dv_j)]
|
|
280
|
+
# sig^2(c) = sum_i sum_j u_i u_j
|
|
281
|
+
self.conunc = []
|
|
282
|
+
for dci in self._dcon:
|
|
283
|
+
|
|
284
|
+
# Create sig(v_i) (dc/dv_i) array.
|
|
285
|
+
u = dci * vu
|
|
286
|
+
# The outer product is all possible pairings of u_i and u_j
|
|
287
|
+
# uu_ij = u_i u_j
|
|
288
|
+
uu = numpy.outer(u, u)
|
|
289
|
+
# Sum these pairings to get sig^2(c)
|
|
290
|
+
sig2c = sum(uu.flatten())
|
|
291
|
+
|
|
292
|
+
self.conunc.append(sig2c**0.5)
|
|
293
|
+
return
|
|
294
|
+
|
|
295
|
+
def formatResults(self, header="", footer="", update=False):
|
|
296
|
+
"""Format the results and return them in a string.
|
|
297
|
+
|
|
298
|
+
This function is called by printResults and saveResults. Overloading
|
|
299
|
+
the formatting here will change all three functions.
|
|
300
|
+
|
|
301
|
+
header -- A header to add to the output (default "")
|
|
302
|
+
footer -- A footer to add to the output (default "")
|
|
303
|
+
update -- Flag indicating whether to call update() (default False).
|
|
304
|
+
|
|
305
|
+
Returns a string containing the formatted results.
|
|
306
|
+
"""
|
|
307
|
+
if update:
|
|
308
|
+
self.update()
|
|
309
|
+
|
|
310
|
+
lines = []
|
|
311
|
+
corrmin = 0.25
|
|
312
|
+
p = self.precision
|
|
313
|
+
pe = "%-" + "%i.%ie" % (p + 6, p)
|
|
314
|
+
pet = "%" + ".%ie" % (p,)
|
|
315
|
+
# Check to see if the uncertainty values are reliable.
|
|
316
|
+
certain = True
|
|
317
|
+
for con in self.conresults.values():
|
|
318
|
+
if (con.dy == 1).all():
|
|
319
|
+
certain = False
|
|
320
|
+
break
|
|
321
|
+
|
|
322
|
+
# User-defined header
|
|
323
|
+
if header:
|
|
324
|
+
lines.append(header)
|
|
325
|
+
|
|
326
|
+
if not certain:
|
|
327
|
+
err_msg = (
|
|
328
|
+
"Some quantities invalid due to missing profile uncertainty"
|
|
329
|
+
)
|
|
330
|
+
if err_msg not in self.messages:
|
|
331
|
+
self.messages.append(err_msg)
|
|
332
|
+
|
|
333
|
+
lines.extend(self.messages)
|
|
334
|
+
|
|
335
|
+
# Overall results
|
|
336
|
+
err_msg = "Overall"
|
|
337
|
+
if not certain:
|
|
338
|
+
err_msg += " (Chi2 and Reduced Chi2 invalid)"
|
|
339
|
+
lines.append(err_msg)
|
|
340
|
+
lines.append(_DASHEDLINE)
|
|
341
|
+
formatstr = "%-14s %.8f"
|
|
342
|
+
lines.append(formatstr % ("Residual", self.residual))
|
|
343
|
+
lines.append(
|
|
344
|
+
formatstr % ("Contributions", self.residual - self.penalty)
|
|
345
|
+
)
|
|
346
|
+
lines.append(formatstr % ("Restraints", self.penalty))
|
|
347
|
+
lines.append(formatstr % ("Chi2", self.chi2))
|
|
348
|
+
lines.append(formatstr % ("Reduced Chi2", self.rchi2))
|
|
349
|
+
lines.append(formatstr % ("Rw", self.rw))
|
|
350
|
+
|
|
351
|
+
# Per-FitContribution results
|
|
352
|
+
if len(self.conresults) > 1:
|
|
353
|
+
keys = list(self.conresults.keys())
|
|
354
|
+
keys.sort(key=numstr)
|
|
355
|
+
|
|
356
|
+
lines.append("")
|
|
357
|
+
err_msg = "Contributions"
|
|
358
|
+
if not certain:
|
|
359
|
+
err_msg += " (Chi2 and Reduced Chi2 invalid)"
|
|
360
|
+
lines.append(err_msg)
|
|
361
|
+
lines.append(_DASHEDLINE)
|
|
362
|
+
formatstr = "%-10s %-42.8f"
|
|
363
|
+
for name in keys:
|
|
364
|
+
res = self.conresults[name]
|
|
365
|
+
lines.append("")
|
|
366
|
+
namestr = name + " (%f)" % res.weight
|
|
367
|
+
lines.append(namestr)
|
|
368
|
+
lines.append("-" * len(namestr))
|
|
369
|
+
lines.append(formatstr % ("Residual", res.residual))
|
|
370
|
+
lines.append(formatstr % ("Chi2", res.chi2))
|
|
371
|
+
lines.append(formatstr % ("Rw", res.rw))
|
|
372
|
+
|
|
373
|
+
# The variables
|
|
374
|
+
if self.varnames:
|
|
375
|
+
lines.append("")
|
|
376
|
+
err_msg = "Variables"
|
|
377
|
+
if not certain:
|
|
378
|
+
err_msg2 = "Uncertainties invalid"
|
|
379
|
+
err_msg += " (%s)" % err_msg2
|
|
380
|
+
lines.append(err_msg)
|
|
381
|
+
lines.append(_DASHEDLINE)
|
|
382
|
+
|
|
383
|
+
varnames = self.varnames
|
|
384
|
+
varvals = self.varvals
|
|
385
|
+
varunc = self.varunc
|
|
386
|
+
varlines = []
|
|
387
|
+
|
|
388
|
+
w = max(map(len, varnames))
|
|
389
|
+
w = str(w + 1)
|
|
390
|
+
# Format the lines
|
|
391
|
+
formatstr = "%-" + w + "s " + pe + " +/- " + pet
|
|
392
|
+
for name, val, unc in zip(varnames, varvals, varunc):
|
|
393
|
+
varlines.append(formatstr % (name, val, unc))
|
|
394
|
+
|
|
395
|
+
varlines.sort()
|
|
396
|
+
lines.extend(varlines)
|
|
397
|
+
|
|
398
|
+
# Fixed variables
|
|
399
|
+
if self.showfixed and self.fixednames:
|
|
400
|
+
varlines = []
|
|
401
|
+
lines.append("")
|
|
402
|
+
lines.append("Fixed Variables")
|
|
403
|
+
lines.append(_DASHEDLINE)
|
|
404
|
+
w = max(map(len, self.fixednames))
|
|
405
|
+
w = str(w + 1)
|
|
406
|
+
formatstr = "%-" + w + "s " + pet
|
|
407
|
+
for name, val in zip(self.fixednames, self.fixedvals):
|
|
408
|
+
varlines.append(formatstr % (name, val))
|
|
409
|
+
varlines.sort()
|
|
410
|
+
lines.extend(varlines)
|
|
411
|
+
|
|
412
|
+
# The constraints
|
|
413
|
+
if self.connames and self.showcon:
|
|
414
|
+
lines.append("")
|
|
415
|
+
err_msg = "Constrained Parameters"
|
|
416
|
+
if not certain:
|
|
417
|
+
err_msg += " (Uncertainties invalid)"
|
|
418
|
+
lines.append(err_msg)
|
|
419
|
+
lines.append(_DASHEDLINE)
|
|
420
|
+
|
|
421
|
+
w = 0
|
|
422
|
+
keys = []
|
|
423
|
+
vals = {}
|
|
424
|
+
for con in self.conresults.values():
|
|
425
|
+
for i, loc in enumerate(con.conlocs):
|
|
426
|
+
names = [obj.name for obj in loc]
|
|
427
|
+
name = ".".join(names)
|
|
428
|
+
w = max(w, len(name))
|
|
429
|
+
val = con.convals[i]
|
|
430
|
+
unc = con.conunc[i]
|
|
431
|
+
keys.append(name)
|
|
432
|
+
vals[name] = (val, unc)
|
|
433
|
+
|
|
434
|
+
keys.sort(key=numstr)
|
|
435
|
+
w = str(w + 1)
|
|
436
|
+
formatstr = "%-" + w + "s %- 15f +/- %-15f"
|
|
437
|
+
for name in keys:
|
|
438
|
+
val, unc = vals[name]
|
|
439
|
+
lines.append(formatstr % (name, val, unc))
|
|
440
|
+
|
|
441
|
+
# Variable correlations
|
|
442
|
+
lines.append("")
|
|
443
|
+
corint = int(corrmin * 100)
|
|
444
|
+
err_msg = "Variable Correlations greater than %i%%" % corint
|
|
445
|
+
if not certain:
|
|
446
|
+
err_msg += " (Correlations invalid)"
|
|
447
|
+
lines.append(err_msg)
|
|
448
|
+
lines.append(_DASHEDLINE)
|
|
449
|
+
tup = []
|
|
450
|
+
cornames = []
|
|
451
|
+
n = len(self.varnames)
|
|
452
|
+
for i in range(n):
|
|
453
|
+
for j in range(i + 1, n):
|
|
454
|
+
name = "corr(%s, %s)" % (varnames[i], varnames[j])
|
|
455
|
+
val = self.cov[i, j] / (self.cov[i, i] * self.cov[j, j]) ** 0.5
|
|
456
|
+
if abs(val) > corrmin:
|
|
457
|
+
cornames.append(name)
|
|
458
|
+
tup.append((val, name))
|
|
459
|
+
|
|
460
|
+
tup.sort(key=lambda vn: abs(vn[0]))
|
|
461
|
+
tup.reverse()
|
|
462
|
+
|
|
463
|
+
if cornames:
|
|
464
|
+
w = max(map(len, cornames))
|
|
465
|
+
w = str(w + 1)
|
|
466
|
+
formatstr = "%-" + w + "s %.4f"
|
|
467
|
+
for val, name in tup:
|
|
468
|
+
lines.append(formatstr % (name, val))
|
|
469
|
+
else:
|
|
470
|
+
lines.append("No correlations greater than %i%%" % corint)
|
|
471
|
+
|
|
472
|
+
# User-defined footer
|
|
473
|
+
if footer:
|
|
474
|
+
lines.append(footer)
|
|
475
|
+
|
|
476
|
+
out = "\n".join(lines) + "\n"
|
|
477
|
+
return out
|
|
478
|
+
|
|
479
|
+
def printResults(self, header="", footer="", update=False):
|
|
480
|
+
"""Format and print the results.
|
|
481
|
+
|
|
482
|
+
header -- A header to add to the output (default "")
|
|
483
|
+
footer -- A footer to add to the output (default "")
|
|
484
|
+
update -- Flag indicating whether to call update() (default False).
|
|
485
|
+
"""
|
|
486
|
+
print(self.formatResults(header, footer, update).rstrip())
|
|
487
|
+
return
|
|
488
|
+
|
|
489
|
+
def __str__(self):
|
|
490
|
+
return self.formatResults()
|
|
491
|
+
|
|
492
|
+
def saveResults(self, filename, header="", footer="", update=False):
|
|
493
|
+
"""Format and save the results.
|
|
494
|
+
|
|
495
|
+
filename - Name of the save file.
|
|
496
|
+
header -- A header to add to the output (default "")
|
|
497
|
+
footer -- A footer to add to the output (default "")
|
|
498
|
+
update -- Flag indicating whether to call update() (default False).
|
|
499
|
+
"""
|
|
500
|
+
# Save the time and user
|
|
501
|
+
from getpass import getuser
|
|
502
|
+
from time import ctime
|
|
503
|
+
|
|
504
|
+
myheader = "Results written: " + ctime() + "\n"
|
|
505
|
+
myheader += "produced by " + getuser() + "\n"
|
|
506
|
+
header = myheader + header
|
|
507
|
+
|
|
508
|
+
res = self.formatResults(header, footer, update)
|
|
509
|
+
f = open(filename, "w")
|
|
510
|
+
f.write(res)
|
|
511
|
+
f.close()
|
|
512
|
+
return
|
|
513
|
+
|
|
514
|
+
|
|
515
|
+
# End class FitResults
|
|
516
|
+
|
|
517
|
+
|
|
518
|
+
class ContributionResults(object):
|
|
519
|
+
"""Class for processing, storing FitContribution results.
|
|
520
|
+
|
|
521
|
+
This does not store the FitContribution.
|
|
522
|
+
|
|
523
|
+
Attributes
|
|
524
|
+
y -- The FitContribution's profile over the calculation range
|
|
525
|
+
(default None).
|
|
526
|
+
dy -- The uncertainty in the FitContribution's profile over the
|
|
527
|
+
calculation range (default None).
|
|
528
|
+
x -- A numpy array of the calculated independent variable for the
|
|
529
|
+
FitContribution (default None).
|
|
530
|
+
ycalc -- A numpy array of the calculated signal for the FitContribution
|
|
531
|
+
(default None).
|
|
532
|
+
residual -- The scalar residual of the FitContribution.
|
|
533
|
+
chi2 -- The chi2 of the FitContribution.
|
|
534
|
+
cumchi2 -- The cumulative chi2 of the FitContribution.
|
|
535
|
+
rw -- The Rw of the FitContribution.
|
|
536
|
+
cumrw -- The cumulative Rw of the FitContribution.
|
|
537
|
+
weight -- The weight of the FitContribution in the recipe.
|
|
538
|
+
conlocs -- The location of the constrained parameters in the
|
|
539
|
+
FitContribution (see the
|
|
540
|
+
RecipeContainer._locateManagedObject method).
|
|
541
|
+
convals -- Values of the constrained parameters.
|
|
542
|
+
conunc -- Uncertainties in the constraint values.
|
|
543
|
+
"""
|
|
544
|
+
|
|
545
|
+
def __init__(self, con, weight, fitres):
|
|
546
|
+
"""Initialize the attributes.
|
|
547
|
+
|
|
548
|
+
con -- The FitContribution
|
|
549
|
+
weight -- The weight of the FitContribution in the recipe
|
|
550
|
+
fitres -- The FitResults instance to contain this ContributionResults
|
|
551
|
+
"""
|
|
552
|
+
self.x = None
|
|
553
|
+
self.y = None
|
|
554
|
+
self.dy = None
|
|
555
|
+
self.ycalc = None
|
|
556
|
+
self.residual = 0
|
|
557
|
+
self.chi2 = 0
|
|
558
|
+
self.rw = 0
|
|
559
|
+
self.weight = 0
|
|
560
|
+
self.conlocs = []
|
|
561
|
+
self.convals = []
|
|
562
|
+
self.conunc = []
|
|
563
|
+
self._init(con, weight, fitres)
|
|
564
|
+
return
|
|
565
|
+
|
|
566
|
+
def _init(self, con, weight, fitres):
|
|
567
|
+
"""Initialize the attributes, for real."""
|
|
568
|
+
# Note that the order of these operations is chosen to reduce
|
|
569
|
+
# computation time.
|
|
570
|
+
|
|
571
|
+
if con.profile is None:
|
|
572
|
+
return
|
|
573
|
+
|
|
574
|
+
recipe = fitres.recipe
|
|
575
|
+
|
|
576
|
+
# Store the weight
|
|
577
|
+
self.weight = weight
|
|
578
|
+
|
|
579
|
+
# First the residual
|
|
580
|
+
res = con.residual()
|
|
581
|
+
self.residual = numpy.dot(res, res)
|
|
582
|
+
|
|
583
|
+
# The arrays
|
|
584
|
+
self.x = numpy.array(con.profile.x)
|
|
585
|
+
self.y = numpy.array(con.profile.y)
|
|
586
|
+
self.dy = numpy.array(con.profile.dy)
|
|
587
|
+
self.ycalc = numpy.array(con.profile.ycalc)
|
|
588
|
+
|
|
589
|
+
# The other metrics
|
|
590
|
+
self._calculateMetrics()
|
|
591
|
+
|
|
592
|
+
# Find the parameters
|
|
593
|
+
for i, constraint in enumerate(recipe._oconstraints):
|
|
594
|
+
par = constraint.par
|
|
595
|
+
loc = con._locateManagedObject(par)
|
|
596
|
+
if loc:
|
|
597
|
+
self.conlocs.append(loc)
|
|
598
|
+
self.convals.append(fitres.convals[i])
|
|
599
|
+
self.conunc.append(fitres.conunc[i])
|
|
600
|
+
|
|
601
|
+
return
|
|
602
|
+
|
|
603
|
+
# FIXME: factor rw, chi2, cumrw, cumchi2 to separate functions.
|
|
604
|
+
def _calculateMetrics(self):
|
|
605
|
+
"""Calculate chi2 and Rw of the recipe."""
|
|
606
|
+
# We take absolute values in case the signal is complex
|
|
607
|
+
num = numpy.abs(self.y - self.ycalc)
|
|
608
|
+
y = numpy.abs(self.y)
|
|
609
|
+
chiv = num / self.dy
|
|
610
|
+
self.cumchi2 = numpy.cumsum(chiv**2)
|
|
611
|
+
# avoid index error for empty array
|
|
612
|
+
self.chi2 = self.cumchi2[-1:].sum()
|
|
613
|
+
yw = y / self.dy
|
|
614
|
+
yw2tot = numpy.dot(yw, yw)
|
|
615
|
+
if yw2tot == 0.0:
|
|
616
|
+
yw2tot = 1.0
|
|
617
|
+
self.cumrw = numpy.sqrt(self.cumchi2 / yw2tot)
|
|
618
|
+
# avoid index error for empty array
|
|
619
|
+
self.rw = self.cumrw[-1:].sum()
|
|
620
|
+
return
|
|
621
|
+
|
|
622
|
+
|
|
623
|
+
# End class ContributionResults
|
|
624
|
+
|
|
625
|
+
|
|
626
|
+
def resultsDictionary(results):
|
|
627
|
+
"""Get dictionary of results from file.
|
|
628
|
+
|
|
629
|
+
This reads the results from file and stores them in a dictionary to be
|
|
630
|
+
returned to the caller. The dictionary may contain non-result entries.
|
|
631
|
+
|
|
632
|
+
results -- An open file-like object, name of a file that contains
|
|
633
|
+
results from FitResults or a string containing fit results.
|
|
634
|
+
"""
|
|
635
|
+
resstr = inputToString(results)
|
|
636
|
+
|
|
637
|
+
rx = {
|
|
638
|
+
"f": r"[+-]? *(?:\d+(?:\.\d*)?|\.\d+)(?:[eE][+-]?\d+)?",
|
|
639
|
+
"n": r"[a-zA-Z_]\w*",
|
|
640
|
+
}
|
|
641
|
+
pat = r"(%(n)s)\s+(%(f)s)" % rx
|
|
642
|
+
|
|
643
|
+
matches = re.findall(pat, resstr)
|
|
644
|
+
# Prefer the first match
|
|
645
|
+
matches.reverse()
|
|
646
|
+
mpairs = dict(matches)
|
|
647
|
+
return mpairs
|
|
648
|
+
|
|
649
|
+
|
|
650
|
+
def initializeRecipe(recipe, results):
|
|
651
|
+
"""Initialize the variables of a recipe from a results file.
|
|
652
|
+
|
|
653
|
+
This reads the results from file and initializes any variables (fixed or
|
|
654
|
+
free) in the recipe to the results values. Note that the recipe has to be
|
|
655
|
+
configured, with variables. This does not reconstruct a FitRecipe.
|
|
656
|
+
|
|
657
|
+
recipe -- A configured recipe with variables
|
|
658
|
+
results -- An open file-like object, name of a file that contains
|
|
659
|
+
results from FitResults or a string containing fit results.
|
|
660
|
+
"""
|
|
661
|
+
|
|
662
|
+
mpairs = resultsDictionary(results)
|
|
663
|
+
if not mpairs:
|
|
664
|
+
raise AttributeError("Cannot find results")
|
|
665
|
+
|
|
666
|
+
# Get variable names
|
|
667
|
+
names = recipe._parameters.keys()
|
|
668
|
+
for vname in names:
|
|
669
|
+
value = mpairs.get(vname)
|
|
670
|
+
if value is not None:
|
|
671
|
+
var = recipe.get(vname)
|
|
672
|
+
var.value = float(value)
|
|
673
|
+
|
|
674
|
+
return
|