source: trunk/GSASIImath.py

Last change on this file was 5639, checked in by vondreele, 2 months ago

fully implement moment restraint so that some (2+) are same as their average.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Date Author Revision URL Id
File size: 231.4 KB
Line 
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'''
11Routines defined in :mod:`GSASIImath` follow.
12'''
13
14from __future__ import division, print_function
15import random as rn
16import numpy as np
17import numpy.linalg as nl
18import numpy.ma as ma
19import time
20import math
21import copy
22import GSASIIpath
23GSASIIpath.SetVersionNumber("$Revision: 5639 $")
24import GSASIIElem as G2el
25import GSASIIlattice as G2lat
26import GSASIIspc as G2spc
27import GSASIIpwd as G2pwd
28import GSASIIobj as G2obj
29import GSASIIfiles as G2fil
30import numpy.fft as fft
31import scipy.optimize as so
32try:
33    import pypowder as pwd
34except ImportError:
35    print ('pypowder is not available - profile calcs. not allowed')
36
37sind = lambda x: np.sin(x*np.pi/180.)
38cosd = lambda x: np.cos(x*np.pi/180.)
39tand = lambda x: np.tan(x*np.pi/180.)
40asind = lambda x: 180.*np.arcsin(x)/np.pi
41acosd = lambda x: 180.*np.arccos(x)/np.pi
42atand = lambda x: 180.*np.arctan(x)/np.pi
43atan2d = lambda y,x: 180.*np.arctan2(y,x)/np.pi
44try:  # 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
49except TypeError:
50    pass
51nxs = np.newaxis
52   
53################################################################################
54#### Hessian least-squares Levenberg-Marquardt routine
55################################################################################
56class G2NormException(Exception): pass
57   
58def 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
105def 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       
125def 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
144def 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
158def 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           
442def 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           
559def 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
589def 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
602def 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       
684def 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
705def 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
716def 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
723def 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   
778def 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   
793def 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   
815def 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   
832def 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
853def 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   
868def 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   
934def 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
966def 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   
994def 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   
1022def 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   
1086def 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   
1095def 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               
1205def 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       
1231def 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
1264def 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
1275def 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
1288def 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               
1308def 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
1333def 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
1379def GetSpnRBData(SpnRB,atId):
1380    for SpnData in SpnRB:
1381        if atId in SpnData['Ids']:
1382            return SpnData
1383
1384def 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   
1425def 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   
1455def 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   
1502def 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       
1516def 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
1529def 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   
1542def 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       
1559def 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       
1580def 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       
1596def 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   
1610def 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   
1624def 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   
1639def 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   
1670def 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   
1711def 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       
1721def 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
1733def 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
1802def 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       
1838def 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       
1875def 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   
1904def 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   
1932def 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   
1989def 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       
2043def 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   
2048def 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
2065def 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   
2077def fracCrenel(tau,Toff,Twid):
2078    Tau = (tau-Toff)%1.
2079    A = np.where(Tau<Twid,1.,0.)
2080    return A
2081   
2082def 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
2091def 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
2187def patchIsoDisp(ISO):
2188    '''patch: look for older ISODISTORT imports (<Nov 2021)'''
2189    print('''
2190======================================================================
2191Warning: The ISODISTORT modes were read before the importer
2192was corrected to save displacement offsets. Will attempt to correct
2193from ParentStructure (correct only if displacivemode values are all
2194zero 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
2205def 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
2243def 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
2287def 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
2343def 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   
2374def 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   
2394def 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
2419def 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   
2442def 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   
2456def 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
2486def 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
2501def 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   
2528def 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   
2538def 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
2558def 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   
2580def 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   
2602def 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   
2616def 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   
2639def 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
2660def 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
2682def 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   
2695def 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
2728def 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
2754def 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
2770def 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       
2780def 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   
2812def 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   
2855def 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
2924def 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
2973def 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
3028def 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       
3091def 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
3196def 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
3212def 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
3239def 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       
3271def 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
3352def 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
3535def 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
3711def 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
3728def 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   
3796def 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   
3865def 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
3933def 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               
3966def 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   
4027def 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   
4129def 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   
4192def 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   
4296def 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       
4325def 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+