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,1552 @@
1
+ #!/usr/bin/env python
2
+ ##############################################################################
3
+ #
4
+ # diffpy.srfit by DANSE Diffraction group
5
+ # Simon J. L. Billinge
6
+ # (c) 2009 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
+ """Wrappers for adapting pyobjcryst.crystal.Crystal to a srfit ParameterSet.
16
+
17
+ This will adapt a Crystal or Molecule object from pyobjcryst into the
18
+ ParameterSet interface. The following classes are adapted.
19
+
20
+ ObjCrystCrystalParSet -- Adapter for pyobjcryst.crystal.Crystal
21
+ ObjCrystAtomParSet -- Adapter for pyobjcryst.atom.Atom
22
+ ObjCrystMoleculeParSet -- Adapter for pyobjcryst.molecule.Molecule
23
+ ObjCrystMolAtomParSet -- Adapter for pyobjcryst.molecule.MolAtom
24
+
25
+ Related to the adaptation of Molecule and MolAtom, there are adaptors for
26
+ specifying molecule restraints.
27
+ ObjCrystBondLengthRestraint
28
+ ObjCrystBondAngleRestraint
29
+ ObjCrystDihedralAngleRestraint
30
+
31
+ There are also Parameters for encapsulating and modifying atoms via their
32
+ relative positions. These Parameters can also act like constraints, and can
33
+ modify the positions of multiple MolAtoms.
34
+ ObjCrystBondLengthParameter
35
+ ObjCrystBondAngleParameter
36
+ ObjCrystDihedralAngleParameter
37
+ """
38
+
39
+ __all__ = ["ObjCrystMoleculeParSet", "ObjCrystCrystalParSet"]
40
+
41
+ import numpy
42
+ from pyobjcryst.molecule import (
43
+ GetBondAngle,
44
+ GetBondLength,
45
+ GetDihedralAngle,
46
+ StretchModeBondAngle,
47
+ StretchModeBondLength,
48
+ StretchModeTorsion,
49
+ )
50
+
51
+ from diffpy.srfit.fitbase.parameter import (
52
+ Parameter,
53
+ ParameterAdapter,
54
+ ParameterProxy,
55
+ )
56
+ from diffpy.srfit.fitbase.parameterset import ParameterSet
57
+ from diffpy.srfit.structure.srrealparset import SrRealParSet
58
+
59
+
60
+ class ObjCrystScattererParSet(ParameterSet):
61
+ """A base adaptor for an Objcryst Scatterer.
62
+
63
+ This class derives from diffpy.srfit.fitbase.parameterset.ParameterSet and
64
+ adapts pyobjcryst.scatterer.Scatterer derivatives (Molecule, Atom) and
65
+ objects with a similar interface (MolAtom). See the ParameterSet class for
66
+ base attributes.
67
+
68
+ Attributes:
69
+ scat -- The adapted pyobjcryst object.
70
+ parent -- The ParameterSet this belongs to
71
+
72
+ Managed Parameters:
73
+ x, y, z -- Scatterer position in crystal coordinates (ParameterWraper)
74
+ occ -- Occupancy of the scatterer on its crystal site
75
+ (ParameterWraper)
76
+ """
77
+
78
+ def __init__(self, name, scat, parent):
79
+ """Initialize.
80
+
81
+ name -- The name of the scatterer
82
+ scat -- The pyobjcryst.Scatterer instance
83
+ parent -- The ParameterSet this belongs to
84
+ """
85
+ ParameterSet.__init__(self, name)
86
+ self.scat = scat
87
+ self.parent = parent
88
+
89
+ # x, y, z, occ
90
+ self.addParameter(ParameterAdapter("x", self.scat, attr="X"))
91
+ self.addParameter(ParameterAdapter("y", self.scat, attr="Y"))
92
+ self.addParameter(ParameterAdapter("z", self.scat, attr="Z"))
93
+ self.addParameter(ParameterAdapter("occ", self.scat, attr="Occupancy"))
94
+ return
95
+
96
+ def isDummy(self):
97
+ """Indicate whether this scatterer is a dummy atom."""
98
+ return False
99
+
100
+ def hasScatterers(self):
101
+ """Indicate if this scatterer has its own scatterers."""
102
+ return hasattr(self, "getScatterers")
103
+
104
+
105
+ # End class ObjCrystScattererParSet
106
+
107
+
108
+ class ObjCrystAtomParSet(ObjCrystScattererParSet):
109
+ """A adaptor for a pyobjcryst.Atom.
110
+
111
+ This class derives from ObjCrystScattererParSet.
112
+
113
+ Attributes:
114
+ scat -- The adapted pyobjcryst.atom.Atom.
115
+ element -- Non-refinable name of the element (property).
116
+ parent -- The ObjCrystCrystalParSet this belongs to.
117
+
118
+ Managed Parameters:
119
+ x, y, z -- Atom position in crystal coordinates (ParameterAdapter)
120
+ occ -- Occupancy of the atom on its crystal location
121
+ (ParameterAdapter)
122
+ Biso -- Isotropic scattering factor (ParameterAdapter).
123
+ B11, B22, B33, B12, B21, B23, B32, B13, B31
124
+ -- Anisotropic displacement factor for scatterer
125
+ (ParameterAdapter or ParameterProxy). Note that the Bij and Bji
126
+ parameters are the same.
127
+ """
128
+
129
+ def __init__(self, name, atom, parent):
130
+ """Initialize.
131
+
132
+ name -- The name of the scatterer
133
+ scat -- The Scatterer instance
134
+ parent -- The ObjCrystCrystalParSet this belongs to
135
+ """
136
+ ObjCrystScattererParSet.__init__(self, name, atom, parent)
137
+ sp = atom.GetScatteringPower()
138
+
139
+ # The B-parameters
140
+ self.addParameter(ParameterAdapter("Biso", sp, attr="Biso"))
141
+ self.addParameter(ParameterAdapter("B11", sp, attr="B11"))
142
+ self.addParameter(ParameterAdapter("B22", sp, attr="B22"))
143
+ self.addParameter(ParameterAdapter("B33", sp, attr="B33"))
144
+ B12 = ParameterAdapter("B12", sp, attr="B12")
145
+ B21 = ParameterProxy("B21", B12)
146
+ B13 = ParameterAdapter("B13", sp, attr="B13")
147
+ B31 = ParameterProxy("B31", B13)
148
+ B23 = ParameterAdapter("B23", sp, attr="B23")
149
+ B32 = ParameterProxy("B32", B23)
150
+ self.addParameter(B12)
151
+ self.addParameter(B21)
152
+ self.addParameter(B13)
153
+ self.addParameter(B31)
154
+ self.addParameter(B23)
155
+ self.addParameter(B32)
156
+
157
+ # Give a value to Biso if it doesn't have one, and this is isotropic
158
+ if sp.IsIsotropic() and self.Biso.value == 0:
159
+ self.Biso.value = 0.5
160
+ return
161
+
162
+ def _getElem(self):
163
+ """Getter for the element type."""
164
+ return self.scat.GetScatteringPower().GetSymbol()
165
+
166
+ element = property(_getElem)
167
+
168
+
169
+ # End class ObjCrystAtomParSet
170
+
171
+
172
+ class ObjCrystMoleculeParSet(ObjCrystScattererParSet):
173
+ """A adaptor for a pyobjcryst.Molecule.
174
+
175
+ This class derives from ObjCrystScattererParSet.
176
+
177
+ Attributes:
178
+ scat -- The adapted pyobjcryst.molecule.Molecule.
179
+ stru -- The adapted pyobjcryst.molecule.Molecule.
180
+ parent -- The ObjCrystCrystalParSet this belongs to.
181
+ ObjCrystMoleculeParSets can be used on their own, in which
182
+ case this is None.
183
+
184
+ Managed Parameters:
185
+ x, y, z -- Molecule position in crystal coordinates (ParameterAdapter)
186
+ occ -- Occupancy of the molecule on its crystal location
187
+ (ParameterAdapter)
188
+ q0, q1, q2, q3 -- Orientational quaternion (ParameterAdapter)
189
+
190
+ Other attributes are inherited from
191
+ diffpy.srfit.fitbase.parameterset.ParameterSet
192
+ """
193
+
194
+ def __init__(self, name, molecule, parent=None):
195
+ """Initialize.
196
+
197
+ name -- The name of the scatterer
198
+ molecule -- The pyobjcryst.Molecule instance
199
+ parent -- The ObjCrystCrystalParSet this belongs to (default None).
200
+ """
201
+ ObjCrystScattererParSet.__init__(self, name, molecule, parent)
202
+ self.stru = molecule
203
+
204
+ # Add orientation quaternion
205
+ self.addParameter(ParameterAdapter("q0", self.scat, attr="Q0"))
206
+ self.addParameter(ParameterAdapter("q1", self.scat, attr="Q1"))
207
+ self.addParameter(ParameterAdapter("q2", self.scat, attr="Q2"))
208
+ self.addParameter(ParameterAdapter("q3", self.scat, attr="Q3"))
209
+
210
+ # Wrap the MolAtoms within the molecule
211
+ self.atoms = []
212
+ anames = []
213
+
214
+ for a in molecule:
215
+
216
+ name = a.GetName()
217
+ if not name:
218
+ raise AttributeError("Each MolAtom must have a name")
219
+ if name in anames:
220
+ raise AttributeError("MolAtom name '%s' is duplicated" % name)
221
+
222
+ atom = ObjCrystMolAtomParSet(name, a, self)
223
+ atom.molecule = self
224
+ self.addParameterSet(atom)
225
+ self.atoms.append(atom)
226
+ anames.append(name)
227
+
228
+ return
229
+
230
+ @classmethod
231
+ def canAdapt(self, stru):
232
+ """Return whether the structure can be adapted by this class."""
233
+ from pyobjcryst.molecule import Molecule
234
+
235
+ return isinstance(stru, Molecule)
236
+
237
+ # Part of SrRealParSet interface
238
+ def useSymmetry(self, use=True):
239
+ """Set this structure to use symmetry.
240
+
241
+ This structure object does not support symmetry.
242
+ """
243
+ return
244
+
245
+ # Part of SrRealParSet interface
246
+ def usingSymmetry(self):
247
+ """Check if symmetry is being used.
248
+
249
+ This structure object does not support symmetry.
250
+ """
251
+ return False
252
+
253
+ # Part of SrRealParSet interface
254
+ def _getSrRealStructure(self):
255
+ """Get the structure object for use with SrReal calculators.
256
+
257
+ Molecule objects are never periodic. Return the object and let
258
+ the SrReal adapters do the proper thing.
259
+ """
260
+ return self.stru
261
+
262
+ def getLattice(self):
263
+ """Get the ParameterSet containing the lattice Parameters."""
264
+ lattice = ParameterSet("lattice")
265
+ lattice.newPar("a", 1.0)
266
+ lattice.newPar("b", 1.0)
267
+ lattice.newPar("c", 1.0)
268
+ lattice.newPar("alpha", 90)
269
+ lattice.newPar("beta", 90)
270
+ lattice.newPar("gamma", 90)
271
+ lattice.angunits = "deg"
272
+ return lattice
273
+
274
+ def getScatterers(self):
275
+ """Get a list of ParameterSets that represents the scatterers.
276
+
277
+ The site positions must be accessible from the list entries via
278
+ the names "x", "y", and "z". The ADPs must be accessible as
279
+ well, but the name and nature of the ADPs (U-factors, B-factors,
280
+ isotropic, anisotropic) depends on the adapted structure.
281
+ """
282
+ return self.atoms
283
+
284
+ def wrapRestraints(self):
285
+ """Wrap the restraints implicit to the molecule.
286
+
287
+ This will wrap MolBonds, MolBondAngles and MolDihedralAngles of
288
+ the Molecule as ObjCrystMoleculeRestraint objects.
289
+ """
290
+ # Wrap restraints. Restraints wrapped in this way cannot be modified
291
+ # from within this class.
292
+ for b in self.scat.GetBondList():
293
+ res = ObjCrystMoleculeRestraint(b)
294
+ self._restraints.add(res)
295
+
296
+ for ba in self.scat.GetBondAngleList():
297
+ res = ObjCrystMoleculeRestraint(ba)
298
+ self._restraints.add(res)
299
+
300
+ for da in self.scat.GetDihedralAngleList():
301
+ res = ObjCrystMoleculeRestraint(da)
302
+ self._restraints.add(res)
303
+
304
+ return
305
+
306
+ def wrapStretchModeParameters(self):
307
+ """Wrap the stretch modes implicit to the Molecule as Parameters.
308
+
309
+ This will wrap StretchModeBondLengths and StretchModeBondAngles of the
310
+ Molecule as Parameters. Note that this requires that the MolBondAtoms
311
+ in the Molecule came in with unique names. Torsion angles are not
312
+ wrapped, as there is not enough information to determine each MolAtom
313
+ in the angle.
314
+
315
+ The Parameters will be given the concatenated name of its constituents.
316
+ bond lengths: "bl_aname1_aname2"
317
+ bond angles: "ba_aname1_aname2_aname3"
318
+ """
319
+ for mode in self.scat.GetStretchModeBondLengthList():
320
+ name1 = mode.mpAtom0.GetName()
321
+ name2 = mode.mpAtom1.GetName()
322
+
323
+ name = "bl_" + "_".join((name1, name2))
324
+
325
+ atom1 = getattr(self, name1)
326
+ atom2 = getattr(self, name2)
327
+
328
+ par = ObjCrystBondLengthParameter(name, atom1, atom2, mode=mode)
329
+
330
+ atoms = []
331
+ for a in mode.GetAtoms():
332
+ name = a.GetName()
333
+ atoms.append(getattr(self, name))
334
+
335
+ par.AddAtoms(atoms)
336
+
337
+ self.addParameter(par)
338
+
339
+ for mode in self.scat.GetStretchModeBondAngleList():
340
+ name1 = mode.mpAtom0.GetName()
341
+ name2 = mode.mpAtom1.GetName()
342
+ name3 = mode.mpAtom2.GetName()
343
+
344
+ name = "ba_" + "_".join((name1, name2, name3))
345
+
346
+ atom1 = getattr(self, name1)
347
+ atom2 = getattr(self, name2)
348
+ atom3 = getattr(self, name3)
349
+
350
+ par = ObjCrystBondAngleParameter(
351
+ name, atom1, atom2, atom3, mode=mode
352
+ )
353
+
354
+ atoms = []
355
+ for a in mode.GetAtoms():
356
+ name = a.GetName()
357
+ atoms.append(getattr(self, name))
358
+ par.AddAtoms(atoms)
359
+
360
+ self.addParameter(par)
361
+
362
+ return
363
+
364
+ def restrainBondLength(
365
+ self, atom1, atom2, length, sigma, delta, scaled=False
366
+ ):
367
+ """Add a bond length restraint.
368
+
369
+ This creates an instance of ObjCrystBondLengthRestraint and adds it to
370
+ the ObjCrystMoleculeParSet.
371
+
372
+ atom1 -- First atom (ObjCrystMolAtomParSet) in the bond
373
+ atom2 -- Second atom (ObjCrystMolAtomParSet) in the bond
374
+ length -- The length of the bond (Angstroms)
375
+ sigma -- The uncertainty of the bond length (Angstroms)
376
+ delta -- The width of the bond (Angstroms)
377
+ scaled -- A flag indicating if the restraint is scaled (multiplied)
378
+ by the unrestrained point-average chi^2 (chi^2/numpoints)
379
+ (default False)
380
+
381
+ Returns
382
+ -------
383
+ The ObjCrystBondLengthRestraint object for use with the
384
+ 'unrestrain' method.
385
+ """
386
+ res = ObjCrystBondLengthRestraint(
387
+ atom1, atom2, length, sigma, delta, scaled
388
+ )
389
+ self._restraints.add(res)
390
+
391
+ return res
392
+
393
+ def restrainBondLengthParameter(
394
+ self, par, length, sigma, delta, scaled=False
395
+ ):
396
+ """Add a bond length restraint.
397
+
398
+ This creates an instance of ObjCrystBondLengthRestraint and adds it to
399
+ the ObjCrystMoleculeParSet.
400
+
401
+ par -- A ObjCrystBondLengthParameter (see addBondLengthParameter)
402
+ length -- The length of the bond (Angstroms)
403
+ sigma -- The uncertainty of the bond length (Angstroms)
404
+ delta -- The width of the bond (Angstroms)
405
+ scaled -- A flag indicating if the restraint is scaled (multiplied)
406
+ by the unrestrained point-average chi^2 (chi^2/numpoints)
407
+ (default False)
408
+
409
+ Returns the ObjCrystBondLengthRestraint object for use with the
410
+ 'unrestrain' method.
411
+ """
412
+ return self.restrainBondLength(
413
+ par.atom1, par.atom2, length, sigma, delta, scaled
414
+ )
415
+
416
+ def restrainBondAngle(
417
+ self, atom1, atom2, atom3, angle, sigma, delta, scaled=False
418
+ ):
419
+ """Add a bond angle restraint.
420
+
421
+ This creates an instance of ObjCrystBondAngleRestraint and adds it to
422
+ the ObjCrystMoleculeParSet.
423
+
424
+ atom1 -- First atom (ObjCrystMolAtomParSet) in the bond angle
425
+ atom2 -- Second (central) atom (ObjCrystMolAtomParSet) in the bond
426
+ angle
427
+ atom3 -- Third atom (ObjCrystMolAtomParSet) in the bond angle
428
+ angle -- The bond angle (radians)
429
+ sigma -- The uncertainty of the bond angle (radians)
430
+ delta -- The width of the bond angle (radians)
431
+ scaled -- A flag indicating if the restraint is scaled (multiplied)
432
+ by the unrestrained point-average chi^2 (chi^2/numpoints)
433
+ (default False).
434
+
435
+ Returns the ObjCrystBondAngleRestraint object for use with the
436
+ 'unrestrain' method.
437
+ """
438
+ res = ObjCrystBondAngleRestraint(
439
+ atom1, atom2, atom3, angle, sigma, delta, scaled
440
+ )
441
+ self._restraints.add(res)
442
+
443
+ return res
444
+
445
+ def restrainBondAngleParameter(
446
+ self, par, angle, sigma, delta, scaled=False
447
+ ):
448
+ """Add a bond angle restraint.
449
+
450
+ This creates an instance of ObjCrystBondAngleRestraint and adds it to
451
+ the ObjCrystMoleculeParSet.
452
+
453
+ par -- A ObjCrystBondAngleParameter (see addBondAngleParameter)
454
+ angle -- The bond angle (radians)
455
+ sigma -- The uncertainty of the bond angle (radians)
456
+ delta -- The width of the bond angle (radians)
457
+ scaled -- A flag indicating if the restraint is scaled (multiplied)
458
+ by the unrestrained point-average chi^2 (chi^2/numpoints)
459
+ (default False).
460
+
461
+ Returns the ObjCrystBondAngleRestraint object for use with the
462
+ 'unrestrain' method.
463
+ """
464
+ return self.restrainBondAngle(
465
+ par.atom1, par.atom2, par.atom3, angle, sigma, delta, scaled
466
+ )
467
+
468
+ def restrainDihedralAngle(
469
+ self, atom1, atom2, atom3, atom4, angle, sigma, delta, scaled=False
470
+ ):
471
+ """Add a dihedral angle restraint.
472
+
473
+ This creates an instance of ObjCrystDihedralAngleRestraint and adds it
474
+ to the ObjCrystMoleculeParSet.
475
+
476
+ atom1 -- First atom (ObjCrystMolAtomParSet) in the angle
477
+ atom2 -- Second (central) atom (ObjCrystMolAtomParSet) in the angle
478
+ atom3 -- Third (central) atom (ObjCrystMolAtomParSet) in the angle
479
+ atom4 -- Fourth atom in the angle (ObjCrystMolAtomParSet)
480
+ angle -- The dihedral angle (radians)
481
+ sigma -- The uncertainty of the dihedral angle (radians)
482
+ delta -- The width of the dihedral angle (radians)
483
+ scaled -- A flag indicating if the restraint is scaled (multiplied)
484
+ by the unrestrained point-average chi^2 (chi^2/numpoints)
485
+ (default False).
486
+
487
+ Returns the ObjCrystDihedralAngleRestraint object for use with the
488
+ 'unrestrain' method.
489
+ """
490
+ res = ObjCrystDihedralAngleRestraint(
491
+ atom1, atom2, atom3, atom4, angle, sigma, delta, scaled
492
+ )
493
+ self._restraints.add(res)
494
+
495
+ return res
496
+
497
+ def restrainDihedralAngleParameter(
498
+ self, par, angle, sigma, delta, scaled=False
499
+ ):
500
+ """Add a dihedral angle restraint.
501
+
502
+ This creates an instance of ObjCrystDihedralAngleRestraint and adds it
503
+ to the ObjCrystMoleculeParSet.
504
+
505
+ par -- A ObjCrystDihedralAngleParameter (see
506
+ addDihedralAngleParameter)
507
+ angle -- The dihedral angle (radians)
508
+ sigma -- The uncertainty of the dihedral angle (radians)
509
+ delta -- The width of the dihedral angle (radians)
510
+ scaled -- A flag indicating if the restraint is scaled (multiplied)
511
+ by the unrestrained point-average chi^2 (chi^2/numpoints)
512
+ (default False).
513
+
514
+ Returns the ObjCrystDihedralAngleRestraint object for use with the
515
+ 'unrestrain' method.
516
+ """
517
+ return self.restrainDihedralAngle(
518
+ par.atom1,
519
+ par.atom2,
520
+ par.atom3,
521
+ par.atom4,
522
+ angle,
523
+ sigma,
524
+ delta,
525
+ scaled,
526
+ )
527
+
528
+ def addBondLengthParameter(
529
+ self, name, atom1, atom2, value=None, const=False
530
+ ):
531
+ """Add a bond length to the Molecule.
532
+
533
+ This creates a ObjCrystBondLengthParameter to the
534
+ ObjCrystMoleculeParSet that can be adjusted during the fit.
535
+
536
+ name -- The name of the ObjCrystBondLengthParameter
537
+ atom1 -- The first atom (ObjCrystMolAtomParSet) in the bond
538
+ atom2 -- The second (mutated) atom (ObjCrystMolAtomParSet) in the
539
+ bond
540
+ value -- An initial value for the bond length. If this is None
541
+ (default), then the current distance between the atoms will
542
+ be used.
543
+ const -- A flag indicating whether the Parameter is constant
544
+ (default False)
545
+
546
+ Returns the new ObjCrystBondLengthParameter.
547
+ """
548
+ par = ObjCrystBondLengthParameter(name, atom1, atom2, value, const)
549
+ self.addParameter(par)
550
+
551
+ return par
552
+
553
+ def addBondAngleParameter(
554
+ self, name, atom1, atom2, atom3, value=None, const=False
555
+ ):
556
+ """Add a bond angle to the Molecule.
557
+
558
+ This creates a ObjCrystBondAngleParameter to the ObjCrystMoleculeParSet
559
+ that can be adjusted during the fit.
560
+
561
+ name -- The name of the ObjCrystBondAngleParameter
562
+ atom1 -- The first atom (ObjCrystMolAtomParSet) in the bond angle
563
+ atom2 -- The second (central) atom (ObjCrystMolAtomParSet) in the
564
+ bond angle
565
+ atom3 -- The third (mutated) atom (ObjCrystMolAtomParSet) in the
566
+ bond angle
567
+ value -- An initial value for the bond angle. If this is None
568
+ (default), then the current bond angle between the atoms
569
+ will be used.
570
+ const -- A flag indicating whether the Parameter is constant
571
+ (default False).
572
+
573
+ Returns the new ObjCrystBondAngleParameter.
574
+ """
575
+ par = ObjCrystBondAngleParameter(
576
+ name, atom1, atom2, atom3, value, const
577
+ )
578
+ self.addParameter(par)
579
+
580
+ return par
581
+
582
+ def addDihedralAngleParameter(
583
+ self, name, atom1, atom2, atom3, atom4, value=None, const=False
584
+ ):
585
+ """Add a dihedral angle to the Molecule.
586
+
587
+ This creates a ObjCrystDihedralAngleParameter to the
588
+ ObjCrystMoleculeParSet that can be adjusted during the fit.
589
+
590
+ name -- The name of the ObjCrystDihedralAngleParameter.
591
+ atom1 -- The first atom (ObjCrystMolAtomParSet) in the dihderal
592
+ angle.
593
+ atom2 -- The second (central) atom (ObjCrystMolAtomParSet) in the
594
+ dihderal angle
595
+ atom3 -- The third (central) atom (ObjCrystMolAtomParSet) in the
596
+ dihderal angle
597
+ atom4 -- The fourth (mutated) atom (ObjCrystMolAtomParSet) in the
598
+ dihderal angle
599
+ value -- An initial value for the dihedral angle. If this is None
600
+ (default), then the current dihedral angle between atoms
601
+ will be used.
602
+ const -- A flag indicating whether the Parameter is constant
603
+ (default False).
604
+
605
+ Returns the new ObjCrystDihedralAngleParameter.
606
+ """
607
+ par = ObjCrystDihedralAngleParameter(
608
+ name, atom1, atom2, atom3, atom4, value, const
609
+ )
610
+ self.addParameter(par)
611
+
612
+ return par
613
+
614
+
615
+ # End class ObjCrystMoleculeParSet
616
+
617
+
618
+ class ObjCrystMolAtomParSet(ObjCrystScattererParSet):
619
+ """A adaptor for an pyobjcryst.molecule.MolAtom.
620
+
621
+ This class derives from srfit.fitbase.parameterset.ParameterSet. Note that
622
+ MolAtom does not derive from Scatterer, but the relevant interface is the
623
+ same within pyobjcryst. See the ParameterSet class for base attributes.
624
+
625
+ Attributes:
626
+ scat -- The adapted pyobjcryst.molecule.MolAtom.
627
+ parent -- The ObjCrystCrystalParSet this belongs to
628
+ element -- Non-refinable name of the element (property).
629
+
630
+ Managed Parameters:
631
+ x, y, z -- Atom position in crystal coordinates (ParameterAdapter)
632
+ occ -- Occupancy of the atom on its crystal location
633
+ (ParameterAdapter)
634
+ Biso -- Isotropic scattering factor (ParameterAdapter). This does
635
+ not exist for dummy atoms. See the 'isDummy' method.
636
+ B11, B22, B33, B12, B21, B23, B32, B13, B31
637
+ -- Anisotropic displacement factor for scatterer
638
+ (ParameterAdapter or ParameterProxy). Note that the Bij and Bji
639
+ parameters are the same.
640
+ """
641
+
642
+ def __init__(self, name, scat, parent):
643
+ """Initialize.
644
+
645
+ name -- The name of the scatterer
646
+ scat -- The Scatterer instance
647
+ parent -- The ObjCrystCrystalParSet this belongs to
648
+ """
649
+ ObjCrystScattererParSet.__init__(self, name, scat, parent)
650
+ sp = scat.GetScatteringPower()
651
+
652
+ # Only wrap this if there is a scattering power
653
+ if sp is not None:
654
+ self.addParameter(ParameterAdapter("Biso", sp, attr="Biso"))
655
+ self.addParameter(ParameterAdapter("B11", sp, attr="B11"))
656
+ self.addParameter(ParameterAdapter("B22", sp, attr="B22"))
657
+ self.addParameter(ParameterAdapter("B33", sp, attr="B33"))
658
+ B12 = ParameterAdapter("B12", sp, attr="B12")
659
+ B21 = ParameterProxy("B21", B12)
660
+ B13 = ParameterAdapter("B13", sp, attr="B13")
661
+ B31 = ParameterProxy("B31", B13)
662
+ B23 = ParameterAdapter("B23", sp, attr="B23")
663
+ B32 = ParameterProxy("B32", B23)
664
+ self.addParameter(B12)
665
+ self.addParameter(B21)
666
+ self.addParameter(B13)
667
+ self.addParameter(B31)
668
+ self.addParameter(B23)
669
+ self.addParameter(B32)
670
+
671
+ return
672
+
673
+ def _getElem(self):
674
+ """Getter for the element type."""
675
+ sp = self.scat.GetScatteringPower()
676
+ if sp:
677
+ return sp.GetSymbol()
678
+ else:
679
+ return "dummy"
680
+
681
+ element = property(_getElem)
682
+
683
+ def isDummy(self):
684
+ """Indicate whether this atom is a dummy atom."""
685
+ return self.scat.IsDummy()
686
+
687
+
688
+ # End class ObjCrystMolAtomParSet
689
+
690
+
691
+ class ObjCrystMoleculeRestraint(object):
692
+ """Base class for adapting pyobjcryst Molecule restraints to srfit.
693
+
694
+ The 'penalty' method calls 'GetLogLikelihood' of the pyobjcryst restraint.
695
+ This implements the 'penalty' method from
696
+ diffpy.srfit.fitbase.restraint.Restraint. The 'restrain' method is not
697
+ needed or implemented.
698
+
699
+ Attributes:
700
+ res -- The pyobjcryst Molecule restraint.
701
+ scaled -- A flag indicating if the restraint is scaled (multiplied) by
702
+ the unrestrained point-average chi^2 (chi^2/numpoints) (default
703
+ False).
704
+ """
705
+
706
+ def __init__(self, res, scaled=False):
707
+ """Create a Restraint-like from a pyobjcryst Molecule restraint.
708
+
709
+ res -- The pyobjcryst Molecule restraint.
710
+ scaled -- A flag indicating if the restraint is scaled (multiplied)
711
+ by the unrestrained point-average chi^2 (chi^2/numpoints)
712
+ (default False).
713
+ """
714
+ self.res = res
715
+ self.scaled = scaled
716
+ return
717
+
718
+ def penalty(self, w=1.0):
719
+ """Calculate the penalty of the restraint.
720
+
721
+ w -- The point-average chi^2 which is optionally used to scale the
722
+ penalty (default 1.0).
723
+ """
724
+ penalty = self.res.GetLogLikelihood()
725
+ if self.scaled:
726
+ penalty *= w
727
+ return penalty
728
+
729
+
730
+ # End class ObjCrystMoleculeRestraint
731
+
732
+
733
+ class ObjCrystBondLengthRestraint(ObjCrystMoleculeRestraint):
734
+ """Restrain the distance between two atoms.
735
+
736
+ Attributes:
737
+ atom1 -- The first atom in the bond (ObjCrystMolAtomParSet)
738
+ atom2 -- The second atom in the bond (ObjCrystMolAtomParSet)
739
+ length -- The length of the bond (Angstroms)
740
+ sigma -- The uncertainty of the bond length (Angstroms)
741
+ delta -- The width of the bond (Angstroms)
742
+ res -- The pyobjcryst BondLength restraint
743
+ scaled -- A flag indicating if the restraint is scaled (multiplied) by
744
+ the unrestrained point-average chi^2 (chi^2/numpoints) (default
745
+ False)
746
+ """
747
+
748
+ def __init__(self, atom1, atom2, length, sigma, delta, scaled=False):
749
+ """Create a bond length restraint.
750
+
751
+ atom1 -- First atom (ObjCrystMolAtomParSet) in the bond
752
+ atom2 -- Second atom (ObjCrystMolAtomParSet) in the bond
753
+ length -- The length of the bond (Angstroms)
754
+ sigma -- The uncertainty of the bond length (Angstroms)
755
+ delta -- The width of the bond (Angstroms)
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
+ """
760
+ self.atom1 = atom1
761
+ self.atom2 = atom2
762
+
763
+ m = self.atom1.scat.GetMolecule()
764
+ res = m.AddBond(atom1.scat, atom2.scat, length, sigma, delta)
765
+
766
+ ObjCrystMoleculeRestraint.__init__(self, res, scaled)
767
+ return
768
+
769
+ # Give access to the parameters of the restraint
770
+ length = property(
771
+ lambda self: self.res.GetLength0(),
772
+ lambda self, val: self.res.SetLength0(val),
773
+ )
774
+ sigma = property(
775
+ lambda self: self.res.GetLengthSigma(),
776
+ lambda self, val: self.res.SetLengthSigma(val),
777
+ )
778
+ delta = property(
779
+ lambda self: self.res.GetLengthDelta(),
780
+ lambda self, val: self.res.SetLengthDelta(val),
781
+ )
782
+
783
+
784
+ # End class ObjCrystBondLengthRestraint
785
+
786
+
787
+ class ObjCrystBondAngleRestraint(ObjCrystMoleculeRestraint):
788
+ """Restrain the angle defined by three atoms.
789
+
790
+ Attributes:
791
+ atom1 -- The first atom in the angle (ObjCrystMolAtomParSet)
792
+ atom2 -- The second atom in the angle (ObjCrystMolAtomParSet)
793
+ atom3 -- The third atom in the angle (ObjCrystMolAtomParSet)
794
+ angle -- The bond angle (radians)
795
+ sigma -- The uncertainty of the bond angle (radians)
796
+ delta -- The width of the bond angle (radians)
797
+ res -- The pyobjcryst BondAngle restraint
798
+ scaled -- A flag indicating if the restraint is scaled (multiplied) by
799
+ the unrestrained point-average chi^2 (chi^2/numpoints) (default
800
+ False)
801
+ """
802
+
803
+ def __init__(self, atom1, atom2, atom3, angle, sigma, delta, scaled=False):
804
+ """Create a bond angle restraint.
805
+
806
+ atom1 -- First atom (ObjCrystMolAtomParSet) in the bond angle
807
+ atom2 -- Second (central) atom (ObjCrystMolAtomParSet) in the bond
808
+ angle
809
+ atom3 -- Third atom (ObjCrystMolAtomParSet) in the bond angle
810
+ angle -- The bond angle (radians)
811
+ sigma -- The uncertainty of the bond angle (radians)
812
+ delta -- The width of the bond angle (radians)
813
+ scaled -- A flag indicating if the restraint is scaled (multiplied)
814
+ by the unrestrained point-average chi^2 (chi^2/numpoints)
815
+ (default False).
816
+ """
817
+ self.atom1 = atom1
818
+ self.atom2 = atom2
819
+ self.atom3 = atom3
820
+
821
+ m = self.atom1.scat.GetMolecule()
822
+ res = m.AddBondAngle(
823
+ atom1.scat, atom2.scat, atom3.scat, angle, sigma, delta
824
+ )
825
+
826
+ ObjCrystMoleculeRestraint.__init__(self, res, scaled)
827
+ return
828
+
829
+ # Give access to the parameters of the restraint
830
+ angle = property(
831
+ lambda self: self.res.GetAngle0(),
832
+ lambda self, val: self.res.SetAngle0(val),
833
+ )
834
+ sigma = property(
835
+ lambda self: self.res.GetAngleSigma(),
836
+ lambda self, val: self.res.SetAngleSigma(val),
837
+ )
838
+ delta = property(
839
+ lambda self: self.res.GetAngleDelta(),
840
+ lambda self, val: self.res.SetAngleDelta(val),
841
+ )
842
+
843
+
844
+ # End class ObjCrystBondAngleRestraint
845
+
846
+
847
+ class ObjCrystDihedralAngleRestraint(ObjCrystMoleculeRestraint):
848
+ """Restrain the dihedral (torsion) angle defined by four atoms.
849
+
850
+ Attributes:
851
+ atom1 -- The first atom in the angle (ObjCrystMolAtomParSet)
852
+ atom2 -- The second (central) atom in the angle (ObjCrystMolAtomParSet)
853
+ atom3 -- The third (central) atom in the angle (ObjCrystMolAtomParSet)
854
+ atom4 -- The fourth atom in the angle (ObjCrystMolAtomParSet)
855
+ angle -- The dihedral angle (radians)
856
+ sigma -- The uncertainty of the dihedral angle (radians)
857
+ delta -- The width of the dihedral angle (radians)
858
+ res -- The pyobjcryst DihedralAngle restraint
859
+ scaled -- A flag indicating if the restraint is scaled (multiplied) by
860
+ the unrestrained point-average chi^2 (chi^2/numpoints) (default
861
+ False)
862
+ """
863
+
864
+ def __init__(
865
+ self, atom1, atom2, atom3, atom4, angle, sigma, delta, scaled=False
866
+ ):
867
+ """Create a dihedral angle restraint.
868
+
869
+ atom1 -- First atom (ObjCrystMolAtomParSet) in the angle
870
+ atom2 -- Second (central) atom (ObjCrystMolAtomParSet) in the angle
871
+ atom3 -- Third (central) atom (ObjCrystMolAtomParSet) in the angle
872
+ atom4 -- Fourth atom in the angle (ObjCrystMolAtomParSet)
873
+ angle -- The dihedral angle (radians)
874
+ sigma -- The uncertainty of the dihedral angle (radians)
875
+ delta -- The width of the dihedral angle (radians)
876
+ scaled -- A flag indicating if the restraint is scaled (multiplied)
877
+ by the unrestrained point-average chi^2 (chi^2/numpoints)
878
+ (default False).
879
+ """
880
+ self.atom1 = atom1
881
+ self.atom2 = atom2
882
+ self.atom3 = atom3
883
+ self.atom4 = atom4
884
+
885
+ m = self.atom1.scat.GetMolecule()
886
+ res = m.AddDihedralAngle(
887
+ atom1.scat, atom2.scat, atom3.scat, atom4.scat, angle, sigma, delta
888
+ )
889
+
890
+ ObjCrystMoleculeRestraint.__init__(self, res, scaled)
891
+ return
892
+
893
+ # Give access to the parameters of the restraint
894
+ angle = property(
895
+ lambda self: self.res.GetAngle0(),
896
+ lambda self, val: self.res.SetAngle0(val),
897
+ )
898
+ sigma = property(
899
+ lambda self: self.res.GetAngleSigma(),
900
+ lambda self, val: self.res.SetAngleSigma(val),
901
+ )
902
+ delta = property(
903
+ lambda self: self.res.GetAngleDelta(),
904
+ lambda self, val: self.res.SetAngleDelta(val),
905
+ )
906
+
907
+
908
+ # End class ObjCrystDihedralAngleRestraint
909
+
910
+
911
+ class StretchModeParameter(Parameter):
912
+ """Partial Parameter class encapsulating pyobjcryst stretch modes.
913
+
914
+ This class relies upon attributes that do not belong to it. Do not
915
+ instantiate this class.
916
+
917
+ Required attributes:
918
+ matoms -- The set of all mutated AtomParSets
919
+ molecule -- The ObjCrystMoleculeParSet the atoms belong to
920
+ mode -- The pyobjcryst.molecule.StretchMode used to change atomic
921
+ positions.
922
+ keepcenter -- Flag indicating whether to keep the center of mass of the
923
+ molecule stationary within the crystal when changing the
924
+ value of the parameter (bool, default True).
925
+ """
926
+
927
+ def __init__(self, name, value=None, const=False):
928
+ """Initialization.
929
+
930
+ name -- The name of this Parameter (must be a valid attribute
931
+ identifier)
932
+ value -- The initial value of this Parameter (default 0).
933
+ const -- A flag inticating whether the Parameter is a constant (like
934
+ pi).
935
+
936
+ Raises ValueError if the name is not a valid attribute identifier
937
+ """
938
+ Parameter.__init__(self, name, value, const)
939
+ self.keepcenter = True
940
+
941
+ def setValue(self, val):
942
+ """Change the value of the Parameter."""
943
+ curval = self.getValue()
944
+ val = float(val)
945
+
946
+ if val == curval:
947
+ return self
948
+
949
+ # The StretchMode expects the change in mutated value.
950
+ delta = val - curval
951
+ self.mode.Stretch(delta, self.keepcenter)
952
+
953
+ # Let Parameter take care of the general details
954
+ Parameter.setValue(self, val)
955
+
956
+ return self
957
+
958
+ def addAtoms(self, atomlist):
959
+ """Associate ObjCrystMolAtomParSets with the Parameter.
960
+
961
+ This will associate additional ObjCrystMolAtomParSets with the
962
+ Parameter. These will be mutated in the exact same way as the
963
+ primary mutated ObjCrystMolAtomParSet. This is useful when a
964
+ group of atoms should move rigidly in response to a change in a
965
+ bond property.
966
+ """
967
+ if not hasattr(atomlist, "__iter__"):
968
+ atomlist = [atomlist]
969
+ # Record the added atoms in the Parameter
970
+ self.matoms.update(atomlist)
971
+ # Make sure we're observing these atoms
972
+ for a in atomlist:
973
+ a.x.addObserver(self._flush)
974
+ a.y.addObserver(self._flush)
975
+ a.z.addObserver(self._flush)
976
+
977
+ # Record the added atoms in the StretchMode
978
+ scatlist = [a.scat for a in atomlist]
979
+ self.mode.AddAtoms(scatlist)
980
+ return self
981
+
982
+ def notify(self, other=()):
983
+ """Notify all mutated Parameters and observers.
984
+
985
+ Some of the mutated parameters will be observing us. At the same
986
+ time we need to observe them. Observable won't let us do both,
987
+ so we notify the Parameters that we mutate directly.
988
+ """
989
+ noneother = ()
990
+ # Notify the atoms that have moved
991
+ for a in self.matoms:
992
+ a.x._flush(noneother)
993
+ a.y._flush(noneother)
994
+ a.z._flush(noneother)
995
+ # Notify the molecule position
996
+ self.molecule.x._flush(noneother)
997
+ self.molecule.y._flush(noneother)
998
+ self.molecule.z._flush(noneother)
999
+
1000
+ # Notify observers
1001
+ Parameter.notify(self, other)
1002
+ return
1003
+
1004
+
1005
+ # End class StretchModeParameter
1006
+
1007
+
1008
+ class ObjCrystBondLengthParameter(StretchModeParameter):
1009
+ """Class for abstracting a bond length in a Molecule to a Parameter.
1010
+
1011
+ This wraps up a pyobjcryst.molecule.StretchModeBondLength object so that
1012
+ the distance between two MolAtoms in a Molecule can be used as an
1013
+ adjustable Parameter. When a bond length is adjusted, the second MolAtom is
1014
+ moved, and the absolute position of the Molecule is altered to preserve the
1015
+ location of the center of mass within the Crystal. Thus, the x, y and z
1016
+ Parameters of the MolAtom and its parent Molecule are altered. This can be
1017
+ changed by setting the 'keepcenter' attribute of the parameter to False.
1018
+
1019
+ This Parameter makes it possible to mutate a MolAtom multiple times in a
1020
+ single refinement step. If these mutations are not orthogonal, then this
1021
+ could lead to nonconvergence of a fit, depending on the optimizer. Consider
1022
+ mutating atom2 of a bond directly, and via a ObjCrystBondLengthParameter.
1023
+ The two mutations of atom2 may be determined independently by the
1024
+ optimizer, in which case the composed mutation will have an unexpected
1025
+ effect on the residual. It is best practice to either modify MolAtom
1026
+ positions directly, or thorough BondLengthParameters, BondAngleParameters
1027
+ and DihedralAngleParameters (which are mutually orthogonal).
1028
+
1029
+ Note that by making a ObjCrystBondLengthParameter constant it also makes
1030
+ the underlying ObjCrystMolAtomParSets constant. When setting it as
1031
+ nonconstant, each ObjCrystMolAtomParSet is set nonconstant. Changing the
1032
+ bond length changes the position of the second MolAtom and Molecule, even
1033
+ if either is set as constant.
1034
+
1035
+ Attributes:
1036
+ atom1 -- The first ObjCrystMolAtomParSet in the bond
1037
+ atom2 -- The second (mutated) ObjCrystMolAtomParSet in the bond
1038
+ matoms -- The set of all mutated ObjCrystMolAtomParSets
1039
+ molecule -- The ObjCrystMoleculeParSet the ObjCrystMolAtomParSets
1040
+ belong to
1041
+ mode -- The pyobjcryst.molecule.StretchModeBondLength for the bond
1042
+
1043
+ Inherited Attributes
1044
+ name -- A name for this Parameter.
1045
+ const -- A flag indicating whether this is considered a constant.
1046
+ _value -- The value of the Parameter. Modified with 'setValue'.
1047
+ value -- Property for 'getValue' and 'setValue'.
1048
+ constraint -- A callable that calculates the value of this Parameter. If
1049
+ this is None (None), the the Parameter is responsible for its
1050
+ own value. The callable takes no arguments.
1051
+ bounds -- A 2-list defining the bounds on the Parameter. This can be
1052
+ used by some optimizers when the Parameter is varied.
1053
+ """
1054
+
1055
+ def __init__(self, name, atom1, atom2, value=None, const=False, mode=None):
1056
+ """Create a ObjCrystBondLengthParameter.
1057
+
1058
+ name -- The name of the ObjCrystBondLengthParameter
1059
+ atom1 -- The first atom (ObjCrystMolAtomParSet) in the bond
1060
+ atom2 -- The second (mutated) atom (ObjCrystMolAtomParSet) in the
1061
+ bond
1062
+ value -- An initial value for the bond length. If this is None
1063
+ (default), then the current distance between the atoms will
1064
+ be used.
1065
+ const -- A flag indicating whether the Parameter is constant
1066
+ (default False)
1067
+ mode -- An extant pyobjcryst.molecule.StretchModeBondLength to use.
1068
+ If this is None (default), then a new StretchModeBondLength
1069
+ will be built.
1070
+ """
1071
+
1072
+ # Create the mode
1073
+ self.mode = mode
1074
+ if mode is None:
1075
+ self.mode = StretchModeBondLength(atom1.scat, atom2.scat, None)
1076
+ # We only add the last atom. This is the one that will move
1077
+ self.mode.AddAtom(atom2.scat)
1078
+ self.matoms = set([atom2])
1079
+
1080
+ # Observe the atom positions
1081
+ for a in [atom1, atom2]:
1082
+ a.x.addObserver(self._flush)
1083
+ a.y.addObserver(self._flush)
1084
+ a.z.addObserver(self._flush)
1085
+
1086
+ self.atom1 = atom1
1087
+ self.atom2 = atom2
1088
+ self.molecule = atom1.parent
1089
+
1090
+ # We do this last so the atoms are defined before we set any values.
1091
+ if value is None:
1092
+ value = GetBondLength(atom1.scat, atom2.scat)
1093
+ StretchModeParameter.__init__(self, name, value, const)
1094
+ self.setConst(const)
1095
+
1096
+ return
1097
+
1098
+ def setConst(self, const=True, value=None):
1099
+ """Toggle the Parameter as constant.
1100
+
1101
+ This sets the underlying ObjCrystMolAtomParSet positions const as well.
1102
+
1103
+ const -- Flag indicating if the Parameter is constant (default
1104
+ True).
1105
+ value -- An optional value for the Parameter (default None). If this
1106
+ is not None, then the Parameter will get a new value,
1107
+ constant or otherwise.
1108
+ """
1109
+ StretchModeParameter.setConst(self, const, value)
1110
+
1111
+ for a in [self.atom1, self.atom2]:
1112
+ a.x.setConst(const)
1113
+ a.y.setConst(const)
1114
+ a.z.setConst(const)
1115
+ return self
1116
+
1117
+ def getValue(self):
1118
+ """This calculates the value if it might have been changed.
1119
+
1120
+ There is no guarantee that the ObjCrystMolAtomParSets underlying
1121
+ the bond won't change, so the bond length is calculated if
1122
+ necessary each time this is called.
1123
+ """
1124
+ if self._value is None:
1125
+ val = GetBondLength(self.atom1.scat, self.atom2.scat)
1126
+ Parameter.setValue(self, val)
1127
+
1128
+ return self._value
1129
+
1130
+
1131
+ # End class ObjCrystBondLengthParameter
1132
+
1133
+
1134
+ class ObjCrystBondAngleParameter(StretchModeParameter):
1135
+ """Class for abstracting a bond angle in a Molecule to a Parameter.
1136
+
1137
+ This wraps up a pyobjcryst.molecule.StretchModeBondAngle object so that the
1138
+ angle defined by three MolAtoms in a Molecule can be used as an adjustable
1139
+ Parameter. When a bond angle is adjusted, the third MolAtom is moved, and
1140
+ the absolute position of the Molecule is altered to preserve the location
1141
+ of the center of mass within the crystal. This can be changed by setting
1142
+ the 'keepcenter' attribute of the parameter to False.
1143
+
1144
+ See precautions in the ObjCrystBondLengthParameter class.
1145
+
1146
+ Attributes
1147
+ atom1 -- The first ObjCrystAtomParSet in the bond angle
1148
+ atom2 -- The second (central) ObjCrystMolAtomParSet in the bond angle
1149
+ atom3 -- The third (mutated) ObjCrystMolAtomParSet in the bond angle
1150
+ matoms -- The set of all mutated ObjCrystMolAtomParSets
1151
+ molecule -- The ObjCrystMoleculeParSet the ObjCrystMolAtomParSets
1152
+ belong to
1153
+ mode -- The pyobjcryst.molecule.StretchModeBondAngle for the bond angle
1154
+
1155
+ Inherited Attributes
1156
+ name -- A name for this Parameter.
1157
+ const -- A flag indicating whether this is considered a constant.
1158
+ _value -- The value of the Parameter. Modified with 'setValue'.
1159
+ value -- Property for 'getValue' and 'setValue'.
1160
+ constraint -- A callable that calculates the value of this Parameter. If
1161
+ this is None (None), the the Parameter is responsible for its
1162
+ own value. The callable takes no arguments.
1163
+ bounds -- A 2-list defining the bounds on the Parameter. This can be
1164
+ used by some optimizers when the Parameter is varied.
1165
+ """
1166
+
1167
+ def __init__(
1168
+ self, name, atom1, atom2, atom3, value=None, const=False, mode=None
1169
+ ):
1170
+ """Create a ObjCrystBondAngleParameter.
1171
+
1172
+ name -- The name of the ObjCrystBondAngleParameter.
1173
+ atom1 -- The first atom (ObjCrystMolAtomParSet) in the bond angle
1174
+ atom2 -- The second (central) atom (ObjCrystMolAtomParSet) in the
1175
+ bond angle
1176
+ atom3 -- The third (mutated) atom (ObjCrystMolAtomParSet) in the
1177
+ bond angle
1178
+ value -- An initial value for the bond length. If this is None
1179
+ (default), then the current bond angle between the atoms
1180
+ will be used.
1181
+ const -- A flag indicating whether the Parameter is constant
1182
+ (default False).
1183
+ mode -- A pre-built mode to place in this Parameter. If this is
1184
+ None (default), then a StretchMode will be built.
1185
+ """
1186
+
1187
+ # Create the stretch mode
1188
+ self.mode = mode
1189
+ if mode is None:
1190
+ self.mode = StretchModeBondAngle(
1191
+ atom1.scat, atom2.scat, atom3.scat, None
1192
+ )
1193
+ # We only add the last atom. This is the one that will move
1194
+ self.mode.AddAtom(atom3.scat)
1195
+ self.matoms = set([atom3])
1196
+
1197
+ # Observe the atom positions
1198
+ for a in [atom1, atom2, atom3]:
1199
+ a.x.addObserver(self._flush)
1200
+ a.y.addObserver(self._flush)
1201
+ a.z.addObserver(self._flush)
1202
+
1203
+ self.atom1 = atom1
1204
+ self.atom2 = atom2
1205
+ self.atom3 = atom3
1206
+ self.molecule = atom1.parent
1207
+
1208
+ # We do this last so the atoms are defined before we set any values.
1209
+ if value is None:
1210
+ value = GetBondAngle(atom1.scat, atom2.scat, atom3.scat)
1211
+ StretchModeParameter.__init__(self, name, value, const)
1212
+ self.setConst(const)
1213
+
1214
+ return
1215
+
1216
+ def setConst(self, const=True, value=None):
1217
+ """Toggle the Parameter as constant.
1218
+
1219
+ This sets the underlying ObjCrystMolAtomParSet positions const as well.
1220
+
1221
+ const -- Flag indicating if the Parameter is constant (default
1222
+ True).
1223
+ value -- An optional value for the Parameter (default None). If this
1224
+ is not None, then the Parameter will get a new value,
1225
+ constant or otherwise.
1226
+ """
1227
+ StretchModeParameter.setConst(self, const, value)
1228
+ for a in [self.atom1, self.atom2, self.atom3]:
1229
+ a.x.setConst(const)
1230
+ a.y.setConst(const)
1231
+ a.z.setConst(const)
1232
+ return self
1233
+
1234
+ def getValue(self):
1235
+ """This calculates the value if it might have been changed.
1236
+
1237
+ There is no guarantee that the MolAtoms underlying the bond
1238
+ angle won't change, so the bond angle is calculated if necessary
1239
+ each time this is called.
1240
+ """
1241
+ if self._value is None:
1242
+ val = GetBondAngle(
1243
+ self.atom1.scat, self.atom2.scat, self.atom3.scat
1244
+ )
1245
+ Parameter.setValue(self, val)
1246
+
1247
+ return self._value
1248
+
1249
+
1250
+ # End class ObjCrystBondAngleParameter
1251
+
1252
+
1253
+ class ObjCrystDihedralAngleParameter(StretchModeParameter):
1254
+ """Class for abstracting a dihedral angle in a Molecule to a Parameter.
1255
+
1256
+ This wraps up a pyobjcryst.molecule.StretchModeTorsion object so that the
1257
+ angle defined by four MolAtoms ([a1-a2].[a3-a4]) in a Molecule can be used
1258
+ as an adjustable parameter. When a dihedral angle is adjusted, the fourth
1259
+ MolAtom is moved, and the absolute position of the Molecule is altered to
1260
+ preserve the location of the center of mass within the crystal. This can
1261
+ be changed by setting the 'keepcenter' attribute of the parameter to False.
1262
+
1263
+ See precautions in the ObjCrystBondLengthParameter class.
1264
+
1265
+ Attributes
1266
+ atom1 -- The first ObjCrystMolAtomParSet in the dihedral angle
1267
+ atom2 -- The second (central) ObjCrystMolAtomParSet in the dihedral
1268
+ angle
1269
+ atom3 -- The third (central) ObjCrystMolAtomParSet in the dihedral angle
1270
+ atom4 -- The fourth (mutated) ObjCrystMolAtomParSet in the dihedral
1271
+ angle
1272
+ matoms -- The set of all mutated ObjCrystMolAtomParSets
1273
+ molecule -- The ObjCrystMoleculeParSet the atoms belong to
1274
+ mode -- The pyobjcryst.molecule.StretchModeTorsion for the dihedral
1275
+ angle
1276
+
1277
+ Inherited Attributes
1278
+ name -- A name for this Parameter.
1279
+ const -- A flag indicating whether this is considered a constant.
1280
+ _value -- The value of the Parameter. Modified with 'setValue'.
1281
+ value -- Property for 'getValue' and 'setValue'.
1282
+ constraint -- A callable that calculates the value of this Parameter. If
1283
+ this is None (None), the the Parameter is responsible for its
1284
+ own value. The callable takes no arguments.
1285
+ bounds -- A 2-list defining the bounds on the Parameter. This can be
1286
+ used by some optimizers when the Parameter is varied.
1287
+ """
1288
+
1289
+ def __init__(
1290
+ self,
1291
+ name,
1292
+ atom1,
1293
+ atom2,
1294
+ atom3,
1295
+ atom4,
1296
+ value=None,
1297
+ const=False,
1298
+ mode=None,
1299
+ ):
1300
+ """Create a ObjCrystDihedralAngleParameter.
1301
+
1302
+ name -- The name of the ObjCrystDihedralAngleParameter
1303
+ atom1 -- The first atom (ObjCrystMolAtomParSet) in the dihderal
1304
+ angle
1305
+ atom2 -- The second (central) atom (ObjCrystMolAtomParSet) in the
1306
+ dihderal angle
1307
+ atom3 -- The third (central) atom (ObjCrystMolAtomParSet) in the
1308
+ dihderal angle
1309
+ atom4 -- The fourth (mutated) atom (ObjCrystMolAtomParSet) in the
1310
+ dihderal angle
1311
+ value -- An initial value for the bond length. If this is None
1312
+ (default), then the current dihedral angle between atoms
1313
+ will be used.
1314
+ const -- A flag indicating whether the Parameter is constant
1315
+ (default False).
1316
+ mode -- A pre-built mode to place in this Parameter. If this is
1317
+ None (default), then a StretchMode will be built.
1318
+ """
1319
+
1320
+ # Create the stretch mode
1321
+ self.mode = mode
1322
+ if mode is None:
1323
+ self.mode = StretchModeTorsion(atom2.scat, atom3.scat, None)
1324
+ # We only add the last atom. This is the one that will move
1325
+ self.mode.AddAtom(atom4.scat)
1326
+ self.matoms = set([atom4])
1327
+
1328
+ # Observe the atom positions
1329
+ for a in [atom1, atom2, atom3, atom4]:
1330
+ a.x.addObserver(self._flush)
1331
+ a.y.addObserver(self._flush)
1332
+ a.z.addObserver(self._flush)
1333
+
1334
+ self.atom1 = atom1
1335
+ self.atom2 = atom2
1336
+ self.atom3 = atom3
1337
+ self.atom4 = atom4
1338
+ self.molecule = atom1.parent
1339
+
1340
+ # We do this last so the atoms are defined before we set any values.
1341
+ if value is None:
1342
+ value = GetDihedralAngle(
1343
+ atom1.scat, atom2.scat, atom3.scat, atom4.scat
1344
+ )
1345
+ StretchModeParameter.__init__(self, name, value, const)
1346
+ self.setConst(const)
1347
+
1348
+ return
1349
+
1350
+ def setConst(self, const=True, value=None):
1351
+ """Toggle the Parameter as constant.
1352
+
1353
+ This sets the underlying ObjCrystMolAtomParSet positions const as well.
1354
+
1355
+ const -- Flag indicating if the Parameter is constant (default
1356
+ True).
1357
+ value -- An optional value for the Parameter (default None). If this
1358
+ is not None, then the Parameter will get a new value,
1359
+ constant or otherwise.
1360
+ """
1361
+ StretchModeParameter.setConst(self, const, value)
1362
+ for a in [self.atom1, self.atom2, self.atom3, self.atom4]:
1363
+ a.x.setConst(const)
1364
+ a.y.setConst(const)
1365
+ a.z.setConst(const)
1366
+ return self
1367
+
1368
+ def getValue(self):
1369
+ """This calculates the value if it might have been changed.
1370
+
1371
+ There is no guarantee that the ObjCrystMolAtomParSets underlying
1372
+ the dihedral angle won't change from some other Parameter, so
1373
+ the value is recalculated each time.
1374
+ """
1375
+ if self._value is None:
1376
+ val = GetDihedralAngle(
1377
+ self.atom1.scat,
1378
+ self.atom2.scat,
1379
+ self.atom3.scat,
1380
+ self.atom4.scat,
1381
+ )
1382
+ Parameter.setValue(self, val)
1383
+
1384
+ return self._value
1385
+
1386
+
1387
+ # End class ObjCrystDihedralAngleParameter
1388
+
1389
+
1390
+ class ObjCrystCrystalParSet(SrRealParSet):
1391
+ """A adaptor for pyobjcryst.crystal.Crystal instance.
1392
+
1393
+ This class derives from diffpy.srfit.fitbase.parameterset.ParameterSet. See
1394
+ this class for base attributes.
1395
+
1396
+ Attributes:
1397
+ stru -- The adapted pyobjcryst.Crystal.
1398
+ scatterers -- The list of aggregated ScattererParSets (either
1399
+ ObjCrystAtomParSet or ObjCrystMoleculeParSet), provided for
1400
+ convenience.
1401
+ _sgpars -- A BaseSpaceGroupParameters object containing free structure
1402
+ Parameters. See the diffpy.srfit.structure.sgconstraints
1403
+ module.
1404
+ sgpars -- property that creates _sgpars when it is needed.
1405
+ angunits -- "rad", the units of angle
1406
+
1407
+ Managed Parameters:
1408
+ a, b, c, alpha, beta, gamma -- Lattice parameters (ParameterAdapter)
1409
+
1410
+ Managed ParameterSets:
1411
+ <sname> -- A ObjCrystScattererParSet (either ObjCrystAtomParSet or
1412
+ ObjCrystMoleculeParSet), where <sname> is the name of the
1413
+ adapted pyobjcryst.atom.Atom or
1414
+ pyobjcryst.molecule.Molecule.
1415
+ """
1416
+
1417
+ def __init__(self, name, cryst):
1418
+ """Initialize.
1419
+
1420
+ name -- A name for this ParameterSet
1421
+ cryst -- An pyobjcryst.Crystal instance.
1422
+ """
1423
+ SrRealParSet.__init__(self, name)
1424
+ self.angunits = "rad"
1425
+ self.stru = cryst
1426
+ self._sgpars = None
1427
+
1428
+ self.addParameter(ParameterAdapter("a", self.stru, attr="a"))
1429
+ self.addParameter(ParameterAdapter("b", self.stru, attr="b"))
1430
+ self.addParameter(ParameterAdapter("c", self.stru, attr="c"))
1431
+ self.addParameter(ParameterAdapter("alpha", self.stru, attr="alpha"))
1432
+ self.addParameter(ParameterAdapter("beta", self.stru, attr="beta"))
1433
+ self.addParameter(ParameterAdapter("gamma", self.stru, attr="gamma"))
1434
+
1435
+ # Now we must loop over the scatterers and create parameter sets from
1436
+ # them.
1437
+ self.scatterers = []
1438
+ snames = []
1439
+
1440
+ for j in range(self.stru.GetNbScatterer()):
1441
+ s = self.stru.GetScatt(j)
1442
+ name = s.GetName()
1443
+ if not name:
1444
+ raise ValueError("Each Scatterer must have a name")
1445
+ if name in snames:
1446
+ raise ValueError("Scatterer name '%s' is duplicated" % name)
1447
+
1448
+ # Now create the proper object
1449
+ cname = s.GetClassName()
1450
+ if cname == "Atom":
1451
+ parset = ObjCrystAtomParSet(name, s, self)
1452
+ elif cname == "Molecule":
1453
+ parset = ObjCrystMoleculeParSet(name, s, self)
1454
+ else:
1455
+ raise TypeError("Unrecognized scatterer '%s'" % cname)
1456
+
1457
+ self.addParameterSet(parset)
1458
+ self.scatterers.append(parset)
1459
+ snames.append(name)
1460
+
1461
+ return
1462
+
1463
+ def _constrainSpaceGroup(self):
1464
+ """Constrain the space group."""
1465
+ if self._sgpars is not None:
1466
+ return self._sgpars
1467
+ sg = self._createSpaceGroup(self.stru.GetSpaceGroup())
1468
+ from diffpy.srfit.structure.sgconstraints import _constrainAsSpaceGroup
1469
+
1470
+ adpsymbols = ["B11", "B22", "B33", "B12", "B13", "B23"]
1471
+ isosymbol = "Biso"
1472
+ sgoffset = [0, 0, 0]
1473
+ self._sgpars = _constrainAsSpaceGroup(
1474
+ self,
1475
+ sg,
1476
+ self.scatterers,
1477
+ sgoffset,
1478
+ adpsymbols=adpsymbols,
1479
+ isosymbol=isosymbol,
1480
+ )
1481
+ return self._sgpars
1482
+
1483
+ sgpars = property(_constrainSpaceGroup)
1484
+
1485
+ @staticmethod
1486
+ def _createSpaceGroup(sgobjcryst):
1487
+ """Create a diffpy.structure SpaceGroup object from pyobjcryst.
1488
+
1489
+ sgobjcryst -- A pyobjcryst.spacegroup.SpaceGroup instance.
1490
+
1491
+ This uses the actual space group operations from the
1492
+ pyobjcryst.spacegroup.SpaceGroup instance so there is no ambiguity
1493
+ about the actual space group.
1494
+ """
1495
+ import copy
1496
+
1497
+ from diffpy.structure.spacegroups import GetSpaceGroup, SymOp
1498
+
1499
+ name = sgobjcryst.GetName()
1500
+ extnstr = ":%s" % sgobjcryst.GetExtension()
1501
+ if name.endswith(extnstr):
1502
+ name = name[: -len(extnstr)]
1503
+
1504
+ # Get whatever spacegroup we can get by name. This will set the proper
1505
+ # crystal system. Creating a copy of the singleton from GetSpaceGroup,
1506
+ # as this function messes with sg.symop_list.
1507
+ sg = copy.copy(GetSpaceGroup(name))
1508
+
1509
+ # Replace the symmetry operations to guarantee that we get it right.
1510
+ symops = sgobjcryst.GetSymmetryOperations()
1511
+ tranops = sgobjcryst.GetTranslationVectors()
1512
+ sg.symop_list = []
1513
+
1514
+ for trans in tranops:
1515
+ for shift, rot in symops:
1516
+ tv = trans + shift
1517
+ tv -= numpy.floor(tv)
1518
+ sg.symop_list.append(SymOp(rot, tv))
1519
+
1520
+ if sgobjcryst.IsCentrosymmetric():
1521
+ center = sgobjcryst.GetInversionCenter()
1522
+ for trans in tranops:
1523
+ for shift, rot in symops:
1524
+ tv = center - trans - shift
1525
+ tv -= numpy.floor(tv)
1526
+ sg.symop_list.append(SymOp(-rot, tv))
1527
+
1528
+ return sg
1529
+
1530
+ @classmethod
1531
+ def canAdapt(self, stru):
1532
+ """Return whether the structure can be adapted by this class."""
1533
+ from pyobjcryst.crystal import Crystal
1534
+
1535
+ return isinstance(stru, Crystal)
1536
+
1537
+ def getLattice(self):
1538
+ """Get the ParameterSet containing the lattice Parameters."""
1539
+ return self
1540
+
1541
+ def getScatterers(self):
1542
+ """Get a list of ParameterSets that represents the scatterers.
1543
+
1544
+ The site positions must be accessible from the list entries via
1545
+ the names "x", "y", and "z". The ADPs must be accessible as
1546
+ well, but the name and nature of the ADPs (U-factors, B-factors,
1547
+ isotropic, anisotropic) depends on the adapted structure.
1548
+ """
1549
+ return self.scatterers
1550
+
1551
+
1552
+ # End class ObjCrystCrystalParSet