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,1074 @@
|
|
|
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
|
+
"""Base classes and tools for constructing a FitRecipe.
|
|
16
|
+
|
|
17
|
+
RecipeContainer is the base class for organizing Parameters, and other
|
|
18
|
+
RecipeContainers. RecipeOrganizer is an extended RecipeContainer that
|
|
19
|
+
incorporates equation building, constraints and Restraints.
|
|
20
|
+
equationFromString creates an Equation instance from a string.
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
__all__ = ["RecipeContainer", "RecipeOrganizer", "equationFromString"]
|
|
24
|
+
|
|
25
|
+
import re
|
|
26
|
+
from collections import OrderedDict
|
|
27
|
+
from functools import partial
|
|
28
|
+
from itertools import chain, groupby
|
|
29
|
+
|
|
30
|
+
import six
|
|
31
|
+
from numpy import inf
|
|
32
|
+
|
|
33
|
+
from diffpy.srfit.equation import Equation
|
|
34
|
+
from diffpy.srfit.equation.builder import EquationFactory
|
|
35
|
+
from diffpy.srfit.fitbase.configurable import Configurable
|
|
36
|
+
from diffpy.srfit.fitbase.constraint import Constraint
|
|
37
|
+
from diffpy.srfit.fitbase.parameter import Parameter
|
|
38
|
+
from diffpy.srfit.fitbase.restraint import Restraint
|
|
39
|
+
from diffpy.srfit.fitbase.validatable import Validatable
|
|
40
|
+
from diffpy.srfit.interface import _recipeorganizer_interface
|
|
41
|
+
from diffpy.srfit.util import _DASHEDLINE
|
|
42
|
+
from diffpy.srfit.util import sortKeyForNumericString as numstr
|
|
43
|
+
from diffpy.srfit.util.nameutils import validateName
|
|
44
|
+
from diffpy.srfit.util.observable import Observable
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class RecipeContainer(Observable, Configurable, Validatable):
|
|
48
|
+
"""Base class for organizing pieces of a FitRecipe.
|
|
49
|
+
|
|
50
|
+
RecipeContainers are hierarchical organizations of Parameters and other
|
|
51
|
+
RecipeContainers. This class provides attribute-access to these contained
|
|
52
|
+
objects. Parameters and other RecipeContainers can be found within the
|
|
53
|
+
hierarchy with the _locateManagedObject method.
|
|
54
|
+
|
|
55
|
+
A RecipeContainer can manage dictionaries for that store various objects.
|
|
56
|
+
These dictionaries can be added to the RecipeContainer using the _manage
|
|
57
|
+
method. RecipeContainer methods that add, remove or retrieve objects will
|
|
58
|
+
work with any managed dictionary. This makes it easy to add new types of
|
|
59
|
+
objects to be contained by a RecipeContainer. By default, the
|
|
60
|
+
RecipeContainer is configured to manage an OrderedDict of Parameter
|
|
61
|
+
objects.
|
|
62
|
+
|
|
63
|
+
RecipeContainer is an Observable, and observes its managed objects and
|
|
64
|
+
Parameters. This allows hierarchical calculation elements, such as
|
|
65
|
+
ProfileGenerator, to detect changes in Parameters and Restraints on which
|
|
66
|
+
it may depend.
|
|
67
|
+
|
|
68
|
+
Attributes
|
|
69
|
+
name -- A name for this RecipeContainer. Names should be unique
|
|
70
|
+
within a RecipeContainer and should be valid attribute
|
|
71
|
+
names.
|
|
72
|
+
_parameters -- A managed OrderedDict of contained Parameters.
|
|
73
|
+
__managed -- A list of managed dictionaries. This is used for
|
|
74
|
+
attribute access, addition and removal.
|
|
75
|
+
_configobjs -- A set of configurable objects that must know of
|
|
76
|
+
configuration changes within this object.
|
|
77
|
+
|
|
78
|
+
Properties
|
|
79
|
+
names -- Variable names (read only). See getNames.
|
|
80
|
+
values -- Variable values (read only). See getValues.
|
|
81
|
+
"""
|
|
82
|
+
|
|
83
|
+
names = property(lambda self: self.getNames())
|
|
84
|
+
values = property(lambda self: self.getValues())
|
|
85
|
+
|
|
86
|
+
def __init__(self, name):
|
|
87
|
+
Observable.__init__(self)
|
|
88
|
+
Configurable.__init__(self)
|
|
89
|
+
validateName(name)
|
|
90
|
+
self.name = name
|
|
91
|
+
self._parameters = OrderedDict()
|
|
92
|
+
|
|
93
|
+
self.__managed = []
|
|
94
|
+
self._manage(self._parameters)
|
|
95
|
+
|
|
96
|
+
return
|
|
97
|
+
|
|
98
|
+
def _manage(self, d):
|
|
99
|
+
"""Manage a dictionary of objects.
|
|
100
|
+
|
|
101
|
+
This adds the dictionary to the __managed list. Dictionaries in
|
|
102
|
+
__managed are used for attribute access, addition, and removal.
|
|
103
|
+
"""
|
|
104
|
+
self.__managed.append(d)
|
|
105
|
+
return
|
|
106
|
+
|
|
107
|
+
def _iterManaged(self):
|
|
108
|
+
"""Get iterator over managed objects."""
|
|
109
|
+
return chain(*(d.values() for d in self.__managed))
|
|
110
|
+
|
|
111
|
+
def iterPars(self, pattern="", recurse=True):
|
|
112
|
+
"""Iterate over the Parameters contained in this object.
|
|
113
|
+
|
|
114
|
+
Parameters
|
|
115
|
+
----------
|
|
116
|
+
pattern : str
|
|
117
|
+
Iterate over parameters with names matching this regular
|
|
118
|
+
expression (all parameters by default).
|
|
119
|
+
recurse : bool
|
|
120
|
+
Recurse into managed objects when True (default).
|
|
121
|
+
"""
|
|
122
|
+
regexp = re.compile(pattern)
|
|
123
|
+
for par in list(self._parameters.values()):
|
|
124
|
+
if regexp.search(par.name):
|
|
125
|
+
yield par
|
|
126
|
+
if not recurse:
|
|
127
|
+
return
|
|
128
|
+
# Iterate over objects within the managed dictionaries.
|
|
129
|
+
managed = self.__managed[:]
|
|
130
|
+
managed.remove(self._parameters)
|
|
131
|
+
for m in managed:
|
|
132
|
+
for obj in m.values():
|
|
133
|
+
if hasattr(obj, "iterPars"):
|
|
134
|
+
for par in obj.iterPars(pattern=pattern):
|
|
135
|
+
yield par
|
|
136
|
+
return
|
|
137
|
+
|
|
138
|
+
def __iter__(self):
|
|
139
|
+
"""Iterate over top-level parameters."""
|
|
140
|
+
return iter(self._parameters.values())
|
|
141
|
+
|
|
142
|
+
def __len__(self):
|
|
143
|
+
"""Get number of top-level parameters."""
|
|
144
|
+
return len(self._parameters)
|
|
145
|
+
|
|
146
|
+
def __getitem__(self, idx):
|
|
147
|
+
"""Get top-level parameters by index."""
|
|
148
|
+
# need to wrap this in a list for python 3 compatibility.
|
|
149
|
+
return list(self._parameters.values())[idx]
|
|
150
|
+
|
|
151
|
+
def __getattr__(self, name):
|
|
152
|
+
"""Gives access to the contained objects as attributes."""
|
|
153
|
+
arg = self.get(name)
|
|
154
|
+
if arg is None:
|
|
155
|
+
raise AttributeError(name)
|
|
156
|
+
return arg
|
|
157
|
+
|
|
158
|
+
# Ensure there is no __dir__ override in the base class.
|
|
159
|
+
assert (
|
|
160
|
+
getattr(Observable, "__dir__", None)
|
|
161
|
+
is getattr(Configurable, "__dir__", None)
|
|
162
|
+
is getattr(Validatable, "__dir__", None)
|
|
163
|
+
is getattr(object, "__dir__", None)
|
|
164
|
+
)
|
|
165
|
+
|
|
166
|
+
def __dir__(self):
|
|
167
|
+
"Return sorted list of attributes for this object."
|
|
168
|
+
rv = set(dir(type(self)))
|
|
169
|
+
rv.update(self.__dict__)
|
|
170
|
+
# self.get fetches looks up for items in all managed dictionaries.
|
|
171
|
+
# Add keys from each dictionary in self.__managed.
|
|
172
|
+
rv.update(*self.__managed)
|
|
173
|
+
rv = sorted(rv)
|
|
174
|
+
return rv
|
|
175
|
+
|
|
176
|
+
# Needed by __setattr__
|
|
177
|
+
_parameters = OrderedDict()
|
|
178
|
+
__managed = []
|
|
179
|
+
|
|
180
|
+
def __setattr__(self, name, value):
|
|
181
|
+
"""Parameter access and object checking."""
|
|
182
|
+
if name in self._parameters:
|
|
183
|
+
par = self._parameters[name]
|
|
184
|
+
if isinstance(value, Parameter):
|
|
185
|
+
par.value = value.value
|
|
186
|
+
else:
|
|
187
|
+
par.value = value
|
|
188
|
+
return
|
|
189
|
+
|
|
190
|
+
m = self.get(name)
|
|
191
|
+
if m is not None:
|
|
192
|
+
raise AttributeError("Cannot set '%s'" % name)
|
|
193
|
+
|
|
194
|
+
super(RecipeContainer, self).__setattr__(name, value)
|
|
195
|
+
return
|
|
196
|
+
|
|
197
|
+
def __delattr__(self, name):
|
|
198
|
+
"""Delete parameters with del.
|
|
199
|
+
|
|
200
|
+
This does not allow deletion of non-parameters, as this may
|
|
201
|
+
require configuration changes that are not yet handled in a
|
|
202
|
+
general way.
|
|
203
|
+
"""
|
|
204
|
+
if name in self._parameters:
|
|
205
|
+
self._removeParameter(self._parameters[name])
|
|
206
|
+
return
|
|
207
|
+
|
|
208
|
+
m = self.get(name)
|
|
209
|
+
if m is not None:
|
|
210
|
+
raise AttributeError("Cannot delete '%s'" % name)
|
|
211
|
+
|
|
212
|
+
super(RecipeContainer, self).__delattr__(name)
|
|
213
|
+
return
|
|
214
|
+
|
|
215
|
+
def get(self, name, default=None):
|
|
216
|
+
"""Get a managed object."""
|
|
217
|
+
for d in self.__managed:
|
|
218
|
+
arg = d.get(name)
|
|
219
|
+
if arg is not None:
|
|
220
|
+
return arg
|
|
221
|
+
|
|
222
|
+
return default
|
|
223
|
+
|
|
224
|
+
def getNames(self):
|
|
225
|
+
"""Get the names of managed parameters."""
|
|
226
|
+
return [p.name for p in self._parameters.values()]
|
|
227
|
+
|
|
228
|
+
def getValues(self):
|
|
229
|
+
"""Get the values of managed parameters."""
|
|
230
|
+
return [p.value for p in self._parameters.values()]
|
|
231
|
+
|
|
232
|
+
def _addObject(self, obj, d, check=True):
|
|
233
|
+
"""Add an object to a managed dictionary.
|
|
234
|
+
|
|
235
|
+
obj -- The object to be stored.
|
|
236
|
+
d -- The managed dictionary to store the object in.
|
|
237
|
+
check -- If True (default), a ValueError is raised an object of the
|
|
238
|
+
given name already exists.
|
|
239
|
+
|
|
240
|
+
Raises ValueError if the object has no name.
|
|
241
|
+
Raises ValueError if the object has the same name as some other managed
|
|
242
|
+
object.
|
|
243
|
+
"""
|
|
244
|
+
|
|
245
|
+
# Check name
|
|
246
|
+
if not obj.name:
|
|
247
|
+
message = "%s has no name" % obj.__class__.__name__
|
|
248
|
+
raise ValueError(message)
|
|
249
|
+
|
|
250
|
+
# Check for extant object in d with same name
|
|
251
|
+
oldobj = d.get(obj.name)
|
|
252
|
+
if check and oldobj is not None:
|
|
253
|
+
message = "%s with name '%s' already exists" % (
|
|
254
|
+
obj.__class__.__name__,
|
|
255
|
+
obj.name,
|
|
256
|
+
)
|
|
257
|
+
raise ValueError(message)
|
|
258
|
+
|
|
259
|
+
# Check for object with same name in other dictionary.
|
|
260
|
+
if oldobj is None and self.get(obj.name) is not None:
|
|
261
|
+
message = "Non-%s with name '%s' already exists" % (
|
|
262
|
+
obj.__class__.__name__,
|
|
263
|
+
obj.name,
|
|
264
|
+
)
|
|
265
|
+
raise ValueError(message)
|
|
266
|
+
|
|
267
|
+
# Detach the old object, if there is one
|
|
268
|
+
if oldobj is not None:
|
|
269
|
+
oldobj.removeObserver(self._flush)
|
|
270
|
+
|
|
271
|
+
# Add the object
|
|
272
|
+
d[obj.name] = obj
|
|
273
|
+
|
|
274
|
+
# Observe the object
|
|
275
|
+
obj.addObserver(self._flush)
|
|
276
|
+
|
|
277
|
+
# Store this as a configurable object
|
|
278
|
+
self._storeConfigurable(obj)
|
|
279
|
+
return
|
|
280
|
+
|
|
281
|
+
def _removeObject(self, obj, d):
|
|
282
|
+
"""Remove an object from a managed dictionary.
|
|
283
|
+
|
|
284
|
+
Raises ValueError if obj is not part of the dictionary.
|
|
285
|
+
"""
|
|
286
|
+
if obj not in d.values():
|
|
287
|
+
m = "'%s' is not part of the %s" % (obj, self.__class__.__name__)
|
|
288
|
+
raise ValueError(m)
|
|
289
|
+
|
|
290
|
+
del d[obj.name]
|
|
291
|
+
obj.removeObserver(self._flush)
|
|
292
|
+
|
|
293
|
+
return
|
|
294
|
+
|
|
295
|
+
def _locateManagedObject(self, obj):
|
|
296
|
+
"""Find the location a managed object within the hierarchy.
|
|
297
|
+
|
|
298
|
+
obj -- The object to find.
|
|
299
|
+
|
|
300
|
+
Returns a list of objects. The first member of the list is this object,
|
|
301
|
+
and each subsequent member is a sub-object of the previous one. The
|
|
302
|
+
last entry in the list is obj. If obj cannot be found, the list is
|
|
303
|
+
empty.
|
|
304
|
+
"""
|
|
305
|
+
loc = [self]
|
|
306
|
+
|
|
307
|
+
# This handles the case that an object is asked to locate itself.
|
|
308
|
+
if obj is self:
|
|
309
|
+
return loc
|
|
310
|
+
|
|
311
|
+
for m in self._iterManaged():
|
|
312
|
+
|
|
313
|
+
# Check locally for the object
|
|
314
|
+
if m is obj:
|
|
315
|
+
loc.append(obj)
|
|
316
|
+
return loc
|
|
317
|
+
|
|
318
|
+
# Check within managed objects
|
|
319
|
+
if hasattr(m, "_locateManagedObject"):
|
|
320
|
+
|
|
321
|
+
subloc = m._locateManagedObject(obj)
|
|
322
|
+
if subloc:
|
|
323
|
+
return loc + subloc
|
|
324
|
+
|
|
325
|
+
return []
|
|
326
|
+
|
|
327
|
+
def _flush(self, other):
|
|
328
|
+
"""Invalidate cached state.
|
|
329
|
+
|
|
330
|
+
This will force any observer to invalidate its state. By default
|
|
331
|
+
this does nothing.
|
|
332
|
+
"""
|
|
333
|
+
self.notify(other)
|
|
334
|
+
return
|
|
335
|
+
|
|
336
|
+
def _validate(self):
|
|
337
|
+
"""Validate my state.
|
|
338
|
+
|
|
339
|
+
This validates that contained Parameters and managed objects are
|
|
340
|
+
valid.
|
|
341
|
+
|
|
342
|
+
Raises AttributeError if validation fails.
|
|
343
|
+
"""
|
|
344
|
+
iterable = chain(self.__iter__(), self._iterManaged())
|
|
345
|
+
self._validateOthers(iterable)
|
|
346
|
+
return
|
|
347
|
+
|
|
348
|
+
|
|
349
|
+
# End class RecipeContainer
|
|
350
|
+
|
|
351
|
+
|
|
352
|
+
class RecipeOrganizer(_recipeorganizer_interface, RecipeContainer):
|
|
353
|
+
"""Extended base class for organizing pieces of a FitRecipe.
|
|
354
|
+
|
|
355
|
+
This class extends RecipeContainer by organizing constraints and
|
|
356
|
+
Restraints, as well as Equations that can be used in Constraint and
|
|
357
|
+
Restraint equations. These constraints and Restraints can be placed at any
|
|
358
|
+
level and a flattened list of them can be retrieved with the
|
|
359
|
+
_getConstraints and _getRestraints methods.
|
|
360
|
+
|
|
361
|
+
Attributes
|
|
362
|
+
name -- A name for this organizer. Names should be unique
|
|
363
|
+
within a RecipeOrganizer and should be valid attribute
|
|
364
|
+
names.
|
|
365
|
+
_calculators -- A managed dictionary of Calculators, indexed by name.
|
|
366
|
+
_parameters -- A managed OrderedDict of contained Parameters.
|
|
367
|
+
_constraints -- A dictionary of Constraints, indexed by the constrained
|
|
368
|
+
Parameter. Constraints can be added using the
|
|
369
|
+
'constrain' method.
|
|
370
|
+
_restraints -- A set of Restraints. Restraints can be added using the
|
|
371
|
+
'restrain' method.
|
|
372
|
+
_eqfactory -- A diffpy.srfit.equation.builder.EquationFactory
|
|
373
|
+
instance that is used create Equations from string.
|
|
374
|
+
|
|
375
|
+
Properties
|
|
376
|
+
names -- Variable names (read only). See getNames.
|
|
377
|
+
values -- Variable values (read only). See getValues.
|
|
378
|
+
|
|
379
|
+
Raises ValueError if the name is not a valid attribute identifier
|
|
380
|
+
"""
|
|
381
|
+
|
|
382
|
+
def __init__(self, name):
|
|
383
|
+
RecipeContainer.__init__(self, name)
|
|
384
|
+
self._restraints = set()
|
|
385
|
+
self._constraints = {}
|
|
386
|
+
self._eqfactory = EquationFactory()
|
|
387
|
+
|
|
388
|
+
self._calculators = {}
|
|
389
|
+
self._manage(self._calculators)
|
|
390
|
+
return
|
|
391
|
+
|
|
392
|
+
# Parameter management
|
|
393
|
+
|
|
394
|
+
def _newParameter(self, name, value, check=True):
|
|
395
|
+
"""Add a new Parameter to the container.
|
|
396
|
+
|
|
397
|
+
This creates a new Parameter and adds it to the container using
|
|
398
|
+
the _addParameter method.
|
|
399
|
+
|
|
400
|
+
Returns the Parameter.
|
|
401
|
+
"""
|
|
402
|
+
p = Parameter(name, value)
|
|
403
|
+
self._addParameter(p, check)
|
|
404
|
+
return p
|
|
405
|
+
|
|
406
|
+
def _addParameter(self, par, check=True):
|
|
407
|
+
"""Store a Parameter.
|
|
408
|
+
|
|
409
|
+
Parameters added in this way are registered with the _eqfactory.
|
|
410
|
+
|
|
411
|
+
par -- The Parameter to be stored.
|
|
412
|
+
check -- If True (default), a ValueError is raised a Parameter of
|
|
413
|
+
the specified name has already been inserted.
|
|
414
|
+
|
|
415
|
+
Raises ValueError if the Parameter has no name.
|
|
416
|
+
Raises ValueError if the Parameter has the same name as a contained
|
|
417
|
+
RecipeContainer.
|
|
418
|
+
"""
|
|
419
|
+
|
|
420
|
+
# Store the Parameter
|
|
421
|
+
RecipeContainer._addObject(self, par, self._parameters, check)
|
|
422
|
+
|
|
423
|
+
# Register the Parameter
|
|
424
|
+
self._eqfactory.registerArgument(par.name, par)
|
|
425
|
+
return
|
|
426
|
+
|
|
427
|
+
def _removeParameter(self, par):
|
|
428
|
+
"""Remove a parameter.
|
|
429
|
+
|
|
430
|
+
This de-registers the Parameter with the _eqfactory. The
|
|
431
|
+
Parameter will remain part of built equations.
|
|
432
|
+
|
|
433
|
+
Note that constraints and restraints involving the Parameter are
|
|
434
|
+
not modified.
|
|
435
|
+
|
|
436
|
+
Raises ValueError if par is not part of the RecipeOrganizer.
|
|
437
|
+
"""
|
|
438
|
+
self._removeObject(par, self._parameters)
|
|
439
|
+
self._eqfactory.deRegisterBuilder(par.name)
|
|
440
|
+
return
|
|
441
|
+
|
|
442
|
+
def registerCalculator(self, f, argnames=None):
|
|
443
|
+
"""Register a Calculator so it can be used within equation strings.
|
|
444
|
+
|
|
445
|
+
A Calculator is an elaborate function that can organize Parameters.
|
|
446
|
+
This creates a function with this class that can be used within string
|
|
447
|
+
equations. The resulting equation can be used in a string with
|
|
448
|
+
arguments like a function or without, in which case the values of the
|
|
449
|
+
Parameters created from argnames will be be used to compute the value.
|
|
450
|
+
|
|
451
|
+
f -- The Calculator to register.
|
|
452
|
+
argnames -- The names of the arguments to f (list or None).
|
|
453
|
+
If this is None, then the argument names will be
|
|
454
|
+
extracted from the function.
|
|
455
|
+
"""
|
|
456
|
+
self._eqfactory.registerOperator(f.name, f)
|
|
457
|
+
self._addObject(f, self._calculators)
|
|
458
|
+
# Register arguments of the calculator
|
|
459
|
+
if argnames is None:
|
|
460
|
+
fncode = f.__call__.__func__.__code__
|
|
461
|
+
argnames = list(fncode.co_varnames)
|
|
462
|
+
argnames = argnames[1 : fncode.co_argcount]
|
|
463
|
+
|
|
464
|
+
for pname in argnames:
|
|
465
|
+
if pname not in self._eqfactory.builders:
|
|
466
|
+
par = self._newParameter(pname, 0)
|
|
467
|
+
else:
|
|
468
|
+
par = self.get(pname)
|
|
469
|
+
f.addLiteral(par)
|
|
470
|
+
|
|
471
|
+
# Now return an equation object
|
|
472
|
+
eq = self._eqfactory.makeEquation(f.name)
|
|
473
|
+
return eq
|
|
474
|
+
|
|
475
|
+
def registerFunction(self, f, name=None, argnames=None):
|
|
476
|
+
"""Register a function so it can be used within equation strings.
|
|
477
|
+
|
|
478
|
+
This creates a function with this class that can be used within string
|
|
479
|
+
equations. The resulting equation does not require the arguments to be
|
|
480
|
+
passed in the equation string, as this will be handled automatically.
|
|
481
|
+
|
|
482
|
+
f -- The callable to register. If this is an Equation
|
|
483
|
+
instance, then all that needs to be provided is a name.
|
|
484
|
+
name -- The name of the function to be used in equations. If
|
|
485
|
+
this is None (default), the method will try to
|
|
486
|
+
determine the name of the function automatically.
|
|
487
|
+
argnames -- The names of the arguments to f (list or None).
|
|
488
|
+
If this is None (default), then the argument names will
|
|
489
|
+
be extracted from the function.
|
|
490
|
+
|
|
491
|
+
Note that name and argnames can be extracted from regular python
|
|
492
|
+
functions (of type 'function'), bound class methods and callable
|
|
493
|
+
classes.
|
|
494
|
+
|
|
495
|
+
Raises TypeError if name or argnames cannot be automatically
|
|
496
|
+
extracted.
|
|
497
|
+
Raises TypeError if an automatically extracted name is '<lambda>'.
|
|
498
|
+
Raises ValueError if f is an Equation object and name is None.
|
|
499
|
+
|
|
500
|
+
Returns the callable Equation object.
|
|
501
|
+
"""
|
|
502
|
+
|
|
503
|
+
# If the function is an equation, we treat it specially. This is
|
|
504
|
+
# required so that the objects observed by the root get observed if the
|
|
505
|
+
# Equation is used within another equation. It is assumed that a plain
|
|
506
|
+
# function is not observable.
|
|
507
|
+
if isinstance(f, Equation):
|
|
508
|
+
if name is None:
|
|
509
|
+
m = "Equation must be given a name"
|
|
510
|
+
raise ValueError(m)
|
|
511
|
+
self._eqfactory.registerOperator(name, f)
|
|
512
|
+
return f
|
|
513
|
+
|
|
514
|
+
# Introspection code
|
|
515
|
+
if name is None or argnames is None:
|
|
516
|
+
|
|
517
|
+
import inspect
|
|
518
|
+
|
|
519
|
+
fncode = None
|
|
520
|
+
|
|
521
|
+
# This will let us offset the argument list to eliminate 'self'
|
|
522
|
+
offset = 0
|
|
523
|
+
|
|
524
|
+
# check regular functions
|
|
525
|
+
if inspect.isfunction(f):
|
|
526
|
+
fncode = f.__code__
|
|
527
|
+
# check class method
|
|
528
|
+
elif inspect.ismethod(f):
|
|
529
|
+
fncode = f.__func__.__code__
|
|
530
|
+
offset = 1
|
|
531
|
+
# check functor
|
|
532
|
+
elif hasattr(f, "__call__") and hasattr(f.__call__, "__func__"):
|
|
533
|
+
fncode = f.__call__.__func__.__code__
|
|
534
|
+
offset = 1
|
|
535
|
+
else:
|
|
536
|
+
m = "Cannot extract name or argnames"
|
|
537
|
+
raise ValueError(m)
|
|
538
|
+
|
|
539
|
+
# Extract the name
|
|
540
|
+
if name is None:
|
|
541
|
+
name = fncode.co_name
|
|
542
|
+
if name == "<lambda>":
|
|
543
|
+
m = "You must supply a name name for a lambda function"
|
|
544
|
+
raise ValueError(m)
|
|
545
|
+
|
|
546
|
+
# Extract the arguments
|
|
547
|
+
if argnames is None:
|
|
548
|
+
argnames = list(fncode.co_varnames)
|
|
549
|
+
argnames = argnames[offset : fncode.co_argcount]
|
|
550
|
+
|
|
551
|
+
# End introspection code
|
|
552
|
+
|
|
553
|
+
# Make missing Parameters
|
|
554
|
+
for pname in argnames:
|
|
555
|
+
if pname not in self._eqfactory.builders:
|
|
556
|
+
self._newParameter(pname, 0)
|
|
557
|
+
|
|
558
|
+
# Initialize and register
|
|
559
|
+
from diffpy.srfit.fitbase.calculator import Calculator
|
|
560
|
+
|
|
561
|
+
if isinstance(f, Calculator):
|
|
562
|
+
for pname in argnames:
|
|
563
|
+
par = self.get(pname)
|
|
564
|
+
f.addLiteral(par)
|
|
565
|
+
self._eqfactory.registerOperator(name, f)
|
|
566
|
+
else:
|
|
567
|
+
self._eqfactory.registerFunction(name, f, argnames)
|
|
568
|
+
|
|
569
|
+
# Now we can create the Equation and return it to the user.
|
|
570
|
+
eq = self._eqfactory.makeEquation(name)
|
|
571
|
+
|
|
572
|
+
return eq
|
|
573
|
+
|
|
574
|
+
def registerStringFunction(self, fstr, name, ns={}):
|
|
575
|
+
"""Register a string function.
|
|
576
|
+
|
|
577
|
+
This creates a function with this class that can be used within string
|
|
578
|
+
equations. The resulting equation does not require the arguments to be
|
|
579
|
+
passed in the function string, as this will be handled automatically.
|
|
580
|
+
|
|
581
|
+
fstr -- A string equation to register.
|
|
582
|
+
name -- The name of the function to be used in equations.
|
|
583
|
+
ns -- A dictionary of Parameters, indexed by name, that are
|
|
584
|
+
used in fstr, but not part of the FitRecipe (default
|
|
585
|
+
{}).
|
|
586
|
+
|
|
587
|
+
Raises ValueError if ns uses a name that is already used for another
|
|
588
|
+
managed object.
|
|
589
|
+
Raises ValueError if the function name is the name of another managed
|
|
590
|
+
object.
|
|
591
|
+
|
|
592
|
+
Returns the callable Equation object.
|
|
593
|
+
"""
|
|
594
|
+
|
|
595
|
+
# Build the equation instance.
|
|
596
|
+
eq = equationFromString(fstr, self._eqfactory, ns=ns, buildargs=True)
|
|
597
|
+
eq.name = name
|
|
598
|
+
|
|
599
|
+
# Register any new Parameters.
|
|
600
|
+
for par in self._eqfactory.newargs:
|
|
601
|
+
self._addParameter(par)
|
|
602
|
+
|
|
603
|
+
# Register the equation as a callable function.
|
|
604
|
+
argnames = eq.argdict.keys()
|
|
605
|
+
return self.registerFunction(eq, name, argnames)
|
|
606
|
+
|
|
607
|
+
def evaluateEquation(self, eqstr, ns={}):
|
|
608
|
+
"""Evaluate a string equation.
|
|
609
|
+
|
|
610
|
+
eqstr -- A string equation to evaluate. The equation is evaluated at
|
|
611
|
+
the current value of the registered Parameters.
|
|
612
|
+
ns -- A dictionary of Parameters, indexed by name, that are
|
|
613
|
+
used in fstr, but not part of the FitRecipe (default {}).
|
|
614
|
+
|
|
615
|
+
Raises ValueError if ns uses a name that is already used for a
|
|
616
|
+
variable.
|
|
617
|
+
"""
|
|
618
|
+
eq = equationFromString(eqstr, self._eqfactory, ns)
|
|
619
|
+
try:
|
|
620
|
+
rv = eq()
|
|
621
|
+
finally:
|
|
622
|
+
self._eqfactory.wipeout(eq)
|
|
623
|
+
return rv
|
|
624
|
+
|
|
625
|
+
def constrain(self, par, con, ns={}):
|
|
626
|
+
"""Constrain a parameter to an equation.
|
|
627
|
+
|
|
628
|
+
Note that only one constraint can exist on a Parameter at a time.
|
|
629
|
+
|
|
630
|
+
par -- The name of a Parameter or a Parameter to constrain.
|
|
631
|
+
con -- A string representation of the constraint equation or a
|
|
632
|
+
Parameter to constrain to. A constraint equation must
|
|
633
|
+
consist of numpy operators and "known" Parameters.
|
|
634
|
+
Parameters are known if they are in the ns argument, or if
|
|
635
|
+
they are managed by this object.
|
|
636
|
+
ns -- A dictionary of Parameters, indexed by name, that are used
|
|
637
|
+
in the parameter, but not part of this object (default {}).
|
|
638
|
+
|
|
639
|
+
Raises ValueError if ns uses a name that is already used for a
|
|
640
|
+
variable.
|
|
641
|
+
Raises ValueError if par is a string but not part of this object or in
|
|
642
|
+
ns.
|
|
643
|
+
Raises ValueError if par is marked as constant.
|
|
644
|
+
"""
|
|
645
|
+
if isinstance(par, six.string_types):
|
|
646
|
+
name = par
|
|
647
|
+
par = self.get(name)
|
|
648
|
+
if par is None:
|
|
649
|
+
par = ns.get(name)
|
|
650
|
+
|
|
651
|
+
if par is None:
|
|
652
|
+
raise ValueError("The parameter cannot be found")
|
|
653
|
+
|
|
654
|
+
if par.const:
|
|
655
|
+
raise ValueError("The parameter '%s' is constant" % par)
|
|
656
|
+
|
|
657
|
+
if isinstance(con, six.string_types):
|
|
658
|
+
eqstr = con
|
|
659
|
+
eq = equationFromString(con, self._eqfactory, ns)
|
|
660
|
+
else:
|
|
661
|
+
eq = Equation(root=con)
|
|
662
|
+
eqstr = con.name
|
|
663
|
+
|
|
664
|
+
eq.name = "_constraint_%s" % par.name
|
|
665
|
+
|
|
666
|
+
# Make and store the constraint
|
|
667
|
+
con = Constraint()
|
|
668
|
+
con.constrain(par, eq)
|
|
669
|
+
# Store the equation string so it can be shown later.
|
|
670
|
+
con.eqstr = eqstr
|
|
671
|
+
self._constraints[par] = con
|
|
672
|
+
|
|
673
|
+
# Our configuration changed
|
|
674
|
+
self._updateConfiguration()
|
|
675
|
+
|
|
676
|
+
return
|
|
677
|
+
|
|
678
|
+
def isConstrained(self, par):
|
|
679
|
+
"""Determine if a Parameter is constrained in this object.
|
|
680
|
+
|
|
681
|
+
par -- The name of a Parameter or a Parameter to check.
|
|
682
|
+
"""
|
|
683
|
+
if isinstance(par, six.string_types):
|
|
684
|
+
name = par
|
|
685
|
+
par = self.get(name)
|
|
686
|
+
|
|
687
|
+
return par in self._constraints
|
|
688
|
+
|
|
689
|
+
def unconstrain(self, *pars):
|
|
690
|
+
"""Unconstrain a Parameter.
|
|
691
|
+
|
|
692
|
+
This removes any constraints on a Parameter.
|
|
693
|
+
|
|
694
|
+
*pars -- The names of Parameters or Parameters to unconstrain.
|
|
695
|
+
|
|
696
|
+
|
|
697
|
+
Raises ValueError if the Parameter is not constrained.
|
|
698
|
+
"""
|
|
699
|
+
update = False
|
|
700
|
+
for par in pars:
|
|
701
|
+
if isinstance(par, six.string_types):
|
|
702
|
+
name = par
|
|
703
|
+
par = self.get(name)
|
|
704
|
+
|
|
705
|
+
if par is None:
|
|
706
|
+
raise ValueError("The parameter cannot be found")
|
|
707
|
+
|
|
708
|
+
if par in self._constraints:
|
|
709
|
+
self._constraints[par].unconstrain()
|
|
710
|
+
del self._constraints[par]
|
|
711
|
+
update = True
|
|
712
|
+
|
|
713
|
+
if update:
|
|
714
|
+
# Our configuration changed
|
|
715
|
+
self._updateConfiguration()
|
|
716
|
+
|
|
717
|
+
else:
|
|
718
|
+
|
|
719
|
+
raise ValueError("The parameter is not constrained")
|
|
720
|
+
|
|
721
|
+
return
|
|
722
|
+
|
|
723
|
+
def getConstrainedPars(self, recurse=False):
|
|
724
|
+
"""Get a list of constrained managed Parameters in this object.
|
|
725
|
+
|
|
726
|
+
recurse -- Recurse into managed objects and retrieve their constrained
|
|
727
|
+
Parameters as well (default False).
|
|
728
|
+
"""
|
|
729
|
+
const = self._getConstraints(recurse)
|
|
730
|
+
return const.keys()
|
|
731
|
+
|
|
732
|
+
def clearConstraints(self, recurse=False):
|
|
733
|
+
"""Clear all constraints managed by this organizer.
|
|
734
|
+
|
|
735
|
+
recurse -- Recurse into managed objects and clear all constraints
|
|
736
|
+
found there as well.
|
|
737
|
+
|
|
738
|
+
This removes constraints that are held in this organizer, no matter
|
|
739
|
+
where the constrained parameters are from.
|
|
740
|
+
"""
|
|
741
|
+
if self._constraints:
|
|
742
|
+
self.unconstrain(*self._constraints)
|
|
743
|
+
|
|
744
|
+
if recurse:
|
|
745
|
+
for m in filter(_has_clear_constraints, self._iterManaged()):
|
|
746
|
+
m.clearConstraints(recurse)
|
|
747
|
+
return
|
|
748
|
+
|
|
749
|
+
def restrain(self, res, lb=-inf, ub=inf, sig=1, scaled=False, ns={}):
|
|
750
|
+
"""Restrain an expression to specified bounds.
|
|
751
|
+
|
|
752
|
+
res -- An equation string or Parameter to restrain.
|
|
753
|
+
lb -- The lower bound on the restraint evaluation (default -inf).
|
|
754
|
+
ub -- The lower bound on the restraint evaluation (default inf).
|
|
755
|
+
sig -- The uncertainty on the bounds (default 1).
|
|
756
|
+
scaled -- A flag indicating if the restraint is scaled (multiplied)
|
|
757
|
+
by the unrestrained point-average chi^2 (chi^2/numpoints)
|
|
758
|
+
(default False).
|
|
759
|
+
ns -- A dictionary of Parameters, indexed by name, that are used
|
|
760
|
+
in the equation string, but not part of the RecipeOrganizer
|
|
761
|
+
(default {}).
|
|
762
|
+
|
|
763
|
+
The penalty is calculated as
|
|
764
|
+
(max(0, lb - val, val - ub)/sig)**2
|
|
765
|
+
and val is the value of the calculated equation. This is multiplied by
|
|
766
|
+
the average chi^2 if scaled is True.
|
|
767
|
+
|
|
768
|
+
Raises ValueError if ns uses a name that is already used for a
|
|
769
|
+
Parameter.
|
|
770
|
+
Raises ValueError if res depends on a Parameter that is not part of
|
|
771
|
+
the RecipeOrganizer and that is not defined in ns.
|
|
772
|
+
|
|
773
|
+
Returns the Restraint object for use with the 'unrestrain' method.
|
|
774
|
+
"""
|
|
775
|
+
|
|
776
|
+
if isinstance(res, six.string_types):
|
|
777
|
+
eqstr = res
|
|
778
|
+
eq = equationFromString(res, self._eqfactory, ns)
|
|
779
|
+
else:
|
|
780
|
+
eq = Equation(root=res)
|
|
781
|
+
eqstr = res.name
|
|
782
|
+
|
|
783
|
+
# Make and store the restraint
|
|
784
|
+
res = Restraint(eq, lb, ub, sig, scaled)
|
|
785
|
+
res.eqstr = eqstr
|
|
786
|
+
self.addRestraint(res)
|
|
787
|
+
return res
|
|
788
|
+
|
|
789
|
+
def addRestraint(self, res):
|
|
790
|
+
"""Add a Restraint instance to the RecipeOrganizer.
|
|
791
|
+
|
|
792
|
+
res -- A Restraint instance.
|
|
793
|
+
"""
|
|
794
|
+
self._restraints.add(res)
|
|
795
|
+
# Our configuration changed. Notify observers.
|
|
796
|
+
self._updateConfiguration()
|
|
797
|
+
return
|
|
798
|
+
|
|
799
|
+
def unrestrain(self, *ress):
|
|
800
|
+
"""Remove a Restraint from the RecipeOrganizer.
|
|
801
|
+
|
|
802
|
+
*ress -- Restraints returned from the 'restrain' method or added
|
|
803
|
+
with the 'addRestraint' method.
|
|
804
|
+
"""
|
|
805
|
+
update = False
|
|
806
|
+
restuple = tuple(self._restraints)
|
|
807
|
+
for res in ress:
|
|
808
|
+
if res in restuple:
|
|
809
|
+
self._restraints.remove(res)
|
|
810
|
+
update = True
|
|
811
|
+
|
|
812
|
+
if update:
|
|
813
|
+
# Our configuration changed
|
|
814
|
+
self._updateConfiguration()
|
|
815
|
+
|
|
816
|
+
return
|
|
817
|
+
|
|
818
|
+
def clearRestraints(self, recurse=False):
|
|
819
|
+
"""Clear all restraints.
|
|
820
|
+
|
|
821
|
+
recurse -- Recurse into managed objects and clear all restraints
|
|
822
|
+
found there as well.
|
|
823
|
+
"""
|
|
824
|
+
self.unrestrain(*self._restraints)
|
|
825
|
+
if recurse:
|
|
826
|
+
for msg in filter(_has_clear_restraints, self._iterManaged()):
|
|
827
|
+
msg.clearRestraints(recurse)
|
|
828
|
+
return
|
|
829
|
+
|
|
830
|
+
def _getConstraints(self, recurse=True):
|
|
831
|
+
"""Get the constrained Parameters for this and managed sub-objects."""
|
|
832
|
+
constraints = {}
|
|
833
|
+
if recurse:
|
|
834
|
+
for m in filter(_has_get_constraints, self._iterManaged()):
|
|
835
|
+
constraints.update(m._getConstraints(recurse))
|
|
836
|
+
|
|
837
|
+
constraints.update(self._constraints)
|
|
838
|
+
|
|
839
|
+
return constraints
|
|
840
|
+
|
|
841
|
+
def _getRestraints(self, recurse=True):
|
|
842
|
+
"""Get the Restraints for this and embedded ParameterSets.
|
|
843
|
+
|
|
844
|
+
This returns a set of Restraint objects.
|
|
845
|
+
"""
|
|
846
|
+
restraints = set(self._restraints)
|
|
847
|
+
if recurse:
|
|
848
|
+
for m in filter(_has_get_restraints, self._iterManaged()):
|
|
849
|
+
restraints.update(m._getRestraints(recurse))
|
|
850
|
+
|
|
851
|
+
return restraints
|
|
852
|
+
|
|
853
|
+
def _validate(self):
|
|
854
|
+
"""Validate my state.
|
|
855
|
+
|
|
856
|
+
This performs RecipeContainer validations. This validates
|
|
857
|
+
contained Restraints and Constraints.
|
|
858
|
+
|
|
859
|
+
Raises AttributeError if validation fails.
|
|
860
|
+
"""
|
|
861
|
+
RecipeContainer._validate(self)
|
|
862
|
+
iterable = chain(self._restraints, self._constraints.values())
|
|
863
|
+
self._validateOthers(iterable)
|
|
864
|
+
return
|
|
865
|
+
|
|
866
|
+
# For printing the configured recipe to screen
|
|
867
|
+
|
|
868
|
+
def _formatManaged(self, prefix=""):
|
|
869
|
+
"""Format hierarchy of managed parameters for showing.
|
|
870
|
+
|
|
871
|
+
Parameters
|
|
872
|
+
----------
|
|
873
|
+
prefix : str
|
|
874
|
+
The leading string to be prefixed to each parameter name.
|
|
875
|
+
|
|
876
|
+
Returns
|
|
877
|
+
-------
|
|
878
|
+
list
|
|
879
|
+
List of formatted lines, one per each Parameter.
|
|
880
|
+
"""
|
|
881
|
+
lines = []
|
|
882
|
+
formatstr = "{:<W}{}"
|
|
883
|
+
# Format own parameters.
|
|
884
|
+
if self._parameters:
|
|
885
|
+
w0 = max(len(n) for n in self._parameters)
|
|
886
|
+
w1 = ((w0 + len(prefix) + 1) // 4 + 1) * 4
|
|
887
|
+
fmt = formatstr.replace("W", str(w1))
|
|
888
|
+
lines.extend(
|
|
889
|
+
fmt.format(prefix + n, p.value)
|
|
890
|
+
for n, p in self._parameters.items()
|
|
891
|
+
)
|
|
892
|
+
# Recurse into managed objects.
|
|
893
|
+
for obj in self._iterManaged():
|
|
894
|
+
if hasattr(obj, "_formatManaged"):
|
|
895
|
+
oprefix = prefix + obj.name + "."
|
|
896
|
+
tlines = obj._formatManaged(prefix=oprefix)
|
|
897
|
+
lines.extend([""] if lines and tlines else [])
|
|
898
|
+
lines.extend(tlines)
|
|
899
|
+
return lines
|
|
900
|
+
|
|
901
|
+
def _formatConstraints(self):
|
|
902
|
+
"""Format constraints for showing.
|
|
903
|
+
|
|
904
|
+
This collects constraints on all levels of the hierarchy and displays
|
|
905
|
+
them with respect to this level.
|
|
906
|
+
|
|
907
|
+
Returns
|
|
908
|
+
-------
|
|
909
|
+
list
|
|
910
|
+
List of formatted lines displaying the defined constraints.
|
|
911
|
+
Return empty list when no constraints were defined.
|
|
912
|
+
"""
|
|
913
|
+
cdict = self._getConstraints()
|
|
914
|
+
# Find each constraint and format the equation
|
|
915
|
+
clines = []
|
|
916
|
+
for par, con in cdict.items():
|
|
917
|
+
loc = self._locateManagedObject(par)
|
|
918
|
+
if loc:
|
|
919
|
+
locstr = ".".join(o.name for o in loc[1:])
|
|
920
|
+
clines.append("%s <-- %s" % (locstr, con.eqstr))
|
|
921
|
+
else:
|
|
922
|
+
clines.append("%s <-- %s" % (par.name, con.eqstr))
|
|
923
|
+
clines.sort(key=numstr)
|
|
924
|
+
return clines
|
|
925
|
+
|
|
926
|
+
def _formatRestraints(self):
|
|
927
|
+
"""Format restraints for showing.
|
|
928
|
+
|
|
929
|
+
This collects restraints on all levels of the hierarchy and displays
|
|
930
|
+
them with respect to this level.
|
|
931
|
+
|
|
932
|
+
Returns
|
|
933
|
+
-------
|
|
934
|
+
list
|
|
935
|
+
List of formatted lines displaying the defined restraints.
|
|
936
|
+
Return empty list when no restraints were defined.
|
|
937
|
+
"""
|
|
938
|
+
rset = self._getRestraints()
|
|
939
|
+
rlines = []
|
|
940
|
+
for res in rset:
|
|
941
|
+
line = "%s: lb = %f, ub = %f, sig = %f, scaled = %s" % (
|
|
942
|
+
res.eqstr,
|
|
943
|
+
res.lb,
|
|
944
|
+
res.ub,
|
|
945
|
+
res.sig,
|
|
946
|
+
res.scaled,
|
|
947
|
+
)
|
|
948
|
+
rlines.append(line)
|
|
949
|
+
rlines.sort(key=numstr)
|
|
950
|
+
return rlines
|
|
951
|
+
|
|
952
|
+
def show(self, pattern="", textwidth=78):
|
|
953
|
+
"""Show the configuration hierarchy on the screen.
|
|
954
|
+
|
|
955
|
+
This will print out a summary of all contained objects.
|
|
956
|
+
|
|
957
|
+
Parameters
|
|
958
|
+
----------
|
|
959
|
+
pattern : str, optional
|
|
960
|
+
Limit output to only those parameters that match this regular
|
|
961
|
+
expression (match all by default).
|
|
962
|
+
textwidth : int, optional
|
|
963
|
+
Trim formatted lines at this text width to avoid folding at
|
|
964
|
+
the screen width. Do not trim when negative or 0.
|
|
965
|
+
"""
|
|
966
|
+
regexp = re.compile(pattern)
|
|
967
|
+
_pmatch_with_re = partial(_pmatch, regexp=regexp)
|
|
968
|
+
# Show sub objects and their parameters
|
|
969
|
+
lines = []
|
|
970
|
+
tlines = self._formatManaged()
|
|
971
|
+
if tlines:
|
|
972
|
+
lines.extend(["Parameters", _DASHEDLINE])
|
|
973
|
+
linesok = filter(_pmatch_with_re, tlines)
|
|
974
|
+
lastnotblank = False
|
|
975
|
+
# squeeze repeated blank lines
|
|
976
|
+
for lastnotblank, g in groupby(linesok, bool):
|
|
977
|
+
lines.extend(g if lastnotblank else [""])
|
|
978
|
+
# remove trailing blank line
|
|
979
|
+
if not lastnotblank:
|
|
980
|
+
lines.pop(-1)
|
|
981
|
+
|
|
982
|
+
# FIXME - parameter names in equations not particularly informative
|
|
983
|
+
# Show constraints
|
|
984
|
+
cmatch = regexp.search
|
|
985
|
+
tlines = self._formatConstraints()
|
|
986
|
+
if tlines:
|
|
987
|
+
if lines:
|
|
988
|
+
lines.append("")
|
|
989
|
+
lines.extend(["Constraints", _DASHEDLINE])
|
|
990
|
+
lines.extend(filter(cmatch, tlines))
|
|
991
|
+
|
|
992
|
+
# FIXME - parameter names in equations not particularly informative
|
|
993
|
+
# Show restraints
|
|
994
|
+
tlines = self._formatRestraints()
|
|
995
|
+
if tlines:
|
|
996
|
+
if lines:
|
|
997
|
+
lines.append("")
|
|
998
|
+
lines.extend(["Restraints", _DASHEDLINE])
|
|
999
|
+
lines.extend(filter(_pmatch_with_re, tlines))
|
|
1000
|
+
|
|
1001
|
+
# Determine effective text width tw.
|
|
1002
|
+
tw = textwidth if (textwidth is not None and textwidth > 0) else None
|
|
1003
|
+
# Avoid outputting "\n" when there is no output.
|
|
1004
|
+
if lines:
|
|
1005
|
+
print("\n".join(s[:tw] for s in lines))
|
|
1006
|
+
return
|
|
1007
|
+
|
|
1008
|
+
|
|
1009
|
+
# End RecipeOrganizer
|
|
1010
|
+
|
|
1011
|
+
|
|
1012
|
+
def equationFromString(
|
|
1013
|
+
eqstr, factory, ns={}, buildargs=False, argclass=Parameter, argkw={}
|
|
1014
|
+
):
|
|
1015
|
+
"""Make an equation from a string.
|
|
1016
|
+
|
|
1017
|
+
eqstr -- A string representation of the equation. The equation must
|
|
1018
|
+
consist of numpy operators and "known" Parameters. Parameters
|
|
1019
|
+
are known if they are in ns, or already defined in the factory.
|
|
1020
|
+
factory -- An EquationFactory instance.
|
|
1021
|
+
ns -- A dictionary of Parameters indexed by name that are used
|
|
1022
|
+
in the eqstr but not already defined in the factory
|
|
1023
|
+
(default {}).
|
|
1024
|
+
buildargs -- A flag indicating whether missing Parameters can be created
|
|
1025
|
+
by the Factory (default False). If False, then the a ValueError
|
|
1026
|
+
will be raised if there are undefined arguments in the eqstr.
|
|
1027
|
+
argclass -- Class to use when creating new Arguments (default
|
|
1028
|
+
Parameter). The class constructor must accept the 'name' key
|
|
1029
|
+
word.
|
|
1030
|
+
argkw -- Key word dictionary to pass to the argclass constructor
|
|
1031
|
+
(default {}).
|
|
1032
|
+
|
|
1033
|
+
Raises ValueError if ns uses a name that is already defined in the factory.
|
|
1034
|
+
Raises ValueError if the equation has undefined parameters.
|
|
1035
|
+
"""
|
|
1036
|
+
|
|
1037
|
+
defined = set(factory.builders.keys())
|
|
1038
|
+
|
|
1039
|
+
# Check if ns overloads any parameters.
|
|
1040
|
+
if defined.intersection(ns.keys()):
|
|
1041
|
+
raise ValueError("ns contains defined names")
|
|
1042
|
+
|
|
1043
|
+
# Register the ns parameters in the equation factory
|
|
1044
|
+
for name, arg in ns.items():
|
|
1045
|
+
factory.registerArgument(name, arg)
|
|
1046
|
+
|
|
1047
|
+
eq = factory.makeEquation(eqstr, buildargs, argclass, argkw)
|
|
1048
|
+
|
|
1049
|
+
# Clean the ns parameters
|
|
1050
|
+
for name in ns:
|
|
1051
|
+
factory.deRegisterBuilder(name)
|
|
1052
|
+
|
|
1053
|
+
return eq
|
|
1054
|
+
|
|
1055
|
+
|
|
1056
|
+
def _has_clear_constraints(msg):
|
|
1057
|
+
return hasattr(msg, "clearConstraints")
|
|
1058
|
+
|
|
1059
|
+
|
|
1060
|
+
def _has_clear_restraints(msg):
|
|
1061
|
+
return hasattr(msg, "clearRestraints")
|
|
1062
|
+
|
|
1063
|
+
|
|
1064
|
+
def _has_get_restraints(msg):
|
|
1065
|
+
return hasattr(msg, "_getRestraints")
|
|
1066
|
+
|
|
1067
|
+
|
|
1068
|
+
def _has_get_constraints(msg):
|
|
1069
|
+
return hasattr(msg, "_getConstraints")
|
|
1070
|
+
|
|
1071
|
+
|
|
1072
|
+
def _pmatch(inp_str, regexp):
|
|
1073
|
+
parts = inp_str.split(None, 1)
|
|
1074
|
+
return len(parts) < 2 or regexp.search(parts[0])
|