1 | # -*- coding: utf-8 -*- |
---|
2 | #GSASIImath - major mathematics routines |
---|
3 | ########### SVN repository information ################### |
---|
4 | # $Date: 2023-07-31 15:49:24 +0000 (Mon, 31 Jul 2023) $ |
---|
5 | # $Author: toby $ |
---|
6 | # $Revision: 5639 $ |
---|
7 | # $URL: trunk/GSASIImath.py $ |
---|
8 | # $Id: GSASIImath.py 5639 2023-07-31 15:49:24Z toby $ |
---|
9 | ########### SVN repository information ################### |
---|
10 | ''' |
---|
11 | Routines defined in :mod:`GSASIImath` follow. |
---|
12 | ''' |
---|
13 | |
---|
14 | from __future__ import division, print_function |
---|
15 | import random as rn |
---|
16 | import numpy as np |
---|
17 | import numpy.linalg as nl |
---|
18 | import numpy.ma as ma |
---|
19 | import time |
---|
20 | import math |
---|
21 | import copy |
---|
22 | import GSASIIpath |
---|
23 | GSASIIpath.SetVersionNumber("$Revision: 5639 $") |
---|
24 | import GSASIIElem as G2el |
---|
25 | import GSASIIlattice as G2lat |
---|
26 | import GSASIIspc as G2spc |
---|
27 | import GSASIIpwd as G2pwd |
---|
28 | import GSASIIobj as G2obj |
---|
29 | import GSASIIfiles as G2fil |
---|
30 | import numpy.fft as fft |
---|
31 | import scipy.optimize as so |
---|
32 | try: |
---|
33 | import pypowder as pwd |
---|
34 | except ImportError: |
---|
35 | print ('pypowder is not available - profile calcs. not allowed') |
---|
36 | |
---|
37 | sind = lambda x: np.sin(x*np.pi/180.) |
---|
38 | cosd = lambda x: np.cos(x*np.pi/180.) |
---|
39 | tand = lambda x: np.tan(x*np.pi/180.) |
---|
40 | asind = lambda x: 180.*np.arcsin(x)/np.pi |
---|
41 | acosd = lambda x: 180.*np.arccos(x)/np.pi |
---|
42 | atand = lambda x: 180.*np.arctan(x)/np.pi |
---|
43 | atan2d = lambda y,x: 180.*np.arctan2(y,x)/np.pi |
---|
44 | try: # fails on doc build |
---|
45 | twopi = 2.0*np.pi |
---|
46 | twopisq = 2.0*np.pi**2 |
---|
47 | _double_min = np.finfo(float).min |
---|
48 | _double_max = np.finfo(float).max |
---|
49 | except TypeError: |
---|
50 | pass |
---|
51 | nxs = np.newaxis |
---|
52 | |
---|
53 | ################################################################################ |
---|
54 | #### Hessian least-squares Levenberg-Marquardt routine |
---|
55 | ################################################################################ |
---|
56 | class G2NormException(Exception): pass |
---|
57 | |
---|
58 | def pinv(a, rcond=1e-15 ): |
---|
59 | ''' |
---|
60 | Compute the (Moore-Penrose) pseudo-inverse of a matrix. |
---|
61 | Modified from numpy.linalg.pinv; assumes a is Hessian & returns no. zeros found |
---|
62 | Calculate the generalized inverse of a matrix using its |
---|
63 | singular-value decomposition (SVD) and including all |
---|
64 | *large* singular values. |
---|
65 | |
---|
66 | :param array a: (M, M) array_like - here assumed to be LS Hessian |
---|
67 | Matrix to be pseudo-inverted. |
---|
68 | :param float rcond: Cutoff for small singular values. |
---|
69 | Singular values smaller (in modulus) than |
---|
70 | `rcond` * largest_singular_value (again, in modulus) |
---|
71 | are set to zero. |
---|
72 | |
---|
73 | :returns: B : (M, M) ndarray |
---|
74 | The pseudo-inverse of `a` |
---|
75 | |
---|
76 | Raises: LinAlgError |
---|
77 | If the SVD computation does not converge. |
---|
78 | |
---|
79 | Notes: |
---|
80 | The pseudo-inverse of a matrix A, denoted :math:`A^+`, is |
---|
81 | defined as: "the matrix that 'solves' [the least-squares problem] |
---|
82 | :math:`Ax = b`," i.e., if :math:`\\bar{x}` is said solution, then |
---|
83 | :math:`A^+` is that matrix such that :math:`\\bar{x} = A^+b`. |
---|
84 | |
---|
85 | It can be shown that if :math:`Q_1 \\Sigma Q_2^T = A` is the singular |
---|
86 | value decomposition of A, then |
---|
87 | :math:`A^+ = Q_2 \\Sigma^+ Q_1^T`, where :math:`Q_{1,2}` are |
---|
88 | orthogonal matrices, :math:`\\Sigma` is a diagonal matrix consisting |
---|
89 | of A's so-called singular values, (followed, typically, by |
---|
90 | zeros), and then :math:`\\Sigma^+` is simply the diagonal matrix |
---|
91 | consisting of the reciprocals of A's singular values |
---|
92 | (again, followed by zeros). [1] |
---|
93 | |
---|
94 | References: |
---|
95 | .. [1] G. Strang, *Linear Algebra and Its Applications*, 2nd Ed., Orlando, FL, Academic Press, Inc., 1980, pp. 139-142. |
---|
96 | |
---|
97 | ''' |
---|
98 | u, s, vt = nl.svd(a) |
---|
99 | cutoff = rcond*np.maximum.reduce(s) |
---|
100 | s = np.where(s>cutoff,1./s,0.) |
---|
101 | nzero = s.shape[0]-np.count_nonzero(s) |
---|
102 | res = np.dot(vt.T,s[:,nxs]*u.T) |
---|
103 | return res,nzero |
---|
104 | |
---|
105 | def dropTerms(bad, hessian, indices, *vectors): |
---|
106 | '''Remove the 'bad' terms from the Hessian and vector |
---|
107 | |
---|
108 | :param tuple bad: a list of variable (row/column) numbers that should be |
---|
109 | removed from the hessian and vector. Example: (0,3) removes the 1st and |
---|
110 | 4th column/row |
---|
111 | :param np.array hessian: a square matrix of length n x n |
---|
112 | :param np.array indices: the indices of the least-squares vector of length n |
---|
113 | referenced to the initial variable list; as this routine is called |
---|
114 | multiple times, more terms may be removed from this list |
---|
115 | :param additional-args: various least-squares model values, length n |
---|
116 | |
---|
117 | :returns: hessian, indices, vector0, vector1,... where the lengths are |
---|
118 | now n' x n' and n', with n' = n - len(bad) |
---|
119 | ''' |
---|
120 | out = [np.delete(np.delete(hessian,bad,1),bad,0),np.delete(indices,bad)] |
---|
121 | for v in vectors: |
---|
122 | out.append(np.delete(v,bad)) |
---|
123 | return out |
---|
124 | |
---|
125 | def setHcorr(info,Amat,xtol,problem=False): |
---|
126 | '''Find & report high correlation terms in covariance matrix |
---|
127 | ''' |
---|
128 | Adiag = np.sqrt(np.diag(Amat)) |
---|
129 | if np.any(np.abs(Adiag) < 1.e-14): raise G2NormException # test for any hard singularities |
---|
130 | Anorm = np.outer(Adiag,Adiag) |
---|
131 | Amat = Amat/Anorm |
---|
132 | Bmat,Nzeros = pinv(Amat,xtol) #Moore-Penrose inversion (via SVD) & count of zeros |
---|
133 | Bmat = Bmat/Anorm |
---|
134 | sig = np.sqrt(np.diag(Bmat)) |
---|
135 | xvar = np.outer(sig,np.ones_like(sig)) |
---|
136 | AcovUp = abs(np.triu(np.divide(np.divide(Bmat,xvar),xvar.T),1)) # elements above diagonal |
---|
137 | if Nzeros or problem: # something is wrong, so report what is found |
---|
138 | m = min(0.99,0.99*np.amax(AcovUp)) |
---|
139 | else: |
---|
140 | m = max(0.95,0.99*np.amax(AcovUp)) |
---|
141 | info['Hcorr'] = [(i,j,AcovUp[i,j]) for i,j in zip(*np.where(AcovUp > m))] |
---|
142 | return Bmat,Nzeros |
---|
143 | |
---|
144 | def setSVDwarn(info,Amat,Nzeros,indices): |
---|
145 | '''Find & report terms causing SVN zeros |
---|
146 | ''' |
---|
147 | if Nzeros == 0: return |
---|
148 | d = np.abs(np.diag(nl.qr(Amat)[1])) |
---|
149 | svdsing = list(np.where(d < 1.e-14)[0]) |
---|
150 | if len(svdsing) < Nzeros: # try to get the Nzeros worst terms |
---|
151 | svdsing = list(np.where(d <= sorted(d)[Nzeros-1])[0]) |
---|
152 | |
---|
153 | |
---|
154 | if not len(svdsing): # make sure at least the worst term is shown |
---|
155 | svdsing = [np.argmin(d)] |
---|
156 | info['SVDsing'] = [indices[i] for i in svdsing] |
---|
157 | |
---|
158 | def HessianLSQ(func,x0,Hess,args=(),ftol=1.49012e-8,xtol=1.e-6, maxcyc=0,lamda=-3,Print=False,refPlotUpdate=None): |
---|
159 | ''' |
---|
160 | Minimize the sum of squares of a function (:math:`f`) evaluated on a series of |
---|
161 | values (y): :math:`\\sum_{y=0}^{N_{obs}} f(y,{args})` |
---|
162 | where :math:`x = arg min(\\sum_{y=0}^{N_{obs}} (func(y)^2,axis=0))` |
---|
163 | |
---|
164 | :param function func: callable method or function |
---|
165 | should take at least one (possibly length N vector) argument and |
---|
166 | returns M floating point numbers. |
---|
167 | :param np.ndarray x0: The starting estimate for the minimization of length N |
---|
168 | :param function Hess: callable method or function |
---|
169 | A required function or method to compute the weighted vector and Hessian for func. |
---|
170 | It must be a symmetric NxN array |
---|
171 | :param tuple args: Any extra arguments to func are placed in this tuple. |
---|
172 | :param float ftol: Relative error desired in the sum of squares. |
---|
173 | :param float xtol: Relative tolerance of zeros in the SVD solution in nl.pinv. |
---|
174 | :param int maxcyc: The maximum number of cycles of refinement to execute, if -1 refine |
---|
175 | until other limits are met (ftol, xtol) |
---|
176 | :param int lamda: initial Marquardt lambda=10**lamda |
---|
177 | :param bool Print: True for printing results (residuals & times) by cycle |
---|
178 | |
---|
179 | :returns: (x,cov_x,infodict) where |
---|
180 | |
---|
181 | * x : ndarray |
---|
182 | The solution (or the result of the last iteration for an unsuccessful |
---|
183 | call). |
---|
184 | * cov_x : ndarray |
---|
185 | Uses the fjac and ipvt optional outputs to construct an |
---|
186 | estimate of the jacobian around the solution. ``None`` if a |
---|
187 | singular matrix encountered (indicates very flat curvature in |
---|
188 | some direction). This matrix must be multiplied by the |
---|
189 | residual standard deviation to get the covariance of the |
---|
190 | parameter estimates -- see curve_fit. |
---|
191 | * infodict : dict, a dictionary of optional outputs with the keys: |
---|
192 | |
---|
193 | * 'fvec' : the function evaluated at the output |
---|
194 | * 'num cyc': |
---|
195 | * 'nfev': number of objective function evaluation calls |
---|
196 | * 'lamMax': |
---|
197 | * 'psing': list of variable variables that have been removed from the refinement |
---|
198 | * 'SVD0': -1 for singlar matrix, -2 for objective function exception, Nzeroes = # of SVD 0's |
---|
199 | * 'Hcorr': list entries (i,j,c) where i & j are of highly correlated variables & c is correlation coeff. |
---|
200 | ''' |
---|
201 | ifConverged = False |
---|
202 | deltaChi2 = -10. |
---|
203 | x0 = np.array(x0, ndmin=1, dtype=np.float64) # make sure that x0 float 1-D |
---|
204 | # array (in case any parameters were set to int) |
---|
205 | n = len(x0) |
---|
206 | if type(args) != type(()): |
---|
207 | args = (args,) |
---|
208 | dlg = None |
---|
209 | if hasattr(args[-1],'Update'): dlg = args[-1] |
---|
210 | if hasattr(dlg,'AdvanceCycle'): dlg.AdvanceCycle(-1) |
---|
211 | |
---|
212 | icycle = 0 |
---|
213 | AmatAll = None # changed only if cycles > 0 |
---|
214 | lamMax = lam = 0 # start w/o Marquardt and add it in if needed |
---|
215 | nfev = 0 |
---|
216 | if Print: |
---|
217 | G2fil.G2Print(' Hessian Levenberg-Marquardt SVD refinement on %d variables:'%(n)) |
---|
218 | XvecAll = np.zeros(n) |
---|
219 | try: |
---|
220 | M2 = func(x0,*args) |
---|
221 | except Exception as Msg: |
---|
222 | if not hasattr(Msg,'msg'): Msg.msg = str(Msg) |
---|
223 | G2fil.G2Print('\nouch #0 unable to evaluate initial objective function\nCheck for an invalid parameter value',mode='error') |
---|
224 | G2fil.G2Print('Use Calculate/"View LS parms" to list all parameter values\n',mode='warn') |
---|
225 | G2fil.G2Print('Error message: '+Msg.msg,mode='warn') |
---|
226 | if GSASIIpath.GetConfigValue('debug'): |
---|
227 | import traceback |
---|
228 | print(traceback.format_exc()) |
---|
229 | raise G2obj.G2Exception('HessianLSQ -- ouch #0: look for invalid parameter (see console)') |
---|
230 | chisq00 = np.sum(M2**2) # starting Chi**2 |
---|
231 | Nobs = len(M2) |
---|
232 | if Print: |
---|
233 | G2fil.G2Print('initial chi^2 %.5g with %d obs.'%(chisq00,Nobs)) |
---|
234 | if n == 0: |
---|
235 | info = {'num cyc':0,'fvec':M2,'nfev':1,'lamMax':0,'psing':[],'SVD0':0} |
---|
236 | info['msg'] = 'no variables: skipping refinement\n' |
---|
237 | info.update({'Converged':True, 'DelChi2':0, 'Xvec':None, 'chisq0':chisq00}) |
---|
238 | return [x0,np.array([]),info] |
---|
239 | indices = range(n) |
---|
240 | while icycle < maxcyc: |
---|
241 | M = M2 |
---|
242 | time0 = time.time() |
---|
243 | nfev += 1 |
---|
244 | chisq0 = np.sum(M**2) |
---|
245 | YvecAll,AmatAll = Hess(x0,*args) # compute hessian & vectors with all variables |
---|
246 | Yvec = copy.copy(YvecAll) |
---|
247 | Amat = copy.copy(AmatAll) |
---|
248 | Xvec = copy.copy(XvecAll) |
---|
249 | # we could remove vars that were dropped in previous cycles here (use indices), but for |
---|
250 | # now let's reset them each cycle in case the singularities go away |
---|
251 | indices = range(n) |
---|
252 | Adiag = np.sqrt(np.diag(Amat)) |
---|
253 | psing = np.where(np.abs(Adiag) < 1.e-14)[0] # find any hard singularities |
---|
254 | if len(psing): |
---|
255 | G2fil.G2Print('ouch #1 dropping singularities for variable(s) #{}'.format( |
---|
256 | psing), mode='warn') |
---|
257 | Amat, indices, Xvec, Yvec, Adiag = dropTerms(psing,Amat, indices, Xvec, Yvec, Adiag) |
---|
258 | Anorm = np.outer(Adiag,Adiag) # normalize matrix & vector |
---|
259 | Yvec /= Adiag |
---|
260 | Amat /= Anorm |
---|
261 | chitol = ftol |
---|
262 | maxdrop = 3 # max number of drop var attempts |
---|
263 | loops = 0 |
---|
264 | while True: #--------- loop as we increase lamda and possibly drop terms |
---|
265 | lamMax = max(lamMax,lam) |
---|
266 | Amatlam = Amat*(1.+np.eye(Amat.shape[0])*lam) |
---|
267 | try: |
---|
268 | Nzeros = 1 |
---|
269 | Ainv,Nzeros = pinv(Amatlam,xtol) # Moore-Penrose SVD inversion |
---|
270 | except nl.LinAlgError: |
---|
271 | loops += 1 |
---|
272 | d = np.abs(np.diag(nl.qr(Amatlam)[1])) |
---|
273 | psing = list(np.where(d < 1.e-14)[0]) |
---|
274 | if not len(psing): # make sure at least the worst term is removed |
---|
275 | psing = [np.argmin(d)] |
---|
276 | G2fil.G2Print('ouch #2 bad SVD inversion; dropping terms for for variable(s) #{}'. |
---|
277 | format(psing), mode='warn') |
---|
278 | Amat, indices, Xvec, Yvec, Adiag = dropTerms(psing,Amat, indices, Xvec, Yvec, Adiag) |
---|
279 | if loops < maxdrop: continue # try again, same lam but fewer vars |
---|
280 | G2fil.G2Print('giving up with ouch #2', mode='error') |
---|
281 | info = {'num cyc':icycle,'fvec':M,'nfev':nfev,'lamMax':lamMax,'SVD0':Nzeros} |
---|
282 | info['psing'] = [i for i in range(n) if i not in indices] |
---|
283 | info['msg'] = 'SVD inversion failure\n' |
---|
284 | return [x0,None,info] |
---|
285 | Xvec = np.inner(Ainv,Yvec)/Adiag #solve for LS terms |
---|
286 | XvecAll[indices] = Xvec # expand |
---|
287 | try: |
---|
288 | M2 = func(x0+XvecAll,*args) |
---|
289 | except Exception as Msg: |
---|
290 | if not hasattr(Msg,'msg'): Msg.msg = str(Msg) |
---|
291 | G2fil.G2Print(Msg.msg,mode='warn') |
---|
292 | loops += 1 |
---|
293 | d = np.abs(np.diag(nl.qr(Amatlam)[1])) |
---|
294 | G2fil.G2Print('ouch #3 unable to evaluate objective function;',mode='error') |
---|
295 | info = {'num cyc':icycle,'fvec':M,'nfev':nfev,'lamMax':lamMax,'SVD0':Nzeros} |
---|
296 | info['psing'] = [i for i in range(n) if i not in indices] |
---|
297 | try: # try to report highly correlated parameters from full Hessian |
---|
298 | setHcorr(info,AmatAll,xtol,problem=True) |
---|
299 | except nl.LinAlgError: |
---|
300 | G2fil.G2Print('Warning: Hessian too ill-conditioned to get full covariance matrix') |
---|
301 | except G2NormException: |
---|
302 | G2fil.G2Print('Warning: Hessian normalization problem') |
---|
303 | except Exception as Msg: |
---|
304 | if not hasattr(Msg,'msg'): Msg.msg = str(Msg) |
---|
305 | G2fil.G2Print('Error determining highly correlated vars',mode='warn') |
---|
306 | G2fil.G2Print(Msg.msg,mode='warn') |
---|
307 | info['msg'] = Msg.msg + '\n\n' |
---|
308 | setSVDwarn(info,Amatlam,Nzeros,indices) |
---|
309 | return [x0,None,info] |
---|
310 | nfev += 1 |
---|
311 | chisq1 = np.sum(M2**2) |
---|
312 | if chisq1 > chisq0*(1.+chitol): #TODO put Alan Coehlo's criteria for lambda here? |
---|
313 | if lam == 0: |
---|
314 | lam = 10.**lamda # set to initial Marquardt term |
---|
315 | else: |
---|
316 | lam *= 10. |
---|
317 | if Print: |
---|
318 | G2fil.G2Print(('divergence: chi^2 %.5g on %d obs. (%d SVD zeros)\n'+ |
---|
319 | '\tincreasing Marquardt lambda to %.1e')%(chisq1,Nobs,Nzeros,lam)) |
---|
320 | if lam > 10.: |
---|
321 | G2fil.G2Print('ouch #4 stuck: chisq-new %.4g > chisq0 %.4g with lambda %.1g'% |
---|
322 | (chisq1,chisq0,lam), mode='warn') |
---|
323 | if GSASIIpath.GetConfigValue('debug'): |
---|
324 | print('Cycle %d: %.2fs' % (icycle,time.time()-time0)) |
---|
325 | try: # report highly correlated parameters from full Hessian, if we can |
---|
326 | info = {'num cyc':icycle,'fvec':M,'nfev':nfev,'lamMax':lamMax, |
---|
327 | 'Converged':False, 'DelChi2':deltaChi2, 'Xvec':XvecAll, |
---|
328 | 'chisq0':chisq00, 'Ouch#4':True} |
---|
329 | info['psing'] = [i for i in range(n) if i not in indices] |
---|
330 | Bmat,Nzeros = setHcorr(info,AmatAll,xtol,problem=True) |
---|
331 | info['SVD0'] = Nzeros |
---|
332 | setSVDwarn(info,Amatlam,Nzeros,indices) |
---|
333 | return [x0,Bmat,info] |
---|
334 | except Exception as Msg: |
---|
335 | if not hasattr(Msg,'msg'): Msg.msg = str(Msg) |
---|
336 | G2fil.G2Print('Error determining highly correlated vars',mode='warn') |
---|
337 | G2fil.G2Print(Msg.msg,mode='warn') |
---|
338 | maxcyc = -1 # no more cycles |
---|
339 | break |
---|
340 | chitol *= 2 |
---|
341 | else: # refinement succeeded |
---|
342 | x0 += XvecAll |
---|
343 | lam /= 10. # drop lam on next cycle |
---|
344 | break |
---|
345 | # complete current cycle |
---|
346 | if hasattr(dlg,'AdvanceCycle'): dlg.AdvanceCycle(icycle) |
---|
347 | deltaChi2 = (chisq0-chisq1)/chisq0 |
---|
348 | if Print: |
---|
349 | if n-len(indices): |
---|
350 | G2fil.G2Print( |
---|
351 | 'Cycle %d: %.2fs Chi2: %.5g; Obs: %d; Lam: %.3g Del: %.3g; drop=%d, SVD=%d'% |
---|
352 | (icycle,time.time()-time0,chisq1,Nobs,lamMax,deltaChi2,n-len(indices),Nzeros)) |
---|
353 | else: |
---|
354 | G2fil.G2Print( |
---|
355 | 'Cycle %d: %.2fs, Chi**2: %.5g for %d obs., Lambda: %.3g, Delta: %.3g, SVD=%d'% |
---|
356 | (icycle,time.time()-time0,chisq1,Nobs,lamMax,deltaChi2,Nzeros)) |
---|
357 | Histograms = args[0][0] |
---|
358 | if refPlotUpdate is not None: refPlotUpdate(Histograms,icycle) # update plot |
---|
359 | if deltaChi2 < ftol: |
---|
360 | ifConverged = True |
---|
361 | if Print: |
---|
362 | G2fil.G2Print("converged") |
---|
363 | break |
---|
364 | icycle += 1 |
---|
365 | #----------------------- refinement complete, compute Covariance matrix w/o Levenberg-Marquardt |
---|
366 | nfev += 1 |
---|
367 | try: |
---|
368 | if icycle == 0: # no parameter changes, skip recalc |
---|
369 | M = M2 |
---|
370 | else: |
---|
371 | M = func(x0,*args) |
---|
372 | except Exception as Msg: |
---|
373 | if not hasattr(Msg,'msg'): Msg.msg = str(Msg) |
---|
374 | G2fil.G2Print(Msg.msg,mode='warn') |
---|
375 | G2fil.G2Print('ouch #5 final objective function re-eval failed',mode='error') |
---|
376 | psing = [i for i in range(n) if i not in indices] |
---|
377 | info = {'num cyc':icycle,'fvec':M2,'nfev':nfev,'lamMax':lamMax,'psing':psing,'SVD0':-2} |
---|
378 | info['msg'] = Msg.msg + '\n' |
---|
379 | setSVDwarn(info,Amatlam,Nzeros,indices) |
---|
380 | return [x0,None,info] |
---|
381 | chisqf = np.sum(M**2) # ending chi**2 |
---|
382 | if not maxcyc: #zero cycle calc exit here |
---|
383 | info = {'num cyc':0,'fvec':M,'nfev':0,'lamMax':0,'SVD0':0, |
---|
384 | 'Converged':True, 'DelChi2':0., 'Xvec':XvecAll, 'chisq0':chisqf} |
---|
385 | return [x0,None,info] |
---|
386 | psing_prev = [i for i in range(n) if i not in indices] # save dropped vars |
---|
387 | if AmatAll is None: # Save some time and use Hessian from the last refinement cycle |
---|
388 | Yvec,Amat = Hess(x0,*args) |
---|
389 | else: |
---|
390 | Yvec = copy.copy(YvecAll) |
---|
391 | Amat = copy.copy(AmatAll) |
---|
392 | indices = range(n) |
---|
393 | info = {} |
---|
394 | try: # report highly correlated parameters from full Hessian, if we can |
---|
395 | Bmat,Nzeros = setHcorr(info,Amat,xtol,problem=False) |
---|
396 | info.update({'num cyc':icycle,'fvec':M,'nfev':nfev,'lamMax':lamMax,'SVD0':Nzeros,'psing':psing_prev, |
---|
397 | 'Converged':ifConverged, 'DelChi2':deltaChi2, 'chisq0':chisq00}) |
---|
398 | if icycle > 0: info.update({'Xvec':XvecAll}) |
---|
399 | setSVDwarn(info,Amat,Nzeros,indices) |
---|
400 | # expand Bmat by filling with zeros if columns have been dropped |
---|
401 | if len(psing_prev): |
---|
402 | ins = [j-i for i,j in enumerate(psing_prev)] |
---|
403 | Bmat = np.insert(np.insert(Bmat,ins,0,1),ins,0,0) |
---|
404 | return [x0,Bmat,info] |
---|
405 | except nl.LinAlgError: |
---|
406 | G2fil.G2Print('Warning: Hessian too ill-conditioned to get full covariance matrix') |
---|
407 | except G2NormException: |
---|
408 | G2fil.G2Print('Warning: Hessian normalization problem') |
---|
409 | except Exception as Msg: |
---|
410 | if not hasattr(Msg,'msg'): Msg.msg = str(Msg) |
---|
411 | G2fil.G2Print('Error determining highly correlated vars',mode='warn') |
---|
412 | G2fil.G2Print(Msg.msg,mode='warn') |
---|
413 | # matrix above inversion failed, drop previously removed variables & try again |
---|
414 | Amat, indices, Yvec = dropTerms(psing_prev, Amat, indices, Yvec) |
---|
415 | Adiag = np.sqrt(np.diag(Amat)) |
---|
416 | Anorm = np.outer(Adiag,Adiag) |
---|
417 | Amat = Amat/Anorm |
---|
418 | try: |
---|
419 | Bmat,Nzeros = pinv(Amat,xtol) #Moore-Penrose inversion (via SVD) & count of zeros |
---|
420 | Bmat = Bmat/Anorm |
---|
421 | except nl.LinAlgError: # this is unexpected. How did we get this far with a singular matrix? |
---|
422 | G2fil.G2Print('ouch #6 linear algebra error in making final v-cov matrix', mode='error') |
---|
423 | psing = list(np.where(np.abs(np.diag(nl.qr(Amat)[1])) < 1.e-14)[0]) |
---|
424 | if not len(psing): # make sure at least the worst term is flagged |
---|
425 | d = np.abs(np.diag(nl.qr(Amat)[1])) |
---|
426 | psing = [np.argmin(d)] |
---|
427 | Amat, indices, Yvec = dropTerms(psing, Amat, indices, Yvec) |
---|
428 | info = {'num cyc':icycle,'fvec':M,'nfev':nfev,'lamMax':lamMax,'SVD0':-1,'Xvec':None, 'chisq0':chisqf} |
---|
429 | info['psing'] = [i for i in range(n) if i not in indices] |
---|
430 | return [x0,None,info] |
---|
431 | # expand Bmat by filling with zeros if columns have been dropped |
---|
432 | psing = [i for i in range(n) if i not in indices] |
---|
433 | if len(psing): |
---|
434 | ins = [j-i for i,j in enumerate(psing)] |
---|
435 | Bmat = np.insert(np.insert(Bmat,ins,0,1),ins,0,0) |
---|
436 | info.update({'num cyc':icycle,'fvec':M,'nfev':nfev,'lamMax':lamMax,'SVD0':Nzeros, |
---|
437 | 'Converged':ifConverged, 'DelChi2':deltaChi2, 'Xvec':XvecAll, 'chisq0':chisq00}) |
---|
438 | info['psing'] = [i for i in range(n) if i not in indices] |
---|
439 | setSVDwarn(info,Amat,Nzeros,indices) |
---|
440 | return [x0,Bmat,info] |
---|
441 | |
---|
442 | def HessianSVD(func,x0,Hess,args=(),ftol=1.49012e-8,xtol=1.e-6, maxcyc=0,lamda=-3,Print=False,refPlotUpdate=None): |
---|
443 | |
---|
444 | ''' |
---|
445 | Minimize the sum of squares of a function (:math:`f`) evaluated on a series of |
---|
446 | values (y): :math:`\\sum_{y=0}^{N_{obs}} f(y,{args})` |
---|
447 | where :math:`x = arg min(\\sum_{y=0}^{N_{obs}} (func(y)^2,axis=0))` |
---|
448 | |
---|
449 | :param function func: callable method or function |
---|
450 | should take at least one (possibly length N vector) argument and |
---|
451 | returns M floating point numbers. |
---|
452 | :param np.ndarray x0: The starting estimate for the minimization of length N |
---|
453 | :param function Hess: callable method or function |
---|
454 | A required function or method to compute the weighted vector and Hessian for func. |
---|
455 | It must be a symmetric NxN array |
---|
456 | :param tuple args: Any extra arguments to func are placed in this tuple. |
---|
457 | :param float ftol: Relative error desired in the sum of squares. |
---|
458 | :param float xtol: Relative tolerance of zeros in the SVD solution in nl.pinv. |
---|
459 | :param int maxcyc: The maximum number of cycles of refinement to execute, if -1 refine |
---|
460 | until other limits are met (ftol, xtol) |
---|
461 | :param bool Print: True for printing results (residuals & times) by cycle |
---|
462 | |
---|
463 | :returns: (x,cov_x,infodict) where |
---|
464 | |
---|
465 | * x : ndarray |
---|
466 | The solution (or the result of the last iteration for an unsuccessful |
---|
467 | call). |
---|
468 | * cov_x : ndarray |
---|
469 | Uses the fjac and ipvt optional outputs to construct an |
---|
470 | estimate of the jacobian around the solution. ``None`` if a |
---|
471 | singular matrix encountered (indicates very flat curvature in |
---|
472 | some direction). This matrix must be multiplied by the |
---|
473 | residual standard deviation to get the covariance of the |
---|
474 | parameter estimates -- see curve_fit. |
---|
475 | * infodict : dict |
---|
476 | a dictionary of optional outputs with the keys: |
---|
477 | |
---|
478 | * 'fvec' : the function evaluated at the output |
---|
479 | * 'num cyc': |
---|
480 | * 'nfev': |
---|
481 | * 'lamMax':0. |
---|
482 | * 'psing': |
---|
483 | * 'SVD0': |
---|
484 | |
---|
485 | ''' |
---|
486 | |
---|
487 | ifConverged = False |
---|
488 | deltaChi2 = -10. |
---|
489 | x0 = np.array(x0, ndmin=1) #might be redundant? |
---|
490 | n = len(x0) |
---|
491 | if type(args) != type(()): |
---|
492 | args = (args,) |
---|
493 | |
---|
494 | icycle = 0 |
---|
495 | nfev = 0 |
---|
496 | if Print: |
---|
497 | G2fil.G2Print(' Hessian SVD refinement on %d variables:'%(n)) |
---|
498 | chisq00 = None |
---|
499 | while icycle < maxcyc: |
---|
500 | time0 = time.time() |
---|
501 | M = func(x0,*args) |
---|
502 | nfev += 1 |
---|
503 | chisq0 = np.sum(M**2) |
---|
504 | if chisq00 is None: chisq00 = chisq0 |
---|
505 | Yvec,Amat = Hess(x0,*args) |
---|
506 | Adiag = np.sqrt(np.diag(Amat)) |
---|
507 | psing = np.where(np.abs(Adiag) < 1.e-14,True,False) |
---|
508 | if np.any(psing): #hard singularity in matrix |
---|
509 | return [x0,None,{'num cyc':icycle,'fvec':M,'nfev':nfev,'lamMax':0.,'psing':psing,'SVD0':-1}] |
---|
510 | Anorm = np.outer(Adiag,Adiag) |
---|
511 | Yvec /= Adiag |
---|
512 | Amat /= Anorm |
---|
513 | if Print: |
---|
514 | G2fil.G2Print('initial chi^2 %.5g'%(chisq0)) |
---|
515 | try: |
---|
516 | Ainv,Nzeros = pinv(Amat,xtol) #do Moore-Penrose inversion (via SVD) |
---|
517 | except nl.LinAlgError: |
---|
518 | G2fil.G2Print('ouch #1 bad SVD inversion; change parameterization', mode='warn') |
---|
519 | psing = list(np.where(np.abs(np.diag(nl.qr(Amat)[1])) < 1.e-14)[0]) |
---|
520 | return [x0,None,{'num cyc':icycle,'fvec':M,'nfev':nfev,'lamMax':0.,'psing':psing,'SVD0':-1}] |
---|
521 | Xvec = np.inner(Ainv,Yvec) #solve |
---|
522 | Xvec /= Adiag |
---|
523 | M2 = func(x0+Xvec,*args) |
---|
524 | nfev += 1 |
---|
525 | chisq1 = np.sum(M2**2) |
---|
526 | deltaChi2 = (chisq0-chisq1)/chisq0 |
---|
527 | if Print: |
---|
528 | G2fil.G2Print(' Cycle: %d, Time: %.2fs, Chi**2: %.5g, Delta: %.3g'%( |
---|
529 | icycle,time.time()-time0,chisq1,deltaChi2)) |
---|
530 | Histograms = args[0][0] |
---|
531 | if refPlotUpdate is not None: refPlotUpdate(Histograms,icycle) # update plot |
---|
532 | if deltaChi2 < ftol: |
---|
533 | ifConverged = True |
---|
534 | if Print: G2fil.G2Print("converged") |
---|
535 | break |
---|
536 | icycle += 1 |
---|
537 | M = func(x0,*args) |
---|
538 | nfev += 1 |
---|
539 | Yvec,Amat = Hess(x0,*args) |
---|
540 | Adiag = np.sqrt(np.diag(Amat)) |
---|
541 | Anorm = np.outer(Adiag,Adiag) |
---|
542 | Amat = Amat/Anorm |
---|
543 | try: |
---|
544 | Bmat,Nzero = pinv(Amat,xtol) #Moore-Penrose inversion (via SVD) & count of zeros |
---|
545 | G2fil.G2Print('Found %d SVD zeros'%(Nzero), mode='warn') |
---|
546 | # Bmat = nl.inv(Amatlam); Nzeros = 0 |
---|
547 | Bmat = Bmat/Anorm |
---|
548 | return [x0,Bmat,{'num cyc':icycle,'fvec':M,'nfev':nfev,'lamMax':0.,'psing':[], |
---|
549 | 'SVD0':Nzero,'Converged': ifConverged, 'DelChi2':deltaChi2, |
---|
550 | 'chisq0':chisq00}] |
---|
551 | except nl.LinAlgError: |
---|
552 | G2fil.G2Print('ouch #2 linear algebra error in making v-cov matrix', mode='error') |
---|
553 | psing = [] |
---|
554 | if maxcyc: |
---|
555 | psing = list(np.where(np.diag(nl.qr(Amat)[1]) < 1.e-14)[0]) |
---|
556 | return [x0,None,{'num cyc':icycle,'fvec':M,'nfev':nfev,'lamMax':0.,'psing':psing,'SVD0':-1, |
---|
557 | 'chisq0':chisq00}] |
---|
558 | |
---|
559 | def getVCov(varyNames,varyList,covMatrix): |
---|
560 | '''obtain variance-covariance terms for a set of variables. NB: the varyList |
---|
561 | and covMatrix were saved by the last least squares refinement so they must match. |
---|
562 | |
---|
563 | :param list varyNames: variable names to find v-cov matric for |
---|
564 | :param list varyList: full list of all variables in v-cov matrix |
---|
565 | :param nparray covMatrix: full variance-covariance matrix from the last |
---|
566 | least squares refinement |
---|
567 | |
---|
568 | :returns: nparray vcov: variance-covariance matrix for the variables given |
---|
569 | in varyNames |
---|
570 | |
---|
571 | ''' |
---|
572 | vcov = np.zeros((len(varyNames),len(varyNames))) |
---|
573 | for i1,name1 in enumerate(varyNames): |
---|
574 | for i2,name2 in enumerate(varyNames): |
---|
575 | try: |
---|
576 | vcov[i1][i2] = covMatrix[varyList.index(name1)][varyList.index(name2)] |
---|
577 | except ValueError: |
---|
578 | vcov[i1][i2] = 0.0 |
---|
579 | # if i1 == i2: |
---|
580 | # vcov[i1][i2] = 1e-20 |
---|
581 | # else: |
---|
582 | # vcov[i1][i2] = 0.0 |
---|
583 | return vcov |
---|
584 | |
---|
585 | ################################################################################ |
---|
586 | #### Atom manipulations |
---|
587 | ################################################################################ |
---|
588 | |
---|
589 | def getAtomPtrs(data,draw=False): |
---|
590 | ''' get atom data pointers cx,ct,cs,cia in Atoms or Draw Atoms lists |
---|
591 | NB:may not match column numbers in displayed table |
---|
592 | |
---|
593 | param: dict: data phase data structure |
---|
594 | draw: boolean True if Draw Atoms list pointers are required |
---|
595 | return: cx,ct,cs,cia pointers to atom xyz, type, site sym, uiso/aniso flag |
---|
596 | ''' |
---|
597 | if draw: |
---|
598 | return data['Drawing']['atomPtrs'] |
---|
599 | else: |
---|
600 | return data['General']['AtomPtrs'] |
---|
601 | |
---|
602 | def FindMolecule(ind,generalData,atomData): #uses numpy & masks - very fast even for proteins! |
---|
603 | |
---|
604 | def getNeighbors(atom,radius): |
---|
605 | Dx = UAtoms-np.array(atom[cx:cx+3]) |
---|
606 | dist = ma.masked_less(np.sqrt(np.sum(np.inner(Amat,Dx)**2,axis=0)),0.5) #gets rid of disorder "bonds" < 0.5A |
---|
607 | sumR = Radii+radius |
---|
608 | return set(ma.nonzero(ma.masked_greater(dist-factor*sumR,0.))[0]) #get indices of bonded atoms |
---|
609 | |
---|
610 | import numpy.ma as ma |
---|
611 | indices = (-1,0,1) |
---|
612 | Units = np.array([[h,k,l] for h in indices for k in indices for l in indices],dtype='f') |
---|
613 | cx,ct,cs,ci = generalData['AtomPtrs'] |
---|
614 | DisAglCtls = generalData['DisAglCtls'] |
---|
615 | SGData = generalData['SGData'] |
---|
616 | Amat,Bmat = G2lat.cell2AB(generalData['Cell'][1:7]) |
---|
617 | radii = DisAglCtls['BondRadii'] |
---|
618 | atomTypes = DisAglCtls['AtomTypes'] |
---|
619 | factor = DisAglCtls['Factors'][0] |
---|
620 | unit = np.zeros(3) |
---|
621 | try: |
---|
622 | indH = atomTypes.index('H') |
---|
623 | radii[indH] = 0.5 |
---|
624 | except: |
---|
625 | pass |
---|
626 | nAtom = len(atomData) |
---|
627 | Indx = list(range(nAtom)) |
---|
628 | UAtoms = [] |
---|
629 | Radii = [] |
---|
630 | for atom in atomData: |
---|
631 | UAtoms.append(np.array(atom[cx:cx+3])) |
---|
632 | Radii.append(radii[atomTypes.index(atom[ct])]) |
---|
633 | UAtoms = np.array(UAtoms) |
---|
634 | Radii = np.array(Radii) |
---|
635 | for nOp,Op in enumerate(SGData['SGOps'][1:]): |
---|
636 | UAtoms = np.concatenate((UAtoms,(np.inner(Op[0],UAtoms[:nAtom]).T+Op[1]))) |
---|
637 | Radii = np.concatenate((Radii,Radii[:nAtom])) |
---|
638 | Indx += Indx[:nAtom] |
---|
639 | for icen,cen in enumerate(SGData['SGCen'][1:]): |
---|
640 | UAtoms = np.concatenate((UAtoms,(UAtoms+cen))) |
---|
641 | Radii = np.concatenate((Radii,Radii)) |
---|
642 | Indx += Indx[:nAtom] |
---|
643 | if SGData['SGInv']: |
---|
644 | UAtoms = np.concatenate((UAtoms,-UAtoms)) |
---|
645 | Radii = np.concatenate((Radii,Radii)) |
---|
646 | Indx += Indx |
---|
647 | UAtoms %= 1. |
---|
648 | mAtoms = len(UAtoms) |
---|
649 | for unit in Units: |
---|
650 | if np.any(unit): #skip origin cell |
---|
651 | UAtoms = np.concatenate((UAtoms,UAtoms[:mAtoms]+unit)) |
---|
652 | Radii = np.concatenate((Radii,Radii[:mAtoms])) |
---|
653 | Indx += Indx[:mAtoms] |
---|
654 | UAtoms = np.array(UAtoms) |
---|
655 | Radii = np.array(Radii) |
---|
656 | newAtoms = [atomData[ind],] |
---|
657 | atomData[ind] = None |
---|
658 | radius = Radii[ind] |
---|
659 | IndB = getNeighbors(newAtoms[-1],radius) |
---|
660 | while True: |
---|
661 | if not len(IndB): |
---|
662 | break |
---|
663 | indb = IndB.pop() |
---|
664 | if atomData[Indx[indb]] == None: |
---|
665 | continue |
---|
666 | while True: |
---|
667 | try: |
---|
668 | jndb = IndB.index(indb) |
---|
669 | IndB.remove(jndb) |
---|
670 | except: |
---|
671 | break |
---|
672 | newAtom = copy.copy(atomData[Indx[indb]]) |
---|
673 | newAtom[cx:cx+3] = UAtoms[indb] #NB: thermal Uij, etc. not transformed! |
---|
674 | newAtoms.append(newAtom) |
---|
675 | atomData[Indx[indb]] = None |
---|
676 | IndB = set(list(IndB)+list(getNeighbors(newAtoms[-1],radius))) |
---|
677 | if len(IndB) > nAtom: |
---|
678 | return 'Assemble molecule cannot be used on extended structures' |
---|
679 | for atom in atomData: |
---|
680 | if atom != None: |
---|
681 | newAtoms.append(atom) |
---|
682 | return newAtoms |
---|
683 | |
---|
684 | def FindAtomIndexByIDs(atomData,loc,IDs,Draw=True): |
---|
685 | '''finds the set of atom array indices for a list of atom IDs. Will search |
---|
686 | either the Atom table or the drawAtom table. |
---|
687 | |
---|
688 | :param list atomData: Atom or drawAtom table containting coordinates, etc. |
---|
689 | :param int loc: location of atom id in atomData record |
---|
690 | :param list IDs: atom IDs to be found |
---|
691 | :param bool Draw: True if drawAtom table to be searched; False if Atom table |
---|
692 | is searched |
---|
693 | |
---|
694 | :returns: list indx: atom (or drawAtom) indices |
---|
695 | |
---|
696 | ''' |
---|
697 | indx = [] |
---|
698 | for i,atom in enumerate(atomData): |
---|
699 | if Draw and atom[loc] in IDs: |
---|
700 | indx.append(i) |
---|
701 | elif atom[loc] in IDs: |
---|
702 | indx.append(i) |
---|
703 | return indx |
---|
704 | |
---|
705 | def FillAtomLookUp(atomData,indx): |
---|
706 | '''create a dictionary of atom indexes with atom IDs as keys |
---|
707 | |
---|
708 | :param list atomData: Atom table to be used |
---|
709 | :param int indx: pointer to position of atom id in atom record (typically cia+8) |
---|
710 | |
---|
711 | :returns: dict atomLookUp: dictionary of atom indexes with atom IDs as keys |
---|
712 | |
---|
713 | ''' |
---|
714 | return {atom[indx]:iatm for iatm,atom in enumerate(atomData)} |
---|
715 | |
---|
716 | def DrawAtomsReplaceByID(data,loc,atom,ID): |
---|
717 | '''Replace all atoms in drawing array with an ID matching the specified value''' |
---|
718 | atomData = data['Drawing']['Atoms'] |
---|
719 | indx = FindAtomIndexByIDs(atomData,loc,[ID,]) |
---|
720 | for ind in indx: |
---|
721 | atomData[ind] = MakeDrawAtom(data,atom,atomData[ind]) |
---|
722 | |
---|
723 | def MakeDrawAtom(data,atom,oldatom=None): |
---|
724 | 'needs a description' |
---|
725 | AA3letter = ['ALA','ARG','ASN','ASP','CYS','GLN','GLU','GLY','HIS','ILE', |
---|
726 | 'LEU','LYS','MET','PHE','PRO','SER','THR','TRP','TYR','VAL','MSE','HOH','WAT','UNK'] |
---|
727 | AA1letter = ['A','R','N','D','C','Q','E','G','H','I', |
---|
728 | 'L','K','M','F','P','S','T','W','Y','V','M',' ',' ',' '] |
---|
729 | generalData = data['General'] |
---|
730 | Amat,Bmat = G2lat.cell2AB(generalData['Cell'][1:7]) |
---|
731 | SGData = generalData['SGData'] |
---|
732 | deftype = G2obj.validateAtomDrawType(GSASIIpath.GetConfigValue('DrawAtoms_default'),generalData) |
---|
733 | if generalData['Type'] in ['nuclear','faulted',]: |
---|
734 | if oldatom: |
---|
735 | opr = oldatom[5] |
---|
736 | if atom[9] == 'A': |
---|
737 | X,U = G2spc.ApplyStringOps(opr,SGData,atom[3:6],atom[11:17]) |
---|
738 | atomInfo = [atom[:2]+list(X)+oldatom[5:9]+atom[9:11]+list(U)+oldatom[17:]][0] |
---|
739 | else: |
---|
740 | X = G2spc.ApplyStringOps(opr,SGData,atom[3:6]) |
---|
741 | atomInfo = [atom[:2]+list(X)+oldatom[5:9]+atom[9:]+oldatom[17:]][0] |
---|
742 | else: |
---|
743 | atomInfo = [atom[:2]+atom[3:6]+['1',]+[deftype]+ |
---|
744 | ['',]+[[255,255,255],]+atom[9:]+[[],[]]][0] |
---|
745 | ct,cs = [1,8] #type & color |
---|
746 | elif generalData['Type'] == 'magnetic': |
---|
747 | if oldatom: |
---|
748 | opr = oldatom[8] |
---|
749 | mom = np.array(atom[7:10]) |
---|
750 | if generalData['Super']: |
---|
751 | SSGData = generalData['SSGData'] |
---|
752 | Mom = G2spc.ApplyStringOpsMom(opr,SGData,SSGData,mom) |
---|
753 | else: |
---|
754 | Mom = G2spc.ApplyStringOpsMom(opr,SGData,None,mom) |
---|
755 | if atom[12] == 'A': |
---|
756 | X,U = G2spc.ApplyStringOps(opr,SGData,atom[3:6],atom[14:20]) |
---|
757 | atomInfo = [atom[:2]+list(X)+list(Mom)+oldatom[8:12]+atom[12:14]+list(U)+oldatom[20:]][0] |
---|
758 | else: |
---|
759 | X = G2spc.ApplyStringOps(opr,SGData,atom[3:6]) |
---|
760 | atomInfo = [atom[:2]+list(X)+list(Mom)+oldatom[8:12]+atom[12:]+oldatom[20:]][0] |
---|
761 | else: |
---|
762 | atomInfo = [atom[:2]+atom[3:6]+atom[7:10]+['1',]+[deftype]+ |
---|
763 | ['',]+[[255,255,255],]+atom[12:]+[[],[]]][0] |
---|
764 | ct,cs = [1,11] #type & color |
---|
765 | elif generalData['Type'] == 'macromolecular': |
---|
766 | try: |
---|
767 | oneLetter = AA3letter.index(atom[1]) |
---|
768 | except ValueError: |
---|
769 | oneLetter = -1 |
---|
770 | atomInfo = [[atom[1].strip()+atom[0],]+ |
---|
771 | [AA1letter[oneLetter]+atom[0],]+atom[2:5]+ |
---|
772 | atom[6:9]+['1',]+[deftype]+['',]+[[255,255,255],]+atom[12:]+[[],[]]][0] |
---|
773 | ct,cs = [4,11] #type & color |
---|
774 | atNum = generalData['AtomTypes'].index(atom[ct]) |
---|
775 | atomInfo[cs] = list(generalData['Color'][atNum]) |
---|
776 | return atomInfo |
---|
777 | |
---|
778 | def GetAtomsById(atomData,atomLookUp,IdList): |
---|
779 | '''gets a list of atoms from Atom table that match a set of atom IDs |
---|
780 | |
---|
781 | :param list atomData: Atom table to be used |
---|
782 | :param dict atomLookUp: dictionary of atom indexes with atom IDs as keys |
---|
783 | :param list IdList: atom IDs to be found |
---|
784 | |
---|
785 | :returns: list atoms: list of atoms found |
---|
786 | |
---|
787 | ''' |
---|
788 | atoms = [] |
---|
789 | for Id in IdList: |
---|
790 | atoms.append(atomData[atomLookUp[Id]]) |
---|
791 | return atoms |
---|
792 | |
---|
793 | def GetAtomItemsById(atomData,atomLookUp,IdList,itemLoc,numItems=1): |
---|
794 | '''gets atom parameters for atoms using atom IDs |
---|
795 | |
---|
796 | :param list atomData: Atom table to be used |
---|
797 | :param dict atomLookUp: dictionary of atom indexes with atom IDs as keys |
---|
798 | :param list IdList: atom IDs to be found |
---|
799 | :param int itemLoc: pointer to desired 1st item in an atom table entry |
---|
800 | :param int numItems: number of items to be retrieved |
---|
801 | |
---|
802 | :returns: type name: description |
---|
803 | |
---|
804 | ''' |
---|
805 | Items = [] |
---|
806 | if not isinstance(IdList,list): |
---|
807 | IdList = [IdList,] |
---|
808 | for Id in IdList: |
---|
809 | if numItems == 1: |
---|
810 | Items.append(atomData[atomLookUp[Id]][itemLoc]) |
---|
811 | else: |
---|
812 | Items.append(atomData[atomLookUp[Id]][itemLoc:itemLoc+numItems]) |
---|
813 | return Items |
---|
814 | |
---|
815 | def GetAtomCoordsByID(pId,parmDict,AtLookup,indx): |
---|
816 | '''default doc string |
---|
817 | |
---|
818 | :param type name: description |
---|
819 | |
---|
820 | :returns: type name: description |
---|
821 | |
---|
822 | ''' |
---|
823 | pfx = [str(pId)+'::A'+i+':' for i in ['x','y','z']] |
---|
824 | dpfx = [str(pId)+'::dA'+i+':' for i in ['x','y','z']] |
---|
825 | XYZ = [] |
---|
826 | for ind in indx: |
---|
827 | names = [pfx[i]+str(AtLookup[ind]) for i in range(3)] |
---|
828 | dnames = [dpfx[i]+str(AtLookup[ind]) for i in range(3)] |
---|
829 | XYZ.append([parmDict[name]+parmDict[dname] for name,dname in zip(names,dnames)]) |
---|
830 | return XYZ |
---|
831 | |
---|
832 | def GetAtomFracByID(pId,parmDict,AtLookup,indx): |
---|
833 | '''default doc string |
---|
834 | |
---|
835 | :param type name: description |
---|
836 | |
---|
837 | :returns: type name: description |
---|
838 | |
---|
839 | ''' |
---|
840 | pfx = str(pId)+'::Afrac:' |
---|
841 | Frac = [] |
---|
842 | for ind in indx: |
---|
843 | name = pfx+str(AtLookup[ind]) |
---|
844 | Frac.append(parmDict[name]) |
---|
845 | return Frac |
---|
846 | |
---|
847 | # for Atom in Atoms: |
---|
848 | # XYZ = Atom[cx:cx+3] |
---|
849 | # if 'A' in Atom[cia]: |
---|
850 | # U6 = Atom[cia+2:cia+8] |
---|
851 | |
---|
852 | |
---|
853 | def GetAtomMomsByID(pId,parmDict,AtLookup,indx): |
---|
854 | '''default doc string |
---|
855 | |
---|
856 | :param type name: description |
---|
857 | |
---|
858 | :returns: type name: description |
---|
859 | |
---|
860 | ''' |
---|
861 | pfx = [str(pId)+'::A'+i+':' for i in ['Mx','My','Mz']] |
---|
862 | Mom = [] |
---|
863 | for ind in indx: |
---|
864 | names = [pfx[i]+str(AtLookup[ind]) for i in range(3)] |
---|
865 | Mom.append([parmDict[name] for name in names]) |
---|
866 | return Mom |
---|
867 | |
---|
868 | def ApplySeqData(data,seqData,PF2=False): |
---|
869 | '''Applies result from seq. refinement to drawing atom positions & Uijs |
---|
870 | |
---|
871 | :param dict data: GSAS-II phase data structure |
---|
872 | :param dict seqData: GSAS-II sequential refinement results structure |
---|
873 | :param bool PF2: if True then seqData is from a sequential run of PDFfit2 |
---|
874 | |
---|
875 | :returns: list drawAtoms: revised Draw Atoms list |
---|
876 | ''' |
---|
877 | cx,ct,cs,cia = getAtomPtrs(data) |
---|
878 | drawingData = data['Drawing'] |
---|
879 | dcx,dct,dcs,dci = getAtomPtrs(data,True) |
---|
880 | atoms = data['Atoms'] |
---|
881 | drawAtoms = drawingData['Atoms'] |
---|
882 | parmDict = copy.deepcopy(seqData['parmDict']) |
---|
883 | if PF2: |
---|
884 | PDFData = data['RMC']['PDFfit'] |
---|
885 | SGData = data['General']['SGData'] |
---|
886 | AtmConstr = PDFData['AtomConstr'] |
---|
887 | for ia,atom in enumerate(atoms): |
---|
888 | atxyz = atom[cx:cx+3] |
---|
889 | for ix in [0,1,2]: |
---|
890 | item = AtmConstr[ia][ix+2] |
---|
891 | if '@' in item: |
---|
892 | Ids = [itm[:2] for itm in item.split('@')[1:]] |
---|
893 | for Id in Ids: |
---|
894 | item = item.replace('@'+Id,str(parmDict[Id][0])) |
---|
895 | atxyz[ix] = eval(item) |
---|
896 | if '@' in AtmConstr[ia][6]: |
---|
897 | itm = AtmConstr[ia][6].split('@')[:2][1] |
---|
898 | atuij = np.array([parmDict[itm][0],parmDict[itm][0],parmDict[itm][0],0.0,0.0,0.0]) |
---|
899 | indx = FindAtomIndexByIDs(drawAtoms,dci,[atom[cia+8],],True) |
---|
900 | for ind in indx: |
---|
901 | drawatom = drawAtoms[ind] |
---|
902 | opr = drawatom[dcs-1] |
---|
903 | #how do I handle Sfrac? - fade the atoms? |
---|
904 | X,U = G2spc.ApplyStringOps(opr,SGData,atxyz,atuij) |
---|
905 | drawatom[dcx:dcx+3] = X |
---|
906 | drawatom[dci-6:dci] = U |
---|
907 | else: |
---|
908 | SGData = data['General']['SGData'] |
---|
909 | pId = data['pId'] |
---|
910 | pfx = '%d::'%(pId) |
---|
911 | for ia,atom in enumerate(atoms): |
---|
912 | dxyz = np.array([parmDict[pfx+'dAx:'+str(ia)],parmDict[pfx+'dAy:'+str(ia)],parmDict[pfx+'dAz:'+str(ia)]]) |
---|
913 | if atom[cia] == 'A': |
---|
914 | atuij = np.array([parmDict[pfx+'AU11:'+str(ia)],parmDict[pfx+'AU22:'+str(ia)],parmDict[pfx+'AU33:'+str(ia)], |
---|
915 | parmDict[pfx+'AU12:'+str(ia)],parmDict[pfx+'AU13:'+str(ia)],parmDict[pfx+'AU23:'+str(ia)]]) |
---|
916 | else: |
---|
917 | atuiso = parmDict[pfx+'AUiso:'+str(ia)] |
---|
918 | atxyz = G2spc.MoveToUnitCell(np.array(atom[cx:cx+3])+dxyz)[0] |
---|
919 | indx = FindAtomIndexByIDs(drawAtoms,dci,[atom[cia+8],],True) |
---|
920 | for ind in indx: |
---|
921 | drawatom = drawAtoms[ind] |
---|
922 | opr = drawatom[dcs-1] |
---|
923 | #how do I handle Sfrac? - fade the atoms? |
---|
924 | if atom[cia] == 'A': |
---|
925 | X,U = G2spc.ApplyStringOps(opr,SGData,atxyz,atuij) |
---|
926 | drawatom[dcx:dcx+3] = X |
---|
927 | drawatom[dci-6:dci] = U |
---|
928 | else: |
---|
929 | X = G2spc.ApplyStringOps(opr,SGData,atxyz) |
---|
930 | drawatom[dcx:dcx+3] = X |
---|
931 | drawatom[dci-7] = atuiso |
---|
932 | return drawAtoms |
---|
933 | |
---|
934 | def FindNeighbors(phase,FrstName,AtNames,notName=''): |
---|
935 | General = phase['General'] |
---|
936 | cx,ct,cs,cia = getAtomPtrs(phase) |
---|
937 | Atoms = phase['Atoms'] |
---|
938 | atNames = [atom[ct-1] for atom in Atoms] |
---|
939 | Cell = General['Cell'][1:7] |
---|
940 | Amat,Bmat = G2lat.cell2AB(Cell) |
---|
941 | atTypes = General['AtomTypes'] |
---|
942 | Radii = np.array(General['BondRadii']) |
---|
943 | try: |
---|
944 | DisAglCtls = General['DisAglCtls'] |
---|
945 | radiusFactor = DisAglCtls['Factors'][0] |
---|
946 | except: |
---|
947 | radiusFactor = 0.85 |
---|
948 | AtInfo = dict(zip(atTypes,Radii)) #or General['BondRadii'] |
---|
949 | Orig = atNames.index(FrstName) |
---|
950 | OId = Atoms[Orig][cia+8] |
---|
951 | OType = Atoms[Orig][ct] |
---|
952 | XYZ = getAtomXYZ(Atoms,cx) |
---|
953 | Neigh = [] |
---|
954 | Ids = [] |
---|
955 | Dx = np.inner(Amat,XYZ-XYZ[Orig]).T |
---|
956 | dist = np.sqrt(np.sum(Dx**2,axis=1)) |
---|
957 | sumR = np.array([AtInfo[OType]+AtInfo[atom[ct]] for atom in Atoms]) |
---|
958 | IndB = ma.nonzero(ma.masked_greater(dist-radiusFactor*sumR,0.)) |
---|
959 | for j in IndB[0]: |
---|
960 | if j != Orig: |
---|
961 | if AtNames[j] not in notName: |
---|
962 | Neigh.append([AtNames[j],dist[j],True]) |
---|
963 | Ids.append(Atoms[j][cia+8]) |
---|
964 | return Neigh,[OId,Ids] |
---|
965 | |
---|
966 | def FindOctahedron(results): |
---|
967 | Octahedron = np.array([[1.,0,0],[0,1.,0],[0,0,1.],[-1.,0,0],[0,-1.,0],[0,0,-1.]]) |
---|
968 | Polygon = np.array([result[3] for result in results]) |
---|
969 | Dists = np.array([np.sqrt(np.sum(axis**2)) for axis in Polygon]) |
---|
970 | bond = np.mean(Dists) |
---|
971 | std = np.std(Dists) |
---|
972 | Norms = Polygon/Dists[:,nxs] |
---|
973 | Tilts = acosd(np.dot(Norms,Octahedron[0])) |
---|
974 | iAng = np.argmin(Tilts) |
---|
975 | Qavec = np.cross(Norms[iAng],Octahedron[0]) |
---|
976 | QA = AVdeg2Q(Tilts[iAng],Qavec) |
---|
977 | newNorms = prodQVQ(QA,Norms) |
---|
978 | Rots = acosd(np.dot(newNorms,Octahedron[1])) |
---|
979 | jAng = np.argmin(Rots) |
---|
980 | Qbvec = np.cross(Norms[jAng],Octahedron[1]) |
---|
981 | QB = AVdeg2Q(Rots[jAng],Qbvec) |
---|
982 | QQ = prodQQ(QA,QB) |
---|
983 | newNorms = prodQVQ(QQ,Norms) |
---|
984 | dispVecs = np.array([norm[:,nxs]-Octahedron.T for norm in newNorms]) |
---|
985 | disp = np.sqrt(np.sum(dispVecs**2,axis=1)) |
---|
986 | dispids = np.argmin(disp,axis=1) |
---|
987 | vecDisp = np.array([dispVecs[i,:,dispid] for i,dispid in enumerate(dispids)]) |
---|
988 | Disps = np.array([disp[i,dispid] for i,dispid in enumerate(dispids)]) |
---|
989 | meanDisp = np.mean(Disps) |
---|
990 | stdDisp = np.std(Disps) |
---|
991 | A,V = Q2AVdeg(QQ) |
---|
992 | return bond,std,meanDisp,stdDisp,A,V,vecDisp |
---|
993 | |
---|
994 | def FindTetrahedron(results): |
---|
995 | Tetrahedron = np.array([[1.,1,1],[1,-1,-1],[-1,1,-1],[-1,-1,1]])/np.sqrt(3.) |
---|
996 | Polygon = np.array([result[3] for result in results]) |
---|
997 | Dists = np.array([np.sqrt(np.sum(axis**2)) for axis in Polygon]) |
---|
998 | bond = np.mean(Dists) |
---|
999 | std = np.std(Dists) |
---|
1000 | Norms = Polygon/Dists[:,nxs] |
---|
1001 | Tilts = acosd(np.dot(Norms,Tetrahedron[0])) |
---|
1002 | iAng = np.argmin(Tilts) |
---|
1003 | Qavec = np.cross(Norms[iAng],Tetrahedron[0]) |
---|
1004 | QA = AVdeg2Q(Tilts[iAng],Qavec) |
---|
1005 | newNorms = prodQVQ(QA,Norms) |
---|
1006 | Rots = acosd(np.dot(newNorms,Tetrahedron[1])) |
---|
1007 | jAng = np.argmin(Rots) |
---|
1008 | Qbvec = np.cross(Norms[jAng],Tetrahedron[1]) |
---|
1009 | QB = AVdeg2Q(Rots[jAng],Qbvec) |
---|
1010 | QQ = prodQQ(QA,QB) |
---|
1011 | newNorms = prodQVQ(QQ,Norms) |
---|
1012 | dispVecs = np.array([norm[:,nxs]-Tetrahedron.T for norm in newNorms]) |
---|
1013 | disp = np.sqrt(np.sum(dispVecs**2,axis=1)) |
---|
1014 | dispids = np.argmin(disp,axis=1) |
---|
1015 | vecDisp = np.array([dispVecs[i,:,dispid] for i,dispid in enumerate(dispids)]) |
---|
1016 | Disps = np.array([disp[i,dispid] for i,dispid in enumerate(dispids)]) |
---|
1017 | meanDisp = np.mean(Disps) |
---|
1018 | stdDisp = np.std(Disps) |
---|
1019 | A,V = Q2AVdeg(QQ) |
---|
1020 | return bond,std,meanDisp,stdDisp,A,V,vecDisp |
---|
1021 | |
---|
1022 | def FindAllNeighbors(phase,FrstName,AtNames,notName='',Orig=None,Short=False, |
---|
1023 | searchType='Bond'): |
---|
1024 | '''Find neighboring atoms |
---|
1025 | Uses Bond search criteria unless searchType is set to non-default |
---|
1026 | ''' |
---|
1027 | if searchType == 'Bond': |
---|
1028 | skey = 'BondRadii' |
---|
1029 | sindex = 0 |
---|
1030 | else: |
---|
1031 | skey = 'AngleRadii' |
---|
1032 | sindex = 1 |
---|
1033 | General = phase['General'] |
---|
1034 | cx,ct,cs,cia = getAtomPtrs(phase) |
---|
1035 | Atoms = phase['Atoms'] |
---|
1036 | atNames = [atom[ct-1] for atom in Atoms] |
---|
1037 | atTypes = [atom[ct] for atom in Atoms] |
---|
1038 | Cell = General['Cell'][1:7] |
---|
1039 | Amat,Bmat = G2lat.cell2AB(Cell) |
---|
1040 | SGData = General['SGData'] |
---|
1041 | indices = (-1,0,1) |
---|
1042 | Units = np.array([[h,k,l] for h in indices for k in indices for l in indices]) |
---|
1043 | AtTypes = General['AtomTypes'] |
---|
1044 | Radii = copy.copy(np.array(General[skey])) |
---|
1045 | try: |
---|
1046 | DisAglCtls = General['DisAglCtls'] |
---|
1047 | radiusFactor = DisAglCtls['Factors'][sindex] |
---|
1048 | Radii = DisAglCtls[skey] |
---|
1049 | except: |
---|
1050 | radiusFactor = 0.85 |
---|
1051 | AtInfo = dict(zip(AtTypes,Radii)) #or General['BondRadii'] |
---|
1052 | if Orig is None: |
---|
1053 | Orig = atNames.index(FrstName) |
---|
1054 | OId = Atoms[Orig][cia+8] |
---|
1055 | OType = Atoms[Orig][ct] |
---|
1056 | XYZ = getAtomXYZ(Atoms,cx) |
---|
1057 | Oxyz = XYZ[Orig] |
---|
1058 | Neigh = [] |
---|
1059 | Ids = [] |
---|
1060 | sumR = np.array([AtInfo[OType]+AtInfo[atom[ct]] for atom in Atoms]) |
---|
1061 | sumR = np.reshape(np.tile(sumR,27),(27,-1)) |
---|
1062 | results = [] |
---|
1063 | for xyz in XYZ: |
---|
1064 | results.append(G2spc.GenAtom(xyz,SGData,False,Move=False)) |
---|
1065 | for iA,result in enumerate(results): |
---|
1066 | for [Txyz,Top,Tunit,Spn] in result: |
---|
1067 | Dx = np.array([Txyz-Oxyz+unit for unit in Units]) |
---|
1068 | dx = np.inner(Dx,Amat) |
---|
1069 | dist = np.sqrt(np.sum(dx**2,axis=1)) |
---|
1070 | IndB = ma.nonzero(ma.masked_greater(dist-radiusFactor*sumR[:,iA],0.)) |
---|
1071 | for iU in IndB[0]: |
---|
1072 | if dist[iU] < 0.001: continue |
---|
1073 | if AtNames[iA%len(AtNames)] != notName: |
---|
1074 | unit = Units[iU] |
---|
1075 | if np.any(unit): |
---|
1076 | Topstr = ' +(%4d)[%2d,%2d,%2d]'%(Top,unit[0],unit[1],unit[2]) |
---|
1077 | else: |
---|
1078 | Topstr = ' +(%4d)'%(Top) |
---|
1079 | if Short: |
---|
1080 | Neigh.append([AtNames[iA%len(AtNames)],dist[iU],True]) |
---|
1081 | else: |
---|
1082 | Neigh.append([AtNames[iA]+Topstr,atTypes[iA],dist[iU],dx[iU]]) |
---|
1083 | Ids.append(Atoms[iA][cia+8]) |
---|
1084 | return Neigh,[OId,Ids] |
---|
1085 | |
---|
1086 | def calcBond(A,Ax,Bx,MTCU): |
---|
1087 | cell = G2lat.A2cell(A) |
---|
1088 | Amat,Bmat = G2lat.cell2AB(cell) |
---|
1089 | M,T,C,U = MTCU |
---|
1090 | Btx = np.inner(M,Bx)+T+C+U |
---|
1091 | Dx = Btx-Ax |
---|
1092 | dist = np.sqrt(np.inner(Amat,Dx)) |
---|
1093 | return dist |
---|
1094 | |
---|
1095 | def AddHydrogens(AtLookUp,General,Atoms,AddHydId): |
---|
1096 | |
---|
1097 | def getTransMat(RXYZ,OXYZ,TXYZ,Amat): |
---|
1098 | Vec = np.inner(Amat,np.array([OXYZ-TXYZ[0],RXYZ-TXYZ[0]])).T |
---|
1099 | Vec /= np.sqrt(np.sum(Vec**2,axis=1))[:,nxs] |
---|
1100 | Mat2 = np.cross(Vec[0],Vec[1]) #UxV |
---|
1101 | Mat2 /= np.sqrt(np.sum(Mat2**2)) |
---|
1102 | Mat3 = np.cross(Mat2,Vec[0]) #(UxV)xU |
---|
1103 | return nl.inv(np.array([Vec[0],Mat2,Mat3])) |
---|
1104 | |
---|
1105 | cx,ct,cs,cia = General['AtomPtrs'] |
---|
1106 | Cell = General['Cell'][1:7] |
---|
1107 | Amat,Bmat = G2lat.cell2AB(Cell) |
---|
1108 | nBonds = AddHydId[-1]+len(AddHydId[1]) |
---|
1109 | Oatom = GetAtomsById(Atoms,AtLookUp,[AddHydId[0],])[0] |
---|
1110 | OXYZ = np.array(Oatom[cx:cx+3]) |
---|
1111 | if 'I' in Oatom[cia]: |
---|
1112 | Uiso = Oatom[cia+1] |
---|
1113 | else: |
---|
1114 | Uiso = (Oatom[cia+2]+Oatom[cia+3]+Oatom[cia+4])/3.0 #simple average |
---|
1115 | Uiso = max(Uiso,0.005) #set floor! |
---|
1116 | Tatoms = GetAtomsById(Atoms,AtLookUp,AddHydId[1]) |
---|
1117 | TXYZ = np.array([tatom[cx:cx+3] for tatom in Tatoms]) #3 x xyz |
---|
1118 | if nBonds == 4: |
---|
1119 | if AddHydId[-1] == 1: |
---|
1120 | Vec = TXYZ-OXYZ |
---|
1121 | Len = np.sqrt(np.sum(np.inner(Amat,Vec).T**2,axis=0)) |
---|
1122 | Vec = np.sum(Vec/Len,axis=0) |
---|
1123 | Len = np.sqrt(np.sum(Vec**2)) |
---|
1124 | Hpos = OXYZ-0.98*np.inner(Bmat,Vec).T/Len |
---|
1125 | HU = 1.1*Uiso |
---|
1126 | return [Hpos,],[HU,] |
---|
1127 | elif AddHydId[-1] == 2: |
---|
1128 | Vec = np.inner(Amat,TXYZ-OXYZ).T |
---|
1129 | Vec[0] += Vec[1] #U - along bisector |
---|
1130 | Vec /= np.sqrt(np.sum(Vec**2,axis=1))[:,nxs] |
---|
1131 | Mat2 = np.cross(Vec[0],Vec[1]) #UxV |
---|
1132 | Mat2 /= np.sqrt(np.sum(Mat2**2)) |
---|
1133 | Mat3 = np.cross(Mat2,Vec[0]) #(UxV)xU |
---|
1134 | iMat = nl.inv(np.array([Vec[0],Mat2,Mat3])) |
---|
1135 | Hpos = np.array([[-0.97*cosd(54.75),0.97*sind(54.75),0.], |
---|
1136 | [-0.97*cosd(54.75),-0.97*sind(54.75),0.]]) |
---|
1137 | HU = 1.2*Uiso*np.ones(2) |
---|
1138 | Hpos = np.inner(Bmat,np.inner(iMat,Hpos).T).T+OXYZ |
---|
1139 | return Hpos,HU |
---|
1140 | else: |
---|
1141 | Ratom = GetAtomsById(Atoms,AtLookUp,[AddHydId[2],])[0] |
---|
1142 | RXYZ = np.array(Ratom[cx:cx+3]) |
---|
1143 | iMat = getTransMat(RXYZ,OXYZ,TXYZ,Amat) |
---|
1144 | a = 0.96*cosd(70.5) |
---|
1145 | b = 0.96*sind(70.5) |
---|
1146 | Hpos = np.array([[a,0.,-b],[a,-b*cosd(30.),0.5*b],[a,b*cosd(30.),0.5*b]]) |
---|
1147 | Hpos = np.inner(Bmat,np.inner(iMat,Hpos).T).T+OXYZ |
---|
1148 | HU = 1.5*Uiso*np.ones(3) |
---|
1149 | return Hpos,HU |
---|
1150 | elif nBonds == 3: |
---|
1151 | if AddHydId[-1] == 1: |
---|
1152 | Vec = np.sum(TXYZ-OXYZ,axis=0) |
---|
1153 | Len = np.sqrt(np.sum(np.inner(Amat,Vec).T**2)) |
---|
1154 | Vec = -0.93*Vec/Len |
---|
1155 | Hpos = OXYZ+Vec |
---|
1156 | HU = 1.1*Uiso |
---|
1157 | return [Hpos,],[HU,] |
---|
1158 | elif AddHydId[-1] == 2: |
---|
1159 | Ratom = GetAtomsById(Atoms,AtLookUp,[AddHydId[2],])[0] |
---|
1160 | RXYZ = np.array(Ratom[cx:cx+3]) |
---|
1161 | iMat = getTransMat(RXYZ,OXYZ,TXYZ,Amat) |
---|
1162 | a = 0.93*cosd(60.) |
---|
1163 | b = 0.93*sind(60.) |
---|
1164 | Hpos = [[a,b,0],[a,-b,0]] |
---|
1165 | Hpos = np.inner(Bmat,np.inner(iMat,Hpos).T).T+OXYZ |
---|
1166 | HU = 1.2*Uiso*np.ones(2) |
---|
1167 | return Hpos,HU |
---|
1168 | else: #2 bonds |
---|
1169 | if 'C' in Oatom[ct]: |
---|
1170 | Vec = TXYZ[0]-OXYZ |
---|
1171 | Len = np.sqrt(np.sum(np.inner(Amat,Vec).T**2)) |
---|
1172 | Vec = -0.93*Vec/Len |
---|
1173 | Hpos = OXYZ+Vec |
---|
1174 | HU = 1.1*Uiso |
---|
1175 | return [Hpos,],[HU,] |
---|
1176 | elif 'O' in Oatom[ct]: |
---|
1177 | mapData = General['Map'] |
---|
1178 | Ratom = GetAtomsById(Atoms,AtLookUp,[AddHydId[2],])[0] |
---|
1179 | RXYZ = np.array(Ratom[cx:cx+3]) |
---|
1180 | iMat = getTransMat(RXYZ,OXYZ,TXYZ,Amat) |
---|
1181 | a = 0.82*cosd(70.5) |
---|
1182 | b = 0.82*sind(70.5) |
---|
1183 | azm = np.arange(0.,360.,5.) |
---|
1184 | Hpos = np.array([[a,b*cosd(x),b*sind(x)] for x in azm]) |
---|
1185 | Hpos = np.inner(Bmat,np.inner(iMat,Hpos).T).T+OXYZ |
---|
1186 | Rhos = np.array([getRho(pos,mapData) for pos in Hpos]) |
---|
1187 | imax = np.argmax(Rhos) |
---|
1188 | HU = 1.5*Uiso |
---|
1189 | return [Hpos[imax],],[HU,] |
---|
1190 | return [],[] |
---|
1191 | |
---|
1192 | #def AtomUij2TLS(atomData,atPtrs,Amat,Bmat,rbObj): #unfinished & not used |
---|
1193 | # '''default doc string |
---|
1194 | # |
---|
1195 | # :param type name: description |
---|
1196 | # |
---|
1197 | # :returns: type name: description |
---|
1198 | # |
---|
1199 | # ''' |
---|
1200 | # for atom in atomData: |
---|
1201 | # XYZ = np.inner(Amat,atom[cx:cx+3]) |
---|
1202 | # if atom[cia] == 'A': |
---|
1203 | # UIJ = atom[cia+2:cia+8] |
---|
1204 | |
---|
1205 | def TLS2Uij(xyz,g,Amat,rbObj): #not used anywhere, but could be? |
---|
1206 | '''default doc string |
---|
1207 | |
---|
1208 | :param type name: description |
---|
1209 | |
---|
1210 | :returns: type name: description |
---|
1211 | |
---|
1212 | ''' |
---|
1213 | TLStype,TLS = rbObj['ThermalMotion'][:2] |
---|
1214 | Tmat = np.zeros((3,3)) |
---|
1215 | Lmat = np.zeros((3,3)) |
---|
1216 | Smat = np.zeros((3,3)) |
---|
1217 | gvec = np.sqrt(np.array([g[0][0]**2,g[1][1]**2,g[2][2]**2, |
---|
1218 | g[0][0]*g[1][1],g[0][0]*g[2][2],g[1][1]*g[2][2]])) |
---|
1219 | if 'T' in TLStype: |
---|
1220 | Tmat = G2lat.U6toUij(TLS[:6]) |
---|
1221 | if 'L' in TLStype: |
---|
1222 | Lmat = G2lat.U6toUij(TLS[6:12]) |
---|
1223 | if 'S' in TLStype: |
---|
1224 | Smat = np.array([[TLS[18],TLS[12],TLS[13]],[TLS[14],TLS[19],TLS[15]],[TLS[16],TLS[17],0] ]) |
---|
1225 | XYZ = np.inner(Amat,xyz) |
---|
1226 | Axyz = np.array([[ 0,XYZ[2],-XYZ[1]], [-XYZ[2],0,XYZ[0]], [XYZ[1],-XYZ[0],0]] ) |
---|
1227 | Umat = Tmat+np.inner(Axyz,Smat)+np.inner(Smat.T,Axyz.T)+np.inner(np.inner(Axyz,Lmat),Axyz.T) |
---|
1228 | beta = np.inner(np.inner(g,Umat),g) |
---|
1229 | return G2lat.UijtoU6(beta)*gvec |
---|
1230 | |
---|
1231 | def AtomTLS2UIJ(atomData,atPtrs,Amat,rbObj): #not used anywhere, but could be? |
---|
1232 | '''default doc string |
---|
1233 | |
---|
1234 | :param type name: description |
---|
1235 | |
---|
1236 | :returns: type name: description |
---|
1237 | |
---|
1238 | ''' |
---|
1239 | cx,ct,cs,cia = atPtrs |
---|
1240 | TLStype,TLS = rbObj['ThermalMotion'][:2] |
---|
1241 | Tmat = np.zeros((3,3)) |
---|
1242 | Lmat = np.zeros((3,3)) |
---|
1243 | Smat = np.zeros((3,3)) |
---|
1244 | G,g = G2lat.A2Gmat(Amat) |
---|
1245 | gvec = 1./np.sqrt(np.array([g[0][0],g[1][1],g[2][2],g[0][1],g[0][2],g[1][2]])) |
---|
1246 | if 'T' in TLStype: |
---|
1247 | Tmat = G2lat.U6toUij(TLS[:6]) |
---|
1248 | if 'L' in TLStype: |
---|
1249 | Lmat = G2lat.U6toUij(TLS[6:12]) |
---|
1250 | if 'S' in TLStype: |
---|
1251 | Smat = np.array([ [TLS[18],TLS[12],TLS[13]], [TLS[14],TLS[19],TLS[15]], [TLS[16],TLS[17],0] ]) |
---|
1252 | for atom in atomData: |
---|
1253 | XYZ = np.inner(Amat,atom[cx:cx+3]) |
---|
1254 | Axyz = np.array([ 0,XYZ[2],-XYZ[1], -XYZ[2],0,XYZ[0], XYZ[1],-XYZ[0],0],ndmin=2 ) |
---|
1255 | if 'U' in TLStype: |
---|
1256 | atom[cia+1] = TLS[0] |
---|
1257 | atom[cia] = 'I' |
---|
1258 | else: |
---|
1259 | atom[cia] = 'A' |
---|
1260 | Umat = Tmat+np.inner(Axyz,Smat)+np.inner(Smat.T,Axyz.T)+np.inner(np.inner(Axyz,Lmat),Axyz.T) |
---|
1261 | beta = np.inner(np.inner(g,Umat),g) |
---|
1262 | atom[cia+2:cia+8] = G2spc.U2Uij(beta/gvec) |
---|
1263 | |
---|
1264 | def GetXYZDist(xyz,XYZ,Amat): |
---|
1265 | '''gets distance from position xyz to all XYZ, xyz & XYZ are np.array |
---|
1266 | and are in crystal coordinates; Amat is crystal to Cart matrix |
---|
1267 | |
---|
1268 | :param type name: description |
---|
1269 | |
---|
1270 | :returns: type name: description |
---|
1271 | |
---|
1272 | ''' |
---|
1273 | return np.sqrt(np.sum(np.inner(Amat,XYZ-xyz)**2,axis=0)) |
---|
1274 | |
---|
1275 | def getAtomXYZ(atoms,cx): |
---|
1276 | '''Create an array of fractional coordinates from the atoms list |
---|
1277 | |
---|
1278 | :param list atoms: atoms object as found in tree |
---|
1279 | :param int cx: offset to where coordinates are found |
---|
1280 | |
---|
1281 | :returns: np.array with shape (n,3) |
---|
1282 | ''' |
---|
1283 | XYZ = [] |
---|
1284 | for atom in atoms: |
---|
1285 | XYZ.append(atom[cx:cx+3]) |
---|
1286 | return np.array(XYZ) |
---|
1287 | |
---|
1288 | def getRBTransMat(X,Y): |
---|
1289 | '''Get transformation for Cartesian axes given 2 vectors |
---|
1290 | X will be parallel to new X-axis; X cross Y will be new Z-axis & |
---|
1291 | (X cross Y) cross Y will be new Y-axis |
---|
1292 | Useful for rigid body axes definintion |
---|
1293 | |
---|
1294 | :param array X: normalized vector |
---|
1295 | :param array Y: normalized vector |
---|
1296 | |
---|
1297 | :returns: array M: transformation matrix |
---|
1298 | |
---|
1299 | use as XYZ' = np.inner(M,XYZ) where XYZ are Cartesian |
---|
1300 | |
---|
1301 | ''' |
---|
1302 | Mat2 = np.cross(X,Y) #UxV-->Z |
---|
1303 | Mat2 /= np.sqrt(np.sum(Mat2**2)) |
---|
1304 | Mat3 = np.cross(Mat2,X) #(UxV)xU-->Y |
---|
1305 | Mat3 /= np.sqrt(np.sum(Mat3**2)) |
---|
1306 | return np.array([X,Mat3,Mat2]) |
---|
1307 | |
---|
1308 | def RotateRBXYZ(Bmat,Cart,oriQ,symAxis=None): |
---|
1309 | '''rotate & transform cartesian coordinates to crystallographic ones |
---|
1310 | no translation applied. To be used for numerical derivatives |
---|
1311 | |
---|
1312 | :param array Bmat: Orthogonalization matrix, see :func:`GSASIIlattice.cell2AB` |
---|
1313 | :param array Cart: 2D array of coordinates |
---|
1314 | :param array Q: quaternion as an np.array |
---|
1315 | :param tuple symAxis: if not None (default), specifies the symmetry |
---|
1316 | axis of the rigid body, which will be aligned to the quaternion vector. |
---|
1317 | :returns: 2D array of fractional coordinates, without translation to origin |
---|
1318 | ''' |
---|
1319 | if symAxis is None: |
---|
1320 | Q = oriQ |
---|
1321 | else: |
---|
1322 | a,v = Q2AV(oriQ) |
---|
1323 | symaxis = np.array(symAxis) |
---|
1324 | vdotsym = min(1.0,max(-1.0,np.vdot(v,symaxis))) |
---|
1325 | xformAng = np.arccos(vdotsym) |
---|
1326 | xformVec = np.cross(symaxis,v) |
---|
1327 | Q = prodQQ(oriQ,AV2Q(xformAng,xformVec)) |
---|
1328 | XYZ = np.zeros_like(Cart) |
---|
1329 | for i,xyz in enumerate(Cart): |
---|
1330 | XYZ[i] = np.inner(Bmat,prodQVQ(Q,xyz)) |
---|
1331 | return XYZ |
---|
1332 | |
---|
1333 | def UpdateRBXYZ(Bmat,RBObj,RBData,RBType): |
---|
1334 | '''returns crystal coordinates for atoms described by RBObj. |
---|
1335 | Note that RBObj['symAxis'], if present, determines the symmetry |
---|
1336 | axis of the rigid body, which will be aligned to the |
---|
1337 | quaternion direction. |
---|
1338 | |
---|
1339 | :param np.array Bmat: see :func:`GSASIIlattice.cell2AB` |
---|
1340 | :param dict rbObj: rigid body selection/orientation information |
---|
1341 | :param dict RBData: rigid body tree data structure |
---|
1342 | :param str RBType: rigid body type, 'Vector' or 'Residue' |
---|
1343 | |
---|
1344 | :returns: coordinates for rigid body as XYZ,Cart where XYZ is |
---|
1345 | the location in crystal coordinates and Cart is in cartesian |
---|
1346 | ''' |
---|
1347 | if RBType == 'Vector': |
---|
1348 | RBRes = RBData[RBType][RBObj['RBId']] |
---|
1349 | vecs = RBRes['rbVect'] |
---|
1350 | mags = RBRes['VectMag'] |
---|
1351 | Cart = np.zeros_like(vecs[0]) |
---|
1352 | for vec,mag in zip(vecs,mags): |
---|
1353 | Cart += vec*mag |
---|
1354 | elif RBType == 'Residue': |
---|
1355 | RBRes = RBData[RBType][RBObj['RBId']] |
---|
1356 | Cart = np.array(RBRes['rbXYZ']) |
---|
1357 | for tor,seq in zip(RBObj['Torsions'],RBRes['rbSeq']): |
---|
1358 | QuatA = AVdeg2Q(tor[0],Cart[seq[0]]-Cart[seq[1]]) |
---|
1359 | Cart[seq[3]] = prodQVQ(QuatA,(Cart[seq[3]]-Cart[seq[1]]))+Cart[seq[1]] |
---|
1360 | elif RBType == 'Spin': |
---|
1361 | Cart = np.zeros(3) |
---|
1362 | XYZ = [np.array(RBObj['Orig'][0]),] |
---|
1363 | return XYZ,Cart |
---|
1364 | # if symmetry axis is defined, place symmetry axis along quaternion |
---|
1365 | if RBObj.get('symAxis') is None: |
---|
1366 | Q = RBObj['Orient'][0] |
---|
1367 | else: |
---|
1368 | a,v = Q2AV(RBObj['Orient'][0]) |
---|
1369 | symaxis = np.array(RBObj.get('symAxis')) |
---|
1370 | vdotsym = min(1.0,max(-1.0,np.vdot(v,symaxis))) |
---|
1371 | xformAng = np.arccos(vdotsym) |
---|
1372 | xformVec = np.cross(symaxis,v) |
---|
1373 | Q = prodQQ(RBObj['Orient'][0],AV2Q(xformAng,xformVec)) |
---|
1374 | XYZ = np.zeros_like(Cart) |
---|
1375 | for i,xyz in enumerate(Cart): |
---|
1376 | XYZ[i] = np.inner(Bmat,prodQVQ(Q,xyz))+RBObj['Orig'][0] |
---|
1377 | return XYZ,Cart |
---|
1378 | |
---|
1379 | def GetSpnRBData(SpnRB,atId): |
---|
1380 | for SpnData in SpnRB: |
---|
1381 | if atId in SpnData['Ids']: |
---|
1382 | return SpnData |
---|
1383 | |
---|
1384 | def UpdateMCSAxyz(Bmat,MCSA): |
---|
1385 | '''default doc string |
---|
1386 | |
---|
1387 | :param type name: description |
---|
1388 | |
---|
1389 | :returns: type name: description |
---|
1390 | |
---|
1391 | ''' |
---|
1392 | xyz = [] |
---|
1393 | atTypes = [] |
---|
1394 | iatm = 0 |
---|
1395 | for model in MCSA['Models'][1:]: #skip the MD model |
---|
1396 | if model['Type'] == 'Atom': |
---|
1397 | xyz.append(model['Pos'][0]) |
---|
1398 | atTypes.append(model['atType']) |
---|
1399 | iatm += 1 |
---|
1400 | else: |
---|
1401 | RBRes = MCSA['rbData'][model['Type']][model['RBId']] |
---|
1402 | Pos = np.array(model['Pos'][0]) |
---|
1403 | Ori = np.array(model['Ori'][0]) |
---|
1404 | Qori = AVdeg2Q(Ori[0],Ori[1:]) |
---|
1405 | if model['Type'] == 'Vector': |
---|
1406 | vecs = RBRes['rbVect'] |
---|
1407 | mags = RBRes['VectMag'] |
---|
1408 | Cart = np.zeros_like(vecs[0]) |
---|
1409 | for vec,mag in zip(vecs,mags): |
---|
1410 | Cart += vec*mag |
---|
1411 | elif model['Type'] == 'Residue': |
---|
1412 | Cart = np.array(RBRes['rbXYZ']) |
---|
1413 | for itor,seq in enumerate(RBRes['rbSeq']): |
---|
1414 | QuatA = AVdeg2Q(model['Tor'][0][itor],Cart[seq[0]]-Cart[seq[1]]) |
---|
1415 | Cart[seq[3]] = prodQVQ(QuatA,(Cart[seq[3]]-Cart[seq[1]]))+Cart[seq[1]] |
---|
1416 | if model['MolCent'][1]: |
---|
1417 | Cart -= model['MolCent'][0] |
---|
1418 | for i,x in enumerate(Cart): |
---|
1419 | xyz.append(np.inner(Bmat,prodQVQ(Qori,x))+Pos) |
---|
1420 | atType = RBRes['rbTypes'][i] |
---|
1421 | atTypes.append(atType) |
---|
1422 | iatm += 1 |
---|
1423 | return np.array(xyz),atTypes |
---|
1424 | |
---|
1425 | def SetMolCent(model,RBData): |
---|
1426 | '''default doc string |
---|
1427 | |
---|
1428 | :param type name: description |
---|
1429 | |
---|
1430 | :returns: type name: description |
---|
1431 | |
---|
1432 | ''' |
---|
1433 | rideList = [] |
---|
1434 | RBRes = RBData[model['Type']][model['RBId']] |
---|
1435 | if model['Type'] == 'Vector': |
---|
1436 | vecs = RBRes['rbVect'] |
---|
1437 | mags = RBRes['VectMag'] |
---|
1438 | Cart = np.zeros_like(vecs[0]) |
---|
1439 | for vec,mag in zip(vecs,mags): |
---|
1440 | Cart += vec*mag |
---|
1441 | elif model['Type'] == 'Residue': |
---|
1442 | Cart = np.array(RBRes['rbXYZ']) |
---|
1443 | for seq in RBRes['rbSeq']: |
---|
1444 | rideList += seq[3] |
---|
1445 | centList = set(range(len(Cart)))-set(rideList) |
---|
1446 | cent = np.zeros(3) |
---|
1447 | for i in centList: |
---|
1448 | cent += Cart[i] |
---|
1449 | model['MolCent'][0] = cent/len(centList) |
---|
1450 | |
---|
1451 | ############################################################################### |
---|
1452 | #### Various utilities |
---|
1453 | ############################################################################### |
---|
1454 | |
---|
1455 | def UpdateRBUIJ(Bmat,Cart,RBObj): |
---|
1456 | '''default doc string |
---|
1457 | |
---|
1458 | :param type name: description |
---|
1459 | |
---|
1460 | :returns: type name: description |
---|
1461 | |
---|
1462 | ''' |
---|
1463 | ''' returns atom I/A, Uiso or UIJ for atoms at XYZ as described by RBObj |
---|
1464 | ''' |
---|
1465 | TLStype,TLS = RBObj['ThermalMotion'][:2] |
---|
1466 | T = np.zeros(6) |
---|
1467 | L = np.zeros(6) |
---|
1468 | S = np.zeros(8) |
---|
1469 | if 'T' in TLStype: |
---|
1470 | T = TLS[:6] |
---|
1471 | if 'L' in TLStype: |
---|
1472 | L = np.array(TLS[6:12])*(np.pi/180.)**2 |
---|
1473 | if 'S' in TLStype: |
---|
1474 | S = np.array(TLS[12:])*(np.pi/180.) |
---|
1475 | g = nl.inv(np.inner(Bmat,Bmat)) |
---|
1476 | gvec = np.sqrt(np.array([g[0][0]**2,g[1][1]**2,g[2][2]**2, |
---|
1477 | g[0][0]*g[1][1],g[0][0]*g[2][2],g[1][1]*g[2][2]])) |
---|
1478 | Uout = [] |
---|
1479 | Q = RBObj['Orient'][0] |
---|
1480 | for X in Cart: |
---|
1481 | X = prodQVQ(Q,X) |
---|
1482 | if 'U' in TLStype: |
---|
1483 | Uout.append(['I',TLS[0],0,0,0,0,0,0]) |
---|
1484 | elif not 'N' in TLStype: |
---|
1485 | U = [0,0,0,0,0,0] |
---|
1486 | U[0] = T[0]+L[1]*X[2]**2+L[2]*X[1]**2-2.0*L[5]*X[1]*X[2]+2.0*(S[2]*X[2]-S[4]*X[1]) |
---|
1487 | U[1] = T[1]+L[0]*X[2]**2+L[2]*X[0]**2-2.0*L[4]*X[0]*X[2]+2.0*(S[5]*X[0]-S[0]*X[2]) |
---|
1488 | U[2] = T[2]+L[1]*X[0]**2+L[0]*X[1]**2-2.0*L[3]*X[1]*X[0]+2.0*(S[1]*X[1]-S[3]*X[0]) |
---|
1489 | U[3] = T[3]+L[4]*X[1]*X[2]+L[5]*X[0]*X[2]-L[3]*X[2]**2-L[2]*X[0]*X[1]+ \ |
---|
1490 | S[4]*X[0]-S[5]*X[1]-(S[6]+S[7])*X[2] |
---|
1491 | U[4] = T[4]+L[3]*X[1]*X[2]+L[5]*X[0]*X[1]-L[4]*X[1]**2-L[1]*X[0]*X[2]+ \ |
---|
1492 | S[3]*X[2]-S[2]*X[0]+S[6]*X[1] |
---|
1493 | U[5] = T[5]+L[3]*X[0]*X[2]+L[4]*X[0]*X[1]-L[5]*X[0]**2-L[0]*X[2]*X[1]+ \ |
---|
1494 | S[0]*X[1]-S[1]*X[2]+S[7]*X[0] |
---|
1495 | Umat = G2lat.U6toUij(U) |
---|
1496 | beta = np.inner(np.inner(Bmat.T,Umat),Bmat) |
---|
1497 | Uout.append(['A',0.0,]+list(G2lat.UijtoU6(beta)*gvec)) |
---|
1498 | else: |
---|
1499 | Uout.append(['N',]) |
---|
1500 | return Uout |
---|
1501 | |
---|
1502 | def GetSHCoeff(pId,parmDict,SHkeys): |
---|
1503 | '''default doc string |
---|
1504 | |
---|
1505 | :param type name: description |
---|
1506 | |
---|
1507 | :returns: type name: description |
---|
1508 | |
---|
1509 | ''' |
---|
1510 | SHCoeff = {} |
---|
1511 | for shkey in SHkeys: |
---|
1512 | shname = str(pId)+'::'+shkey |
---|
1513 | SHCoeff[shkey] = parmDict[shname] |
---|
1514 | return SHCoeff |
---|
1515 | |
---|
1516 | def getMass(generalData): |
---|
1517 | '''Computes mass of unit cell contents |
---|
1518 | |
---|
1519 | :param dict generalData: The General dictionary in Phase |
---|
1520 | |
---|
1521 | :returns: float mass: Crystal unit cell mass in AMU. |
---|
1522 | |
---|
1523 | ''' |
---|
1524 | mass = 0. |
---|
1525 | for i,elem in enumerate(generalData['AtomTypes']): |
---|
1526 | mass += generalData['NoAtoms'][elem]*generalData['AtomMass'][i] |
---|
1527 | return max(mass,1.0) |
---|
1528 | |
---|
1529 | def getDensity(generalData): |
---|
1530 | '''calculate crystal structure density |
---|
1531 | |
---|
1532 | :param dict generalData: The General dictionary in Phase |
---|
1533 | |
---|
1534 | :returns: float density: crystal density in gm/cm^3 |
---|
1535 | |
---|
1536 | ''' |
---|
1537 | mass = getMass(generalData) |
---|
1538 | Volume = generalData['Cell'][7] |
---|
1539 | density = mass/(0.6022137*Volume) |
---|
1540 | return density,Volume/mass |
---|
1541 | |
---|
1542 | def getWave(Parms): |
---|
1543 | '''returns wavelength from Instrument parameters dictionary |
---|
1544 | |
---|
1545 | :param dict Parms: Instrument parameters; |
---|
1546 | must contain: |
---|
1547 | Lam: single wavelength |
---|
1548 | or |
---|
1549 | Lam1: Ka1 radiation wavelength |
---|
1550 | |
---|
1551 | :returns: float wave: wavelength |
---|
1552 | |
---|
1553 | ''' |
---|
1554 | try: |
---|
1555 | return Parms['Lam'][1] |
---|
1556 | except KeyError: |
---|
1557 | return Parms['Lam1'][1] |
---|
1558 | |
---|
1559 | def getMeanWave(Parms): |
---|
1560 | '''returns mean wavelength from Instrument parameters dictionary |
---|
1561 | |
---|
1562 | :param dict Parms: Instrument parameters; |
---|
1563 | must contain: |
---|
1564 | Lam: single wavelength |
---|
1565 | or |
---|
1566 | Lam1,Lam2: Ka1,Ka2 radiation wavelength |
---|
1567 | I(L2)/I(L1): Ka2/Ka1 ratio |
---|
1568 | |
---|
1569 | :returns: float wave: mean wavelength |
---|
1570 | |
---|
1571 | ''' |
---|
1572 | try: |
---|
1573 | return Parms['Lam'][1] |
---|
1574 | except KeyError: |
---|
1575 | meanLam = (Parms['Lam1'][1]+Parms['I(L2)/I(L1)'][1]*Parms['Lam2'][1])/ \ |
---|
1576 | (1.+Parms['I(L2)/I(L1)'][1]) |
---|
1577 | return meanLam |
---|
1578 | |
---|
1579 | |
---|
1580 | def El2Mass(Elements): |
---|
1581 | '''compute molecular weight from Elements |
---|
1582 | |
---|
1583 | :param dict Elements: elements in molecular formula; |
---|
1584 | each must contain |
---|
1585 | Num: number of atoms in formula |
---|
1586 | Mass: at. wt. |
---|
1587 | |
---|
1588 | :returns: float mass: molecular weight. |
---|
1589 | |
---|
1590 | ''' |
---|
1591 | mass = 0 |
---|
1592 | for El in Elements: |
---|
1593 | mass += Elements[El]['Num']*Elements[El]['Mass'] |
---|
1594 | return mass |
---|
1595 | |
---|
1596 | def Den2Vol(Elements,density): |
---|
1597 | '''converts density to molecular volume |
---|
1598 | |
---|
1599 | :param dict Elements: elements in molecular formula; |
---|
1600 | each must contain |
---|
1601 | Num: number of atoms in formula |
---|
1602 | Mass: at. wt. |
---|
1603 | :param float density: material density in gm/cm^3 |
---|
1604 | |
---|
1605 | :returns: float volume: molecular volume in A^3 |
---|
1606 | |
---|
1607 | ''' |
---|
1608 | return El2Mass(Elements)/(density*0.6022137) |
---|
1609 | |
---|
1610 | def Vol2Den(Elements,volume): |
---|
1611 | '''converts volume to density |
---|
1612 | |
---|
1613 | :param dict Elements: elements in molecular formula; |
---|
1614 | each must contain |
---|
1615 | Num: number of atoms in formula |
---|
1616 | Mass: at. wt. |
---|
1617 | :param float volume: molecular volume in A^3 |
---|
1618 | |
---|
1619 | :returns: float density: material density in gm/cm^3 |
---|
1620 | |
---|
1621 | ''' |
---|
1622 | return El2Mass(Elements)/(volume*0.6022137) |
---|
1623 | |
---|
1624 | def El2EstVol(Elements): |
---|
1625 | '''Estimate volume from molecular formula; assumes atom volume = 10A^3 |
---|
1626 | |
---|
1627 | :param dict Elements: elements in molecular formula; |
---|
1628 | each must contain |
---|
1629 | Num: number of atoms in formula |
---|
1630 | |
---|
1631 | :returns: float volume: estimate of molecular volume in A^3 |
---|
1632 | |
---|
1633 | ''' |
---|
1634 | vol = 0 |
---|
1635 | for El in Elements: |
---|
1636 | vol += 10.*Elements[El]['Num'] |
---|
1637 | return vol |
---|
1638 | |
---|
1639 | def XScattDen(Elements,vol,wave=0.): |
---|
1640 | '''Estimate X-ray scattering density from molecular formula & volume; |
---|
1641 | ignores valence, but includes anomalous effects |
---|
1642 | |
---|
1643 | :param dict Elements: elements in molecular formula; |
---|
1644 | each element must contain |
---|
1645 | Num: number of atoms in formula |
---|
1646 | Z: atomic number |
---|
1647 | :param float vol: molecular volume in A^3 |
---|
1648 | :param float wave: optional wavelength in A |
---|
1649 | |
---|
1650 | :returns: float rho: scattering density in 10^10cm^-2; |
---|
1651 | if wave > 0 the includes f' contribution |
---|
1652 | :returns: float mu: if wave>0 absorption coeff in cm^-1 ; otherwise 0 |
---|
1653 | :returns: float fpp: if wave>0 f" in 10^10cm^-2; otherwise 0 |
---|
1654 | |
---|
1655 | ''' |
---|
1656 | rho = 0 |
---|
1657 | mu = 0 |
---|
1658 | fpp = 0 |
---|
1659 | if wave: |
---|
1660 | Xanom = XAnomAbs(Elements,wave) |
---|
1661 | for El in Elements: |
---|
1662 | f0 = Elements[El]['Z'] |
---|
1663 | if wave: |
---|
1664 | f0 += Xanom[El][0] |
---|
1665 | fpp += Xanom[El][1]*Elements[El]['Num'] |
---|
1666 | mu += Xanom[El][2]*Elements[El]['Num'] |
---|
1667 | rho += Elements[El]['Num']*f0 |
---|
1668 | return 28.179*rho/vol,mu/vol,28.179*fpp/vol |
---|
1669 | |
---|
1670 | def NCScattDen(Elements,vol,wave=0.): |
---|
1671 | '''Estimate neutron scattering density from molecular formula & volume; |
---|
1672 | ignores valence, but includes anomalous effects |
---|
1673 | |
---|
1674 | :param dict Elements: elements in molecular formula; |
---|
1675 | each element must contain |
---|
1676 | Num: number of atoms in formula |
---|
1677 | Z: atomic number |
---|
1678 | :param float vol: molecular volume in A^3 |
---|
1679 | :param float wave: optional wavelength in A |
---|
1680 | |
---|
1681 | :returns: float rho: scattering density in 10^10cm^-2; |
---|
1682 | if wave > 0 the includes f' contribution |
---|
1683 | :returns: float mu: if wave>0 absorption coeff in cm^-1 ; otherwise 0 |
---|
1684 | :returns: float fpp: if wave>0 f" in 10^10cm^-2; otherwise 0 |
---|
1685 | |
---|
1686 | ''' |
---|
1687 | rho = 0 |
---|
1688 | mu = 0 |
---|
1689 | bpp = 0 |
---|
1690 | for El in Elements: |
---|
1691 | isotope = Elements[El]['Isotope'] |
---|
1692 | b0 = Elements[El]['Isotopes'][isotope]['SL'][0] |
---|
1693 | mu += Elements[El]['Isotopes'][isotope].get('SA',0.)*Elements[El]['Num'] |
---|
1694 | if wave and 'BW-LS' in Elements[El]['Isotopes'][isotope]: |
---|
1695 | Re,Im,E0,gam,A,E1,B,E2 = Elements[El]['Isotopes'][isotope]['BW-LS'][1:] |
---|
1696 | Emev = 81.80703/wave**2 |
---|
1697 | T0 = Emev-E0 |
---|
1698 | T1 = Emev-E1 |
---|
1699 | T2 = Emev-E2 |
---|
1700 | D0 = T0**2+gam**2 |
---|
1701 | D1 = T1**2+gam**2 |
---|
1702 | D2 = T2**2+gam**2 |
---|
1703 | b0 += Re*(T0/D0+A*T1/D1+B*T2/D2) |
---|
1704 | bpp += Im*(1/D0+A/D1+B/D2) |
---|
1705 | else: |
---|
1706 | bpp += Elements[El]['Isotopes'][isotope]['SL'][1] |
---|
1707 | rho += Elements[El]['Num']*b0 |
---|
1708 | if wave: mu *= wave |
---|
1709 | return 100.*rho/vol,mu/vol,100.*bpp/vol |
---|
1710 | |
---|
1711 | def wavekE(wavekE): |
---|
1712 | '''Convert wavelength to energy & vise versa |
---|
1713 | |
---|
1714 | :param float waveKe:wavelength in A or energy in kE |
---|
1715 | |
---|
1716 | :returns float waveKe:the other one |
---|
1717 | |
---|
1718 | ''' |
---|
1719 | return 12.397639/wavekE |
---|
1720 | |
---|
1721 | def XAnomAbs(Elements,wave): |
---|
1722 | kE = wavekE(wave) |
---|
1723 | Xanom = {} |
---|
1724 | for El in Elements: |
---|
1725 | Orbs = G2el.GetXsectionCoeff(El) |
---|
1726 | Xanom[El] = G2el.FPcalc(Orbs, kE) |
---|
1727 | return Xanom #f',f", mu |
---|
1728 | |
---|
1729 | ################################################################################ |
---|
1730 | #### Modulation math |
---|
1731 | ################################################################################ |
---|
1732 | |
---|
1733 | def makeWaves(waveTypes,FSSdata,XSSdata,USSdata,MSSdata,Mast): |
---|
1734 | ''' |
---|
1735 | waveTypes: array nAtoms: 'Fourier','ZigZag' or 'Block' |
---|
1736 | FSSdata: array 2 x atoms x waves (sin,cos terms) |
---|
1737 | XSSdata: array 2x3 x atoms X waves (sin,cos terms) |
---|
1738 | USSdata: array 2x6 x atoms X waves (sin,cos terms) |
---|
1739 | MSSdata: array 2x3 x atoms X waves (sin,cos terms) |
---|
1740 | |
---|
1741 | Mast: array orthogonalization matrix for Uij |
---|
1742 | ''' |
---|
1743 | ngl = 36 #selected for integer steps for 1/6,1/4,1/3... |
---|
1744 | glTau,glWt = pwd.pygauleg(0.,1.,ngl) #get Gauss-Legendre intervals & weights |
---|
1745 | Ax = np.array(XSSdata[:3]).T #atoms x waves x sin pos mods |
---|
1746 | Bx = np.array(XSSdata[3:]).T #...cos pos mods |
---|
1747 | Af = np.array(FSSdata[0]).T #sin frac mods x waves x atoms |
---|
1748 | Bf = np.array(FSSdata[1]).T #cos frac mods... |
---|
1749 | Au = Mast*np.array(G2lat.U6toUij(USSdata[:6])).T #atoms x waves x sin Uij mods as betaij |
---|
1750 | Bu = Mast*np.array(G2lat.U6toUij(USSdata[6:])).T #...cos Uij mods as betaij |
---|
1751 | Am = np.array(MSSdata[:3]).T #atoms x waves x sin pos mods |
---|
1752 | Bm = np.array(MSSdata[3:]).T #...cos pos mods |
---|
1753 | nWaves = [Af.shape[1],Ax.shape[1],Au.shape[1],Am.shape[1]] |
---|
1754 | if nWaves[0]: |
---|
1755 | tauF = np.arange(1.,nWaves[0]+1)[:,nxs]*glTau #Fwaves x ngl |
---|
1756 | FmodA = Af[:,:,nxs]*np.sin(twopi*tauF[nxs,:,:]) #atoms X Fwaves X ngl |
---|
1757 | FmodB = Bf[:,:,nxs]*np.cos(twopi*tauF[nxs,:,:]) |
---|
1758 | Fmod = np.sum(1.0+FmodA+FmodB,axis=1) #atoms X ngl; sum waves |
---|
1759 | else: |
---|
1760 | Fmod = 1.0 |
---|
1761 | XmodZ = np.zeros((Ax.shape[0],Ax.shape[1],3,ngl)) |
---|
1762 | XmodA = np.zeros((Ax.shape[0],Ax.shape[1],3,ngl)) |
---|
1763 | XmodB = np.zeros((Ax.shape[0],Ax.shape[1],3,ngl)) |
---|
1764 | for iatm in range(Ax.shape[0]): |
---|
1765 | nx = 0 |
---|
1766 | if 'ZigZag' in waveTypes[iatm]: |
---|
1767 | nx = 1 |
---|
1768 | Tmm = Ax[iatm][0][:2] |
---|
1769 | XYZmax = np.array([Ax[iatm][0][2],Bx[iatm][0][0],Bx[iatm][0][1]]) |
---|
1770 | XmodZ[iatm][0] += posZigZag(glTau,Tmm,XYZmax).T |
---|
1771 | elif 'Block' in waveTypes[iatm]: |
---|
1772 | nx = 1 |
---|
1773 | Tmm = Ax[iatm][0][:2] |
---|
1774 | XYZmax = np.array([Ax[iatm][0][2],Bx[iatm][0][0],Bx[iatm][0][1]]) |
---|
1775 | XmodZ[iatm][0] += posBlock(glTau,Tmm,XYZmax).T |
---|
1776 | tauX = np.arange(1.,nWaves[1]+1-nx)[:,nxs]*glTau #Xwaves x ngl |
---|
1777 | if nx: |
---|
1778 | XmodA[iatm][:-nx] = Ax[iatm,nx:,:,nxs]*np.sin(twopi*tauX[nxs,:,nxs,:]) #atoms X waves X 3 X ngl |
---|
1779 | XmodB[iatm][:-nx] = Bx[iatm,nx:,:,nxs]*np.cos(twopi*tauX[nxs,:,nxs,:]) #ditto |
---|
1780 | else: |
---|
1781 | XmodA[iatm] = Ax[iatm,:,:,nxs]*np.sin(twopi*tauX[nxs,:,nxs,:]) #atoms X waves X 3 X ngl |
---|
1782 | XmodB[iatm] = Bx[iatm,:,:,nxs]*np.cos(twopi*tauX[nxs,:,nxs,:]) #ditto |
---|
1783 | Xmod = np.sum(XmodA+XmodB+XmodZ,axis=1) #atoms X 3 X ngl; sum waves |
---|
1784 | Xmod = np.swapaxes(Xmod,1,2) |
---|
1785 | if nWaves[2]: |
---|
1786 | tauU = np.arange(1.,nWaves[2]+1)[:,nxs]*glTau #Uwaves x ngl |
---|
1787 | UmodA = Au[:,:,:,:,nxs]*np.sin(twopi*tauU[nxs,:,nxs,nxs,:]) #atoms x waves x 3x3 x ngl |
---|
1788 | UmodB = Bu[:,:,:,:,nxs]*np.cos(twopi*tauU[nxs,:,nxs,nxs,:]) #ditto |
---|
1789 | Umod = np.swapaxes(np.sum(UmodA+UmodB,axis=1),1,3) #atoms x 3x3 x ngl; sum waves |
---|
1790 | else: |
---|
1791 | Umod = 1.0 |
---|
1792 | if nWaves[3]: |
---|
1793 | tauM = np.arange(1.,nWaves[3]+1-nx)[:,nxs]*glTau #Mwaves x ngl |
---|
1794 | MmodA = Am[:,:,:,nxs]*np.sin(twopi*tauM[nxs,:,nxs,:]) #atoms X waves X 3 X tau |
---|
1795 | MmodB = Bm[:,:,:,nxs]*np.cos(twopi*tauM[nxs,:,nxs,:]) #ditto |
---|
1796 | Mmod = np.sum(MmodA+MmodB,axis=1) |
---|
1797 | Mmod = np.swapaxes(Mmod,1,2) #Mxyz,Ntau,Natm |
---|
1798 | else: |
---|
1799 | Mmod = 1.0 |
---|
1800 | return ngl,nWaves,Fmod,Xmod,Umod,Mmod,glTau,glWt |
---|
1801 | |
---|
1802 | def MagMod(glTau,XYZ,modQ,MSSdata,SGData,SSGData): |
---|
1803 | ''' |
---|
1804 | this needs to make magnetic moment modulations & magnitudes as |
---|
1805 | fxn of gTau points; NB: this allows only 1 mag. wave fxn. |
---|
1806 | ''' |
---|
1807 | Am = np.array(MSSdata[3:]).T[:,0,:] #atoms x cos mag mods; only 1 wave used |
---|
1808 | Bm = np.array(MSSdata[:3]).T[:,0,:] #...sin mag mods |
---|
1809 | SGMT = np.array([ops[0] for ops in SGData['SGOps']]) #not .T!! (no diff for MnWO4 & best for DyMnGe) |
---|
1810 | Sinv = np.array([nl.inv(ops[0]) for ops in SSGData['SSGOps']]) |
---|
1811 | SGT = np.array([ops[1] for ops in SSGData['SSGOps']]) |
---|
1812 | if SGData['SGInv']: |
---|
1813 | SGMT = np.vstack((SGMT,-SGMT)) |
---|
1814 | Sinv = np.vstack((Sinv,-Sinv)) |
---|
1815 | SGT = np.vstack((SGT,-SGT)) |
---|
1816 | SGMT = np.vstack([SGMT for cen in SGData['SGCen']]) |
---|
1817 | Sinv = np.vstack([Sinv for cen in SGData['SGCen']]) |
---|
1818 | SGT = np.vstack([SGT+cen for cen in SSGData['SSGCen']])%1. |
---|
1819 | if SGData['SGGray']: |
---|
1820 | SGMT = np.vstack((SGMT,SGMT)) |
---|
1821 | Sinv = np.vstack((Sinv,Sinv)) |
---|
1822 | SGT = np.vstack((SGT,SGT+np.array([0.,0.,0.,.5])))%1. |
---|
1823 | AMR = np.swapaxes(np.inner(Am,SGMT),0,1) #Nops,Natm,Mxyz |
---|
1824 | BMR = np.swapaxes(np.inner(Bm,SGMT),0,1) |
---|
1825 | epsinv = Sinv[:,3,3] |
---|
1826 | mst = np.inner(Sinv[:,:3,:3],modQ)-epsinv[:,nxs]*modQ #van Smaalen Eq. 3.3 |
---|
1827 | phi = np.inner(XYZ,modQ).T+np.inner(SGT[:,:3],modQ)[:,nxs]+SGT[:,3,nxs] # +,+ best for MnWO4 & DyMnGe |
---|
1828 | TA = np.sum(mst[nxs,:,:]*(XYZ-SGT[:,:3][nxs,:,:]),axis=-1).T |
---|
1829 | phase = TA[nxs,:,:] + epsinv[nxs,:,nxs]*glTau[:,nxs,nxs]+phi[nxs,:,:] #+ best for MnWO4 |
---|
1830 | psin = np.sin(twopi*phase) #tau,ops,atms |
---|
1831 | pcos = np.cos(twopi*phase) |
---|
1832 | MmodAR = AMR[nxs,:,:,:]*pcos[:,:,:,nxs] #Re cos term; tau,ops,atms, Mxyz |
---|
1833 | MmodBR = BMR[nxs,:,:,:]*psin[:,:,:,nxs] #Re sin term |
---|
1834 | MmodAI = AMR[nxs,:,:,:]*psin[:,:,:,nxs] #Im cos term |
---|
1835 | MmodBI = BMR[nxs,:,:,:]*pcos[:,:,:,nxs] #Im sin term |
---|
1836 | return MmodAR,MmodBR,MmodAI,MmodBI #Ntau,Nops,Natm,Mxyz; Re, Im cos & sin parts |
---|
1837 | |
---|
1838 | def MagMod2(glTau,xyz,modQ,MSSdata,SGData,SSGData): |
---|
1839 | ''' |
---|
1840 | this needs to make magnetic moment modulations & magnitudes as |
---|
1841 | fxn of gTau points; NB: this allows only 1 mag. wave fxn. |
---|
1842 | ''' |
---|
1843 | Am = np.array(MSSdata[3:]).T[:,0,:] #atoms x cos mag mods; only 1 wave used |
---|
1844 | Bm = np.array(MSSdata[:3]).T[:,0,:] #...sin mag mods |
---|
1845 | SGMT = np.array([ops[0] for ops in SGData['SGOps']]) #not .T!! (no diff for MnWO4 & best for DyMnGe) |
---|
1846 | Sinv = np.array([nl.inv(ops[0]) for ops in SSGData['SSGOps']]) |
---|
1847 | SGT = np.array([ops[1] for ops in SSGData['SSGOps']]) |
---|
1848 | if SGData['SGInv']: |
---|
1849 | SGMT = np.vstack((SGMT,-SGMT)) |
---|
1850 | Sinv = np.vstack((Sinv,-Sinv)) |
---|
1851 | SGT = np.vstack((SGT,-SGT)) |
---|
1852 | SGMT = np.vstack([SGMT for cen in SGData['SGCen']]) |
---|
1853 | Sinv = np.vstack([Sinv for cen in SGData['SGCen']]) |
---|
1854 | SGT = np.vstack([SGT+cen for cen in SSGData['SSGCen']])%1. |
---|
1855 | if SGData['SGGray']: |
---|
1856 | SGMT = np.vstack((SGMT,SGMT)) |
---|
1857 | Sinv = np.vstack((Sinv,Sinv)) |
---|
1858 | SGT = np.vstack((SGT,SGT+np.array([0.,0.,0.,.5])))%1. |
---|
1859 | XYZ = np.array([(np.inner(xyzi,SGMT)+SGT[:,:3])%1. for xyzi in xyz.T]) #Natn,Nop,xyz |
---|
1860 | AMR = np.swapaxes(np.inner(Am,SGMT),0,1) #Nops,Natm,Mxyz |
---|
1861 | BMR = np.swapaxes(np.inner(Bm,SGMT),0,1) |
---|
1862 | epsinv = Sinv[:,3,3] |
---|
1863 | mst = np.inner(Sinv[:,:3,:3],modQ)-epsinv[:,nxs]*modQ #van Smaalen Eq. 3.3 |
---|
1864 | phi = np.inner(XYZ,modQ).T+np.inner(SGT[:,:3],modQ)[:,nxs]+SGT[:,3,nxs] # +,+ best for MnWO4 & DyMnGe |
---|
1865 | TA = np.sum(mst[nxs,:,:]*(XYZ-SGT[:,:3][nxs,:,:]),axis=-1).T |
---|
1866 | phase = TA[nxs,:,:] + epsinv[nxs,:,nxs]*glTau[:,nxs,nxs]+phi[nxs,:,:] #+ best for MnWO4 |
---|
1867 | psin = np.sin(twopi*phase) #tau,ops,atms |
---|
1868 | pcos = np.cos(twopi*phase) |
---|
1869 | MmodAR = AMR[nxs,:,:,:]*pcos[:,:,:,nxs] #Re cos term; tau,ops,atms, Mxyz |
---|
1870 | MmodBR = BMR[nxs,:,:,:]*psin[:,:,:,nxs] #Re sin term |
---|
1871 | MmodAI = AMR[nxs,:,:,:]*psin[:,:,:,nxs] #Im cos term |
---|
1872 | MmodBI = BMR[nxs,:,:,:]*pcos[:,:,:,nxs] #Im sin term |
---|
1873 | return XYZ,MmodAR,MmodBR,MmodAI,MmodBI #Ntau,Nops,Natm,Mxyz; Re, Im cos & sin parts |
---|
1874 | |
---|
1875 | def Modulation(H,HP,nWaves,Fmod,Xmod,Umod,glTau,glWt): |
---|
1876 | ''' |
---|
1877 | H: array nRefBlk x ops X hklt |
---|
1878 | HP: array nRefBlk x ops X hklt proj to hkl |
---|
1879 | nWaves: list number of waves for frac, pos, uij & mag |
---|
1880 | Fmod: array 2 x atoms x waves (sin,cos terms) |
---|
1881 | Xmod: array atoms X 3 X ngl |
---|
1882 | Umod: array atoms x 3x3 x ngl |
---|
1883 | glTau,glWt: arrays Gauss-Lorentzian pos & wts |
---|
1884 | ''' |
---|
1885 | |
---|
1886 | if nWaves[2]: #uij (adp) waves |
---|
1887 | if len(HP.shape) > 2: |
---|
1888 | HbH = np.exp(-np.sum(HP[:,:,nxs,nxs,:]*np.inner(HP,Umod),axis=-1)) # refBlk x ops x atoms x ngl add Overhauser corr.? |
---|
1889 | else: |
---|
1890 | HbH = np.exp(-np.sum(HP[:,nxs,nxs,:]*np.inner(HP,Umod),axis=-1)) # refBlk x ops x atoms x ngl add Overhauser corr.? |
---|
1891 | else: |
---|
1892 | HbH = 1.0 |
---|
1893 | HdotX = np.inner(HP,Xmod) #refBlk x ops x atoms X ngl |
---|
1894 | if len(H.shape) > 2: |
---|
1895 | D = H[:,:,3:]*glTau[nxs,nxs,:] #m*e*tau; refBlk x ops X ngl |
---|
1896 | HdotXD = twopi*(HdotX+D[:,:,nxs,:]) |
---|
1897 | else: |
---|
1898 | D = H[:,3:]*glTau[nxs,:] #m*e*tau; refBlk x ops X ngl |
---|
1899 | HdotXD = twopi*(HdotX+D[:,nxs,:]) |
---|
1900 | cosHA = np.sum(Fmod*HbH*np.cos(HdotXD)*glWt,axis=-1) #real part; refBlk X ops x atoms; sum for G-L integration |
---|
1901 | sinHA = np.sum(Fmod*HbH*np.sin(HdotXD)*glWt,axis=-1) #imag part; ditto |
---|
1902 | return np.array([cosHA,sinHA]) # 2 x refBlk x SGops x atoms |
---|
1903 | |
---|
1904 | def ModulationTw(H,HP,nWaves,Fmod,Xmod,Umod,glTau,glWt): |
---|
1905 | ''' |
---|
1906 | H: array nRefBlk x tw x ops X hklt |
---|
1907 | HP: array nRefBlk x tw x ops X hklt proj to hkl |
---|
1908 | Fmod: array 2 x atoms x waves (sin,cos terms) |
---|
1909 | Xmod: array atoms X ngl X 3 |
---|
1910 | Umod: array atoms x ngl x 3x3 |
---|
1911 | glTau,glWt: arrays Gauss-Lorentzian pos & wts |
---|
1912 | ''' |
---|
1913 | |
---|
1914 | if nWaves[2]: |
---|
1915 | if len(HP.shape) > 3: #Blocks of reflections |
---|
1916 | HbH = np.exp(-np.sum(HP[:,:,nxs,nxs,:]*np.inner(HP,Umod),axis=-1)) # refBlk x ops x atoms x ngl add Overhauser corr.? |
---|
1917 | else: #single reflections |
---|
1918 | HbH = np.exp(-np.sum(HP[:,nxs,nxs,:]*np.inner(HP,Umod),axis=-1)) # refBlk x ops x atoms x ngl add Overhauser corr.? |
---|
1919 | else: |
---|
1920 | HbH = 1.0 |
---|
1921 | HdotX = np.inner(HP,Xmod) #refBlk x tw x ops x atoms X ngl |
---|
1922 | if len(H.shape) > 3: |
---|
1923 | D = glTau*H[:,:,:,3:,nxs] #m*e*tau; refBlk x tw x ops X ngl |
---|
1924 | HdotXD = twopi*(HdotX+D[:,:,:,nxs,:]) |
---|
1925 | else: |
---|
1926 | D = H*glTau[nxs,:] #m*e*tau; refBlk x ops X ngl |
---|
1927 | HdotXD = twopi*(HdotX+D[:,nxs,:]) |
---|
1928 | cosHA = np.sum(Fmod*HbH*np.cos(HdotXD)*glWt,axis=-1) #real part; refBlk X ops x atoms; sum for G-L integration |
---|
1929 | sinHA = np.sum(Fmod*HbH*np.sin(HdotXD)*glWt,axis=-1) #imag part; ditto |
---|
1930 | return np.array([cosHA,sinHA]) # 2 x refBlk x SGops x atoms |
---|
1931 | |
---|
1932 | def makeWavesDerv(ngl,waveTypes,FSSdata,XSSdata,USSdata,Mast): |
---|
1933 | ''' |
---|
1934 | Only for Fourier waves for fraction, position & adp (probably not used for magnetism) |
---|
1935 | FSSdata: array 2 x atoms x waves (sin,cos terms) |
---|
1936 | XSSdata: array 2x3 x atoms X waves (sin,cos terms) |
---|
1937 | USSdata: array 2x6 x atoms X waves (sin,cos terms) |
---|
1938 | Mast: array orthogonalization matrix for Uij |
---|
1939 | ''' |
---|
1940 | glTau,glWt = pwd.pygauleg(0.,1.,ngl) #get Gauss-Legendre intervals & weights |
---|
1941 | waveShapes = [FSSdata.T.shape,XSSdata.T.shape,USSdata.T.shape] |
---|
1942 | Af = np.array(FSSdata[0]).T #sin frac mods x waves x atoms |
---|
1943 | Bf = np.array(FSSdata[1]).T #cos frac mods... |
---|
1944 | Ax = np.array(XSSdata[:3]).T #...cos pos mods x waves x atoms |
---|
1945 | Bx = np.array(XSSdata[3:]).T #...cos pos mods |
---|
1946 | Au = Mast*np.array(G2lat.U6toUij(USSdata[:6])).T #atoms x waves x sin Uij mods |
---|
1947 | Bu = Mast*np.array(G2lat.U6toUij(USSdata[6:])).T #...cos Uij mods |
---|
1948 | nWaves = [Af.shape[1],Ax.shape[1],Au.shape[1]] |
---|
1949 | StauX = np.zeros((Ax.shape[0],Ax.shape[1],3,ngl)) #atoms x waves x 3 x ngl |
---|
1950 | CtauX = np.zeros((Ax.shape[0],Ax.shape[1],3,ngl)) |
---|
1951 | ZtauXt = np.zeros((Ax.shape[0],2,3,ngl)) #atoms x Tminmax x 3 x ngl |
---|
1952 | ZtauXx = np.zeros((Ax.shape[0],3,ngl)) #atoms x XYZmax x ngl |
---|
1953 | for iatm in range(Ax.shape[0]): |
---|
1954 | nx = 0 |
---|
1955 | if 'ZigZag' in waveTypes[iatm]: |
---|
1956 | nx = 1 |
---|
1957 | elif 'Block' in waveTypes[iatm]: |
---|
1958 | nx = 1 |
---|
1959 | tauX = np.arange(1.,nWaves[1]+1-nx)[:,nxs]*glTau #Xwaves x ngl |
---|
1960 | if nx: |
---|
1961 | StauX[iatm][nx:] = np.ones_like(Ax)[iatm,nx:,:,nxs]*np.sin(twopi*tauX)[nxs,:,nxs,:] #atoms X waves X 3(xyz) X ngl |
---|
1962 | CtauX[iatm][nx:] = np.ones_like(Bx)[iatm,nx:,:,nxs]*np.cos(twopi*tauX)[nxs,:,nxs,:] #ditto |
---|
1963 | else: |
---|
1964 | StauX[iatm] = np.ones_like(Ax)[iatm,:,:,nxs]*np.sin(twopi*tauX)[nxs,:,nxs,:] #atoms X waves X 3(xyz) X ngl |
---|
1965 | CtauX[iatm] = np.ones_like(Bx)[iatm,:,:,nxs]*np.cos(twopi*tauX)[nxs,:,nxs,:] #ditto |
---|
1966 | if nWaves[0]: |
---|
1967 | tauF = np.arange(1.,nWaves[0]+1)[:,nxs]*glTau #Fwaves x ngl |
---|
1968 | StauF = np.ones_like(Af)[:,:,nxs]*np.sin(twopi*tauF)[nxs,:,:] #also dFmod/dAf |
---|
1969 | CtauF = np.ones_like(Bf)[:,:,nxs]*np.cos(twopi*tauF)[nxs,:,:] #also dFmod/dBf |
---|
1970 | else: |
---|
1971 | StauF = 1.0 |
---|
1972 | CtauF = 1.0 |
---|
1973 | if nWaves[2]: |
---|
1974 | tauU = np.arange(1.,nWaves[2]+1)[:,nxs]*glTau #Uwaves x ngl |
---|
1975 | StauU = np.ones_like(Au)[:,:,:,:,nxs]*np.sin(twopi*tauU)[nxs,:,nxs,nxs,:] #also dUmodA/dAu |
---|
1976 | CtauU = np.ones_like(Bu)[:,:,:,:,nxs]*np.cos(twopi*tauU)[nxs,:,nxs,nxs,:] #also dUmodB/dBu |
---|
1977 | UmodA = Au[:,:,:,:,nxs]*StauU #atoms x waves x 3x3 x ngl |
---|
1978 | UmodB = Bu[:,:,:,:,nxs]*CtauU #ditto |
---|
1979 | #derivs need to be ops x atoms x waves x 6uij; ops x atoms x waves x ngl x 6uij before sum |
---|
1980 | StauU = np.rollaxis(np.rollaxis(np.swapaxes(StauU,2,4),-1),-1) |
---|
1981 | CtauU = np.rollaxis(np.rollaxis(np.swapaxes(CtauU,2,4),-1),-1) |
---|
1982 | else: |
---|
1983 | StauU = 1.0 |
---|
1984 | CtauU = 1.0 |
---|
1985 | UmodA = 0. |
---|
1986 | UmodB = 0. |
---|
1987 | return waveShapes,[StauF,CtauF],[StauX,CtauX,ZtauXt,ZtauXx],[StauU,CtauU],UmodA+UmodB |
---|
1988 | |
---|
1989 | def ModulationDerv(H,HP,Hij,nWaves,waveShapes,Fmod,Xmod,UmodAB,SCtauF,SCtauX,SCtauU,glTau,glWt): |
---|
1990 | ''' |
---|
1991 | Compute Fourier modulation derivatives |
---|
1992 | H: array ops X hklt proj to hkl |
---|
1993 | HP: array ops X hklt proj to hkl |
---|
1994 | Hij: array 2pi^2[a*^2h^2 b*^2k^2 c*^2l^2 a*b*hk a*c*hl b*c*kl] of projected hklm to hkl space |
---|
1995 | ''' |
---|
1996 | |
---|
1997 | Mf = [H.shape[0],]+list(waveShapes[0]) #=[ops,atoms,waves,2] (sin+cos frac mods) |
---|
1998 | dGdMfC = np.zeros(Mf) |
---|
1999 | dGdMfS = np.zeros(Mf) |
---|
2000 | Mx = [H.shape[0],]+list(waveShapes[1]) #=[ops,atoms,waves,6] (sin+cos pos mods) |
---|
2001 | dGdMxC = np.zeros(Mx) |
---|
2002 | dGdMxS = np.zeros(Mx) |
---|
2003 | Mu = [H.shape[0],]+list(waveShapes[2]) #=[ops,atoms,waves,12] (sin+cos Uij mods) |
---|
2004 | dGdMuC = np.zeros(Mu) |
---|
2005 | dGdMuS = np.zeros(Mu) |
---|
2006 | |
---|
2007 | D = twopi*H[:,3][:,nxs]*glTau[nxs,:] #m*e*tau; ops X ngl |
---|
2008 | HdotX = twopi*np.inner(HP,Xmod) #ops x atoms X ngl |
---|
2009 | HdotXD = HdotX+D[:,nxs,:] |
---|
2010 | if nWaves[2]: |
---|
2011 | Umod = np.swapaxes((UmodAB),2,4) #atoms x waves x ngl x 3x3 (symmetric so I can do this!) |
---|
2012 | HuH = np.sum(HP[:,nxs,nxs,nxs]*np.inner(HP,Umod),axis=-1) #ops x atoms x waves x ngl |
---|
2013 | HuH = np.sum(HP[:,nxs,nxs,nxs]*np.inner(HP,Umod),axis=-1) #ops x atoms x waves x ngl |
---|
2014 | HbH = np.exp(-np.sum(HuH,axis=-2)) # ops x atoms x ngl; sum waves - OK vs Modulation version |
---|
2015 | # part1 = -np.exp(-HuH)*Fmod[nxs,:,nxs,:] #ops x atoms x waves x ngl |
---|
2016 | part1 = -np.exp(-HuH)*Fmod #ops x atoms x waves x ngl |
---|
2017 | dUdAu = Hij[:,nxs,nxs,nxs,:]*np.rollaxis(G2lat.UijtoU6(SCtauU[0]),0,4)[nxs,:,:,:,:] #ops x atoms x waves x ngl x 6sinUij |
---|
2018 | dUdBu = Hij[:,nxs,nxs,nxs,:]*np.rollaxis(G2lat.UijtoU6(SCtauU[1]),0,4)[nxs,:,:,:,:] #ops x atoms x waves x ngl x 6cosUij |
---|
2019 | dGdMuCa = np.sum(part1[:,:,:,:,nxs]*dUdAu*np.cos(HdotXD)[:,:,nxs,:,nxs]*glWt[nxs,nxs,nxs,:,nxs],axis=-2) #ops x atoms x waves x 6uij; G-L sum |
---|
2020 | dGdMuCb = np.sum(part1[:,:,:,:,nxs]*dUdBu*np.cos(HdotXD)[:,:,nxs,:,nxs]*glWt[nxs,nxs,nxs,:,nxs],axis=-2) |
---|
2021 | dGdMuC = np.concatenate((dGdMuCa,dGdMuCb),axis=-1) #ops x atoms x waves x 12uij |
---|
2022 | dGdMuSa = np.sum(part1[:,:,:,:,nxs]*dUdAu*np.sin(HdotXD)[:,:,nxs,:,nxs]*glWt[nxs,nxs,nxs,:,nxs],axis=-2) #ops x atoms x waves x 6uij; G-L sum |
---|
2023 | dGdMuSb = np.sum(part1[:,:,:,:,nxs]*dUdBu*np.sin(HdotXD)[:,:,nxs,:,nxs]*glWt[nxs,nxs,nxs,:,nxs],axis=-2) |
---|
2024 | dGdMuS = np.concatenate((dGdMuSa,dGdMuSb),axis=-1) #ops x atoms x waves x 12uij |
---|
2025 | else: |
---|
2026 | HbH = np.ones_like(HdotX) |
---|
2027 | dHdXA = twopi*HP[:,nxs,nxs,nxs,:]*np.swapaxes(SCtauX[0],-1,-2)[nxs,:,:,:,:] #ops x atoms x sine waves x ngl x xyz |
---|
2028 | dHdXB = twopi*HP[:,nxs,nxs,nxs,:]*np.swapaxes(SCtauX[1],-1,-2)[nxs,:,:,:,:] #ditto - cos waves |
---|
2029 | # ops x atoms x waves x 2xyz - real part - good |
---|
2030 | # dGdMxCa = -np.sum((Fmod[nxs,:,:]*HbH)[:,:,nxs,:,nxs]*(dHdXA*np.sin(HdotXD)[:,:,nxs,:,nxs])*glWt[nxs,nxs,nxs,:,nxs],axis=-2) |
---|
2031 | # dGdMxCb = -np.sum((Fmod[nxs,:,:]*HbH)[:,:,nxs,:,nxs]*(dHdXB*np.sin(HdotXD)[:,:,nxs,:,nxs])*glWt[nxs,nxs,nxs,:,nxs],axis=-2) |
---|
2032 | dGdMxCa = -np.sum((Fmod*HbH)[:,:,nxs,:,nxs]*(dHdXA*np.sin(HdotXD)[:,:,nxs,:,nxs])*glWt[nxs,nxs,nxs,:,nxs],axis=-2) |
---|
2033 | dGdMxCb = -np.sum((Fmod*HbH)[:,:,nxs,:,nxs]*(dHdXB*np.sin(HdotXD)[:,:,nxs,:,nxs])*glWt[nxs,nxs,nxs,:,nxs],axis=-2) |
---|
2034 | dGdMxC = np.concatenate((dGdMxCa,dGdMxCb),axis=-1) |
---|
2035 | # ops x atoms x waves x 2xyz - imag part - good |
---|
2036 | # dGdMxSa = np.sum((Fmod[nxs,:,:]*HbH)[:,:,nxs,:,nxs]*(dHdXA*np.cos(HdotXD)[:,:,nxs,:,nxs])*glWt[nxs,nxs,nxs,:,nxs],axis=-2) |
---|
2037 | # dGdMxSb = np.sum((Fmod[nxs,:,:]*HbH)[:,:,nxs,:,nxs]*(dHdXB*np.cos(HdotXD)[:,:,nxs,:,nxs])*glWt[nxs,nxs,nxs,:,nxs],axis=-2) |
---|
2038 | dGdMxSa = np.sum((Fmod*HbH)[:,:,nxs,:,nxs]*(dHdXA*np.cos(HdotXD)[:,:,nxs,:,nxs])*glWt[nxs,nxs,nxs,:,nxs],axis=-2) |
---|
2039 | dGdMxSb = np.sum((Fmod*HbH)[:,:,nxs,:,nxs]*(dHdXB*np.cos(HdotXD)[:,:,nxs,:,nxs])*glWt[nxs,nxs,nxs,:,nxs],axis=-2) |
---|
2040 | dGdMxS = np.concatenate((dGdMxSa,dGdMxSb),axis=-1) |
---|
2041 | return [dGdMfC,dGdMfS],[dGdMxC,dGdMxS],[dGdMuC,dGdMuS] |
---|
2042 | |
---|
2043 | def posFourier(tau,psin,pcos): |
---|
2044 | A = np.array([ps[:,nxs]*np.sin(2*np.pi*(i+1)*tau) for i,ps in enumerate(psin)]) |
---|
2045 | B = np.array([pc[:,nxs]*np.cos(2*np.pi*(i+1)*tau) for i,pc in enumerate(pcos)]) |
---|
2046 | return np.sum(A,axis=0)+np.sum(B,axis=0) |
---|
2047 | |
---|
2048 | def posZigZag(T,Tmm,Xmax): |
---|
2049 | DT = Tmm[1]-Tmm[0] |
---|
2050 | Su = 2.*Xmax/DT |
---|
2051 | Sd = 2.*Xmax/(1.-DT) |
---|
2052 | A = np.array([np.where( 0.< (t-Tmm[0])%1. <= DT, -Xmax+Su*((t-Tmm[0])%1.), Xmax-Sd*((t-Tmm[1])%1.)) for t in T]) |
---|
2053 | return A |
---|
2054 | |
---|
2055 | #def posZigZagDerv(T,Tmm,Xmax): |
---|
2056 | # DT = Tmm[1]-Tmm[0] |
---|
2057 | # Su = 2.*Xmax/DT |
---|
2058 | # Sd = 2.*Xmax/(1.-DT) |
---|
2059 | # dAdT = np.zeros((2,3,len(T))) |
---|
2060 | # dAdT[0] = np.array([np.where(Tmm[0] < t <= Tmm[1],Su*(t-Tmm[0]-1)/DT,-Sd*(t-Tmm[1])/(1.-DT)) for t in T]).T |
---|
2061 | # dAdT[1] = np.array([np.where(Tmm[0] < t <= Tmm[1],-Su*(t-Tmm[0])/DT,Sd*(t-Tmm[1])/(1.-DT)) for t in T]).T |
---|
2062 | # dAdX = np.ones(3)[:,nxs]*np.array([np.where(Tmm[0] < t%1. <= Tmm[1],-1.+2.*(t-Tmm[0])/DT,1.-2.*(t-Tmm[1])%1./DT) for t in T]) |
---|
2063 | # return dAdT,dAdX |
---|
2064 | |
---|
2065 | def posBlock(T,Tmm,Xmax): |
---|
2066 | A = np.array([np.where(Tmm[0] < t%1. <= Tmm[1],-Xmax,Xmax) for t in T]) |
---|
2067 | return A |
---|
2068 | |
---|
2069 | #def posBlockDerv(T,Tmm,Xmax): |
---|
2070 | # dAdT = np.zeros((2,3,len(T))) |
---|
2071 | # ind = np.searchsorted(T,Tmm) |
---|
2072 | # dAdT[0,:,ind[0]] = -Xmax/len(T) |
---|
2073 | # dAdT[1,:,ind[1]] = Xmax/len(T) |
---|
2074 | # dAdX = np.ones(3)[:,nxs]*np.array([np.where(Tmm[0] < t <= Tmm[1],-1.,1.) for t in T]) #OK |
---|
2075 | # return dAdT,dAdX |
---|
2076 | |
---|
2077 | def fracCrenel(tau,Toff,Twid): |
---|
2078 | Tau = (tau-Toff)%1. |
---|
2079 | A = np.where(Tau<Twid,1.,0.) |
---|
2080 | return A |
---|
2081 | |
---|
2082 | def fracFourier(tau,fsin,fcos): |
---|
2083 | if len(fsin) == 1: |
---|
2084 | A = np.array([fsin[0]*np.sin(2.*np.pi*tau)]) |
---|
2085 | B = np.array([fcos[0]*np.cos(2.*np.pi*tau)]) |
---|
2086 | else: |
---|
2087 | A = np.array([fs[:,nxs]*np.sin(2.*np.pi*(i+1)*tau) for i,fs in enumerate(fsin)]) |
---|
2088 | B = np.array([fc[:,nxs]*np.cos(2.*np.pi*(i+1)*tau) for i,fc in enumerate(fcos)]) |
---|
2089 | return np.sum(A,axis=0)+np.sum(B,axis=0) |
---|
2090 | |
---|
2091 | def ApplyModulation(data,tau): |
---|
2092 | '''Applies modulation to drawing atom positions & Uijs for given tau |
---|
2093 | ''' |
---|
2094 | generalData = data['General'] |
---|
2095 | cell = generalData['Cell'][1:7] |
---|
2096 | G,g = G2lat.cell2Gmat(cell) |
---|
2097 | SGData = generalData['SGData'] |
---|
2098 | SSGData = generalData['SSGData'] |
---|
2099 | cx,ct,cs,cia = getAtomPtrs(data) |
---|
2100 | drawingData = data['Drawing'] |
---|
2101 | modul = generalData['SuperVec'][0] |
---|
2102 | dcx,dct,dcs,dci = getAtomPtrs(data,True) |
---|
2103 | atoms = data['Atoms'] |
---|
2104 | drawAtoms = drawingData['Atoms'] |
---|
2105 | Fade = np.ones(len(drawAtoms)) |
---|
2106 | for atom in atoms: |
---|
2107 | atxyz = np.array(atom[cx:cx+3]) |
---|
2108 | atuij = np.array(atom[cia+2:cia+8]) |
---|
2109 | Sfrac = atom[-1]['SS1']['Sfrac'] |
---|
2110 | Spos = atom[-1]['SS1']['Spos'] |
---|
2111 | Sadp = atom[-1]['SS1']['Sadp'] |
---|
2112 | if generalData['Type'] == 'magnetic': |
---|
2113 | Smag = atom[-1]['SS1']['Smag'] |
---|
2114 | atmom = np.array(atom[cx+4:cx+7]) |
---|
2115 | indx = FindAtomIndexByIDs(drawAtoms,dci,[atom[cia+8],],True) |
---|
2116 | for ind in indx: |
---|
2117 | drawatom = drawAtoms[ind] |
---|
2118 | opr = drawatom[dcs-1] |
---|
2119 | sop,ssop,icent,cent,unit = G2spc.OpsfromStringOps(opr,SGData,SSGData) |
---|
2120 | drxyz = (np.inner(sop[0],atxyz)+sop[1]+cent)*icent+np.array(unit) |
---|
2121 | tauT = G2spc.getTauT(tau,sop,ssop,drxyz,modul)[-1] |
---|
2122 | tauT *= icent #invert wave on -1 |
---|
2123 | # print(tau,tauT,opr,G2spc.MT2text(sop).replace(' ',''),G2spc.SSMT2text(ssop).replace(' ','')) |
---|
2124 | wave = np.zeros(3) |
---|
2125 | uwave = np.zeros(6) |
---|
2126 | mom = np.zeros(3) |
---|
2127 | if len(Sfrac): |
---|
2128 | scof = [] |
---|
2129 | ccof = [] |
---|
2130 | waveType = Sfrac[0] |
---|
2131 | for i,sfrac in enumerate(Sfrac[1:]): |
---|
2132 | if not i and 'Crenel' in waveType: |
---|
2133 | Fade[ind] += fracCrenel(tauT,sfrac[0][0],sfrac[0][1]) |
---|
2134 | else: |
---|
2135 | scof.append(sfrac[0][0]) |
---|
2136 | ccof.append(sfrac[0][1]) |
---|
2137 | if len(scof): |
---|
2138 | Fade[ind] += np.sum(fracFourier(tauT,scof,ccof)) |
---|
2139 | if len(Spos): |
---|
2140 | scof = [] |
---|
2141 | ccof = [] |
---|
2142 | waveType = Spos[0] |
---|
2143 | for i,spos in enumerate(Spos[1:]): |
---|
2144 | if waveType in ['ZigZag','Block'] and not i: |
---|
2145 | Tminmax = spos[0][:2] |
---|
2146 | XYZmax = np.array(spos[0][2:5]) |
---|
2147 | if waveType == 'Block': |
---|
2148 | wave = np.array(posBlock([tauT,],Tminmax,XYZmax))[0] |
---|
2149 | elif waveType == 'ZigZag': |
---|
2150 | wave = np.array(posZigZag([tauT,],Tminmax,XYZmax))[0] |
---|
2151 | else: |
---|
2152 | scof.append(spos[0][:3]) |
---|
2153 | ccof.append(spos[0][3:]) |
---|
2154 | if len(scof): |
---|
2155 | wave += np.sum(posFourier(tauT,np.array(scof),np.array(ccof)),axis=1) |
---|
2156 | if generalData['Type'] == 'magnetic' and len(Smag): |
---|
2157 | scof = [] |
---|
2158 | ccof = [] |
---|
2159 | waveType = Smag[0] |
---|
2160 | for i,spos in enumerate(Smag[1:]): |
---|
2161 | scof.append(spos[0][:3]) |
---|
2162 | ccof.append(spos[0][3:]) |
---|
2163 | if len(scof): |
---|
2164 | mom += np.sum(posFourier(tauT,np.array(scof),np.array(ccof)),axis=1) |
---|
2165 | if len(Sadp): |
---|
2166 | scof = [] |
---|
2167 | ccof = [] |
---|
2168 | waveType = Sadp[0] |
---|
2169 | for i,sadp in enumerate(Sadp[1:]): |
---|
2170 | scof.append(sadp[0][:6]) |
---|
2171 | ccof.append(sadp[0][6:]) |
---|
2172 | ures = posFourier(tauT,np.array(scof),np.array(ccof)) |
---|
2173 | if np.any(ures): |
---|
2174 | uwave += np.sum(ures,axis=1) |
---|
2175 | if atom[cia] == 'A': |
---|
2176 | X,U = G2spc.ApplyStringOps(opr,SGData,atxyz+wave,atuij+uwave) |
---|
2177 | drawatom[dcx:dcx+3] = X |
---|
2178 | drawatom[dci-6:dci] = U |
---|
2179 | else: |
---|
2180 | X = G2spc.ApplyStringOps(opr,SGData,atxyz+wave) |
---|
2181 | drawatom[dcx:dcx+3] = X |
---|
2182 | if generalData['Type'] == 'magnetic': |
---|
2183 | M = G2spc.ApplyStringOpsMom(opr,SGData,SSGData,atmom+mom) |
---|
2184 | drawatom[dcx+3:dcx+6] = M |
---|
2185 | return drawAtoms,Fade |
---|
2186 | |
---|
2187 | def patchIsoDisp(ISO): |
---|
2188 | '''patch: look for older ISODISTORT imports (<Nov 2021)''' |
---|
2189 | print(''' |
---|
2190 | ====================================================================== |
---|
2191 | Warning: The ISODISTORT modes were read before the importer |
---|
2192 | was corrected to save displacement offsets. Will attempt to correct |
---|
2193 | from ParentStructure (correct only if displacivemode values are all |
---|
2194 | zero in initial CIF.) Reimporting is suggested. |
---|
2195 | ====================================================================== |
---|
2196 | ''') |
---|
2197 | ISO['G2coordOffset'] = [] |
---|
2198 | for Ilbl in ISO['IsoVarList']: |
---|
2199 | albl = Ilbl[:Ilbl.rfind('_')] |
---|
2200 | v = Ilbl[Ilbl.rfind('_')+1:] |
---|
2201 | ISO['G2coordOffset'].append( |
---|
2202 | ISO['ParentStructure'][albl][['dx','dy','dz'].index(v)] |
---|
2203 | ) # this will be wrong if the _iso_deltacoordinate_value are not zero |
---|
2204 | |
---|
2205 | def CalcIsoDisp(Phase,parmDict={},covdata={}): |
---|
2206 | '''Compute the ISODISTORT displacement variable values from the |
---|
2207 | atomic coordinates, applying the p::dA?:n displacements if parmDict |
---|
2208 | is supplied. Uncertainties are computed if covdata is supplied. |
---|
2209 | ''' |
---|
2210 | ISO = Phase['ISODISTORT'] |
---|
2211 | if 'G2coordOffset' not in ISO: patchIsoDisp(ISO) # patch Nov 2021 |
---|
2212 | atmIndex = {a[-1]:i for i,a in enumerate(Phase['Atoms'])} |
---|
2213 | cx,ct,cs,ci = getAtomPtrs(Phase,False) |
---|
2214 | coordOffset = {xyz:cx+i for i,xyz in enumerate(('x','y','z'))} |
---|
2215 | # get uncertainties on modes and compute them for displacements |
---|
2216 | if 'varyList' in covdata: |
---|
2217 | modes = [i.name for i in ISO['G2ModeList']] |
---|
2218 | covDict = dict(zip(covdata.get('varyList',[]),covdata.get('sig',[]))) |
---|
2219 | modeEsds = [covDict.get(str(g2),-1) for g2 in modes] |
---|
2220 | vcov = getVCov(modes,covdata['varyList'],covdata['covMatrix']) |
---|
2221 | normMode2Var = ISO['Mode2VarMatrix']*ISO['NormList'] |
---|
2222 | # to get displacements from modes use np.dot(normMode2Var,vector-of-mode-values) |
---|
2223 | sigMat = np.inner(normMode2Var,np.inner(normMode2Var,vcov)) |
---|
2224 | var = np.diag(sigMat) |
---|
2225 | dispEsds = list(np.where(var>0.,np.sqrt(var),-0.0001)) |
---|
2226 | else: |
---|
2227 | dispEsds = len(ISO['G2VarList'])*[-0.0001] |
---|
2228 | modeEsds = len(ISO['G2ModeList'])*[-0.0001] |
---|
2229 | |
---|
2230 | dispValues = [] |
---|
2231 | for iso,g2,off in zip(ISO['IsoVarList'],ISO['G2VarList'],ISO['G2coordOffset']): |
---|
2232 | if g2.atom not in atmIndex: |
---|
2233 | print('Atom not found in atom list',g2) |
---|
2234 | return [],[],[],[] |
---|
2235 | atm = Phase['Atoms'][atmIndex[g2.atom]] |
---|
2236 | pos = atm[coordOffset[g2.name[-1]]] + parmDict.get(str(g2),0.0) - off |
---|
2237 | dispValues.append(pos) |
---|
2238 | |
---|
2239 | modeValues = np.dot(ISO['Var2ModeMatrix'],dispValues) / ISO['NormList'] |
---|
2240 | |
---|
2241 | return dispValues,dispEsds,modeValues,modeEsds |
---|
2242 | |
---|
2243 | def CalcIsoCoords(Phase,parmDict,covdata={}): |
---|
2244 | '''Compute the coordinate positions from ISODISTORT displacement mode values |
---|
2245 | Uncertainties are computed if covdata is supplied. |
---|
2246 | |
---|
2247 | :param dict Phase: contents of tree entry for selected phase |
---|
2248 | :param dict parmDict: a dict with values for the modes; note that in the |
---|
2249 | parmDict from refinements the mode values are not normalized, |
---|
2250 | but this assumes they are. |
---|
2251 | :param dict Phase: full covariance information from tree |
---|
2252 | |
---|
2253 | :returns: modeDict,posDict where modeDict contains pairs of mode values |
---|
2254 | and mode s.u. values; posDict contains pairs of displacement values |
---|
2255 | and their s.u. values. |
---|
2256 | ''' |
---|
2257 | ISO = Phase['ISODISTORT'] |
---|
2258 | if 'G2coordOffset' not in ISO: patchIsoDisp(ISO) # patch Nov 2021 |
---|
2259 | modes = [i.name for i in ISO['G2ModeList']] # modes from the parmDict |
---|
2260 | normMode2Var = ISO['Mode2VarMatrix']*ISO['NormList'] |
---|
2261 | modeVals = [] |
---|
2262 | for i in modes: |
---|
2263 | if i not in parmDict: |
---|
2264 | print('Mode ',i,'not defined in the parameter dictionary') |
---|
2265 | return {},{} |
---|
2266 | try: |
---|
2267 | modeVals.append(float(parmDict[i][0])) |
---|
2268 | except: |
---|
2269 | modeVals.append(float(parmDict[i])) |
---|
2270 | if 'varyList' in covdata: |
---|
2271 | covDict = dict(zip(covdata.get('varyList',[]),covdata.get('sig',[]))) |
---|
2272 | modeEsds = [covDict.get(str(g2),-1) for g2 in modes] |
---|
2273 | vcov = getVCov(modes,covdata['varyList'],covdata['covMatrix']) |
---|
2274 | sigMat = np.inner(normMode2Var,np.inner(normMode2Var,vcov)) |
---|
2275 | var = np.diag(sigMat) |
---|
2276 | dispEsds = list(np.where(var>0.,np.sqrt(var),-0.0001)) |
---|
2277 | else: |
---|
2278 | modeEsds = len(ISO['G2ModeList'])*[-0.0001] |
---|
2279 | dispEsds = len(ISO['G2VarList'])*[-0.0001] |
---|
2280 | dispValues = np.dot(normMode2Var,modeVals) |
---|
2281 | modeDict = {str(g2):([val,esd]) for val,g2,esd in |
---|
2282 | zip(modeVals,ISO['G2ModeList'],modeEsds)} |
---|
2283 | posDict = {str(g2).replace('::dA','::A'):([dsp,esd]) for dsp,g2,off,esd in |
---|
2284 | zip(dispValues,ISO['G2VarList'],ISO['G2coordOffset'],dispEsds)} |
---|
2285 | return modeDict,posDict |
---|
2286 | |
---|
2287 | def ApplyModeDisp(data): |
---|
2288 | ''' Applies ISODISTORT mode displacements to atom lists. |
---|
2289 | This changes the contents of the Draw Atoms positions and |
---|
2290 | the Atoms positions. |
---|
2291 | |
---|
2292 | :param dict data: the contents of the Phase data tree item for a |
---|
2293 | particular phase |
---|
2294 | ''' |
---|
2295 | generalData = data['General'] |
---|
2296 | Atoms= data['Atoms'] |
---|
2297 | ISOdata = data['ISODISTORT'] |
---|
2298 | coords = ISOdata['G2parentCoords'] |
---|
2299 | modeDisp = np.array(ISOdata['modeDispl'])*np.array(ISOdata['NormList']) |
---|
2300 | mode2var = np.array(ISOdata['Mode2VarMatrix']) |
---|
2301 | varDisp = np.sum(mode2var*modeDisp,axis=1) |
---|
2302 | vardict = dict(zip(ISOdata['IsoVarList'],varDisp)) |
---|
2303 | cell = generalData['Cell'][1:7] |
---|
2304 | G,g = G2lat.cell2Gmat(cell) |
---|
2305 | SGData = generalData['SGData'] |
---|
2306 | cx,ct,cs,cia = getAtomPtrs(data) |
---|
2307 | atNames = [atm[ct-1] for atm in Atoms] |
---|
2308 | parNames = [[atName+'_dx',atName+'_dy',atName+'_dz',] for atName in atNames] |
---|
2309 | if data['Drawing']: |
---|
2310 | drawingData = data['Drawing'] |
---|
2311 | dcx,dct,dcs,dci = getAtomPtrs(data,True) |
---|
2312 | atoms = data['Atoms'] |
---|
2313 | drawAtoms = drawingData['Atoms'] |
---|
2314 | for iat,atom in enumerate(atoms): |
---|
2315 | atxyz = coords[iat] |
---|
2316 | SytSym = G2spc.SytSym(atxyz,SGData)[0] |
---|
2317 | CSIX = G2spc.GetCSxinel(SytSym) |
---|
2318 | displ = np.zeros(3) |
---|
2319 | for ip,parm in enumerate(parNames[iat]): |
---|
2320 | if parm in vardict: |
---|
2321 | displ[ip] = vardict[parm] |
---|
2322 | displ = G2spc.AtomDxSymFix(displ,SytSym,CSIX) |
---|
2323 | atom[cx:cx+3] = atxyz+displ |
---|
2324 | indx = FindAtomIndexByIDs(drawAtoms,dci,[atom[cia+8],],True) |
---|
2325 | for ind in indx: |
---|
2326 | drawatom = drawAtoms[ind] |
---|
2327 | opr = drawatom[dcs-1] |
---|
2328 | X = G2spc.ApplyStringOps(opr,SGData,atxyz+displ) |
---|
2329 | drawatom[dcx:dcx+3] = X |
---|
2330 | return None |
---|
2331 | else: |
---|
2332 | return 'Draw structure first' |
---|
2333 | |
---|
2334 | |
---|
2335 | # gauleg.py Gauss Legendre numerical quadrature, x and w computation |
---|
2336 | # integrate from a to b using n evaluations of the function f(x) |
---|
2337 | # usage: from gauleg import gaulegf |
---|
2338 | # x,w = gaulegf( a, b, n) |
---|
2339 | # area = 0.0 |
---|
2340 | # for i in range(1,n+1): # yes, 1..n |
---|
2341 | # area += w[i]*f(x[i]) |
---|
2342 | |
---|
2343 | def gaulegf(a, b, n): |
---|
2344 | x = range(n+1) # x[0] unused |
---|
2345 | w = range(n+1) # w[0] unused |
---|
2346 | eps = 3.0E-14 |
---|
2347 | m = (n+1)/2 |
---|
2348 | xm = 0.5*(b+a) |
---|
2349 | xl = 0.5*(b-a) |
---|
2350 | for i in range(1,m+1): |
---|
2351 | z = math.cos(3.141592654*(i-0.25)/(n+0.5)) |
---|
2352 | while True: |
---|
2353 | p1 = 1.0 |
---|
2354 | p2 = 0.0 |
---|
2355 | for j in range(1,n+1): |
---|
2356 | p3 = p2 |
---|
2357 | p2 = p1 |
---|
2358 | p1 = ((2.0*j-1.0)*z*p2-(j-1.0)*p3)/j |
---|
2359 | |
---|
2360 | pp = n*(z*p1-p2)/(z*z-1.0) |
---|
2361 | z1 = z |
---|
2362 | z = z1 - p1/pp |
---|
2363 | if abs(z-z1) <= eps: |
---|
2364 | break |
---|
2365 | |
---|
2366 | x[i] = xm - xl*z |
---|
2367 | x[n+1-i] = xm + xl*z |
---|
2368 | w[i] = 2.0*xl/((1.0-z*z)*pp*pp) |
---|
2369 | w[n+1-i] = w[i] |
---|
2370 | return np.array(x), np.array(w) |
---|
2371 | # end gaulegf |
---|
2372 | |
---|
2373 | |
---|
2374 | def BessJn(nmax,x): |
---|
2375 | ''' compute Bessel function J(n,x) from scipy routine & recurrance relation |
---|
2376 | returns sequence of J(n,x) for n in range [-nmax...0...nmax] |
---|
2377 | |
---|
2378 | :param integer nmax: maximul order for Jn(x) |
---|
2379 | :param float x: argument for Jn(x) |
---|
2380 | |
---|
2381 | :returns numpy array: [J(-nmax,x)...J(0,x)...J(nmax,x)] |
---|
2382 | |
---|
2383 | ''' |
---|
2384 | import scipy.special as sp |
---|
2385 | bessJn = np.zeros(2*nmax+1) |
---|
2386 | bessJn[nmax] = sp.j0(x) |
---|
2387 | bessJn[nmax+1] = sp.j1(x) |
---|
2388 | bessJn[nmax-1] = -bessJn[nmax+1] |
---|
2389 | for i in range(2,nmax+1): |
---|
2390 | bessJn[i+nmax] = 2*(i-1)*bessJn[nmax+i-1]/x-bessJn[nmax+i-2] |
---|
2391 | bessJn[nmax-i] = bessJn[i+nmax]*(-1)**i |
---|
2392 | return bessJn |
---|
2393 | |
---|
2394 | def BessIn(nmax,x): |
---|
2395 | ''' compute modified Bessel function I(n,x) from scipy routines & recurrance relation |
---|
2396 | returns sequence of I(n,x) for n in range [-nmax...0...nmax] |
---|
2397 | |
---|
2398 | :param integer nmax: maximul order for In(x) |
---|
2399 | :param float x: argument for In(x) |
---|
2400 | |
---|
2401 | :returns numpy array: [I(-nmax,x)...I(0,x)...I(nmax,x)] |
---|
2402 | |
---|
2403 | ''' |
---|
2404 | import scipy.special as sp |
---|
2405 | bessIn = np.zeros(2*nmax+1) |
---|
2406 | bessIn[nmax] = sp.i0(x) |
---|
2407 | bessIn[nmax+1] = sp.i1(x) |
---|
2408 | bessIn[nmax-1] = bessIn[nmax+1] |
---|
2409 | for i in range(2,nmax+1): |
---|
2410 | bessIn[i+nmax] = bessIn[nmax+i-2]-2*(i-1)*bessIn[nmax+i-1]/x |
---|
2411 | bessIn[nmax-i] = bessIn[i+nmax] |
---|
2412 | return bessIn |
---|
2413 | |
---|
2414 | |
---|
2415 | ################################################################################ |
---|
2416 | #### distance, angle, planes, torsion stuff |
---|
2417 | ################################################################################ |
---|
2418 | |
---|
2419 | def CalcDist(distance_dict, distance_atoms, parmDict): |
---|
2420 | if not len(parmDict): |
---|
2421 | return 0. |
---|
2422 | pId = distance_dict['pId'] |
---|
2423 | A = [parmDict['%s::A%d'%(pId,i)] for i in range(6)] |
---|
2424 | Amat = G2lat.cell2AB(G2lat.A2cell(A))[0] |
---|
2425 | Oxyz = [parmDict['%s::A%s:%d'%(pId,x,distance_atoms[0])] for x in ['x','y','z']] |
---|
2426 | Txyz = [parmDict['%s::A%s:%d'%(pId,x,distance_atoms[1])] for x in ['x','y','z']] |
---|
2427 | inv = 1 |
---|
2428 | symNo = distance_dict['symNo'] |
---|
2429 | if symNo < 0: |
---|
2430 | inv = -1 |
---|
2431 | symNo *= -1 |
---|
2432 | cen = symNo//100 |
---|
2433 | op = symNo%100-1 |
---|
2434 | M,T = distance_dict['SGData']['SGOps'][op] |
---|
2435 | D = T*inv+distance_dict['SGData']['SGCen'][cen] |
---|
2436 | D += distance_dict['cellNo'] |
---|
2437 | Txyz = np.inner(M*inv,Txyz)+D |
---|
2438 | dist = np.sqrt(np.sum(np.inner(Amat,(Txyz-Oxyz))**2)) |
---|
2439 | # GSASIIpath.IPyBreak() |
---|
2440 | return dist |
---|
2441 | |
---|
2442 | def CalcDistDeriv(distance_dict, distance_atoms, parmDict): |
---|
2443 | if not len(parmDict): |
---|
2444 | return None |
---|
2445 | pId = distance_dict['pId'] |
---|
2446 | A = [parmDict['%s::A%d'%(pId,i)] for i in range(6)] |
---|
2447 | Amat = G2lat.cell2AB(G2lat.A2cell(A))[0] |
---|
2448 | Oxyz = [parmDict['%s::A%s:%d'%(pId,x,distance_atoms[0])] for x in ['x','y','z']] |
---|
2449 | Txyz = [parmDict['%s::A%s:%d'%(pId,x,distance_atoms[1])] for x in ['x','y','z']] |
---|
2450 | symNo = distance_dict['symNo'] |
---|
2451 | Tunit = distance_dict['cellNo'] |
---|
2452 | SGData = distance_dict['SGData'] |
---|
2453 | deriv = getDistDerv(Oxyz,Txyz,Amat,Tunit,symNo,SGData) |
---|
2454 | return deriv |
---|
2455 | |
---|
2456 | def CalcAngle(angle_dict, angle_atoms, parmDict): |
---|
2457 | if not len(parmDict): |
---|
2458 | return 0. |
---|
2459 | pId = angle_dict['pId'] |
---|
2460 | A = [parmDict['%s::A%d'%(pId,i)] for i in range(6)] |
---|
2461 | Amat = G2lat.cell2AB(G2lat.A2cell(A))[0] |
---|
2462 | Oxyz = [parmDict['%s::A%s:%d'%(pId,x,angle_atoms[0])] for x in ['x','y','z']] |
---|
2463 | Axyz = [parmDict['%s::A%s:%d'%(pId,x,angle_atoms[1][0])] for x in ['x','y','z']] |
---|
2464 | Bxyz = [parmDict['%s::A%s:%d'%(pId,x,angle_atoms[1][1])] for x in ['x','y','z']] |
---|
2465 | ABxyz = [Axyz,Bxyz] |
---|
2466 | symNo = angle_dict['symNo'] |
---|
2467 | vec = np.zeros((2,3)) |
---|
2468 | for i in range(2): |
---|
2469 | inv = 1 |
---|
2470 | if symNo[i] < 0: |
---|
2471 | inv = -1 |
---|
2472 | cen = inv*symNo[i]//100 |
---|
2473 | op = inv*symNo[i]%100-1 |
---|
2474 | M,T = angle_dict['SGData']['SGOps'][op] |
---|
2475 | D = T*inv+angle_dict['SGData']['SGCen'][cen] |
---|
2476 | D += angle_dict['cellNo'][i] |
---|
2477 | ABxyz[i] = np.inner(M*inv,ABxyz[i])+D |
---|
2478 | vec[i] = np.inner(Amat,(ABxyz[i]-Oxyz)) |
---|
2479 | dist = np.sqrt(np.sum(vec[i]**2)) |
---|
2480 | if not dist: |
---|
2481 | return 0. |
---|
2482 | vec[i] /= dist |
---|
2483 | angle = acosd(np.sum(vec[0]*vec[1])) |
---|
2484 | return angle |
---|
2485 | |
---|
2486 | def CalcAngleDeriv(angle_dict, angle_atoms, parmDict): |
---|
2487 | if not len(parmDict): |
---|
2488 | return None |
---|
2489 | pId = angle_dict['pId'] |
---|
2490 | A = [parmDict['%s::A%d'%(pId,i)] for i in range(6)] |
---|
2491 | Amat = G2lat.cell2AB(G2lat.A2cell(A))[0] |
---|
2492 | Oxyz = [parmDict['%s::A%s:%d'%(pId,x,angle_atoms[0])] for x in ['x','y','z']] |
---|
2493 | Axyz = [parmDict['%s::A%s:%d'%(pId,x,angle_atoms[1][0])] for x in ['x','y','z']] |
---|
2494 | Bxyz = [parmDict['%s::A%s:%d'%(pId,x,angle_atoms[1][1])] for x in ['x','y','z']] |
---|
2495 | symNo = angle_dict['symNo'] |
---|
2496 | Tunit = angle_dict['cellNo'] |
---|
2497 | SGData = angle_dict['SGData'] |
---|
2498 | deriv = getAngleDerv(Oxyz,Axyz,Bxyz,Amat,Tunit,symNo,SGData) |
---|
2499 | return deriv |
---|
2500 | |
---|
2501 | def getSyXYZ(XYZ,ops,SGData): |
---|
2502 | '''default doc |
---|
2503 | |
---|
2504 | :param type name: description |
---|
2505 | |
---|
2506 | :returns: type name: description |
---|
2507 | |
---|
2508 | ''' |
---|
2509 | XYZout = np.zeros_like(XYZ) |
---|
2510 | for i,[xyz,op] in enumerate(zip(XYZ,ops)): |
---|
2511 | if op == '1': |
---|
2512 | XYZout[i] = xyz |
---|
2513 | else: |
---|
2514 | oprs = op.split('+') |
---|
2515 | unit = [0,0,0] |
---|
2516 | if len(oprs)>1: |
---|
2517 | unit = np.array(list(eval(oprs[1]))) |
---|
2518 | syop =int(oprs[0]) |
---|
2519 | inv = syop//abs(syop) |
---|
2520 | syop *= inv |
---|
2521 | cent = syop//100 |
---|
2522 | syop %= 100 |
---|
2523 | syop -= 1 |
---|
2524 | M,T = SGData['SGOps'][syop] |
---|
2525 | XYZout[i] = (np.inner(M,xyz)+T)*inv+SGData['SGCen'][cent]+unit |
---|
2526 | return XYZout |
---|
2527 | |
---|
2528 | def getRestDist(XYZ,Amat): |
---|
2529 | '''default doc string |
---|
2530 | |
---|
2531 | :param type name: description |
---|
2532 | |
---|
2533 | :returns: type name: description |
---|
2534 | |
---|
2535 | ''' |
---|
2536 | return np.sqrt(np.sum(np.inner(Amat,(XYZ[1]-XYZ[0]))**2)) |
---|
2537 | |
---|
2538 | def getRestDeriv(Func,XYZ,Amat,ops,SGData): |
---|
2539 | '''default doc string |
---|
2540 | |
---|
2541 | :param type name: description |
---|
2542 | |
---|
2543 | :returns: type name: description |
---|
2544 | |
---|
2545 | ''' |
---|
2546 | deriv = np.zeros((len(XYZ),3)) |
---|
2547 | dx = 0.00001 |
---|
2548 | for j,xyz in enumerate(XYZ): |
---|
2549 | for i,x in enumerate(np.array([[dx,0,0],[0,dx,0],[0,0,dx]])): |
---|
2550 | XYZ[j] -= x |
---|
2551 | d1 = Func(getSyXYZ(XYZ,ops,SGData),Amat) |
---|
2552 | XYZ[j] += 2*x |
---|
2553 | d2 = Func(getSyXYZ(XYZ,ops,SGData),Amat) |
---|
2554 | XYZ[j] -= x |
---|
2555 | deriv[j][i] = (d1-d2)/(2*dx) |
---|
2556 | return deriv.flatten() |
---|
2557 | |
---|
2558 | def getRestAngle(XYZ,Amat): |
---|
2559 | '''default doc string |
---|
2560 | |
---|
2561 | :param type name: description |
---|
2562 | |
---|
2563 | :returns: type name: description |
---|
2564 | |
---|
2565 | ''' |
---|
2566 | |
---|
2567 | def calcVec(Ox,Tx,Amat): |
---|
2568 | return np.inner(Amat,(Tx-Ox)) |
---|
2569 | |
---|
2570 | VecA = calcVec(XYZ[1],XYZ[0],Amat) |
---|
2571 | VecA /= np.sqrt(np.sum(VecA**2)) |
---|
2572 | VecB = calcVec(XYZ[1],XYZ[2],Amat) |
---|
2573 | VecB /= np.sqrt(np.sum(VecB**2)) |
---|
2574 | edge = VecB-VecA |
---|
2575 | edge = np.sum(edge**2) |
---|
2576 | angle = (2.-edge)/2. |
---|
2577 | angle = max(angle,-1.) |
---|
2578 | return acosd(angle) |
---|
2579 | |
---|
2580 | def getRestPlane(XYZ,Amat): |
---|
2581 | '''default doc string |
---|
2582 | |
---|
2583 | :param type name: description |
---|
2584 | |
---|
2585 | :returns: type name: description |
---|
2586 | |
---|
2587 | ''' |
---|
2588 | sumXYZ = np.zeros(3) |
---|
2589 | for xyz in XYZ: |
---|
2590 | sumXYZ += xyz |
---|
2591 | sumXYZ /= len(XYZ) |
---|
2592 | XYZ = np.array(XYZ)-sumXYZ |
---|
2593 | XYZ = np.inner(Amat,XYZ).T |
---|
2594 | Zmat = np.zeros((3,3)) |
---|
2595 | for i,xyz in enumerate(XYZ): |
---|
2596 | Zmat += np.outer(xyz.T,xyz) |
---|
2597 | Evec,Emat = nl.eig(Zmat) |
---|
2598 | Evec = np.sqrt(Evec)/(len(XYZ)-3) |
---|
2599 | Order = np.argsort(Evec) |
---|
2600 | return Evec[Order[0]] |
---|
2601 | |
---|
2602 | def getRestChiral(XYZ,Amat): |
---|
2603 | '''default doc string |
---|
2604 | |
---|
2605 | :param type name: description |
---|
2606 | |
---|
2607 | :returns: type name: description |
---|
2608 | |
---|
2609 | ''' |
---|
2610 | VecA = np.empty((3,3)) |
---|
2611 | VecA[0] = np.inner(XYZ[1]-XYZ[0],Amat) |
---|
2612 | VecA[1] = np.inner(XYZ[2]-XYZ[0],Amat) |
---|
2613 | VecA[2] = np.inner(XYZ[3]-XYZ[0],Amat) |
---|
2614 | return nl.det(VecA) |
---|
2615 | |
---|
2616 | def getRestTorsion(XYZ,Amat): |
---|
2617 | '''default doc string |
---|
2618 | |
---|
2619 | :param type name: description |
---|
2620 | |
---|
2621 | :returns: type name: description |
---|
2622 | |
---|
2623 | ''' |
---|
2624 | VecA = np.empty((3,3)) |
---|
2625 | VecA[0] = np.inner(XYZ[1]-XYZ[0],Amat) |
---|
2626 | VecA[1] = np.inner(XYZ[2]-XYZ[1],Amat) |
---|
2627 | VecA[2] = np.inner(XYZ[3]-XYZ[2],Amat) |
---|
2628 | D = nl.det(VecA) |
---|
2629 | Mag = np.sqrt(np.sum(VecA*VecA,axis=1)) |
---|
2630 | P12 = np.sum(VecA[0]*VecA[1])/(Mag[0]*Mag[1]) |
---|
2631 | P13 = np.sum(VecA[0]*VecA[2])/(Mag[0]*Mag[2]) |
---|
2632 | P23 = np.sum(VecA[1]*VecA[2])/(Mag[1]*Mag[2]) |
---|
2633 | Ang = 1.0 |
---|
2634 | if abs(P12) < 1.0 and abs(P23) < 1.0: |
---|
2635 | Ang = (P12*P23-P13)/(np.sqrt(1.-P12**2)*np.sqrt(1.-P23**2)) |
---|
2636 | TOR = (acosd(Ang)*D/abs(D)+720.)%360. |
---|
2637 | return TOR |
---|
2638 | |
---|
2639 | def calcTorsionEnergy(TOR,Coeff=[]): |
---|
2640 | '''default doc string |
---|
2641 | |
---|
2642 | :param type name: description |
---|
2643 | |
---|
2644 | :returns: type name: description |
---|
2645 | |
---|
2646 | ''' |
---|
2647 | sum = 0. |
---|
2648 | if len(Coeff): |
---|
2649 | cof = np.reshape(Coeff,(3,3)).T |
---|
2650 | delt = TOR-cof[1] |
---|
2651 | delt = np.where(delt<-180.,delt+360.,delt) |
---|
2652 | delt = np.where(delt>180.,delt-360.,delt) |
---|
2653 | term = -cof[2]*delt**2 |
---|
2654 | val = cof[0]*np.exp(term/1000.0) |
---|
2655 | pMax = cof[0][np.argmin(val)] |
---|
2656 | Eval = np.sum(val) |
---|
2657 | sum = Eval-pMax |
---|
2658 | return sum,Eval |
---|
2659 | |
---|
2660 | def getTorsionDeriv(XYZ,Amat,Coeff): |
---|
2661 | '''default doc string |
---|
2662 | |
---|
2663 | :param type name: description |
---|
2664 | |
---|
2665 | :returns: type name: description |
---|
2666 | |
---|
2667 | ''' |
---|
2668 | deriv = np.zeros((len(XYZ),3)) |
---|
2669 | dx = 0.00001 |
---|
2670 | for j,xyz in enumerate(XYZ): |
---|
2671 | for i,x in enumerate(np.array([[dx,0,0],[0,dx,0],[0,0,dx]])): |
---|
2672 | XYZ[j] -= x |
---|
2673 | tor = getRestTorsion(XYZ,Amat) |
---|
2674 | p1,d1 = calcTorsionEnergy(tor,Coeff) |
---|
2675 | XYZ[j] += 2*x |
---|
2676 | tor = getRestTorsion(XYZ,Amat) |
---|
2677 | p2,d2 = calcTorsionEnergy(tor,Coeff) |
---|
2678 | XYZ[j] -= x |
---|
2679 | deriv[j][i] = (p2-p1)/(2*dx) |
---|
2680 | return deriv.flatten() |
---|
2681 | |
---|
2682 | def getRestRama(XYZ,Amat): |
---|
2683 | '''Computes a pair of torsion angles in a 5 atom string |
---|
2684 | |
---|
2685 | :param nparray XYZ: crystallographic coordinates of 5 atoms |
---|
2686 | :param nparray Amat: crystal to cartesian transformation matrix |
---|
2687 | |
---|
2688 | :returns: list (phi,psi) two torsion angles in degrees |
---|
2689 | |
---|
2690 | ''' |
---|
2691 | phi = getRestTorsion(XYZ[:5],Amat) |
---|
2692 | psi = getRestTorsion(XYZ[1:],Amat) |
---|
2693 | return phi,psi |
---|
2694 | |
---|
2695 | def calcRamaEnergy(phi,psi,Coeff=[]): |
---|
2696 | '''Computes pseudo potential energy from a pair of torsion angles and a |
---|
2697 | numerical description of the potential energy surface. Used to create |
---|
2698 | penalty function in LS refinement: |
---|
2699 | :math:`Eval(\\phi,\\psi) = C[0]*exp(-V/1000)` |
---|
2700 | |
---|
2701 | where :math:`V = -C[3] * (\\phi-C[1])^2 - C[4]*(\\psi-C[2])^2 - 2*(\\phi-C[1])*(\\psi-C[2])` |
---|
2702 | |
---|
2703 | :param float phi: first torsion angle (:math:`\\phi`) |
---|
2704 | :param float psi: second torsion angle (:math:`\\psi`) |
---|
2705 | :param list Coeff: pseudo potential coefficients |
---|
2706 | |
---|
2707 | :returns: list (sum,Eval): pseudo-potential difference from minimum & value; |
---|
2708 | sum is used for penalty function. |
---|
2709 | |
---|
2710 | ''' |
---|
2711 | sum = 0. |
---|
2712 | Eval = 0. |
---|
2713 | if len(Coeff): |
---|
2714 | cof = Coeff.T |
---|
2715 | dPhi = phi-cof[1] |
---|
2716 | dPhi = np.where(dPhi<-180.,dPhi+360.,dPhi) |
---|
2717 | dPhi = np.where(dPhi>180.,dPhi-360.,dPhi) |
---|
2718 | dPsi = psi-cof[2] |
---|
2719 | dPsi = np.where(dPsi<-180.,dPsi+360.,dPsi) |
---|
2720 | dPsi = np.where(dPsi>180.,dPsi-360.,dPsi) |
---|
2721 | val = -cof[3]*dPhi**2-cof[4]*dPsi**2-2.0*cof[5]*dPhi*dPsi |
---|
2722 | val = cof[0]*np.exp(val/1000.) |
---|
2723 | pMax = cof[0][np.argmin(val)] |
---|
2724 | Eval = np.sum(val) |
---|
2725 | sum = Eval-pMax |
---|
2726 | return sum,Eval |
---|
2727 | |
---|
2728 | def getRamaDeriv(XYZ,Amat,Coeff): |
---|
2729 | '''Computes numerical derivatives of torsion angle pair pseudo potential |
---|
2730 | with respect of crystallographic atom coordinates of the 5 atom sequence |
---|
2731 | |
---|
2732 | :param nparray XYZ: crystallographic coordinates of 5 atoms |
---|
2733 | :param nparray Amat: crystal to cartesian transformation matrix |
---|
2734 | :param list Coeff: pseudo potential coefficients |
---|
2735 | |
---|
2736 | :returns: list (deriv) derivatives of pseudopotential with respect to 5 atom |
---|
2737 | crystallographic xyz coordinates. |
---|
2738 | |
---|
2739 | ''' |
---|
2740 | deriv = np.zeros((len(XYZ),3)) |
---|
2741 | dx = 0.00001 |
---|
2742 | for j,xyz in enumerate(XYZ): |
---|
2743 | for i,x in enumerate(np.array([[dx,0,0],[0,dx,0],[0,0,dx]])): |
---|
2744 | XYZ[j] -= x |
---|
2745 | phi,psi = getRestRama(XYZ,Amat) |
---|
2746 | p1,d1 = calcRamaEnergy(phi,psi,Coeff) |
---|
2747 | XYZ[j] += 2*x |
---|
2748 | phi,psi = getRestRama(XYZ,Amat) |
---|
2749 | p2,d2 = calcRamaEnergy(phi,psi,Coeff) |
---|
2750 | XYZ[j] -= x |
---|
2751 | deriv[j][i] = (p2-p1)/(2*dx) |
---|
2752 | return deriv.flatten() |
---|
2753 | |
---|
2754 | def getRestPolefig(ODFln,SamSym,Grid): |
---|
2755 | '''default doc string |
---|
2756 | |
---|
2757 | :param type name: description |
---|
2758 | |
---|
2759 | :returns: type name: description |
---|
2760 | |
---|
2761 | ''' |
---|
2762 | X,Y = np.meshgrid(np.linspace(1.,-1.,Grid),np.linspace(-1.,1.,Grid)) |
---|
2763 | R,P = np.sqrt(X**2+Y**2).flatten(),atan2d(Y,X).flatten() |
---|
2764 | R = np.where(R <= 1.,2.*atand(R),0.0) |
---|
2765 | Z = np.zeros_like(R) |
---|
2766 | Z = G2lat.polfcal(ODFln,SamSym,R,P) |
---|
2767 | Z = np.reshape(Z,(Grid,Grid)) |
---|
2768 | return np.reshape(R,(Grid,Grid)),np.reshape(P,(Grid,Grid)),Z |
---|
2769 | |
---|
2770 | def getRestPolefigDerv(HKL,Grid,SHCoeff): |
---|
2771 | '''default doc string |
---|
2772 | |
---|
2773 | :param type name: description |
---|
2774 | |
---|
2775 | :returns: type name: description |
---|
2776 | |
---|
2777 | ''' |
---|
2778 | pass |
---|
2779 | |
---|
2780 | def getDistDerv(Oxyz,Txyz,Amat,Tunit,Top,SGData): |
---|
2781 | '''default doc string |
---|
2782 | |
---|
2783 | :param type name: description |
---|
2784 | |
---|
2785 | :returns: type name: description |
---|
2786 | |
---|
2787 | ''' |
---|
2788 | def calcDist(Ox,Tx,U,inv,C,M,T,Amat): |
---|
2789 | TxT = inv*(np.inner(M,Tx)+T)+C+U |
---|
2790 | return np.sqrt(np.sum(np.inner(Amat,(TxT-Ox))**2)) |
---|
2791 | |
---|
2792 | inv = Top/abs(Top) |
---|
2793 | cent = abs(Top)//100 |
---|
2794 | op = abs(Top)%100-1 |
---|
2795 | M,T = SGData['SGOps'][op] |
---|
2796 | C = SGData['SGCen'][cent] |
---|
2797 | dx = .00001 |
---|
2798 | deriv = np.zeros(6) |
---|
2799 | for i in [0,1,2]: |
---|
2800 | Oxyz[i] -= dx |
---|
2801 | d0 = calcDist(Oxyz,Txyz,Tunit,inv,C,M,T,Amat) |
---|
2802 | Oxyz[i] += 2*dx |
---|
2803 | deriv[i] = (calcDist(Oxyz,Txyz,Tunit,inv,C,M,T,Amat)-d0)/(2.*dx) |
---|
2804 | Oxyz[i] -= dx |
---|
2805 | Txyz[i] -= dx |
---|
2806 | d0 = calcDist(Oxyz,Txyz,Tunit,inv,C,M,T,Amat) |
---|
2807 | Txyz[i] += 2*dx |
---|
2808 | deriv[i+3] = (calcDist(Oxyz,Txyz,Tunit,inv,C,M,T,Amat)-d0)/(2.*dx) |
---|
2809 | Txyz[i] -= dx |
---|
2810 | return deriv |
---|
2811 | |
---|
2812 | def getAngleDerv(Oxyz,Axyz,Bxyz,Amat,Tunit,symNo,SGData): |
---|
2813 | |
---|
2814 | def calcAngle(Oxyz,ABxyz,Amat,Tunit,symNo,SGData): |
---|
2815 | vec = np.zeros((2,3)) |
---|
2816 | for i in range(2): |
---|
2817 | inv = 1 |
---|
2818 | if symNo[i] < 0: |
---|
2819 | inv = -1 |
---|
2820 | cen = inv*symNo[i]//100 |
---|
2821 | op = inv*symNo[i]%100-1 |
---|
2822 | M,T = SGData['SGOps'][op] |
---|
2823 | D = T*inv+SGData['SGCen'][cen] |
---|
2824 | D += Tunit[i] |
---|
2825 | ABxyz[i] = np.inner(M*inv,ABxyz[i])+D |
---|
2826 | vec[i] = np.inner(Amat,(ABxyz[i]-Oxyz)) |
---|
2827 | dist = np.sqrt(np.sum(vec[i]**2)) |
---|
2828 | if not dist: |
---|
2829 | return 0. |
---|
2830 | vec[i] /= dist |
---|
2831 | angle = acosd(np.sum(vec[0]*vec[1])) |
---|
2832 | # GSASIIpath.IPyBreak() |
---|
2833 | return angle |
---|
2834 | |
---|
2835 | dx = .00001 |
---|
2836 | deriv = np.zeros(9) |
---|
2837 | for i in [0,1,2]: |
---|
2838 | Oxyz[i] -= dx |
---|
2839 | a0 = calcAngle(Oxyz,[Axyz,Bxyz],Amat,Tunit,symNo,SGData) |
---|
2840 | Oxyz[i] += 2*dx |
---|
2841 | deriv[i] = (calcAngle(Oxyz,[Axyz,Bxyz],Amat,Tunit,symNo,SGData)-a0)/(2.*dx) |
---|
2842 | Oxyz[i] -= dx |
---|
2843 | Axyz[i] -= dx |
---|
2844 | a0 = calcAngle(Oxyz,[Axyz,Bxyz],Amat,Tunit,symNo,SGData) |
---|
2845 | Axyz[i] += 2*dx |
---|
2846 | deriv[i+3] = (calcAngle(Oxyz,[Axyz,Bxyz],Amat,Tunit,symNo,SGData)-a0)/(2.*dx) |
---|
2847 | Axyz[i] -= dx |
---|
2848 | Bxyz[i] -= dx |
---|
2849 | a0 = calcAngle(Oxyz,[Axyz,Bxyz],Amat,Tunit,symNo,SGData) |
---|
2850 | Bxyz[i] += 2*dx |
---|
2851 | deriv[i+6] = (calcAngle(Oxyz,[Axyz,Bxyz],Amat,Tunit,symNo,SGData)-a0)/(2.*dx) |
---|
2852 | Bxyz[i] -= dx |
---|
2853 | return deriv |
---|
2854 | |
---|
2855 | def getAngSig(VA,VB,Amat,SGData,covData={}): |
---|
2856 | '''default doc string |
---|
2857 | |
---|
2858 | :param type name: description |
---|
2859 | |
---|
2860 | :returns: type name: description |
---|
2861 | |
---|
2862 | ''' |
---|
2863 | def calcVec(Ox,Tx,U,inv,C,M,T,Amat): |
---|
2864 | TxT = inv*(np.inner(M,Tx)+T)+C+U |
---|
2865 | return np.inner(Amat,(TxT-Ox)) |
---|
2866 | |
---|
2867 | def calcAngle(Ox,TxA,TxB,unitA,unitB,invA,CA,MA,TA,invB,CB,MB,TB,Amat): |
---|
2868 | VecA = calcVec(Ox,TxA,unitA,invA,CA,MA,TA,Amat) |
---|
2869 | VecA /= np.sqrt(np.sum(VecA**2)) |
---|
2870 | VecB = calcVec(Ox,TxB,unitB,invB,CB,MB,TB,Amat) |
---|
2871 | VecB /= np.sqrt(np.sum(VecB**2)) |
---|
2872 | edge = VecB-VecA |
---|
2873 | edge = np.sum(edge**2) |
---|
2874 | angle = (2.-edge)/2. |
---|
2875 | angle = max(angle,-1.) |
---|
2876 | return acosd(angle) |
---|
2877 | |
---|
2878 | OxAN,OxA,TxAN,TxA,unitA,TopA = VA |
---|
2879 | OxBN,OxB,TxBN,TxB,unitB,TopB = VB |
---|
2880 | invA = invB = 1 |
---|
2881 | invA = TopA//abs(TopA) |
---|
2882 | invB = TopB//abs(TopB) |
---|
2883 | centA = abs(TopA)//100 |
---|
2884 | centB = abs(TopB)//100 |
---|
2885 | opA = abs(TopA)%100-1 |
---|
2886 | opB = abs(TopB)%100-1 |
---|
2887 | MA,TA = SGData['SGOps'][opA] |
---|
2888 | MB,TB = SGData['SGOps'][opB] |
---|
2889 | CA = SGData['SGCen'][centA] |
---|
2890 | CB = SGData['SGCen'][centB] |
---|
2891 | if 'covMatrix' in covData: |
---|
2892 | covMatrix = covData['covMatrix'] |
---|
2893 | varyList = covData['varyList'] |
---|
2894 | AngVcov = getVCov(OxAN+TxAN+TxBN,varyList,covMatrix) |
---|
2895 | dx = .00001 |
---|
2896 | dadx = np.zeros(9) |
---|
2897 | Ang = calcAngle(OxA,TxA,TxB,unitA,unitB,invA,CA,MA,TA,invB,CB,MB,TB,Amat) |
---|
2898 | for i in [0,1,2]: |
---|
2899 | OxA[i] -= dx |
---|
2900 | a0 = calcAngle(OxA,TxA,TxB,unitA,unitB,invA,CA,MA,TA,invB,CB,MB,TB,Amat) |
---|
2901 | OxA[i] += 2*dx |
---|
2902 | dadx[i] = (calcAngle(OxA,TxA,TxB,unitA,unitB,invA,CA,MA,TA,invB,CB,MB,TB,Amat)-a0)/(2*dx) |
---|
2903 | OxA[i] -= dx |
---|
2904 | |
---|
2905 | TxA[i] -= dx |
---|
2906 | a0 = calcAngle(OxA,TxA,TxB,unitA,unitB,invA,CA,MA,TA,invB,CB,MB,TB,Amat) |
---|
2907 | TxA[i] += 2*dx |
---|
2908 | dadx[i+3] = (calcAngle(OxA,TxA,TxB,unitA,unitB,invA,CA,MA,TA,invB,CB,MB,TB,Amat)-a0)/(2*dx) |
---|
2909 | TxA[i] -= dx |
---|
2910 | |
---|
2911 | TxB[i] -= dx |
---|
2912 | a0 = calcAngle(OxA,TxA,TxB,unitA,unitB,invA,CA,MA,TA,invB,CB,MB,TB,Amat) |
---|
2913 | TxB[i] += 2*dx |
---|
2914 | dadx[i+6] = (calcAngle(OxA,TxA,TxB,unitA,unitB,invA,CA,MA,TA,invB,CB,MB,TB,Amat)-a0)/(2*dx) |
---|
2915 | TxB[i] -= dx |
---|
2916 | |
---|
2917 | sigAng = np.sqrt(np.inner(dadx,np.inner(AngVcov,dadx))) |
---|
2918 | if sigAng < 0.01: |
---|
2919 | sigAng = 0.0 |
---|
2920 | return Ang,sigAng |
---|
2921 | else: |
---|
2922 | return calcAngle(OxA,TxA,TxB,unitA,unitB,invA,CA,MA,TA,invB,CB,MB,TB,Amat),0.0 |
---|
2923 | |
---|
2924 | def GetDistSig(Oatoms,Atoms,Amat,SGData,covData={}): |
---|
2925 | '''default doc string |
---|
2926 | |
---|
2927 | :param type name: description |
---|
2928 | |
---|
2929 | :returns: type name: description |
---|
2930 | |
---|
2931 | ''' |
---|
2932 | def calcDist(Atoms,SyOps,Amat): |
---|
2933 | XYZ = [] |
---|
2934 | for i,atom in enumerate(Atoms): |
---|
2935 | Inv,M,T,C,U = SyOps[i] |
---|
2936 | XYZ.append(np.array(atom[1:4])) |
---|
2937 | XYZ[-1] = Inv*(np.inner(M,np.array(XYZ[-1]))+T)+C+U |
---|
2938 | XYZ[-1] = np.inner(Amat,XYZ[-1]).T |
---|
2939 | V1 = XYZ[1]-XYZ[0] |
---|
2940 | return np.sqrt(np.sum(V1**2)) |
---|
2941 | |
---|
2942 | SyOps = [] |
---|
2943 | names = [] |
---|
2944 | for i,atom in enumerate(Oatoms): |
---|
2945 | names += atom[-1] |
---|
2946 | Op,unit = Atoms[i][-1] |
---|
2947 | inv = Op//abs(Op) |
---|
2948 | m,t = SGData['SGOps'][abs(Op)%100-1] |
---|
2949 | c = SGData['SGCen'][abs(Op)//100] |
---|
2950 | SyOps.append([inv,m,t,c,unit]) |
---|
2951 | Dist = calcDist(Oatoms,SyOps,Amat) |
---|
2952 | |
---|
2953 | sig = -0.001 |
---|
2954 | if 'covMatrix' in covData: |
---|
2955 | dx = .00001 |
---|
2956 | dadx = np.zeros(6) |
---|
2957 | for i in range(6): |
---|
2958 | ia = i//3 |
---|
2959 | ix = i%3 |
---|
2960 | Oatoms[ia][ix+1] += dx |
---|
2961 | a0 = calcDist(Oatoms,SyOps,Amat) |
---|
2962 | Oatoms[ia][ix+1] -= 2*dx |
---|
2963 | dadx[i] = (calcDist(Oatoms,SyOps,Amat)-a0)/(2.*dx) |
---|
2964 | covMatrix = covData['covMatrix'] |
---|
2965 | varyList = covData['varyList'] |
---|
2966 | DistVcov = getVCov(names,varyList,covMatrix) |
---|
2967 | sig = np.sqrt(np.inner(dadx,np.inner(DistVcov,dadx))) |
---|
2968 | if sig < 0.001: |
---|
2969 | sig = -0.001 |
---|
2970 | |
---|
2971 | return Dist,sig |
---|
2972 | |
---|
2973 | def GetAngleSig(Oatoms,Atoms,Amat,SGData,covData={}): |
---|
2974 | '''default doc string |
---|
2975 | |
---|
2976 | :param type name: description |
---|
2977 | |
---|
2978 | :returns: type name: description |
---|
2979 | |
---|
2980 | ''' |
---|
2981 | |
---|
2982 | def calcAngle(Atoms,SyOps,Amat): |
---|
2983 | XYZ = [] |
---|
2984 | for i,atom in enumerate(Atoms): |
---|
2985 | Inv,M,T,C,U = SyOps[i] |
---|
2986 | XYZ.append(np.array(atom[1:4])) |
---|
2987 | XYZ[-1] = Inv*(np.inner(M,np.array(XYZ[-1]))+T)+C+U |
---|
2988 | XYZ[-1] = np.inner(Amat,XYZ[-1]).T |
---|
2989 | V1 = XYZ[1]-XYZ[0] |
---|
2990 | V1 /= np.sqrt(np.sum(V1**2)) |
---|
2991 | V2 = XYZ[1]-XYZ[2] |
---|
2992 | V2 /= np.sqrt(np.sum(V2**2)) |
---|
2993 | V3 = V2-V1 |
---|
2994 | cang = min(1.,max((2.-np.sum(V3**2))/2.,-1.)) |
---|
2995 | return acosd(cang) |
---|
2996 | |
---|
2997 | SyOps = [] |
---|
2998 | names = [] |
---|
2999 | for i,atom in enumerate(Oatoms): |
---|
3000 | names += atom[-1] |
---|
3001 | Op,unit = Atoms[i][-1] |
---|
3002 | inv = Op//abs(Op) |
---|
3003 | m,t = SGData['SGOps'][abs(Op)%100-1] |
---|
3004 | c = SGData['SGCen'][abs(Op)//100] |
---|
3005 | SyOps.append([inv,m,t,c,unit]) |
---|
3006 | Angle = calcAngle(Oatoms,SyOps,Amat) |
---|
3007 | |
---|
3008 | sig = -0.01 |
---|
3009 | if 'covMatrix' in covData: |
---|
3010 | dx = .00001 |
---|
3011 | dadx = np.zeros(9) |
---|
3012 | for i in range(9): |
---|
3013 | ia = i//3 |
---|
3014 | ix = i%3 |
---|
3015 | Oatoms[ia][ix+1] += dx |
---|
3016 | a0 = calcAngle(Oatoms,SyOps,Amat) |
---|
3017 | Oatoms[ia][ix+1] -= 2*dx |
---|
3018 | dadx[i] = (calcAngle(Oatoms,SyOps,Amat)-a0)/(2.*dx) |
---|
3019 | covMatrix = covData['covMatrix'] |
---|
3020 | varyList = covData['varyList'] |
---|
3021 | AngVcov = getVCov(names,varyList,covMatrix) |
---|
3022 | sig = np.sqrt(np.inner(dadx,np.inner(AngVcov,dadx))) |
---|
3023 | if sig < 0.01: |
---|
3024 | sig = -0.01 |
---|
3025 | |
---|
3026 | return Angle,sig |
---|
3027 | |
---|
3028 | def GetTorsionSig(Oatoms,Atoms,Amat,SGData,covData={}): |
---|
3029 | '''default doc string |
---|
3030 | |
---|
3031 | :param type name: description |
---|
3032 | |
---|
3033 | :returns: type name: description |
---|
3034 | |
---|
3035 | ''' |
---|
3036 | |
---|
3037 | def calcTorsion(Atoms,SyOps,Amat): |
---|
3038 | |
---|
3039 | XYZ = [] |
---|
3040 | for i,atom in enumerate(Atoms): |
---|
3041 | Inv,M,T,C,U = SyOps[i] |
---|
3042 | XYZ.append(np.array(atom[1:4])) |
---|
3043 | XYZ[-1] = Inv*(np.inner(M,np.array(XYZ[-1]))+T)+C+U |
---|
3044 | XYZ[-1] = np.inner(Amat,XYZ[-1]).T |
---|
3045 | V1 = XYZ[1]-XYZ[0] |
---|
3046 | V2 = XYZ[2]-XYZ[1] |
---|
3047 | V3 = XYZ[3]-XYZ[2] |
---|
3048 | V1 /= np.sqrt(np.sum(V1**2)) |
---|
3049 | V2 /= np.sqrt(np.sum(V2**2)) |
---|
3050 | V3 /= np.sqrt(np.sum(V3**2)) |
---|
3051 | M = np.array([V1,V2,V3]) |
---|
3052 | D = nl.det(M) |
---|
3053 | P12 = np.dot(V1,V2) |
---|
3054 | P13 = np.dot(V1,V3) |
---|
3055 | P23 = np.dot(V2,V3) |
---|
3056 | Tors = acosd((P12*P23-P13)/(np.sqrt(1.-P12**2)*np.sqrt(1.-P23**2)))*D/abs(D) |
---|
3057 | return Tors |
---|
3058 | |
---|
3059 | SyOps = [] |
---|
3060 | names = [] |
---|
3061 | for i,atom in enumerate(Oatoms): |
---|
3062 | names += atom[-1] |
---|
3063 | Op,unit = Atoms[i][-1] |
---|
3064 | inv = Op//abs(Op) |
---|
3065 | m,t = SGData['SGOps'][abs(Op)%100-1] |
---|
3066 | c = SGData['SGCen'][abs(Op)//100] |
---|
3067 | SyOps.append([inv,m,t,c,unit]) |
---|
3068 | Tors = calcTorsion(Oatoms,SyOps,Amat) |
---|
3069 | |
---|
3070 | sig = -0.01 |
---|
3071 | if 'covMatrix' in covData: |
---|
3072 | dx = .00001 |
---|
3073 | dadx = np.zeros(12) |
---|
3074 | for i in range(12): |
---|
3075 | ia = i//3 |
---|
3076 | ix = i%3 |
---|
3077 | Oatoms[ia][ix+1] -= dx |
---|
3078 | a0 = calcTorsion(Oatoms,SyOps,Amat) |
---|
3079 | Oatoms[ia][ix+1] += 2*dx |
---|
3080 | dadx[i] = (calcTorsion(Oatoms,SyOps,Amat)-a0)/(2.*dx) |
---|
3081 | Oatoms[ia][ix+1] -= dx |
---|
3082 | covMatrix = covData['covMatrix'] |
---|
3083 | varyList = covData['varyList'] |
---|
3084 | TorVcov = getVCov(names,varyList,covMatrix) |
---|
3085 | sig = np.sqrt(np.inner(dadx,np.inner(TorVcov,dadx))) |
---|
3086 | if sig < 0.01: |
---|
3087 | sig = -0.01 |
---|
3088 | |
---|
3089 | return Tors,sig |
---|
3090 | |
---|
3091 | def GetDATSig(Oatoms,Atoms,Amat,SGData,covData={}): |
---|
3092 | '''default doc string |
---|
3093 | |
---|
3094 | :param type name: description |
---|
3095 | |
---|
3096 | :returns: type name: description |
---|
3097 | |
---|
3098 | ''' |
---|
3099 | |
---|
3100 | def calcDist(Atoms,SyOps,Amat): |
---|
3101 | XYZ = [] |
---|
3102 | for i,atom in enumerate(Atoms): |
---|
3103 | Inv,M,T,C,U = SyOps[i] |
---|
3104 | XYZ.append(np.array(atom[1:4])) |
---|
3105 | XYZ[-1] = Inv*(np.inner(M,np.array(XYZ[-1]))+T)+C+U |
---|
3106 | XYZ[-1] = np.inner(Amat,XYZ[-1]).T |
---|
3107 | V1 = XYZ[1]-XYZ[0] |
---|
3108 | return np.sqrt(np.sum(V1**2)) |
---|
3109 | |
---|
3110 | def calcAngle(Atoms,SyOps,Amat): |
---|
3111 | XYZ = [] |
---|
3112 | for i,atom in enumerate(Atoms): |
---|
3113 | Inv,M,T,C,U = SyOps[i] |
---|
3114 | XYZ.append(np.array(atom[1:4])) |
---|
3115 | XYZ[-1] = Inv*(np.inner(M,np.array(XYZ[-1]))+T)+C+U |
---|
3116 | XYZ[-1] = np.inner(Amat,XYZ[-1]).T |
---|
3117 | V1 = XYZ[1]-XYZ[0] |
---|
3118 | V1 /= np.sqrt(np.sum(V1**2)) |
---|
3119 | V2 = XYZ[1]-XYZ[2] |
---|
3120 | V2 /= np.sqrt(np.sum(V2**2)) |
---|
3121 | V3 = V2-V1 |
---|
3122 | cang = min(1.,max((2.-np.sum(V3**2))/2.,-1.)) |
---|
3123 | return acosd(cang) |
---|
3124 | |
---|
3125 | def calcTorsion(Atoms,SyOps,Amat): |
---|
3126 | |
---|
3127 | XYZ = [] |
---|
3128 | for i,atom in enumerate(Atoms): |
---|
3129 | Inv,M,T,C,U = SyOps[i] |
---|
3130 | XYZ.append(np.array(atom[1:4])) |
---|
3131 | XYZ[-1] = Inv*(np.inner(M,np.array(XYZ[-1]))+T)+C+U |
---|
3132 | XYZ[-1] = np.inner(Amat,XYZ[-1]).T |
---|
3133 | V1 = XYZ[1]-XYZ[0] |
---|
3134 | V2 = XYZ[2]-XYZ[1] |
---|
3135 | V3 = XYZ[3]-XYZ[2] |
---|
3136 | V1 /= np.sqrt(np.sum(V1**2)) |
---|
3137 | V2 /= np.sqrt(np.sum(V2**2)) |
---|
3138 | V3 /= np.sqrt(np.sum(V3**2)) |
---|
3139 | M = np.array([V1,V2,V3]) |
---|
3140 | D = nl.det(M) |
---|
3141 | P12 = np.dot(V1,V2) |
---|
3142 | P13 = np.dot(V1,V3) |
---|
3143 | P23 = np.dot(V2,V3) |
---|
3144 | Tors = acosd((P12*P23-P13)/(np.sqrt(1.-P12**2)*np.sqrt(1.-P23**2)))*D/abs(D) |
---|
3145 | return Tors |
---|
3146 | |
---|
3147 | SyOps = [] |
---|
3148 | names = [] |
---|
3149 | for i,atom in enumerate(Oatoms): |
---|
3150 | names += atom[-1] |
---|
3151 | Op,unit = Atoms[i][-1] |
---|
3152 | inv = Op//abs(Op) |
---|
3153 | m,t = SGData['SGOps'][abs(Op)%100-1] |
---|
3154 | c = SGData['SGCen'][abs(Op)//100] |
---|
3155 | SyOps.append([inv,m,t,c,unit]) |
---|
3156 | M = len(Oatoms) |
---|
3157 | if M == 2: |
---|
3158 | Val = calcDist(Oatoms,SyOps,Amat) |
---|
3159 | elif M == 3: |
---|
3160 | Val = calcAngle(Oatoms,SyOps,Amat) |
---|
3161 | else: |
---|
3162 | Val = calcTorsion(Oatoms,SyOps,Amat) |
---|
3163 | |
---|
3164 | sigVals = [-0.001,-0.01,-0.01] |
---|
3165 | sig = sigVals[M-3] |
---|
3166 | if 'covMatrix' in covData: |
---|
3167 | dx = .00001 |
---|
3168 | N = M*3 |
---|
3169 | dadx = np.zeros(N) |
---|
3170 | for i in range(N): |
---|
3171 | ia = i//3 |
---|
3172 | ix = i%3 |
---|
3173 | Oatoms[ia][ix+1] += dx |
---|
3174 | if M == 2: |
---|
3175 | a0 = calcDist(Oatoms,SyOps,Amat) |
---|
3176 | elif M == 3: |
---|
3177 | a0 = calcAngle(Oatoms,SyOps,Amat) |
---|
3178 | else: |
---|
3179 | a0 = calcTorsion(Oatoms,SyOps,Amat) |
---|
3180 | Oatoms[ia][ix+1] -= 2*dx |
---|
3181 | if M == 2: |
---|
3182 | dadx[i] = (calcDist(Oatoms,SyOps,Amat)-a0)/(2.*dx) |
---|
3183 | elif M == 3: |
---|
3184 | dadx[i] = (calcAngle(Oatoms,SyOps,Amat)-a0)/(2.*dx) |
---|
3185 | else: |
---|
3186 | dadx[i] = (calcTorsion(Oatoms,SyOps,Amat)-a0)/(2.*dx) |
---|
3187 | covMatrix = covData['covMatrix'] |
---|
3188 | varyList = covData['varyList'] |
---|
3189 | Vcov = getVCov(names,varyList,covMatrix) |
---|
3190 | sig = np.sqrt(np.inner(dadx,np.inner(Vcov,dadx))) |
---|
3191 | if sig < sigVals[M-3]: |
---|
3192 | sig = sigVals[M-3] |
---|
3193 | |
---|
3194 | return Val,sig |
---|
3195 | |
---|
3196 | def GetMag(mag,Cell): |
---|
3197 | ''' |
---|
3198 | Compute magnetic moment magnitude. |
---|
3199 | :param list mag: atom magnetic moment parms (must be magnetic!) |
---|
3200 | :param list Cell: lattice parameters |
---|
3201 | |
---|
3202 | :returns: moment magnitude as float |
---|
3203 | |
---|
3204 | ''' |
---|
3205 | G = G2lat.fillgmat(Cell) |
---|
3206 | ast = np.sqrt(np.diag(G)) |
---|
3207 | GS = G/np.outer(ast,ast) |
---|
3208 | mag = np.array(mag) |
---|
3209 | Mag = np.sqrt(np.inner(mag,np.inner(mag,GS))) |
---|
3210 | return Mag |
---|
3211 | |
---|
3212 | def GetMagDerv(mag,Cell): |
---|
3213 | ''' |
---|
3214 | Compute magnetic moment derivatives numerically |
---|
3215 | :param list mag: atom magnetic moment parms (must be magnetic!) |
---|
3216 | :param list Cell: lattice parameters |
---|
3217 | |
---|
3218 | :returns: moment derivatives as floats |
---|
3219 | |
---|
3220 | ''' |
---|
3221 | def getMag(m): |
---|
3222 | return np.sqrt(np.inner(m,np.inner(m,GS))) |
---|
3223 | |
---|
3224 | derv = np.zeros(3) |
---|
3225 | dm = 0.0001 |
---|
3226 | twodm = 2.*dm |
---|
3227 | G = G2lat.fillgmat(Cell) |
---|
3228 | ast = np.sqrt(np.diag(G)) |
---|
3229 | GS = G/np.outer(ast,ast) |
---|
3230 | mag = np.array(mag) |
---|
3231 | for i in [0,1,2]: |
---|
3232 | mag[i] += dm |
---|
3233 | Magp = getMag(mag) |
---|
3234 | mag[i] -= 2*dm |
---|
3235 | derv[i] = (Magp-getMag(mag)) |
---|
3236 | mag[i] += dm |
---|
3237 | return derv/twodm |
---|
3238 | |
---|
3239 | def searchBondRestr(origAtoms,targAtoms,bond,Factor,GType,SGData,Amat, |
---|
3240 | defESD=0.01,dlg=None): |
---|
3241 | '''Search for bond distance restraints. |
---|
3242 | ''' |
---|
3243 | foundBonds = [] |
---|
3244 | indices = (-2,-1,0,1,2) |
---|
3245 | Units = np.array([[h,k,l] for h in indices for k in indices for l in indices]) |
---|
3246 | Norig = 0 |
---|
3247 | for Oid,Otype,Ocoord in origAtoms: |
---|
3248 | Norig += 1 |
---|
3249 | if dlg: dlg.Update(Norig) |
---|
3250 | for Tid,Ttype,Tcoord in targAtoms: |
---|
3251 | if 'macro' in GType: |
---|
3252 | result = [[Tcoord,1,[0,0,0],[]],] |
---|
3253 | else: |
---|
3254 | result = G2spc.GenAtom(Tcoord,SGData,False,Move=False) |
---|
3255 | for Txyz,Top,Tunit,Spn in result: |
---|
3256 | Dx = (Txyz-np.array(Ocoord))+Units |
---|
3257 | dx = np.inner(Amat,Dx) |
---|
3258 | dist = ma.masked_less(np.sqrt(np.sum(dx**2,axis=0)),bond/Factor) |
---|
3259 | IndB = ma.nonzero(ma.masked_greater(dist,bond*Factor)) |
---|
3260 | if np.any(IndB): |
---|
3261 | for indb in IndB: |
---|
3262 | for i in range(len(indb)): |
---|
3263 | unit = Units[indb][i]+Tunit |
---|
3264 | if np.any(unit): |
---|
3265 | Topstr = '%d+%d,%d,%d'%(Top,unit[0],unit[1],unit[2]) |
---|
3266 | else: |
---|
3267 | Topstr = str(Top) |
---|
3268 | foundBonds.append([[Oid,Tid],['1',Topstr],bond,defESD]) |
---|
3269 | return foundBonds |
---|
3270 | |
---|
3271 | def ValEsd(value,esd=0,nTZ=False): |
---|
3272 | '''Format a floating point number with a given level of precision or |
---|
3273 | with in crystallographic format with a "esd", as value(esd). If esd is |
---|
3274 | negative the number is formatted with the level of significant figures |
---|
3275 | appropriate if abs(esd) were the esd, but the esd is not included. |
---|
3276 | if the esd is zero, approximately 6 significant figures are printed. |
---|
3277 | nTZ=True causes "extra" zeros to be removed after the decimal place. |
---|
3278 | for example: |
---|
3279 | |
---|
3280 | * "1.235(3)" for value=1.2346 & esd=0.003 |
---|
3281 | * "1.235(3)e4" for value=12346. & esd=30 |
---|
3282 | * "1.235(3)e6" for value=0.12346e7 & esd=3000 |
---|
3283 | * "1.235" for value=1.2346 & esd=-0.003 |
---|
3284 | * "1.240" for value=1.2395 & esd=-0.003 |
---|
3285 | * "1.24" for value=1.2395 & esd=-0.003 with nTZ=True |
---|
3286 | * "1.23460" for value=1.2346 & esd=0.0 |
---|
3287 | |
---|
3288 | :param float value: number to be formatted |
---|
3289 | :param float esd: uncertainty or if esd < 0, specifies level of |
---|
3290 | precision to be shown e.g. esd=-0.01 gives 2 places beyond decimal |
---|
3291 | :param bool nTZ: True to remove trailing zeros (default is False) |
---|
3292 | :returns: value(esd) or value as a string |
---|
3293 | |
---|
3294 | ''' |
---|
3295 | # Note: this routine is Python 3 compatible -- I think |
---|
3296 | cutoff = 3.16228 #=(sqrt(10); same as old GSAS was 1.95 |
---|
3297 | if math.isnan(value): # invalid value, bail out |
---|
3298 | return '?' |
---|
3299 | if math.isnan(esd): # invalid esd, treat as zero |
---|
3300 | esd = 0 |
---|
3301 | esdoff = 5 |
---|
3302 | # if esd < 1.e-5: |
---|
3303 | # esd = 0 |
---|
3304 | # esdoff = 5 |
---|
3305 | elif esd != 0: |
---|
3306 | # transform the esd to a one or two digit integer |
---|
3307 | l = math.log10(abs(esd)) % 1. |
---|
3308 | if l < math.log10(cutoff): l+= 1. |
---|
3309 | intesd = int(round(10**l)) # esd as integer |
---|
3310 | # determine the number of digits offset for the esd |
---|
3311 | esdoff = int(round(math.log10(intesd*1./abs(esd)))) |
---|
3312 | else: |
---|
3313 | esdoff = 5 |
---|
3314 | valoff = 0 |
---|
3315 | if abs(value) < abs(esdoff): # value is effectively zero |
---|
3316 | pass |
---|
3317 | elif esdoff < 0 or abs(value) > 1.0e6 or abs(value) < 1.0e-4: # use scientific notation |
---|
3318 | # where the digit offset is to the left of the decimal place or where too many |
---|
3319 | # digits are needed |
---|
3320 | if abs(value) > 1: |
---|
3321 | valoff = int(math.log10(abs(value))) |
---|
3322 | elif abs(value) > 0: |
---|
3323 | valoff = int(math.log10(abs(value))-0.9999999) |
---|
3324 | else: |
---|
3325 | valoff = 0 |
---|
3326 | if esd != 0: |
---|
3327 | if valoff+esdoff < 0: |
---|
3328 | valoff = esdoff = 0 |
---|
3329 | out = ("{:."+str(valoff+esdoff)+"f}").format(value/10**valoff) # format the value |
---|
3330 | elif valoff != 0: # esd = 0; exponential notation ==> esdoff decimal places |
---|
3331 | out = ("{:."+str(esdoff)+"f}").format(value/10**valoff) # format the value |
---|
3332 | else: # esd = 0; non-exponential notation ==> esdoff+1 significant digits |
---|
3333 | if abs(value) > 0: |
---|
3334 | extra = -math.log10(abs(value)) |
---|
3335 | else: |
---|
3336 | extra = 0 |
---|
3337 | if extra > 0: extra += 1 |
---|
3338 | out = ("{:."+str(max(0,esdoff+int(extra)))+"f}").format(value) # format the value |
---|
3339 | if esd > 0: |
---|
3340 | out += ("({:d})").format(intesd) # add the esd |
---|
3341 | elif nTZ and '.' in out: |
---|
3342 | out = out.rstrip('0') # strip zeros to right of decimal |
---|
3343 | out = out.rstrip('.') # and decimal place when not needed |
---|
3344 | if valoff != 0: |
---|
3345 | out += ("e{:d}").format(valoff) # add an exponent, when needed |
---|
3346 | return out |
---|
3347 | |
---|
3348 | ############################################################################### |
---|
3349 | #### Protein validation - "ERRATV2" analysis |
---|
3350 | ############################################################################### |
---|
3351 | |
---|
3352 | def validProtein(Phase,old): |
---|
3353 | |
---|
3354 | def sumintact(intact): |
---|
3355 | return {'CC':intact['CC'],'NN':intact['NN'],'OO':intact['OO'], |
---|
3356 | 'CN':(intact['CN']+intact['NC']),'CO':(intact['CO']+intact['OC']), |
---|
3357 | 'NO':(intact['NO']+intact['ON'])} |
---|
3358 | |
---|
3359 | resNames = ['ALA','ARG','ASN','ASP','CYS','GLN','GLU','GLY','HIS','ILE', |
---|
3360 | 'LEU','LYS','MET','PHE','PRO','SER','THR','TRP','TYR','VAL','MSE'] |
---|
3361 | # data from errat.f |
---|
3362 | b1_old = np.array([ |
---|
3363 | [1154.343, 600.213, 1051.018, 1132.885, 960.738], |
---|
3364 | [600.213, 1286.818, 1282.042, 957.156, 612.789], |
---|
3365 | [1051.018, 1282.042, 3519.471, 991.974, 1226.491], |
---|
3366 | [1132.885, 957.156, 991.974, 1798.672, 820.355], |
---|
3367 | [960.738, 612.789, 1226.491, 820.355, 2428.966] |
---|
3368 | ]) |
---|
3369 | avg_old = np.array([ 0.225, 0.281, 0.071, 0.237, 0.044]) #Table 1 3.5A Obsd. Fr. p 1513 |
---|
3370 | # data taken from erratv2.ccp |
---|
3371 | b1 = np.array([ |
---|
3372 | [5040.279078850848200, 3408.805141583649400, 4152.904423767300600, 4236.200004171890200, 5054.781210204625500], |
---|
3373 | [3408.805141583648900, 8491.906094010220800, 5958.881777877950300, 1521.387352718486200, 4304.078200827221700], |
---|
3374 | [4152.904423767301500, 5958.881777877952100, 7637.167089335050100, 6620.715738223072500, 5287.691183798410700], |
---|
3375 | [4236.200004171890200, 1521.387352718486200, 6620.715738223072500, 18368.343774298410000, 4050.797811118806700], |
---|
3376 | [5054.781210204625500, 4304.078200827220800, 5287.691183798409800, 4050.797811118806700, 6666.856740479164700]]) |
---|
3377 | avg = np.array([0.192765509919262, 0.195575208778518, 0.275322406824210, 0.059102357035642, 0.233154192767480]) |
---|
3378 | General = Phase['General'] |
---|
3379 | Amat,Bmat = G2lat.cell2AB(General['Cell'][1:7]) |
---|
3380 | cx,ct,cs,cia = getAtomPtrs(Phase) |
---|
3381 | Atoms = Phase['Atoms'] |
---|
3382 | cartAtoms = [] |
---|
3383 | xyzmin = 999.*np.ones(3) |
---|
3384 | xyzmax = -999.*np.ones(3) |
---|
3385 | #select residue atoms,S,Se --> O make cartesian |
---|
3386 | for atom in Atoms: |
---|
3387 | if atom[1] in resNames: |
---|
3388 | cartAtoms.append(atom[:cx+3]) |
---|
3389 | if atom[4].strip() in ['S','Se']: |
---|
3390 | if not old: |
---|
3391 | continue #S,Se skipped for erratv2? |
---|
3392 | cartAtoms[-1][3] = 'Os' |
---|
3393 | cartAtoms[-1][4] = 'O' |
---|
3394 | cartAtoms[-1][cx:cx+3] = np.inner(Amat,cartAtoms[-1][cx:cx+3]) |
---|
3395 | cartAtoms[-1].append(atom[cia+8]) |
---|
3396 | XYZ = np.array([atom[cx:cx+3] for atom in cartAtoms]) |
---|
3397 | xyzmin = np.array([np.min(XYZ.T[i]) for i in [0,1,2]]) |
---|
3398 | xyzmax = np.array([np.max(XYZ.T[i]) for i in [0,1,2]]) |
---|
3399 | nbox = list(np.array(np.ceil((xyzmax-xyzmin)/4.),dtype=int))+[15,] |
---|
3400 | Boxes = np.zeros(nbox,dtype=int) |
---|
3401 | iBox = np.array([np.trunc((XYZ.T[i]-xyzmin[i])/4.) for i in [0,1,2]],dtype=int).T |
---|
3402 | for ib,box in enumerate(iBox): #put in a try for too many atoms in box (IndexError)? |
---|
3403 | try: |
---|
3404 | Boxes[box[0],box[1],box[2],0] += 1 |
---|
3405 | Boxes[box[0],box[1],box[2],Boxes[box[0],box[1],box[2],0]] = ib |
---|
3406 | except IndexError: |
---|
3407 | G2fil.G2Print('Error: too many atoms in box' ) |
---|
3408 | continue |
---|
3409 | #Box content checks with errat.f $ erratv2.cpp ibox1 arrays |
---|
3410 | indices = (-1,0,1) |
---|
3411 | Units = np.array([[h,k,l] for h in indices for k in indices for l in indices]) |
---|
3412 | dsmax = 3.75**2 |
---|
3413 | if old: |
---|
3414 | dsmax = 3.5**2 |
---|
3415 | chains = [] |
---|
3416 | resIntAct = [] |
---|
3417 | chainIntAct = [] |
---|
3418 | res = [] |
---|
3419 | resNames = [] |
---|
3420 | resIDs = {} |
---|
3421 | resname = [] |
---|
3422 | resID = {} |
---|
3423 | newChain = True |
---|
3424 | intact = {'CC':0,'CN':0,'CO':0,'NN':0,'NO':0,'OO':0,'NC':0,'OC':0,'ON':0} |
---|
3425 | for ia,atom in enumerate(cartAtoms): |
---|
3426 | jntact = {'CC':0,'CN':0,'CO':0,'NN':0,'NO':0,'OO':0,'NC':0,'OC':0,'ON':0} |
---|
3427 | if atom[2] not in chains: #get chain id & save residue sequence from last chain |
---|
3428 | chains.append(atom[2]) |
---|
3429 | if len(resIntAct): |
---|
3430 | resIntAct.append(sumintact(intact)) |
---|
3431 | chainIntAct.append(resIntAct) |
---|
3432 | resNames += resname |
---|
3433 | resIDs.update(resID) |
---|
3434 | res = [] |
---|
3435 | resname = [] |
---|
3436 | resID = {} |
---|
3437 | resIntAct = [] |
---|
3438 | intact = {'CC':0,'CN':0,'CO':0,'NN':0,'NO':0,'OO':0,'NC':0,'OC':0,'ON':0} |
---|
3439 | newChain = True |
---|
3440 | if atom[0] not in res: #new residue, get residue no. |
---|
3441 | if res and int(res[-1]) != int(atom[0])-1: #a gap in chain - not new chain |
---|
3442 | intact = {'CC':0,'CN':0,'CO':0,'NN':0,'NO':0,'OO':0,'NC':0,'OC':0,'ON':0} |
---|
3443 | ires = int(res[-1]) |
---|
3444 | for i in range(int(atom[0])-ires-1): |
---|
3445 | res.append(str(ires+i+1)) |
---|
3446 | resname.append('') |
---|
3447 | resIntAct.append(sumintact(intact)) |
---|
3448 | res.append(atom[0]) |
---|
3449 | name = '%s-%s%s'%(atom[2],atom[0],atom[1]) |
---|
3450 | resname.append(name) |
---|
3451 | resID[name] = atom[-1] |
---|
3452 | if not newChain: |
---|
3453 | resIntAct.append(sumintact(intact)) |
---|
3454 | intact = {'CC':0,'CN':0,'CO':0,'NN':0,'NO':0,'OO':0,'NC':0,'OC':0,'ON':0} |
---|
3455 | newChain = False |
---|
3456 | ibox = iBox[ia] #box location of atom |
---|
3457 | tgts = [] |
---|
3458 | for unit in Units: #assemble list of all possible target atoms |
---|
3459 | jbox = ibox+unit |
---|
3460 | if np.all(jbox>=0) and np.all((jbox-nbox[:3])<0): |
---|
3461 | tgts += list(Boxes[jbox[0],jbox[1],jbox[2]]) |
---|
3462 | tgts = list(set(tgts)) |
---|
3463 | tgts = [tgt for tgt in tgts if atom[:3] != cartAtoms[tgt][:3]] #exclude same residue |
---|
3464 | tgts = [tgt for tgt in tgts if np.sum((XYZ[ia]-XYZ[tgt])**2) < dsmax] |
---|
3465 | ires = int(atom[0]) |
---|
3466 | if old: |
---|
3467 | if atom[3].strip() == 'C': |
---|
3468 | tgts = [tgt for tgt in tgts if not (cartAtoms[tgt][3].strip() == 'N' and int(cartAtoms[tgt][0]) in [ires-1,ires+1])] |
---|
3469 | elif atom[3].strip() == 'N': |
---|
3470 | tgts = [tgt for tgt in tgts if not (cartAtoms[tgt][3].strip() in ['C','CA'] and int(cartAtoms[tgt][0]) in [ires-1,ires+1])] |
---|
3471 | elif atom[3].strip() == 'CA': |
---|
3472 | tgts = [tgt for tgt in tgts if not (cartAtoms[tgt][3].strip() == 'N' and int(cartAtoms[tgt][0]) in [ires-1,ires+1])] |
---|
3473 | else: |
---|
3474 | tgts = [tgt for tgt in tgts if not int(cartAtoms[tgt][0]) in [ires+1,ires+2,ires+3,ires+4,ires+5,ires+6,ires+7,ires+8]] |
---|
3475 | if atom[3].strip() == 'C': |
---|
3476 | tgts = [tgt for tgt in tgts if not (cartAtoms[tgt][3].strip() == 'N' and int(cartAtoms[tgt][0]) == ires+1)] |
---|
3477 | elif atom[3].strip() == 'N': |
---|
3478 | tgts = [tgt for tgt in tgts if not (cartAtoms[tgt][3].strip() == 'C' and int(cartAtoms[tgt][0]) == ires-1)] |
---|
3479 | for tgt in tgts: |
---|
3480 | dsqt = np.sqrt(np.sum((XYZ[ia]-XYZ[tgt])**2)) |
---|
3481 | mult = 1.0 |
---|
3482 | if dsqt > 3.25 and not old: |
---|
3483 | mult = 2.*(3.75-dsqt) |
---|
3484 | intype = atom[4].strip()+cartAtoms[tgt][4].strip() |
---|
3485 | if 'S' not in intype: |
---|
3486 | intact[intype] += mult |
---|
3487 | jntact[intype] += mult |
---|
3488 | # print ia,atom[0]+atom[1]+atom[3],tgts,jntact['CC'],jntact['CN']+jntact['NC'],jntact['CO']+jntact['OC'],jntact['NN'],jntact['NO']+jntact['ON'] |
---|
3489 | resNames += resname |
---|
3490 | resIDs.update(resID) |
---|
3491 | resIntAct.append(sumintact(intact)) |
---|
3492 | chainIntAct.append(resIntAct) |
---|
3493 | chainProb = [] |
---|
3494 | for ich,chn in enumerate(chains): |
---|
3495 | IntAct = chainIntAct[ich] |
---|
3496 | nRes = len(IntAct) |
---|
3497 | Probs = [0.,0.,0.,0.] #skip 1st 4 residues in chain |
---|
3498 | for i in range(4,nRes-4): |
---|
3499 | if resNames[i]: |
---|
3500 | mtrx = np.zeros(5) |
---|
3501 | summ = 0. |
---|
3502 | for j in range(i-4,i+5): |
---|
3503 | summ += np.sum(np.array(list(IntAct[j].values()))) |
---|
3504 | if old: |
---|
3505 | mtrx[0] += IntAct[j]['CC'] |
---|
3506 | mtrx[1] += IntAct[j]['CO'] |
---|
3507 | mtrx[2] += IntAct[j]['NN'] |
---|
3508 | mtrx[3] += IntAct[j]['NO'] |
---|
3509 | mtrx[4] += IntAct[j]['OO'] |
---|
3510 | else: |
---|
3511 | mtrx[0] += IntAct[j]['CC'] |
---|
3512 | mtrx[1] += IntAct[j]['CN'] |
---|
3513 | mtrx[2] += IntAct[j]['CO'] |
---|
3514 | mtrx[3] += IntAct[j]['NN'] |
---|
3515 | mtrx[4] += IntAct[j]['NO'] |
---|
3516 | mtrx /= summ |
---|
3517 | # print i+1,mtrx*summ |
---|
3518 | if old: |
---|
3519 | mtrx -= avg_old |
---|
3520 | prob = np.inner(np.inner(mtrx,b1_old),mtrx) |
---|
3521 | else: |
---|
3522 | mtrx -= avg |
---|
3523 | prob = np.inner(np.inner(mtrx,b1),mtrx) |
---|
3524 | else: #skip the gaps |
---|
3525 | prob = 0.0 |
---|
3526 | Probs.append(prob) |
---|
3527 | Probs += 4*[0.,] #skip last 4 residues in chain |
---|
3528 | chainProb += Probs |
---|
3529 | return resNames,chainProb,resIDs |
---|
3530 | |
---|
3531 | ################################################################################ |
---|
3532 | #### Texture fitting stuff |
---|
3533 | ################################################################################ |
---|
3534 | |
---|
3535 | def FitTexture(General,Gangls,refData,keyList,pgbar): |
---|
3536 | import pytexture as ptx |
---|
3537 | ptx.pyqlmninit() #initialize fortran arrays for spherical harmonics |
---|
3538 | |
---|
3539 | def printSpHarm(textureData,SHtextureSig): |
---|
3540 | Tindx = 1.0 |
---|
3541 | Tvar = 0.0 |
---|
3542 | print ('\n Spherical harmonics texture: Order:' + str(textureData['Order'])) |
---|
3543 | names = ['omega','chi','phi'] |
---|
3544 | namstr = ' names :' |
---|
3545 | ptstr = ' values:' |
---|
3546 | sigstr = ' esds :' |
---|
3547 | for name in names: |
---|
3548 | namstr += '%12s'%('Sample '+name) |
---|
3549 | ptstr += '%12.3f'%(textureData['Sample '+name][1]) |
---|
3550 | if 'Sample '+name in SHtextureSig: |
---|
3551 | sigstr += '%12.3f'%(SHtextureSig['Sample '+name]) |
---|
3552 | else: |
---|
3553 | sigstr += 12*' ' |
---|
3554 | print (namstr) |
---|
3555 | print (ptstr) |
---|
3556 | print (sigstr) |
---|
3557 | print ('\n Texture coefficients:') |
---|
3558 | SHcoeff = textureData['SH Coeff'][1] |
---|
3559 | SHkeys = list(SHcoeff.keys()) |
---|
3560 | nCoeff = len(SHcoeff) |
---|
3561 | nBlock = nCoeff//10+1 |
---|
3562 | iBeg = 0 |
---|
3563 | iFin = min(iBeg+10,nCoeff) |
---|
3564 | for block in range(nBlock): |
---|
3565 | namstr = ' names :' |
---|
3566 | ptstr = ' values:' |
---|
3567 | sigstr = ' esds :' |
---|
3568 | for name in SHkeys[iBeg:iFin]: |
---|
3569 | if 'C' in name: |
---|
3570 | l = 2.0*eval(name.strip('C'))[0]+1 |
---|
3571 | Tindx += SHcoeff[name]**2/l |
---|
3572 | namstr += '%12s'%(name) |
---|
3573 | ptstr += '%12.3f'%(SHcoeff[name]) |
---|
3574 | if name in SHtextureSig: |
---|
3575 | Tvar += (2.*SHcoeff[name]*SHtextureSig[name]/l)**2 |
---|
3576 | sigstr += '%12.3f'%(SHtextureSig[name]) |
---|
3577 | else: |
---|
3578 | sigstr += 12*' ' |
---|
3579 | print (namstr) |
---|
3580 | print (ptstr) |
---|
3581 | print (sigstr) |
---|
3582 | iBeg += 10 |
---|
3583 | iFin = min(iBeg+10,nCoeff) |
---|
3584 | print(' Texture index J = %.3f(%d)'%(Tindx,int(1000*np.sqrt(Tvar)))) |
---|
3585 | |
---|
3586 | def Dict2Values(parmdict, varylist): |
---|
3587 | '''Use before call to leastsq to setup list of values for the parameters |
---|
3588 | in parmdict, as selected by key in varylist''' |
---|
3589 | return [parmdict[key] for key in varylist] |
---|
3590 | |
---|
3591 | def Values2Dict(parmdict, varylist, values): |
---|
3592 | ''' Use after call to leastsq to update the parameter dictionary with |
---|
3593 | values corresponding to keys in varylist''' |
---|
3594 | parmdict.update(list(zip(varylist,values))) |
---|
3595 | |
---|
3596 | def errSpHarm(values,SGData,cell,Gangls,shModel,refData,parmDict,varyList,pgbar): |
---|
3597 | parmDict.update(list(zip(varyList,values))) |
---|
3598 | Mat = np.empty(0) |
---|
3599 | sumObs = 0 |
---|
3600 | Sangls = [parmDict['Sample '+'omega'],parmDict['Sample '+'chi'],parmDict['Sample '+'phi']] |
---|
3601 | for hist in Gangls.keys(): |
---|
3602 | Refs = refData[hist] |
---|
3603 | Refs[:,5] = np.where(Refs[:,5]>0.,Refs[:,5],0.) |
---|
3604 | wt = 1./np.sqrt(np.fmax(Refs[:,4],.25)) |
---|
3605 | # wt = 1./np.max(Refs[:,4],.25) |
---|
3606 | sumObs += np.sum(wt*Refs[:,5]) |
---|
3607 | Refs[:,6] = 1. |
---|
3608 | H = Refs[:,:3] |
---|
3609 | phi,beta = G2lat.CrsAng(H,cell,SGData) |
---|
3610 | psi,gam,x,x = G2lat.SamAng(Refs[:,3]/2.,Gangls[hist],Sangls,False) #assume not Bragg-Brentano! |
---|
3611 | for item in parmDict: |
---|
3612 | if 'C' in item: |
---|
3613 | L,M,N = eval(item.strip('C')) |
---|
3614 | Kcl = G2lat.GetKcl(L,N,SGData['SGLaue'],phi,beta) |
---|
3615 | Ksl,x,x = G2lat.GetKsl(L,M,shModel,psi,gam) |
---|
3616 | Lnorm = G2lat.Lnorm(L) |
---|
3617 | Refs[:,6] += parmDict[item]*Lnorm*Kcl*Ksl |
---|
3618 | mat = wt*(Refs[:,5]-Refs[:,6]) |
---|
3619 | Mat = np.concatenate((Mat,mat)) |
---|
3620 | sumD = np.sum(np.abs(Mat)) |
---|
3621 | R = min(100.,100.*sumD/sumObs) |
---|
3622 | pgbar.Raise() |
---|
3623 | pgbar.Update(int(R),newmsg='Residual = %5.2f'%(R)) |
---|
3624 | print (' Residual: %.3f%%'%(R)) |
---|
3625 | return Mat |
---|
3626 | |
---|
3627 | def dervSpHarm(values,SGData,cell,Gangls,shModel,refData,parmDict,varyList,pgbar): |
---|
3628 | Mat = np.empty(0) |
---|
3629 | Sangls = [parmDict['Sample omega'],parmDict['Sample chi'],parmDict['Sample phi']] |
---|
3630 | for hist in Gangls.keys(): |
---|
3631 | mat = np.zeros((len(varyList),len(refData[hist]))) |
---|
3632 | Refs = refData[hist] |
---|
3633 | H = Refs[:,:3] |
---|
3634 | wt = 1./np.sqrt(np.fmax(Refs[:,4],.25)) |
---|
3635 | # wt = 1./np.max(Refs[:,4],.25) |
---|
3636 | phi,beta = G2lat.CrsAng(H,cell,SGData) |
---|
3637 | psi,gam,dPdA,dGdA = G2lat.SamAng(Refs[:,3]/2.,Gangls[hist],Sangls,False) #assume not Bragg-Brentano! |
---|
3638 | for j,item in enumerate(varyList): |
---|
3639 | if 'C' in item: |
---|
3640 | L,M,N = eval(item.strip('C')) |
---|
3641 | Kcl = G2lat.GetKcl(L,N,SGData['SGLaue'],phi,beta) |
---|
3642 | Ksl,dKdp,dKdg = G2lat.GetKsl(L,M,shModel,psi,gam) |
---|
3643 | Lnorm = G2lat.Lnorm(L) |
---|
3644 | mat[j] = -wt*Lnorm*Kcl*Ksl |
---|
3645 | for k,itema in enumerate(['Sample omega','Sample chi','Sample phi']): |
---|
3646 | try: |
---|
3647 | l = varyList.index(itema) |
---|
3648 | mat[l] -= parmDict[item]*wt*Lnorm*Kcl*(dKdp*dPdA[k]+dKdg*dGdA[k]) |
---|
3649 | except ValueError: |
---|
3650 | pass |
---|
3651 | if len(Mat): |
---|
3652 | Mat = np.concatenate((Mat,mat.T)) |
---|
3653 | else: |
---|
3654 | Mat = mat.T |
---|
3655 | print ('deriv') |
---|
3656 | return Mat |
---|
3657 | |
---|
3658 | print (' Fit texture for '+General['Name']) |
---|
3659 | SGData = General['SGData'] |
---|
3660 | cell = General['Cell'][1:7] |
---|
3661 | Texture = General['SH Texture'] |
---|
3662 | if not Texture['Order']: |
---|
3663 | return 'No spherical harmonics coefficients' |
---|
3664 | varyList = [] |
---|
3665 | parmDict = copy.copy(Texture['SH Coeff'][1]) |
---|
3666 | for item in ['Sample omega','Sample chi','Sample phi']: |
---|
3667 | parmDict[item] = Texture[item][1] |
---|
3668 | if Texture[item][0]: |
---|
3669 | varyList.append(item) |
---|
3670 | if Texture['SH Coeff'][0]: |
---|
3671 | varyList += list(Texture['SH Coeff'][1].keys()) |
---|
3672 | while True: |
---|
3673 | begin = time.time() |
---|
3674 | values = np.array(Dict2Values(parmDict, varyList)) |
---|
3675 | result = so.leastsq(errSpHarm,values,Dfun=dervSpHarm,full_output=True,ftol=1.e-6, |
---|
3676 | args=(SGData,cell,Gangls,Texture['Model'],refData,parmDict,varyList,pgbar)) |
---|
3677 | ncyc = int(result[2]['nfev']//2) |
---|
3678 | if ncyc: |
---|
3679 | runtime = time.time()-begin |
---|
3680 | chisq = np.sum(result[2]['fvec']**2) |
---|
3681 | Values2Dict(parmDict, varyList, result[0]) |
---|
3682 | GOF = chisq/(len(result[2]['fvec'])-len(varyList)) #reduced chi^2 |
---|
3683 | G2fil.G2Print ('Number of function calls: %d Number of observations: %d Number of parameters: %d'%(result[2]['nfev'],len(result[2]['fvec']),len(varyList))) |
---|
3684 | G2fil.G2Print ('refinement time = %8.3fs, %8.3fs/cycle'%(runtime,runtime/ncyc)) |
---|
3685 | try: |
---|
3686 | sig = np.sqrt(np.diag(result[1])*GOF) |
---|
3687 | if np.any(np.isnan(sig)): |
---|
3688 | G2fil.G2Print ('*** Least squares aborted - some invalid esds possible ***', mode='error') |
---|
3689 | break #refinement succeeded - finish up! |
---|
3690 | except ValueError: #result[1] is None on singular matrix |
---|
3691 | G2fil.G2Print ('**** Refinement failed - singular matrix ****', mode='error') |
---|
3692 | return None |
---|
3693 | else: |
---|
3694 | break |
---|
3695 | |
---|
3696 | if ncyc: |
---|
3697 | for parm in parmDict: |
---|
3698 | if 'C' in parm: |
---|
3699 | Texture['SH Coeff'][1][parm] = parmDict[parm] |
---|
3700 | else: |
---|
3701 | Texture[parm][1] = parmDict[parm] |
---|
3702 | sigDict = dict(zip(varyList,sig)) |
---|
3703 | printSpHarm(Texture,sigDict) |
---|
3704 | |
---|
3705 | return None |
---|
3706 | |
---|
3707 | ################################################################################ |
---|
3708 | #### Fourier & charge flip stuff |
---|
3709 | ################################################################################ |
---|
3710 | |
---|
3711 | def adjHKLmax(SGData,Hmax): |
---|
3712 | '''default doc string |
---|
3713 | |
---|
3714 | :param type name: description |
---|
3715 | |
---|
3716 | :returns: type name: description |
---|
3717 | |
---|
3718 | ''' |
---|
3719 | if SGData['SGLaue'] in ['3','3m1','31m','6/m','6/mmm']: |
---|
3720 | Hmax[0] = int(math.ceil(Hmax[0]/6.))*6 |
---|
3721 | Hmax[1] = int(math.ceil(Hmax[1]/6.))*6 |
---|
3722 | Hmax[2] = int(math.ceil(Hmax[2]/4.))*4 |
---|
3723 | else: |
---|
3724 | Hmax[0] = int(math.ceil(Hmax[0]/4.))*4 |
---|
3725 | Hmax[1] = int(math.ceil(Hmax[1]/4.))*4 |
---|
3726 | Hmax[2] = int(math.ceil(Hmax[2]/4.))*4 |
---|
3727 | |
---|
3728 | def OmitMap(data,reflDict,pgbar=None): |
---|
3729 | '''default doc string |
---|
3730 | |
---|
3731 | :param type name: description |
---|
3732 | |
---|
3733 | :returns: type name: description |
---|
3734 | |
---|
3735 | ''' |
---|
3736 | generalData = data['General'] |
---|
3737 | if not generalData['Map']['MapType']: |
---|
3738 | G2fil.G2Print ('**** ERROR - Fourier map not defined') |
---|
3739 | return |
---|
3740 | mapData = generalData['Map'] |
---|
3741 | dmin = mapData['GridStep']*2. |
---|
3742 | SGData = generalData['SGData'] |
---|
3743 | SGMT = np.array([ops[0].T for ops in SGData['SGOps']]) |
---|
3744 | SGT = np.array([ops[1] for ops in SGData['SGOps']]) |
---|
3745 | cell = generalData['Cell'][1:8] |
---|
3746 | A = G2lat.cell2A(cell[:6]) |
---|
3747 | Hmax = np.asarray(G2lat.getHKLmax(dmin,SGData,A),dtype='i')+1 |
---|
3748 | adjHKLmax(SGData,Hmax) |
---|
3749 | Fhkl = np.zeros(shape=2*Hmax,dtype='c16') |
---|
3750 | time0 = time.time() |
---|
3751 | for iref,ref in enumerate(reflDict['RefList']): |
---|
3752 | if ref[4] >= dmin: |
---|
3753 | Fosq,Fcsq,ph = ref[8:11] |
---|
3754 | Uniq = np.inner(ref[:3],SGMT) |
---|
3755 | Phi = np.inner(ref[:3],SGT) |
---|
3756 | for i,hkl in enumerate(Uniq): #uses uniq |
---|
3757 | hkl = np.asarray(hkl,dtype='i') |
---|
3758 | dp = 360.*Phi[i] #and phi |
---|
3759 | a = cosd(ph+dp) |
---|
3760 | b = sind(ph+dp) |
---|
3761 | phasep = complex(a,b) |
---|
3762 | phasem = complex(a,-b) |
---|
3763 | if '2Fo-Fc' in mapData['MapType']: |
---|
3764 | F = 2.*np.sqrt(Fosq)-np.sqrt(Fcsq) |
---|
3765 | else: |
---|
3766 | F = np.sqrt(Fosq) |
---|
3767 | h,k,l = hkl+Hmax |
---|
3768 | Fhkl[h,k,l] = F*phasep |
---|
3769 | h,k,l = -hkl+Hmax |
---|
3770 | Fhkl[h,k,l] = F*phasem |
---|
3771 | rho0 = fft.fftn(fft.fftshift(Fhkl))/cell[6] |
---|
3772 | M = np.mgrid[0:4,0:4,0:4] |
---|
3773 | blkIds = np.array(list(zip(M[0].flatten(),M[1].flatten(),M[2].flatten()))) |
---|
3774 | iBeg = blkIds*rho0.shape//4 |
---|
3775 | iFin = (blkIds+1)*rho0.shape//4 |
---|
3776 | rho_omit = np.zeros_like(rho0) |
---|
3777 | nBlk = 0 |
---|
3778 | for iB,iF in zip(iBeg,iFin): |
---|
3779 | rho1 = np.copy(rho0) |
---|
3780 | rho1[iB[0]:iF[0],iB[1]:iF[1],iB[2]:iF[2]] = 0. |
---|
3781 | Fnew = fft.ifftshift(fft.ifftn(rho1)) |
---|
3782 | Fnew = np.where(Fnew,Fnew,1.0) #avoid divide by zero |
---|
3783 | phase = Fnew/np.absolute(Fnew) |
---|
3784 | OFhkl = np.absolute(Fhkl)*phase |
---|
3785 | rho1 = np.real(fft.fftn(fft.fftshift(OFhkl)))*(1.+0j) |
---|
3786 | rho_omit[iB[0]:iF[0],iB[1]:iF[1],iB[2]:iF[2]] = np.copy(rho1[iB[0]:iF[0],iB[1]:iF[1],iB[2]:iF[2]]) |
---|
3787 | nBlk += 1 |
---|
3788 | pgbar.Raise() |
---|
3789 | pgbar.Update(nBlk) |
---|
3790 | mapData['rho'] = np.real(rho_omit)/cell[6] |
---|
3791 | mapData['rhoMax'] = max(np.max(mapData['rho']),-np.min(mapData['rho'])) |
---|
3792 | mapData['minmax'] = [np.max(mapData['rho']),np.min(mapData['rho'])] |
---|
3793 | G2fil.G2Print ('Omit map time: %.4f no. elements: %d dimensions: %s'%(time.time()-time0,Fhkl.size,str(Fhkl.shape))) |
---|
3794 | return mapData |
---|
3795 | |
---|
3796 | def FourierMap(data,reflDict): |
---|
3797 | '''default doc string |
---|
3798 | |
---|
3799 | :param type name: description |
---|
3800 | |
---|
3801 | :returns: type name: description |
---|
3802 | |
---|
3803 | ''' |
---|
3804 | generalData = data['General'] |
---|
3805 | mapData = generalData['Map'] |
---|
3806 | dmin = mapData['GridStep']*2. |
---|
3807 | SGData = generalData['SGData'] |
---|
3808 | SGMT = np.array([ops[0].T for ops in SGData['SGOps']]) |
---|
3809 | SGT = np.array([ops[1] for ops in SGData['SGOps']]) |
---|
3810 | cell = generalData['Cell'][1:8] |
---|
3811 | A = G2lat.cell2A(cell[:6]) |
---|
3812 | Hmax = np.asarray(G2lat.getHKLmax(dmin,SGData,A),dtype='i')+1 |
---|
3813 | adjHKLmax(SGData,Hmax) |
---|
3814 | Fhkl = np.zeros(shape=2*Hmax,dtype='c16') |
---|
3815 | # Fhkl[0,0,0] = generalData['F000X'] |
---|
3816 | time0 = time.time() |
---|
3817 | for iref,ref in enumerate(reflDict['RefList']): |
---|
3818 | if ref[4] > dmin: |
---|
3819 | Fosq,Fcsq,ph = ref[8:11] |
---|
3820 | Uniq = np.inner(ref[:3],SGMT) |
---|
3821 | Phi = np.inner(ref[:3],SGT) |
---|
3822 | for i,hkl in enumerate(Uniq): #uses uniq |
---|
3823 | hkl = np.asarray(hkl,dtype='i') |
---|
3824 | dp = 360.*Phi[i] #and phi |
---|
3825 | a = cosd(ph+dp) |
---|
3826 | b = sind(ph+dp) |
---|
3827 | phasep = complex(a,b) |
---|
3828 | phasem = complex(a,-b) |
---|
3829 | if 'Fobs' in mapData['MapType']: |
---|
3830 | F = np.where(Fosq>0.,np.sqrt(Fosq),0.) |
---|
3831 | h,k,l = hkl+Hmax |
---|
3832 | Fhkl[h,k,l] = F*phasep |
---|
3833 | h,k,l = -hkl+Hmax |
---|
3834 | Fhkl[h,k,l] = F*phasem |
---|
3835 | elif 'Fcalc' in mapData['MapType']: |
---|
3836 | F = np.sqrt(Fcsq) |
---|
3837 | h,k,l = hkl+Hmax |
---|
3838 | Fhkl[h,k,l] = F*phasep |
---|
3839 | h,k,l = -hkl+Hmax |
---|
3840 | Fhkl[h,k,l] = F*phasem |
---|
3841 | elif 'delt-F' in mapData['MapType']: |
---|
3842 | dF = np.where(Fosq>0.,np.sqrt(Fosq),0.)-np.sqrt(Fcsq) |
---|
3843 | h,k,l = hkl+Hmax |
---|
3844 | Fhkl[h,k,l] = dF*phasep |
---|
3845 | h,k,l = -hkl+Hmax |
---|
3846 | Fhkl[h,k,l] = dF*phasem |
---|
3847 | elif '2*Fo-Fc' in mapData['MapType']: |
---|
3848 | F = 2.*np.where(Fosq>0.,np.sqrt(Fosq),0.)-np.sqrt(Fcsq) |
---|
3849 | h,k,l = hkl+Hmax |
---|
3850 | Fhkl[h,k,l] = F*phasep |
---|
3851 | h,k,l = -hkl+Hmax |
---|
3852 | Fhkl[h,k,l] = F*phasem |
---|
3853 | elif 'Patterson' in mapData['MapType']: |
---|
3854 | h,k,l = hkl+Hmax |
---|
3855 | Fhkl[h,k,l] = complex(Fosq,0.) |
---|
3856 | h,k,l = -hkl+Hmax |
---|
3857 | Fhkl[h,k,l] = complex(Fosq,0.) |
---|
3858 | rho = fft.fftn(fft.fftshift(Fhkl))/cell[6] |
---|
3859 | G2fil.G2Print ('Fourier map time: %.4f no. elements: %d dimensions: %s'%(time.time()-time0,Fhkl.size,str(Fhkl.shape))) |
---|
3860 | mapData['Type'] = reflDict['Type'] |
---|
3861 | mapData['rho'] = np.real(rho) |
---|
3862 | mapData['rhoMax'] = max(np.max(mapData['rho']),-np.min(mapData['rho'])) |
---|
3863 | mapData['minmax'] = [np.max(mapData['rho']),np.min(mapData['rho'])] |
---|
3864 | |
---|
3865 | def Fourier4DMap(data,reflDict): |
---|
3866 | '''default doc string |
---|
3867 | |
---|
3868 | :param type name: description |
---|
3869 | |
---|
3870 | :returns: type name: description |
---|
3871 | |
---|
3872 | ''' |
---|
3873 | generalData = data['General'] |
---|
3874 | map4DData = generalData['4DmapData'] |
---|
3875 | mapData = generalData['Map'] |
---|
3876 | dmin = mapData['GridStep']*2. |
---|
3877 | SGData = generalData['SGData'] |
---|
3878 | SSGData = generalData['SSGData'] |
---|
3879 | SSGMT = np.array([ops[0].T for ops in SSGData['SSGOps']]) |
---|
3880 | SSGT = np.array([ops[1] for ops in SSGData['SSGOps']]) |
---|
3881 | cell = generalData['Cell'][1:8] |
---|
3882 | A = G2lat.cell2A(cell[:6]) |
---|
3883 | maxM = 4 |
---|
3884 | Hmax = G2lat.getHKLmax(dmin,SGData,A)+[maxM,] |
---|
3885 | adjHKLmax(SGData,Hmax) |
---|
3886 | Hmax = np.asarray(Hmax,dtype='i')+1 |
---|
3887 | Fhkl = np.zeros(shape=2*Hmax,dtype='c16') |
---|
3888 | time0 = time.time() |
---|
3889 | for iref,ref in enumerate(reflDict['RefList']): |
---|
3890 | if ref[5] > dmin: |
---|
3891 | Fosq,Fcsq,ph = ref[9:12] |
---|
3892 | Fosq = np.where(Fosq>0.,Fosq,0.) #can't use Fo^2 < 0 |
---|
3893 | Uniq = np.inner(ref[:4],SSGMT) |
---|
3894 | Phi = np.inner(ref[:4],SSGT) |
---|
3895 | for i,hkl in enumerate(Uniq): #uses uniq |
---|
3896 | hkl = np.asarray(hkl,dtype='i') |
---|
3897 | dp = 360.*Phi[i] #and phi |
---|
3898 | a = cosd(ph+dp) |
---|
3899 | b = sind(ph+dp) |
---|
3900 | phasep = complex(a,b) |
---|
3901 | phasem = complex(a,-b) |
---|
3902 | if 'Fobs' in mapData['MapType']: |
---|
3903 | F = np.sqrt(Fosq) |
---|
3904 | h,k,l,m = hkl+Hmax |
---|
3905 | Fhkl[h,k,l,m] = F*phasep |
---|
3906 | h,k,l,m = -hkl+Hmax |
---|
3907 | Fhkl[h,k,l,m] = F*phasem |
---|
3908 | elif 'Fcalc' in mapData['MapType']: |
---|
3909 | F = np.sqrt(Fcsq) |
---|
3910 | h,k,l,m = hkl+Hmax |
---|
3911 | Fhkl[h,k,l,m] = F*phasep |
---|
3912 | h,k,l,m = -hkl+Hmax |
---|
3913 | Fhkl[h,k,l,m] = F*phasem |
---|
3914 | elif 'delt-F' in mapData['MapType']: |
---|
3915 | dF = np.sqrt(Fosq)-np.sqrt(Fcsq) |
---|
3916 | h,k,l,m = hkl+Hmax |
---|
3917 | Fhkl[h,k,l,m] = dF*phasep |
---|
3918 | h,k,l,m = -hkl+Hmax |
---|
3919 | Fhkl[h,k,l,m] = dF*phasem |
---|
3920 | SSrho = fft.fftn(fft.fftshift(Fhkl))/cell[6] #4D map |
---|
3921 | rho = fft.fftn(fft.fftshift(Fhkl[:,:,:,maxM+1]))/cell[6] #3D map |
---|
3922 | map4DData['rho'] = np.real(SSrho) |
---|
3923 | map4DData['rhoMax'] = max(np.max(map4DData['rho']),-np.min(map4DData['rho'])) |
---|
3924 | map4DData['minmax'] = [np.max(map4DData['rho']),np.min(map4DData['rho'])] |
---|
3925 | map4DData['Type'] = reflDict['Type'] |
---|
3926 | mapData['Type'] = reflDict['Type'] |
---|
3927 | mapData['rho'] = np.real(rho) |
---|
3928 | mapData['rhoMax'] = max(np.max(mapData['rho']),-np.min(mapData['rho'])) |
---|
3929 | mapData['minmax'] = [np.max(mapData['rho']),np.min(mapData['rho'])] |
---|
3930 | G2fil.G2Print ('Fourier map time: %.4f no. elements: %d dimensions: %s'%(time.time()-time0,Fhkl.size,str(Fhkl.shape))) |
---|
3931 | |
---|
3932 | # map printing for testing purposes |
---|
3933 | def printRho(SGLaue,rho,rhoMax): |
---|
3934 | '''default doc string |
---|
3935 | |
---|
3936 | :param type name: description |
---|
3937 | |
---|
3938 | :returns: type name: description |
---|
3939 | |
---|
3940 | ''' |
---|
3941 | dim = len(rho.shape) |
---|
3942 | if dim == 2: |
---|
3943 | ix,jy = rho.shape |
---|
3944 | for j in range(jy): |
---|
3945 | line = '' |
---|
3946 | if SGLaue in ['3','3m1','31m','6/m','6/mmm']: |
---|
3947 | line += (jy-j)*' ' |
---|
3948 | for i in range(ix): |
---|
3949 | r = int(100*rho[i,j]/rhoMax) |
---|
3950 | line += '%4d'%(r) |
---|
3951 | print (line+'\n') |
---|
3952 | else: |
---|
3953 | ix,jy,kz = rho.shape |
---|
3954 | for k in range(kz): |
---|
3955 | print ('k = %d'%k) |
---|
3956 | for j in range(jy): |
---|
3957 | line = '' |
---|
3958 | if SGLaue in ['3','3m1','31m','6/m','6/mmm']: |
---|
3959 | line += (jy-j)*' ' |
---|
3960 | for i in range(ix): |
---|
3961 | r = int(100*rho[i,j,k]/rhoMax) |
---|
3962 | line += '%4d'%(r) |
---|
3963 | print (line+'\n') |
---|
3964 | ## keep this |
---|
3965 | |
---|
3966 | def findOffset(SGData,A,Fhkl): |
---|
3967 | '''default doc string |
---|
3968 | |
---|
3969 | :param type name: description |
---|
3970 | |
---|
3971 | :returns: type name: description |
---|
3972 | |
---|
3973 | ''' |
---|
3974 | if SGData['SpGrp'] == 'P 1': |
---|
3975 | return [0,0,0] |
---|
3976 | hklShape = Fhkl.shape |
---|
3977 | hklHalf = np.array(hklShape)//2 |
---|
3978 | sortHKL = np.argsort(Fhkl.flatten()) |
---|
3979 | Fdict = {} |
---|
3980 | for hkl in sortHKL: |
---|
3981 | HKL = np.unravel_index(hkl,hklShape) |
---|
3982 | F = Fhkl[HKL[0]][HKL[1]][HKL[2]] |
---|
3983 | if F == 0.: |
---|
3984 | break |
---|
3985 | Fdict['%.6f'%(np.absolute(F))] = hkl |
---|
3986 | Flist = np.flipud(np.sort(list(Fdict.keys()))) |
---|
3987 | F = str(1.e6) |
---|
3988 | i = 0 |
---|
3989 | DH = [] |
---|
3990 | Dphi = [] |
---|
3991 | Hmax = 2*np.asarray(G2lat.getHKLmax(3.5,SGData,A),dtype='i') |
---|
3992 | for F in Flist: |
---|
3993 | hkl = np.unravel_index(Fdict[F],hklShape) |
---|
3994 | if np.any(np.abs(hkl-hklHalf)-Hmax > 0): |
---|
3995 | continue |
---|
3996 | iabsnt,mulp,Uniq,Phi = G2spc.GenHKLf(list(hkl-hklHalf),SGData) |
---|
3997 | Uniq = np.array(Uniq,dtype='i') |
---|
3998 | Phi = np.array(Phi) |
---|
3999 | Uniq = np.concatenate((Uniq,-Uniq))+hklHalf # put in Friedel pairs & make as index to Farray |
---|
4000 | Phi = np.concatenate((Phi,-Phi)) # and their phase shifts |
---|
4001 | Fh0 = Fhkl[hkl[0],hkl[1],hkl[2]] |
---|
4002 | ang0 = np.angle(Fh0,deg=True)/360. |
---|
4003 | for H,phi in list(zip(Uniq,Phi))[1:]: |
---|
4004 | ang = (np.angle(Fhkl[int(H[0]),int(H[1]),int(H[2])],deg=True)/360.-phi) |
---|
4005 | dH = H-hkl |
---|
4006 | dang = ang-ang0 |
---|
4007 | DH.append(dH) |
---|
4008 | Dphi.append((dang+.5) % 1.0) |
---|
4009 | if i > 20 or len(DH) > 30: |
---|
4010 | break |
---|
4011 | i += 1 |
---|
4012 | DH = np.array(DH) |
---|
4013 | G2fil.G2Print (' map offset no.of terms: %d from %d reflections'%(len(DH),len(Flist))) |
---|
4014 | Dphi = np.array(Dphi) |
---|
4015 | steps = np.array(hklShape) |
---|
4016 | X,Y,Z = np.meshgrid(np.linspace(0,1,steps[0]),np.linspace(0,1,steps[1]),np.linspace(0,1,steps[2])) |
---|
4017 | XYZ = np.array(list(zip(X.flatten(),Y.flatten(),Z.flatten()))) |
---|
4018 | Dang = (np.dot(XYZ,DH.T)+.5)%1.-Dphi |
---|
4019 | Mmap = np.reshape(np.sum((Dang)**2,axis=1),newshape=steps)/len(DH) |
---|
4020 | hist,bins = np.histogram(Mmap,bins=1000) |
---|
4021 | chisq = np.min(Mmap) |
---|
4022 | DX = -np.array(np.unravel_index(np.argmin(Mmap),Mmap.shape)) |
---|
4023 | ptext = ' map offset chi**2: %.3f, map offset: %d %d %d'%(chisq,DX[0],DX[1],DX[2]) |
---|
4024 | G2fil.G2Print(ptext) |
---|
4025 | return DX,ptext |
---|
4026 | |
---|
4027 | def ChargeFlip(data,reflDict,pgbar): |
---|
4028 | '''default doc string |
---|
4029 | |
---|
4030 | :param type name: description |
---|
4031 | |
---|
4032 | :returns: type name: description |
---|
4033 | |
---|
4034 | ''' |
---|
4035 | generalData = data['General'] |
---|
4036 | mapData = generalData['Map'] |
---|
4037 | flipData = generalData['Flip'] |
---|
4038 | FFtable = {} |
---|
4039 | if 'None' not in flipData['Norm element']: |
---|
4040 | normElem = flipData['Norm element'].upper() |
---|
4041 | FFs = G2el.GetFormFactorCoeff(normElem.split('+')[0].split('-')[0]) |
---|
4042 | for ff in FFs: |
---|
4043 | if ff['Symbol'] == normElem: |
---|
4044 | FFtable.update(ff) |
---|
4045 | dmin = flipData['GridStep']*2. |
---|
4046 | SGData = generalData['SGData'] |
---|
4047 | SGMT = np.array([ops[0].T for ops in SGData['SGOps']]) |
---|
4048 | SGT = np.array([ops[1] for ops in SGData['SGOps']]) |
---|
4049 | cell = generalData['Cell'][1:8] |
---|
4050 | A = G2lat.cell2A(cell[:6]) |
---|
4051 | Vol = cell[6] |
---|
4052 | im = 0 |
---|
4053 | if generalData['Modulated'] == True: |
---|
4054 | im = 1 |
---|
4055 | Hmax = np.asarray(G2lat.getHKLmax(dmin,SGData,A),dtype='i')+1 |
---|
4056 | adjHKLmax(SGData,Hmax) |
---|
4057 | Ehkl = np.zeros(shape=2*Hmax,dtype='c16') #2X64bits per complex no. |
---|
4058 | time0 = time.time() |
---|
4059 | for iref,ref in enumerate(reflDict['RefList']): |
---|
4060 | dsp = ref[4+im] |
---|
4061 | if im and ref[3]: #skip super lattice reflections - result is 3D projection |
---|
4062 | continue |
---|
4063 | if dsp > dmin: |
---|
4064 | ff = 0.1*Vol #est. no. atoms for ~10A**3/atom |
---|
4065 | if FFtable: |
---|
4066 | SQ = 0.25/dsp**2 |
---|
4067 | ff *= G2el.ScatFac(FFtable,SQ)[0] |
---|
4068 | if ref[8+im] > 0.: #use only +ve Fobs**2 |
---|
4069 | E = np.sqrt(ref[8+im])/ff |
---|
4070 | else: |
---|
4071 | E = 0. |
---|
4072 | ph = ref[10] |
---|
4073 | ph = rn.uniform(0.,360.) |
---|
4074 | Uniq = np.inner(ref[:3],SGMT) |
---|
4075 | Phi = np.inner(ref[:3],SGT) |
---|
4076 | for i,hkl in enumerate(Uniq): #uses uniq |
---|
4077 | hkl = np.asarray(hkl,dtype='i') |
---|
4078 | dp = 360.*Phi[i] #and phi |
---|
4079 | a = cosd(ph+dp) |
---|
4080 | b = sind(ph+dp) |
---|
4081 | phasep = complex(a,b) |
---|
4082 | phasem = complex(a,-b) |
---|
4083 | h,k,l = hkl+Hmax |
---|
4084 | Ehkl[h,k,l] = E*phasep |
---|
4085 | h,k,l = -hkl+Hmax |
---|
4086 | Ehkl[h,k,l] = E*phasem |
---|
4087 | # Ehkl[Hmax] = 0.00001 #this to preserve F[0,0,0] |
---|
4088 | testHKL = np.array(flipData['testHKL'])+Hmax |
---|
4089 | CEhkl = copy.copy(Ehkl) |
---|
4090 | MEhkl = ma.array(Ehkl,mask=(Ehkl==0.0)) |
---|
4091 | Emask = ma.getmask(MEhkl) |
---|
4092 | sumE = np.sum(ma.array(np.absolute(CEhkl),mask=Emask)) |
---|
4093 | Ncyc = 0 |
---|
4094 | old = np.seterr(all='raise') |
---|
4095 | twophases = [] |
---|
4096 | while True: |
---|
4097 | CErho = np.real(fft.fftn(fft.fftshift(CEhkl)))*(1.+0j) |
---|
4098 | CEsig = np.std(CErho) |
---|
4099 | CFrho = np.where(np.real(CErho) >= flipData['k-factor']*CEsig,CErho,-CErho) |
---|
4100 | CFrho = np.where(np.real(CErho) <= flipData['k-Max']*CEsig,CFrho,-CFrho) #solves U atom problem! |
---|
4101 | CFhkl = fft.ifftshift(fft.ifftn(CFrho)) |
---|
4102 | CFhkl = np.where(CFhkl,CFhkl,1.0) #avoid divide by zero |
---|
4103 | phase = CFhkl/np.absolute(CFhkl) |
---|
4104 | twophases.append([np.angle(phase[h,k,l]) for h,k,l in testHKL]) |
---|
4105 | CEhkl = np.absolute(Ehkl)*phase |
---|
4106 | Ncyc += 1 |
---|
4107 | sumCF = np.sum(ma.array(np.absolute(CFhkl),mask=Emask)) |
---|
4108 | DEhkl = np.absolute(np.absolute(Ehkl)/sumE-np.absolute(CFhkl)/sumCF) |
---|
4109 | Rcf = min(100.,np.sum(ma.array(DEhkl,mask=Emask)*100.)) |
---|
4110 | if Rcf < 5.: |
---|
4111 | break |
---|
4112 | GoOn = pgbar.Update(int(Rcf),newmsg='%s%8.3f%s\n%s %d'%('Residual Rcf =',Rcf,'%','No.cycles = ',Ncyc))[0] |
---|
4113 | if not GoOn or Ncyc > 10000: |
---|
4114 | break |
---|
4115 | np.seterr(**old) |
---|
4116 | G2fil.G2Print (' Charge flip time: %.4f'%(time.time()-time0),'no. elements: %d'%(Ehkl.size)) |
---|
4117 | CErho = np.real(fft.fftn(fft.fftshift(CEhkl)))/10. #? to get on same scale as e-map |
---|
4118 | ctext = ' No.cycles = %d Residual Rcf =%8.3f%s Map size: %s'%(Ncyc,Rcf,'%',str(CErho.shape)) |
---|
4119 | G2fil.G2Print (ctext) |
---|
4120 | roll,ptext = findOffset(SGData,A,CEhkl) #CEhkl needs to be just the observed set, not the full set! |
---|
4121 | |
---|
4122 | mapData['Rcf'] = Rcf |
---|
4123 | mapData['rho'] = np.roll(np.roll(np.roll(CErho,roll[0],axis=0),roll[1],axis=1),roll[2],axis=2) |
---|
4124 | mapData['rhoMax'] = max(np.max(mapData['rho']),-np.min(mapData['rho'])) |
---|
4125 | mapData['minmax'] = [np.max(mapData['rho']),np.min(mapData['rho'])] |
---|
4126 | mapData['Type'] = reflDict['Type'] |
---|
4127 | return mapData,twophases,ptext,ctext |
---|
4128 | |
---|
4129 | def findSSOffset(SGData,SSGData,A,Fhklm): |
---|
4130 | '''default doc string |
---|
4131 | |
---|
4132 | :param type name: description |
---|
4133 | |
---|
4134 | :returns: type name: description |
---|
4135 | |
---|
4136 | ''' |
---|
4137 | if SGData['SpGrp'] == 'P 1': |
---|
4138 | return [0,0,0,0] |
---|
4139 | hklmShape = Fhklm.shape |
---|
4140 | hklmHalf = np.array(hklmShape)/2 |
---|
4141 | sortHKLM = np.argsort(Fhklm.flatten()) |
---|
4142 | Fdict = {} |
---|
4143 | for hklm in sortHKLM: |
---|
4144 | HKLM = np.unravel_index(hklm,hklmShape) |
---|
4145 | F = Fhklm[HKLM[0]][HKLM[1]][HKLM[2]][HKLM[3]] |
---|
4146 | if F == 0.: |
---|
4147 | break |
---|
4148 | Fdict['%.6f'%(np.absolute(F))] = hklm |
---|
4149 | Flist = np.flipud(np.sort(list(Fdict.keys()))) |
---|
4150 | F = str(1.e6) |
---|
4151 | i = 0 |
---|
4152 | DH = [] |
---|
4153 | Dphi = [] |
---|
4154 | SSGMT = np.array([ops[0].T for ops in SSGData['SSGOps']]) |
---|
4155 | SSGT = np.array([ops[1] for ops in SSGData['SSGOps']]) |
---|
4156 | Hmax = 2*np.asarray(G2lat.getHKLmax(3.5,SGData,A),dtype='i') |
---|
4157 | for F in Flist: |
---|
4158 | hklm = np.unravel_index(Fdict[F],hklmShape) |
---|
4159 | if np.any(np.abs(hklm-hklmHalf)[:3]-Hmax > 0): |
---|
4160 | continue |
---|
4161 | Uniq = np.inner(hklm-hklmHalf,SSGMT) |
---|
4162 | Phi = np.inner(hklm-hklmHalf,SSGT) |
---|
4163 | Uniq = np.concatenate((Uniq,-Uniq))+hklmHalf # put in Friedel pairs & make as index to Farray |
---|
4164 | Phi = np.concatenate((Phi,-Phi)) # and their phase shifts |
---|
4165 | Fh0 = Fhklm[hklm[0],hklm[1],hklm[2],hklm[3]] |
---|
4166 | ang0 = np.angle(Fh0,deg=True)/360. |
---|
4167 | for H,phi in list(zip(Uniq,Phi))[1:]: |
---|
4168 | H = np.array(H,dtype=int) |
---|
4169 | ang = (np.angle(Fhklm[H[0],H[1],H[2],H[3]],deg=True)/360.-phi) |
---|
4170 | dH = H-hklm |
---|
4171 | dang = ang-ang0 |
---|
4172 | DH.append(dH) |
---|
4173 | Dphi.append((dang+.5) % 1.0) |
---|
4174 | if i > 20 or len(DH) > 30: |
---|
4175 | break |
---|
4176 | i += 1 |
---|
4177 | DH = np.array(DH) |
---|
4178 | G2fil.G2Print (' map offset no.of terms: %d from %d reflections'%(len(DH),len(Flist))) |
---|
4179 | Dphi = np.array(Dphi) |
---|
4180 | steps = np.array(hklmShape) |
---|
4181 | X,Y,Z,T = np.mgrid[0:1:1./steps[0],0:1:1./steps[1],0:1:1./steps[2],0:1:1./steps[3]] |
---|
4182 | XYZT = np.array(list(zip(X.flatten(),Y.flatten(),Z.flatten(),T.flatten()))) |
---|
4183 | Dang = (np.dot(XYZT,DH.T)+.5)%1.-Dphi |
---|
4184 | Mmap = np.reshape(np.sum((Dang)**2,axis=1),newshape=steps)/len(DH) |
---|
4185 | hist,bins = np.histogram(Mmap,bins=1000) |
---|
4186 | chisq = np.min(Mmap) |
---|
4187 | DX = -np.array(np.unravel_index(np.argmin(Mmap),Mmap.shape)) |
---|
4188 | ptext = ' map offset chi**2: %.3f, map offset: %d %d %d %d'%(chisq,DX[0],DX[1],DX[2],DX[3]) |
---|
4189 | G2fil.G2Print(ptext) |
---|
4190 | return DX,ptext |
---|
4191 | |
---|
4192 | def SSChargeFlip(data,reflDict,pgbar): |
---|
4193 | '''default doc string |
---|
4194 | |
---|
4195 | :param type name: description |
---|
4196 | |
---|
4197 | :returns: type name: description |
---|
4198 | |
---|
4199 | ''' |
---|
4200 | generalData = data['General'] |
---|
4201 | mapData = generalData['Map'] |
---|
4202 | map4DData = {} |
---|
4203 | flipData = generalData['Flip'] |
---|
4204 | FFtable = {} |
---|
4205 | if 'None' not in flipData['Norm element']: |
---|
4206 | normElem = flipData['Norm element'].upper() |
---|
4207 | FFs = G2el.GetFormFactorCoeff(normElem.split('+')[0].split('-')[0]) |
---|
4208 | for ff in FFs: |
---|
4209 | if ff['Symbol'] == normElem: |
---|
4210 | FFtable.update(ff) |
---|
4211 | dmin = flipData['GridStep']*2. |
---|
4212 | SGData = generalData['SGData'] |
---|
4213 | SSGData = generalData['SSGData'] |
---|
4214 | SSGMT = np.array([ops[0].T for ops in SSGData['SSGOps']]) |
---|
4215 | SSGT = np.array([ops[1] for ops in SSGData['SSGOps']]) |
---|
4216 | cell = generalData['Cell'][1:8] |
---|
4217 | A = G2lat.cell2A(cell[:6]) |
---|
4218 | Vol = cell[6] |
---|
4219 | maxM = 4 |
---|
4220 | Hmax = np.asarray(G2lat.getHKLmax(dmin,SGData,A)+[maxM,],dtype='i')+1 |
---|
4221 | adjHKLmax(SGData,Hmax) |
---|
4222 | Ehkl = np.zeros(shape=2*Hmax,dtype='c16') #2X64bits per complex no. |
---|
4223 | time0 = time.time() |
---|
4224 | for iref,ref in enumerate(reflDict['RefList']): |
---|
4225 | dsp = ref[5] |
---|
4226 | if dsp > dmin: |
---|
4227 | ff = 0.1*Vol #est. no. atoms for ~10A**3/atom |
---|
4228 | if FFtable: |
---|
4229 | SQ = 0.25/dsp**2 |
---|
4230 | ff *= G2el.ScatFac(FFtable,SQ)[0] |
---|
4231 | if ref[9] > 0.: #use only +ve Fobs**2 |
---|
4232 | E = np.sqrt(ref[9])/ff |
---|
4233 | else: |
---|
4234 | E = 0. |
---|
4235 | ph = ref[11] |
---|
4236 | ph = rn.uniform(0.,360.) |
---|
4237 | Uniq = np.inner(ref[:4],SSGMT) |
---|
4238 | Phi = np.inner(ref[:4],SSGT) |
---|
4239 | for i,hklm in enumerate(Uniq): #uses uniq |
---|
4240 | hklm = np.asarray(hklm,dtype='i') |
---|
4241 | dp = 360.*Phi[i] #and phi |
---|
4242 | a = cosd(ph+dp) |
---|
4243 | b = sind(ph+dp) |
---|
4244 | phasep = complex(a,b) |
---|
4245 | phasem = complex(a,-b) |
---|
4246 | h,k,l,m = hklm+Hmax |
---|
4247 | Ehkl[h,k,l,m] = E*phasep |
---|
4248 | h,k,l,m = -hklm+Hmax #Friedel pair refl. |
---|
4249 | Ehkl[h,k,l,m] = E*phasem |
---|
4250 | # Ehkl[Hmax] = 0.00001 #this to preserve F[0,0,0] |
---|
4251 | CEhkl = copy.copy(Ehkl) |
---|
4252 | MEhkl = ma.array(Ehkl,mask=(Ehkl==0.0)) |
---|
4253 | Emask = ma.getmask(MEhkl) |
---|
4254 | sumE = np.sum(ma.array(np.absolute(CEhkl),mask=Emask)) |
---|
4255 | Ncyc = 0 |
---|
4256 | old = np.seterr(all='raise') |
---|
4257 | while True: |
---|
4258 | CErho = np.real(fft.fftn(fft.fftshift(CEhkl)))*(1.+0j) |
---|
4259 | CEsig = np.std(CErho) |
---|
4260 | CFrho = np.where(np.real(CErho) >= flipData['k-factor']*CEsig,CErho,-CErho) |
---|
4261 | CFrho = np.where(np.real(CErho) <= flipData['k-Max']*CEsig,CFrho,-CFrho) #solves U atom problem! |
---|
4262 | CFhkl = fft.ifftshift(fft.ifftn(CFrho)) |
---|
4263 | CFhkl = np.where(CFhkl,CFhkl,1.0) #avoid divide by zero |
---|
4264 | phase = CFhkl/np.absolute(CFhkl) |
---|
4265 | CEhkl = np.absolute(Ehkl)*phase |
---|
4266 | Ncyc += 1 |
---|
4267 | sumCF = np.sum(ma.array(np.absolute(CFhkl),mask=Emask)) |
---|
4268 | DEhkl = np.absolute(np.absolute(Ehkl)/sumE-np.absolute(CFhkl)/sumCF) |
---|
4269 | Rcf = min(100.,np.sum(ma.array(DEhkl,mask=Emask)*100.)) |
---|
4270 | if Rcf < 5.: |
---|
4271 | break |
---|
4272 | GoOn = pgbar.Update(int(Rcf),newmsg='%s%8.3f%s\n%s %d'%('Residual Rcf =',Rcf,'%','No.cycles = ',Ncyc))[0] |
---|
4273 | if not GoOn or Ncyc > 10000: |
---|
4274 | break |
---|
4275 | np.seterr(**old) |
---|
4276 | G2fil.G2Print (' Charge flip time: %.4f no. elements: %d'%(time.time()-time0,Ehkl.size)) |
---|
4277 | CErho = np.real(fft.fftn(fft.fftshift(CEhkl[:,:,:,maxM+1])))/10. #? to get on same scale as e-map |
---|
4278 | SSrho = np.real(fft.fftn(fft.fftshift(CEhkl)))/10. #? ditto |
---|
4279 | ctext = ' No.cycles = %d Residual Rcf =%8.3f%s Map size: %s'%(Ncyc,Rcf,'%',str(CErho.shape)) |
---|
4280 | G2fil.G2Print (ctext) |
---|
4281 | roll,ptext = findSSOffset(SGData,SSGData,A,CEhkl) #CEhkl needs to be just the observed set, not the full set! |
---|
4282 | |
---|
4283 | mapData['Rcf'] = Rcf |
---|
4284 | mapData['rho'] = np.roll(np.roll(np.roll(CErho,roll[0],axis=0),roll[1],axis=1),roll[2],axis=2) |
---|
4285 | mapData['rhoMax'] = max(np.max(mapData['rho']),-np.min(mapData['rho'])) |
---|
4286 | mapData['minmax'] = [np.max(mapData['rho']),np.min(mapData['rho'])] |
---|
4287 | mapData['Type'] = reflDict['Type'] |
---|
4288 | |
---|
4289 | map4DData['Rcf'] = Rcf |
---|
4290 | map4DData['rho'] = np.real(np.roll(np.roll(np.roll(np.roll(SSrho,roll[0],axis=0),roll[1],axis=1),roll[2],axis=2),roll[3],axis=3)) |
---|
4291 | map4DData['rhoMax'] = max(np.max(map4DData['rho']),-np.min(map4DData['rho'])) |
---|
4292 | map4DData['minmax'] = [np.max(map4DData['rho']),np.min(map4DData['rho'])] |
---|
4293 | map4DData['Type'] = reflDict['Type'] |
---|
4294 | return mapData,map4DData,ptext,ctext |
---|
4295 | |
---|
4296 | def getRho(xyz,mapData): |
---|
4297 | ''' get scattering density at a point by 8-point interpolation |
---|
4298 | param xyz: coordinate to be probed |
---|
4299 | param: mapData: dict of map data |
---|
4300 | |
---|
4301 | :returns: density at xyz |
---|
4302 | ''' |
---|
4303 | rollMap = lambda rho,roll: np.roll(np.roll(np.roll(rho,roll[0],axis=0),roll[1],axis=1),roll[2],axis=2) |
---|
4304 | if not len(mapData): |
---|
4305 | return 0.0 |
---|
4306 | rho = copy.copy(mapData['rho']) #don't mess up original |
---|
4307 | if not len(rho): |
---|
4308 | return 0.0 |
---|
4309 | mapShape = np.array(rho.shape) |
---|
4310 | mapStep = 1./mapShape |
---|
4311 | X = np.array(xyz)%1. #get into unit cell |
---|
4312 | I = np.array(X*mapShape,dtype='int') |
---|
4313 | D = X-I*mapStep #position inside map cell |
---|
4314 | D12 = D[0]*D[1] |
---|
4315 | D13 = D[0]*D[2] |
---|
4316 | D23 = D[1]*D[2] |
---|
4317 | D123 = np.prod(D) |
---|
4318 | Rho = rollMap(rho,-I) #shifts map so point is in corner |
---|
4319 | R = Rho[0,0,0]*(1.-np.sum(D))+Rho[1,0,0]*D[0]+Rho[0,1,0]*D[1]+Rho[0,0,1]*D[2]+ \ |
---|
4320 | Rho[1,1,1]*D123+Rho[0,1,1]*(D23-D123)+Rho[1,0,1]*(D13-D123)+Rho[1,1,0]*(D12-D123)+ \ |
---|
4321 | Rho[0,0,0]*(D12+D13+D23-D123)-Rho[0,0,1]*(D13+D23-D123)- \ |
---|
4322 | Rho[0,1,0]*(D23+D12-D123)-Rho[1,0,0]*(D13+D12-D123) |
---|
4323 | return R |
---|
4324 | |
---|
4325 | def getRhos(XYZ,rho): |
---|
4326 | ''' get scattering density at an array of point by 8-point interpolation |
---|
4327 | this is faster than gerRho which is only used for single points. However, getRhos is |
---|
4328 | replaced by scipy.ndimage.interpolation.map_coordinates which does a better job & is just as fast. |
---|
4329 | Thus, getRhos is unused in GSAS-II at this time. |
---|
4330 | param xyz: array coordinates to be probed Nx3 |
---|
4331 | param: rho: array copy of map (NB: don't use original!) |
---|
4332 | |
---|
4333 | :returns: density at xyz |
---|
4334 | ''' |
---|
4335 | def getBoxes(rho,I): |
---|
4336 | Rhos = np.zeros((2,2,2)) |
---|
4337 | Mx,My,Mz = rho.shape |
---|
4338 | Ix,Iy,Iz = I |
---|
4339 | Rhos = np.array([[[rho[Ix%Mx,Iy%My,Iz%Mz],rho[Ix%Mx,Iy%My,(Iz+1)%Mz]], |
---|
4340 | [rho[Ix%Mx,(Iy+1)%My,Iz%Mz],rho[Ix%Mx,(Iy+1)%My,(Iz+1)%Mz]]], |
---|
4341 | [[rho[(Ix+1)%Mx,Iy%My,Iz%Mz],rho[(Ix+1)%Mx,Iy%My,(Iz+1)%Mz]], |
---|
4342 | [rho[(Ix+1)%Mx,(Iy+1)%My,Iz%Mz],rho[(Ix+1)%Mx,(Iy+1)%My,(Iz+ |
---|