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.
Files changed (73) hide show
  1. diffpy/__init__.py +15 -0
  2. diffpy/srfit/__init__.py +41 -0
  3. diffpy/srfit/equation/__init__.py +31 -0
  4. diffpy/srfit/equation/builder.py +702 -0
  5. diffpy/srfit/equation/equationmod.py +213 -0
  6. diffpy/srfit/equation/literals/__init__.py +71 -0
  7. diffpy/srfit/equation/literals/abcs.py +81 -0
  8. diffpy/srfit/equation/literals/argument.py +76 -0
  9. diffpy/srfit/equation/literals/literal.py +69 -0
  10. diffpy/srfit/equation/literals/operators.py +380 -0
  11. diffpy/srfit/equation/visitors/__init__.py +94 -0
  12. diffpy/srfit/equation/visitors/argfinder.py +62 -0
  13. diffpy/srfit/equation/visitors/printer.py +120 -0
  14. diffpy/srfit/equation/visitors/swapper.py +142 -0
  15. diffpy/srfit/equation/visitors/validator.py +113 -0
  16. diffpy/srfit/equation/visitors/visitor.py +62 -0
  17. diffpy/srfit/exceptions.py +29 -0
  18. diffpy/srfit/fitbase/__init__.py +53 -0
  19. diffpy/srfit/fitbase/calculator.py +110 -0
  20. diffpy/srfit/fitbase/configurable.py +57 -0
  21. diffpy/srfit/fitbase/constraint.py +117 -0
  22. diffpy/srfit/fitbase/fitcontribution.py +351 -0
  23. diffpy/srfit/fitbase/fithook.py +227 -0
  24. diffpy/srfit/fitbase/fitrecipe.py +826 -0
  25. diffpy/srfit/fitbase/fitresults.py +674 -0
  26. diffpy/srfit/fitbase/parameter.py +314 -0
  27. diffpy/srfit/fitbase/parameterset.py +110 -0
  28. diffpy/srfit/fitbase/profile.py +405 -0
  29. diffpy/srfit/fitbase/profilegenerator.py +168 -0
  30. diffpy/srfit/fitbase/profileparser.py +179 -0
  31. diffpy/srfit/fitbase/recipeorganizer.py +1074 -0
  32. diffpy/srfit/fitbase/restraint.py +117 -0
  33. diffpy/srfit/fitbase/simplerecipe.py +230 -0
  34. diffpy/srfit/fitbase/validatable.py +59 -0
  35. diffpy/srfit/interface/__init__.py +33 -0
  36. diffpy/srfit/interface/interface.py +180 -0
  37. diffpy/srfit/pdf/__init__.py +24 -0
  38. diffpy/srfit/pdf/basepdfgenerator.py +296 -0
  39. diffpy/srfit/pdf/characteristicfunctions.py +432 -0
  40. diffpy/srfit/pdf/debyepdfgenerator.py +115 -0
  41. diffpy/srfit/pdf/pdfcontribution.py +311 -0
  42. diffpy/srfit/pdf/pdfgenerator.py +77 -0
  43. diffpy/srfit/pdf/pdfparser.py +226 -0
  44. diffpy/srfit/sas/__init__.py +30 -0
  45. diffpy/srfit/sas/prcalculator.py +139 -0
  46. diffpy/srfit/sas/sasgenerator.py +70 -0
  47. diffpy/srfit/sas/sasimport.py +66 -0
  48. diffpy/srfit/sas/sasparameter.py +70 -0
  49. diffpy/srfit/sas/sasparser.py +133 -0
  50. diffpy/srfit/sas/sasprofile.py +94 -0
  51. diffpy/srfit/structure/__init__.py +60 -0
  52. diffpy/srfit/structure/basestructureparset.py +62 -0
  53. diffpy/srfit/structure/bvsrestraint.py +101 -0
  54. diffpy/srfit/structure/cctbxparset.py +292 -0
  55. diffpy/srfit/structure/diffpyparset.py +303 -0
  56. diffpy/srfit/structure/objcrystparset.py +1552 -0
  57. diffpy/srfit/structure/sgconstraints.py +761 -0
  58. diffpy/srfit/structure/srrealparset.py +92 -0
  59. diffpy/srfit/util/__init__.py +50 -0
  60. diffpy/srfit/util/argbinders.py +29 -0
  61. diffpy/srfit/util/inpututils.py +50 -0
  62. diffpy/srfit/util/nameutils.py +45 -0
  63. diffpy/srfit/util/observable.py +93 -0
  64. diffpy/srfit/util/tagmanager.py +150 -0
  65. diffpy/srfit/util/weakrefcallable.py +170 -0
  66. diffpy/srfit/version.py +26 -0
  67. diffpy_srfit-3.1.0.dist-info/METADATA +216 -0
  68. diffpy_srfit-3.1.0.dist-info/RECORD +73 -0
  69. diffpy_srfit-3.1.0.dist-info/WHEEL +5 -0
  70. diffpy_srfit-3.1.0.dist-info/licenses/AUTHORS.rst +10 -0
  71. diffpy_srfit-3.1.0.dist-info/licenses/LICENSE.rst +138 -0
  72. diffpy_srfit-3.1.0.dist-info/licenses/LICENSE_DANSE.rst +34 -0
  73. 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])