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,826 @@
|
|
|
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
|
+
"""FitRecipe class.
|
|
16
|
+
|
|
17
|
+
FitRecipes organize FitContributions, variables, Restraints and
|
|
18
|
+
Constraints to create a recipe of the system you wish to optimize. From
|
|
19
|
+
the client's perspective, the FitRecipe is a residual calculator. The
|
|
20
|
+
residual method does the work of updating variable values, which get
|
|
21
|
+
propagated to the Parameters of the underlying FitContributions via the
|
|
22
|
+
variables and Constraints. This class needs no special knowledge of the
|
|
23
|
+
type of FitContribution or data being used. Thus, it is suitable for
|
|
24
|
+
combining residual equations from various types of refinements into a
|
|
25
|
+
single residual.
|
|
26
|
+
|
|
27
|
+
Variables added to a FitRecipe can be tagged with string identifiers.
|
|
28
|
+
Variables can be later retrieved or manipulated by tag. The tag name
|
|
29
|
+
"__fixed" is reserved.
|
|
30
|
+
|
|
31
|
+
See the examples in the documentation for how to create an optimization
|
|
32
|
+
problem using FitRecipe.
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
__all__ = ["FitRecipe"]
|
|
36
|
+
|
|
37
|
+
from collections import OrderedDict
|
|
38
|
+
|
|
39
|
+
import six
|
|
40
|
+
from numpy import array, concatenate, dot, sqrt
|
|
41
|
+
|
|
42
|
+
from diffpy.srfit.fitbase.fithook import PrintFitHook
|
|
43
|
+
from diffpy.srfit.fitbase.parameter import ParameterProxy
|
|
44
|
+
from diffpy.srfit.fitbase.recipeorganizer import RecipeOrganizer
|
|
45
|
+
from diffpy.srfit.interface import _fitrecipe_interface
|
|
46
|
+
from diffpy.srfit.util.tagmanager import TagManager
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class FitRecipe(_fitrecipe_interface, RecipeOrganizer):
|
|
50
|
+
"""FitRecipe class.
|
|
51
|
+
|
|
52
|
+
Attributes
|
|
53
|
+
name -- A name for this FitRecipe.
|
|
54
|
+
fithooks -- List of FitHook instances that can pass information out
|
|
55
|
+
of the system during a refinement. By default, the is
|
|
56
|
+
populated by a PrintFitHook instance.
|
|
57
|
+
_constraints -- A dictionary of Constraints, indexed by the constrained
|
|
58
|
+
Parameter. Constraints can be added using the
|
|
59
|
+
'constrain' method.
|
|
60
|
+
_oconstraints -- An ordered list of the constraints from this and all
|
|
61
|
+
sub-components.
|
|
62
|
+
_calculators -- A managed dictionary of Calculators.
|
|
63
|
+
_contributions -- A managed OrderedDict of FitContributions.
|
|
64
|
+
_parameters -- A managed OrderedDict of parameters (in this case the
|
|
65
|
+
parameters are varied).
|
|
66
|
+
_parsets -- A managed dictionary of ParameterSets.
|
|
67
|
+
_eqfactory -- A diffpy.srfit.equation.builder.EquationFactory
|
|
68
|
+
instance that is used to create constraints and
|
|
69
|
+
restraints from string
|
|
70
|
+
_restraintlist -- A list of restraints from this and all sub-components.
|
|
71
|
+
_restraints -- A set of Restraints. Restraints can be added using the
|
|
72
|
+
'restrain' or 'confine' methods.
|
|
73
|
+
_ready -- A flag indicating if all attributes are ready for the
|
|
74
|
+
calculation.
|
|
75
|
+
_tagmanager -- A TagManager instance for managing tags on Parameters.
|
|
76
|
+
_weights -- List of weighing factors for each FitContribution. The
|
|
77
|
+
weights are multiplied by the residual of the
|
|
78
|
+
FitContribution when determining the overall residual.
|
|
79
|
+
_fixedtag -- "__fixed", used for tagging variables as fixed. Don't
|
|
80
|
+
use this tag unless you want issues.
|
|
81
|
+
|
|
82
|
+
Properties
|
|
83
|
+
names -- Variable names (read only). See getNames.
|
|
84
|
+
values -- Variable values (read only). See getValues.
|
|
85
|
+
fixednames -- Names of the fixed refinable variables (read only).
|
|
86
|
+
fixedvalues -- Values of the fixed refinable variables (read only).
|
|
87
|
+
bounds -- Bounds on parameters (read only). See getBounds.
|
|
88
|
+
bounds2 -- Bounds on parameters (read only). See getBounds2.
|
|
89
|
+
"""
|
|
90
|
+
|
|
91
|
+
fixednames = property(
|
|
92
|
+
lambda self: [
|
|
93
|
+
v.name
|
|
94
|
+
for v in self._parameters.values()
|
|
95
|
+
if not (self.isFree(v) or self.isConstrained(v))
|
|
96
|
+
],
|
|
97
|
+
doc="names of the fixed refinable variables",
|
|
98
|
+
)
|
|
99
|
+
fixedvalues = property(
|
|
100
|
+
lambda self: array(
|
|
101
|
+
[
|
|
102
|
+
v.value
|
|
103
|
+
for v in self._parameters.values()
|
|
104
|
+
if not (self.isFree(v) or self.isConstrained(v))
|
|
105
|
+
]
|
|
106
|
+
),
|
|
107
|
+
doc="values of the fixed refinable variables",
|
|
108
|
+
)
|
|
109
|
+
bounds = property(lambda self: self.getBounds())
|
|
110
|
+
bounds2 = property(lambda self: self.getBounds2())
|
|
111
|
+
|
|
112
|
+
def __init__(self, name="fit"):
|
|
113
|
+
"""Initialization."""
|
|
114
|
+
RecipeOrganizer.__init__(self, name)
|
|
115
|
+
self.fithooks = []
|
|
116
|
+
self.pushFitHook(PrintFitHook())
|
|
117
|
+
self._restraintlist = []
|
|
118
|
+
self._oconstraints = []
|
|
119
|
+
self._ready = False
|
|
120
|
+
self._fixedtag = "__fixed"
|
|
121
|
+
|
|
122
|
+
self._weights = []
|
|
123
|
+
self._tagmanager = TagManager()
|
|
124
|
+
|
|
125
|
+
self._parsets = {}
|
|
126
|
+
self._manage(self._parsets)
|
|
127
|
+
|
|
128
|
+
self._contributions = OrderedDict()
|
|
129
|
+
self._manage(self._contributions)
|
|
130
|
+
|
|
131
|
+
return
|
|
132
|
+
|
|
133
|
+
def pushFitHook(self, fithook, index=None):
|
|
134
|
+
"""Add a FitHook to be called within the residual method.
|
|
135
|
+
|
|
136
|
+
The hook is an object for reporting updates, or more fundamentally,
|
|
137
|
+
passing information out of the system during a refinement. See the
|
|
138
|
+
diffpy.srfit.fitbase.fithook.FitHook class for the required interface.
|
|
139
|
+
Added FitHooks will be called sequentially during refinement.
|
|
140
|
+
|
|
141
|
+
fithook -- FitHook instance to add to the sequence
|
|
142
|
+
index -- Index for inserting fithook into the list of fit hooks. If
|
|
143
|
+
this is None (default), the fithook is added to the end.
|
|
144
|
+
"""
|
|
145
|
+
if index is None:
|
|
146
|
+
index = len(self.fithooks)
|
|
147
|
+
self.fithooks.insert(index, fithook)
|
|
148
|
+
# Make sure the added FitHook gets its reset method called.
|
|
149
|
+
self._updateConfiguration()
|
|
150
|
+
return
|
|
151
|
+
|
|
152
|
+
def popFitHook(self, fithook=None, index=-1):
|
|
153
|
+
"""Remove a FitHook by index or reference.
|
|
154
|
+
|
|
155
|
+
fithook -- FitHook instance to remove from the sequence. If this is
|
|
156
|
+
None (default), default to index.
|
|
157
|
+
index -- Index of FitHook instance to remove (default -1).
|
|
158
|
+
|
|
159
|
+
Raises ValueError if fithook is not None, but is not present in the
|
|
160
|
+
sequence.
|
|
161
|
+
Raises IndexError if the sequence is empty or index is out of range.
|
|
162
|
+
"""
|
|
163
|
+
if fithook is not None:
|
|
164
|
+
self.fithooks.remove(fithook)
|
|
165
|
+
return
|
|
166
|
+
self.fithook.remove(index)
|
|
167
|
+
return
|
|
168
|
+
|
|
169
|
+
def getFitHooks(self):
|
|
170
|
+
"""Get the sequence of FitHook instances."""
|
|
171
|
+
return self.fithooks[:]
|
|
172
|
+
|
|
173
|
+
def clearFitHooks(self):
|
|
174
|
+
"""Clear the FitHook sequence."""
|
|
175
|
+
del self.fithooks[:]
|
|
176
|
+
return
|
|
177
|
+
|
|
178
|
+
def addContribution(self, con, weight=1.0):
|
|
179
|
+
"""Add a FitContribution to the FitRecipe.
|
|
180
|
+
|
|
181
|
+
con -- The FitContribution to be stored.
|
|
182
|
+
|
|
183
|
+
Raises ValueError if the FitContribution has no name
|
|
184
|
+
Raises ValueError if the FitContribution has the same name as some
|
|
185
|
+
other managed object.
|
|
186
|
+
"""
|
|
187
|
+
self._addObject(con, self._contributions, True)
|
|
188
|
+
self._weights.append(weight)
|
|
189
|
+
return
|
|
190
|
+
|
|
191
|
+
def setWeight(self, con, weight):
|
|
192
|
+
"""Set the weight of a FitContribution."""
|
|
193
|
+
idx = list(self._contributions.values()).index(con)
|
|
194
|
+
self._weights[idx] = weight
|
|
195
|
+
return
|
|
196
|
+
|
|
197
|
+
def addParameterSet(self, parset):
|
|
198
|
+
"""Add a ParameterSet to the hierarchy.
|
|
199
|
+
|
|
200
|
+
parset -- The ParameterSet to be stored.
|
|
201
|
+
|
|
202
|
+
Raises ValueError if the ParameterSet has no name.
|
|
203
|
+
Raises ValueError if the ParameterSet has the same name as some other
|
|
204
|
+
managed object.
|
|
205
|
+
"""
|
|
206
|
+
self._addObject(parset, self._parsets, True)
|
|
207
|
+
return
|
|
208
|
+
|
|
209
|
+
def removeParameterSet(self, parset):
|
|
210
|
+
"""Remove a ParameterSet from the hierarchy.
|
|
211
|
+
|
|
212
|
+
Raises ValueError if parset is not managed by this object.
|
|
213
|
+
"""
|
|
214
|
+
self._removeObject(parset, self._parsets)
|
|
215
|
+
return
|
|
216
|
+
|
|
217
|
+
def residual(self, p=[]):
|
|
218
|
+
"""Calculate the vector residual to be optimized.
|
|
219
|
+
|
|
220
|
+
Arguments
|
|
221
|
+
p -- The list of current variable values, provided in the same order
|
|
222
|
+
as the '_parameters' list. If p is an empty iterable (default),
|
|
223
|
+
then it is assumed that the parameters have already been
|
|
224
|
+
updated in some other way, and the explicit update within this
|
|
225
|
+
function is skipped.
|
|
226
|
+
|
|
227
|
+
The residual is by default the weighted concatenation of each
|
|
228
|
+
FitContribution's residual, plus the value of each restraint. The array
|
|
229
|
+
returned, denoted chiv, is such that
|
|
230
|
+
dot(chiv, chiv) = chi^2 + restraints.
|
|
231
|
+
"""
|
|
232
|
+
|
|
233
|
+
# Prepare, if necessary
|
|
234
|
+
self._prepare()
|
|
235
|
+
|
|
236
|
+
for fithook in self.fithooks:
|
|
237
|
+
fithook.precall(self)
|
|
238
|
+
|
|
239
|
+
# Update the variable parameters.
|
|
240
|
+
self._applyValues(p)
|
|
241
|
+
|
|
242
|
+
# Update the constraints. These are ordered such that the list only
|
|
243
|
+
# needs to be cycled once.
|
|
244
|
+
for con in self._oconstraints:
|
|
245
|
+
con.update()
|
|
246
|
+
|
|
247
|
+
# Calculate the bare chiv
|
|
248
|
+
chiv = concatenate(
|
|
249
|
+
[
|
|
250
|
+
wi * ci.residual().flatten()
|
|
251
|
+
for wi, ci in zip(self._weights, self._contributions.values())
|
|
252
|
+
]
|
|
253
|
+
)
|
|
254
|
+
|
|
255
|
+
# Calculate the point-average chi^2
|
|
256
|
+
w = dot(chiv, chiv) / len(chiv)
|
|
257
|
+
# Now we must append the restraints
|
|
258
|
+
penalties = [sqrt(res.penalty(w)) for res in self._restraintlist]
|
|
259
|
+
chiv = concatenate([chiv, penalties])
|
|
260
|
+
|
|
261
|
+
for fithook in self.fithooks:
|
|
262
|
+
fithook.postcall(self, chiv)
|
|
263
|
+
|
|
264
|
+
return chiv
|
|
265
|
+
|
|
266
|
+
def scalarResidual(self, p=[]):
|
|
267
|
+
"""Calculate the scalar residual to be optimized.
|
|
268
|
+
|
|
269
|
+
Arguments
|
|
270
|
+
p -- The list of current variable values, provided in the same order
|
|
271
|
+
as the '_parameters' list. If p is an empty iterable (default),
|
|
272
|
+
then it is assumed that the parameters have already been
|
|
273
|
+
updated in some other way, and the explicit update within this
|
|
274
|
+
function is skipped.
|
|
275
|
+
|
|
276
|
+
The residual is by default the weighted concatenation of each
|
|
277
|
+
FitContribution's residual, plus the value of each restraint. The array
|
|
278
|
+
returned, denoted chiv, is such that
|
|
279
|
+
dot(chiv, chiv) = chi^2 + restraints.
|
|
280
|
+
"""
|
|
281
|
+
chiv = self.residual(p)
|
|
282
|
+
return dot(chiv, chiv)
|
|
283
|
+
|
|
284
|
+
def __call__(self, p=[]):
|
|
285
|
+
"""Same as scalarResidual method."""
|
|
286
|
+
return self.scalarResidual(p)
|
|
287
|
+
|
|
288
|
+
def _prepare(self):
|
|
289
|
+
"""Prepare for the residual calculation, if necessary.
|
|
290
|
+
|
|
291
|
+
This will prepare the data attributes to be used in the residual
|
|
292
|
+
calculation.
|
|
293
|
+
|
|
294
|
+
This updates the local restraints with those of the
|
|
295
|
+
contributions.
|
|
296
|
+
|
|
297
|
+
Raises AttributeError if there are variables without a value.
|
|
298
|
+
"""
|
|
299
|
+
|
|
300
|
+
# Only prepare if the configuration has changed within the recipe
|
|
301
|
+
# hierarchy.
|
|
302
|
+
if self._ready:
|
|
303
|
+
return
|
|
304
|
+
|
|
305
|
+
# Inform the fit hooks that we're updating things
|
|
306
|
+
for fithook in self.fithooks:
|
|
307
|
+
fithook.reset(self)
|
|
308
|
+
|
|
309
|
+
# Check Profiles
|
|
310
|
+
self.__verifyProfiles()
|
|
311
|
+
|
|
312
|
+
# Check parameters
|
|
313
|
+
self.__verifyParameters()
|
|
314
|
+
|
|
315
|
+
# Update constraints and restraints.
|
|
316
|
+
self.__collectConstraintsAndRestraints()
|
|
317
|
+
|
|
318
|
+
# We do this here so that the calculations that take place during the
|
|
319
|
+
# validation use the most current values of the parameters. In most
|
|
320
|
+
# cases, this will save us from recalculating them later.
|
|
321
|
+
for con in self._oconstraints:
|
|
322
|
+
con.update()
|
|
323
|
+
|
|
324
|
+
# Validate!
|
|
325
|
+
self._validate()
|
|
326
|
+
|
|
327
|
+
self._ready = True
|
|
328
|
+
|
|
329
|
+
return
|
|
330
|
+
|
|
331
|
+
def __verifyProfiles(self):
|
|
332
|
+
"""Verify that each FitContribution has a Profile."""
|
|
333
|
+
# Check for profile values
|
|
334
|
+
for con in self._contributions.values():
|
|
335
|
+
if con.profile is None:
|
|
336
|
+
m = "FitContribution '%s' does not have a Profile" % con.name
|
|
337
|
+
raise AttributeError(m)
|
|
338
|
+
if (
|
|
339
|
+
con.profile.x is None
|
|
340
|
+
or con.profile.y is None
|
|
341
|
+
or con.profile.dy is None
|
|
342
|
+
):
|
|
343
|
+
|
|
344
|
+
m = "Profile for '%s' is missing data" % con.name
|
|
345
|
+
raise AttributeError(m)
|
|
346
|
+
return
|
|
347
|
+
|
|
348
|
+
def __verifyParameters(self):
|
|
349
|
+
"""Verify that all Parameters have values."""
|
|
350
|
+
|
|
351
|
+
# Get all parameters with a value of None
|
|
352
|
+
badpars = []
|
|
353
|
+
for par in self.iterPars():
|
|
354
|
+
try:
|
|
355
|
+
par.getValue()
|
|
356
|
+
except ValueError:
|
|
357
|
+
badpars.append(par)
|
|
358
|
+
|
|
359
|
+
# Get the bad names
|
|
360
|
+
badnames = []
|
|
361
|
+
for par in badpars:
|
|
362
|
+
objlist = self._locateManagedObject(par)
|
|
363
|
+
names = [obj.name for obj in objlist]
|
|
364
|
+
badnames.append(".".join(names))
|
|
365
|
+
|
|
366
|
+
# Construct an error message, if necessary
|
|
367
|
+
m = ""
|
|
368
|
+
if len(badnames) == 1:
|
|
369
|
+
m = "%s is not defined or needs an initial value" % badnames[0]
|
|
370
|
+
elif len(badnames) > 0:
|
|
371
|
+
s1 = ",".join(badnames[:-1])
|
|
372
|
+
s2 = badnames[-1]
|
|
373
|
+
m = "%s and %s are not defined or need initial values" % (s1, s2)
|
|
374
|
+
|
|
375
|
+
if m:
|
|
376
|
+
raise AttributeError(m)
|
|
377
|
+
|
|
378
|
+
return
|
|
379
|
+
|
|
380
|
+
def __collectConstraintsAndRestraints(self):
|
|
381
|
+
"""Collect the Constraints and Restraints from subobjects."""
|
|
382
|
+
from functools import cmp_to_key
|
|
383
|
+
from itertools import chain
|
|
384
|
+
|
|
385
|
+
rset = set(self._restraints)
|
|
386
|
+
cdict = {}
|
|
387
|
+
|
|
388
|
+
for org in chain(self._contributions.values(), self._parsets.values()):
|
|
389
|
+
rset.update(org._getRestraints())
|
|
390
|
+
cdict.update(org._getConstraints())
|
|
391
|
+
cdict.update(self._constraints)
|
|
392
|
+
|
|
393
|
+
# The order of the restraint list does not matter
|
|
394
|
+
self._restraintlist = list(rset)
|
|
395
|
+
|
|
396
|
+
# Reorder the constraints. Constraints are ordered such that a given
|
|
397
|
+
# constraint is placed before its dependencies.
|
|
398
|
+
self._oconstraints = list(cdict.values())
|
|
399
|
+
|
|
400
|
+
# Create a depth-1 map of the constraint dependencies
|
|
401
|
+
depmap = {}
|
|
402
|
+
for con in self._oconstraints:
|
|
403
|
+
depmap[con] = set()
|
|
404
|
+
# Now check the constraint's equation for constrained arguments
|
|
405
|
+
for arg in con.eq.args:
|
|
406
|
+
if arg in cdict:
|
|
407
|
+
depmap[con].add(cdict[arg])
|
|
408
|
+
|
|
409
|
+
# Turn the dependency map into multi-level map.
|
|
410
|
+
def _extendDeps(con):
|
|
411
|
+
deps = set(depmap[con])
|
|
412
|
+
for dep in depmap[con]:
|
|
413
|
+
deps.update(_extendDeps(dep))
|
|
414
|
+
|
|
415
|
+
return deps
|
|
416
|
+
|
|
417
|
+
for con in depmap:
|
|
418
|
+
depmap[con] = _extendDeps(con)
|
|
419
|
+
|
|
420
|
+
# Now sort the constraints based on the dependency map.
|
|
421
|
+
def cmp(x, y):
|
|
422
|
+
# x == y if neither of them have dependencies
|
|
423
|
+
if not depmap[x] and not depmap[y]:
|
|
424
|
+
return 0
|
|
425
|
+
# x > y if y is a dependency of x
|
|
426
|
+
# x > y if y has no dependencies
|
|
427
|
+
if y in depmap[x] or not depmap[y]:
|
|
428
|
+
return 1
|
|
429
|
+
# x < y if x is a dependency of y
|
|
430
|
+
# x < y if x has no dependencies
|
|
431
|
+
if x in depmap[y] or not depmap[x]:
|
|
432
|
+
return -1
|
|
433
|
+
# If there are dependencies, but there is no relationship, the
|
|
434
|
+
# constraints are equivalent
|
|
435
|
+
return 0
|
|
436
|
+
|
|
437
|
+
self._oconstraints.sort(key=cmp_to_key(cmp))
|
|
438
|
+
|
|
439
|
+
return
|
|
440
|
+
|
|
441
|
+
# Variable manipulation
|
|
442
|
+
|
|
443
|
+
def addVar(
|
|
444
|
+
self, par, value=None, name=None, fixed=False, tag=None, tags=[]
|
|
445
|
+
):
|
|
446
|
+
"""Add a variable to be refined.
|
|
447
|
+
|
|
448
|
+
par -- A Parameter that will be varied during a fit.
|
|
449
|
+
value -- An initial value for the variable. If this is None
|
|
450
|
+
(default), then the current value of par will be used.
|
|
451
|
+
name -- A name for this variable. If name is None (default), then
|
|
452
|
+
the name of the parameter will be used.
|
|
453
|
+
fixed -- Fix the variable so that it does not vary (default False).
|
|
454
|
+
tag -- A tag for the variable. This can be used to retrieve, fix
|
|
455
|
+
or free variables by tag (default None). Note that a
|
|
456
|
+
variable is automatically tagged with its name and "all".
|
|
457
|
+
tags -- A list of tags (default []). Both tag and tags can be
|
|
458
|
+
applied.
|
|
459
|
+
|
|
460
|
+
Returns the ParameterProxy (variable) for the passed Parameter.
|
|
461
|
+
|
|
462
|
+
Raises ValueError if the name of the variable is already taken by
|
|
463
|
+
another managed object.
|
|
464
|
+
Raises ValueError if par is constant.
|
|
465
|
+
Raises ValueError if par is constrained.
|
|
466
|
+
"""
|
|
467
|
+
name = name or par.name
|
|
468
|
+
|
|
469
|
+
if par.const:
|
|
470
|
+
raise ValueError("The parameter '%s' is constant" % par)
|
|
471
|
+
|
|
472
|
+
if par.constrained:
|
|
473
|
+
raise ValueError("The parameter '%s' is constrained" % par)
|
|
474
|
+
|
|
475
|
+
var = ParameterProxy(name, par)
|
|
476
|
+
if value is not None:
|
|
477
|
+
var.setValue(value)
|
|
478
|
+
|
|
479
|
+
self._addParameter(var)
|
|
480
|
+
|
|
481
|
+
if fixed:
|
|
482
|
+
self.fix(var)
|
|
483
|
+
|
|
484
|
+
# Tag with passed tags and by name
|
|
485
|
+
self._tagmanager.tag(var, var.name)
|
|
486
|
+
self._tagmanager.tag(var, "all")
|
|
487
|
+
self._tagmanager.tag(var, *tags)
|
|
488
|
+
if tag is not None:
|
|
489
|
+
self._tagmanager.tag(var, tag)
|
|
490
|
+
return var
|
|
491
|
+
|
|
492
|
+
def delVar(self, var):
|
|
493
|
+
"""Remove a variable.
|
|
494
|
+
|
|
495
|
+
Note that constraints and restraints involving the variable are not
|
|
496
|
+
modified.
|
|
497
|
+
|
|
498
|
+
var -- A variable of the FitRecipe.
|
|
499
|
+
|
|
500
|
+
Raises ValueError if var is not part of the FitRecipe.
|
|
501
|
+
"""
|
|
502
|
+
|
|
503
|
+
self._removeParameter(var)
|
|
504
|
+
self._tagmanager.untag(var)
|
|
505
|
+
return
|
|
506
|
+
|
|
507
|
+
def __delattr__(self, name):
|
|
508
|
+
if name in self._parameters:
|
|
509
|
+
self.delVar(self._parameters[name])
|
|
510
|
+
return
|
|
511
|
+
super(FitRecipe, self).__delattr__(name)
|
|
512
|
+
return
|
|
513
|
+
|
|
514
|
+
def newVar(self, name, value=None, fixed=False, tag=None, tags=[]):
|
|
515
|
+
"""Create a new variable of the fit.
|
|
516
|
+
|
|
517
|
+
This method lets new variables be created that are not tied to a
|
|
518
|
+
Parameter. Orphan variables may cause a fit to fail, depending on the
|
|
519
|
+
optimization routine, and therefore should only be created to be used
|
|
520
|
+
in constraint or restraint equations.
|
|
521
|
+
|
|
522
|
+
name -- The name of the variable. The variable will be able to be
|
|
523
|
+
used by this name in restraint and constraint equations.
|
|
524
|
+
value -- An initial value for the variable. If this is None
|
|
525
|
+
(default), then the variable will be given the value of the
|
|
526
|
+
first non-None-valued Parameter constrained to it. If this
|
|
527
|
+
fails, an error will be thrown when 'residual' is called.
|
|
528
|
+
fixed -- Fix the variable so that it does not vary (default False).
|
|
529
|
+
The variable will still be managed by the FitRecipe.
|
|
530
|
+
tag -- A tag for the variable. This can be used to fix and free
|
|
531
|
+
variables by tag (default None). Note that a variable is
|
|
532
|
+
automatically tagged with its name and "all".
|
|
533
|
+
tags -- A list of tags (default []). Both tag and tags can be
|
|
534
|
+
applied.
|
|
535
|
+
|
|
536
|
+
Returns the new variable (Parameter instance).
|
|
537
|
+
"""
|
|
538
|
+
# This will fix the Parameter
|
|
539
|
+
var = self._newParameter(name, value)
|
|
540
|
+
|
|
541
|
+
# We may explicitly free it
|
|
542
|
+
if not fixed:
|
|
543
|
+
self.free(var)
|
|
544
|
+
|
|
545
|
+
# Tag with passed tags
|
|
546
|
+
self._tagmanager.tag(var, *tags)
|
|
547
|
+
if tag is not None:
|
|
548
|
+
self._tagmanager.tag(var, tag)
|
|
549
|
+
|
|
550
|
+
return var
|
|
551
|
+
|
|
552
|
+
def _newParameter(self, name, value, check=True):
|
|
553
|
+
"""Overloaded to tag variables.
|
|
554
|
+
|
|
555
|
+
See RecipeOrganizer._newParameter
|
|
556
|
+
"""
|
|
557
|
+
par = RecipeOrganizer._newParameter(self, name, value, check)
|
|
558
|
+
# tag this
|
|
559
|
+
self._tagmanager.tag(par, par.name)
|
|
560
|
+
self._tagmanager.tag(par, "all")
|
|
561
|
+
self.fix(par.name)
|
|
562
|
+
return par
|
|
563
|
+
|
|
564
|
+
def __getVarAndCheck(self, var):
|
|
565
|
+
"""Get the actual variable from var.
|
|
566
|
+
|
|
567
|
+
var -- A variable of the FitRecipe, or the name of a variable.
|
|
568
|
+
|
|
569
|
+
Returns the variable or None if the variable cannot be found in the
|
|
570
|
+
_parameters list.
|
|
571
|
+
"""
|
|
572
|
+
if isinstance(var, six.string_types):
|
|
573
|
+
var = self._parameters.get(var)
|
|
574
|
+
|
|
575
|
+
if var not in self._parameters.values():
|
|
576
|
+
raise ValueError("Passed variable is not part of the FitRecipe")
|
|
577
|
+
|
|
578
|
+
return var
|
|
579
|
+
|
|
580
|
+
def __getVarsFromArgs(self, *args, **kw):
|
|
581
|
+
"""Get a list of variables from passed arguments.
|
|
582
|
+
|
|
583
|
+
This method accepts string or variable arguments. An argument of
|
|
584
|
+
"all" selects all variables. Keyword arguments must be parameter
|
|
585
|
+
names, followed by a value to assign to the fixed variable. This
|
|
586
|
+
method is used by the fix and free methods.
|
|
587
|
+
|
|
588
|
+
Raises ValueError if an unknown variable, name or tag is passed,
|
|
589
|
+
or if a tag is passed in a keyword.
|
|
590
|
+
"""
|
|
591
|
+
# Process args. Each variable is tagged with its name, so this is easy.
|
|
592
|
+
strargs = set(
|
|
593
|
+
[arg for arg in args if isinstance(arg, six.string_types)]
|
|
594
|
+
)
|
|
595
|
+
varargs = set(args) - strargs
|
|
596
|
+
# Check that the tags are valid
|
|
597
|
+
alltags = set(self._tagmanager.alltags())
|
|
598
|
+
badtags = strargs - alltags
|
|
599
|
+
if badtags:
|
|
600
|
+
names = ",".join(badtags)
|
|
601
|
+
raise ValueError("Variables or tags cannot be found (%s)" % names)
|
|
602
|
+
|
|
603
|
+
# Check that variables are valid
|
|
604
|
+
allvars = set(self._parameters.values())
|
|
605
|
+
badvars = varargs - allvars
|
|
606
|
+
if badvars:
|
|
607
|
+
names = ",".join(v.name for v in badvars)
|
|
608
|
+
raise ValueError("Variables cannot be found (%s)" % names)
|
|
609
|
+
|
|
610
|
+
# Make sure that we only have parameters in kw
|
|
611
|
+
kwnames = set(kw.keys())
|
|
612
|
+
allnames = set(self._parameters.keys())
|
|
613
|
+
badkw = kwnames - allnames
|
|
614
|
+
if badkw:
|
|
615
|
+
names = ",".join(badkw)
|
|
616
|
+
raise ValueError("Tags cannot be passed as keywords (%s)" % names)
|
|
617
|
+
|
|
618
|
+
# Now get all the objects referred to in the arguments.
|
|
619
|
+
varargs |= self._tagmanager.union(*strargs)
|
|
620
|
+
varargs |= self._tagmanager.union(*kw.keys())
|
|
621
|
+
return varargs
|
|
622
|
+
|
|
623
|
+
def fix(self, *args, **kw):
|
|
624
|
+
"""Fix a parameter by reference, name or tag.
|
|
625
|
+
|
|
626
|
+
A fixed variable is not refined. Variables are free by default.
|
|
627
|
+
|
|
628
|
+
This method accepts string or variable arguments. An argument of
|
|
629
|
+
"all" selects all variables. Keyword arguments must be parameter
|
|
630
|
+
names, followed by a value to assign to the fixed variable.
|
|
631
|
+
|
|
632
|
+
Raises ValueError if an unknown Parameter, name or tag is
|
|
633
|
+
passed, or if a tag is passed in a keyword.
|
|
634
|
+
"""
|
|
635
|
+
# Check the inputs and get the variables from them
|
|
636
|
+
varargs = self.__getVarsFromArgs(*args, **kw)
|
|
637
|
+
|
|
638
|
+
# Fix all of these
|
|
639
|
+
for var in varargs:
|
|
640
|
+
self._tagmanager.tag(var, self._fixedtag)
|
|
641
|
+
|
|
642
|
+
# Set the kw values
|
|
643
|
+
for name, val in kw.items():
|
|
644
|
+
self.get(name).value = val
|
|
645
|
+
|
|
646
|
+
return
|
|
647
|
+
|
|
648
|
+
def free(self, *args, **kw):
|
|
649
|
+
"""Free a parameter by reference, name or tag.
|
|
650
|
+
|
|
651
|
+
A free variable is refined. Variables are free by default.
|
|
652
|
+
Constrained variables are not free.
|
|
653
|
+
|
|
654
|
+
This method accepts string or variable arguments. An argument of
|
|
655
|
+
"all" selects all variables. Keyword arguments must be parameter
|
|
656
|
+
names, followed by a value to assign to the fixed variable.
|
|
657
|
+
|
|
658
|
+
Raises ValueError if an unknown Parameter, name or tag is
|
|
659
|
+
passed, or if a tag is passed in a keyword.
|
|
660
|
+
"""
|
|
661
|
+
# Check the inputs and get the variables from them
|
|
662
|
+
varargs = self.__getVarsFromArgs(*args, **kw)
|
|
663
|
+
|
|
664
|
+
# Free all of these
|
|
665
|
+
for var in varargs:
|
|
666
|
+
if not var.constrained:
|
|
667
|
+
self._tagmanager.untag(var, self._fixedtag)
|
|
668
|
+
|
|
669
|
+
# Set the kw values
|
|
670
|
+
for name, val in kw.items():
|
|
671
|
+
self.get(name).value = val
|
|
672
|
+
|
|
673
|
+
return
|
|
674
|
+
|
|
675
|
+
def isFree(self, var):
|
|
676
|
+
"""Check if a variable is fixed."""
|
|
677
|
+
return not self._tagmanager.hasTags(var, self._fixedtag)
|
|
678
|
+
|
|
679
|
+
def unconstrain(self, *pars):
|
|
680
|
+
"""Unconstrain a Parameter.
|
|
681
|
+
|
|
682
|
+
This removes any constraints on a Parameter. If the Parameter is also a
|
|
683
|
+
variable of the recipe, it will be freed as well.
|
|
684
|
+
|
|
685
|
+
*pars -- The names of Parameters or Parameters to unconstrain.
|
|
686
|
+
|
|
687
|
+
|
|
688
|
+
Raises ValueError if the Parameter is not constrained.
|
|
689
|
+
"""
|
|
690
|
+
update = False
|
|
691
|
+
for par in pars:
|
|
692
|
+
if isinstance(par, six.string_types):
|
|
693
|
+
name = par
|
|
694
|
+
par = self.get(name)
|
|
695
|
+
|
|
696
|
+
if par is None:
|
|
697
|
+
raise ValueError("The parameter cannot be found")
|
|
698
|
+
|
|
699
|
+
if par in self._constraints:
|
|
700
|
+
self._constraints[par].unconstrain()
|
|
701
|
+
del self._constraints[par]
|
|
702
|
+
update = True
|
|
703
|
+
|
|
704
|
+
if par in self._parameters.values():
|
|
705
|
+
self._tagmanager.untag(par, self._fixedtag)
|
|
706
|
+
|
|
707
|
+
if update:
|
|
708
|
+
# Our configuration changed
|
|
709
|
+
self._updateConfiguration()
|
|
710
|
+
|
|
711
|
+
return
|
|
712
|
+
|
|
713
|
+
def constrain(self, par, con, ns={}):
|
|
714
|
+
"""Constrain a parameter to an equation.
|
|
715
|
+
|
|
716
|
+
Note that only one constraint can exist on a Parameter at a time.
|
|
717
|
+
|
|
718
|
+
This is overloaded to set the value of con if it represents a variable
|
|
719
|
+
and its current value is None. A constrained variable will be set as
|
|
720
|
+
fixed.
|
|
721
|
+
|
|
722
|
+
par -- The Parameter to constrain.
|
|
723
|
+
con -- A string representation of the constraint equation or a
|
|
724
|
+
Parameter to constrain to. A constraint equation must
|
|
725
|
+
consist of numpy operators and "known" Parameters.
|
|
726
|
+
Parameters are known if they are in the ns argument, or if
|
|
727
|
+
they are managed by this object.
|
|
728
|
+
ns -- A dictionary of Parameters, indexed by name, that are used
|
|
729
|
+
in the eqstr, but not part of this object (default {}).
|
|
730
|
+
|
|
731
|
+
Raises ValueError if ns uses a name that is already used for a
|
|
732
|
+
variable.
|
|
733
|
+
Raises ValueError if eqstr depends on a Parameter that is not part of
|
|
734
|
+
the FitRecipe and that is not defined in ns.
|
|
735
|
+
Raises ValueError if par is marked as constant.
|
|
736
|
+
"""
|
|
737
|
+
if isinstance(par, six.string_types):
|
|
738
|
+
name = par
|
|
739
|
+
par = self.get(name)
|
|
740
|
+
if par is None:
|
|
741
|
+
par = ns.get(name)
|
|
742
|
+
if par is None:
|
|
743
|
+
raise ValueError("The parameter '%s' cannot be found" % name)
|
|
744
|
+
|
|
745
|
+
if con in self._parameters.keys():
|
|
746
|
+
con = self._parameters[con]
|
|
747
|
+
|
|
748
|
+
if par.const:
|
|
749
|
+
raise ValueError("The parameter '%s' is constant" % par)
|
|
750
|
+
|
|
751
|
+
# This will pass the value of a constrained parameter to the initial
|
|
752
|
+
# value of a parameter constraint.
|
|
753
|
+
if con in self._parameters.values():
|
|
754
|
+
val = con.getValue()
|
|
755
|
+
if val is None:
|
|
756
|
+
val = par.getValue()
|
|
757
|
+
con.setValue(val)
|
|
758
|
+
|
|
759
|
+
if par in self._parameters.values():
|
|
760
|
+
self.fix(par)
|
|
761
|
+
|
|
762
|
+
RecipeOrganizer.constrain(self, par, con, ns)
|
|
763
|
+
return
|
|
764
|
+
|
|
765
|
+
def getValues(self):
|
|
766
|
+
"""Get the current values of the variables in a list."""
|
|
767
|
+
return array(
|
|
768
|
+
[v.value for v in self._parameters.values() if self.isFree(v)]
|
|
769
|
+
)
|
|
770
|
+
|
|
771
|
+
def getNames(self):
|
|
772
|
+
"""Get the names of the variables in a list."""
|
|
773
|
+
return [v.name for v in self._parameters.values() if self.isFree(v)]
|
|
774
|
+
|
|
775
|
+
def getBounds(self):
|
|
776
|
+
"""Get the bounds on variables in a list.
|
|
777
|
+
|
|
778
|
+
Returns a list of (lb, ub) pairs, where lb is the lower bound
|
|
779
|
+
and ub is the upper bound.
|
|
780
|
+
"""
|
|
781
|
+
return [v.bounds for v in self._parameters.values() if self.isFree(v)]
|
|
782
|
+
|
|
783
|
+
def getBounds2(self):
|
|
784
|
+
"""Get the bounds on variables in two lists.
|
|
785
|
+
|
|
786
|
+
Returns lower- and upper-bound lists of variable bounds.
|
|
787
|
+
"""
|
|
788
|
+
bounds = self.getBounds()
|
|
789
|
+
lb = array([b[0] for b in bounds])
|
|
790
|
+
ub = array([b[1] for b in bounds])
|
|
791
|
+
return lb, ub
|
|
792
|
+
|
|
793
|
+
def boundsToRestraints(self, sig=1, scaled=False):
|
|
794
|
+
"""Turn all bounded parameters into restraints.
|
|
795
|
+
|
|
796
|
+
The bounds become limits on the restraint.
|
|
797
|
+
|
|
798
|
+
sig -- The uncertainty on the bounds (scalar or iterable,
|
|
799
|
+
default 1).
|
|
800
|
+
scaled -- Scale the restraints, see restrain.
|
|
801
|
+
"""
|
|
802
|
+
pars = self._parameters.values()
|
|
803
|
+
if not hasattr(sig, "__iter__"):
|
|
804
|
+
sig = [sig] * len(pars)
|
|
805
|
+
for par, x in zip(pars, sig):
|
|
806
|
+
self.restrain(
|
|
807
|
+
par, par.bounds[0], par.bounds[1], sig=x, scaled=scaled
|
|
808
|
+
)
|
|
809
|
+
return
|
|
810
|
+
|
|
811
|
+
def _applyValues(self, p):
|
|
812
|
+
"""Apply variable values to the variables."""
|
|
813
|
+
if len(p) == 0:
|
|
814
|
+
return
|
|
815
|
+
vargen = (v for v in self._parameters.values() if self.isFree(v))
|
|
816
|
+
for var, pval in zip(vargen, p):
|
|
817
|
+
var.setValue(pval)
|
|
818
|
+
return
|
|
819
|
+
|
|
820
|
+
def _updateConfiguration(self):
|
|
821
|
+
"""Notify RecipeContainers in hierarchy of configuration change."""
|
|
822
|
+
self._ready = False
|
|
823
|
+
return
|
|
824
|
+
|
|
825
|
+
|
|
826
|
+
# End of file
|