source: trunk/GSASIImath.py @ 4598

Last change on this file since 4598 was 4598, checked in by vondreele, 5 years ago

cosmetic changes

  • Property svn:eol-style set to native
  • Property svn:keywords set to Date Author Revision URL Id
File size: 196.6 KB
Line 
1# -*- coding: utf-8 -*-
2#GSASIImath - major mathematics routines
3########### SVN repository information ###################
4# $Date: 2020-10-16 17:00:27 +0000 (Fri, 16 Oct 2020) $
5# $Author: vondreele $
6# $Revision: 4598 $
7# $URL: trunk/GSASIImath.py $
8# $Id: GSASIImath.py 4598 2020-10-16 17:00:27Z vondreele $
9########### SVN repository information ###################
10'''
11*GSASIImath: computation module*
12================================
13
14Routines for least-squares minimization and other stuff
15
16'''
17from __future__ import division, print_function
18import random as rn
19import numpy as np
20import numpy.linalg as nl
21import numpy.ma as ma
22import time
23import math
24import copy
25import GSASIIpath
26GSASIIpath.SetVersionNumber("$Revision: 4598 $")
27import GSASIIElem as G2el
28import GSASIIlattice as G2lat
29import GSASIIspc as G2spc
30import GSASIIpwd as G2pwd
31import GSASIIfiles as G2fil
32import numpy.fft as fft
33import scipy.optimize as so
34try:
35    import pypowder as pwd
36except ImportError:
37    print ('pypowder is not available - profile calcs. not allowed')
38
39sind = lambda x: np.sin(x*np.pi/180.)
40cosd = lambda x: np.cos(x*np.pi/180.)
41tand = lambda x: np.tan(x*np.pi/180.)
42asind = lambda x: 180.*np.arcsin(x)/np.pi
43acosd = lambda x: 180.*np.arccos(x)/np.pi
44atand = lambda x: 180.*np.arctan(x)/np.pi
45atan2d = lambda y,x: 180.*np.arctan2(y,x)/np.pi
46try:  # fails on doc build
47    twopi = 2.0*np.pi
48    twopisq = 2.0*np.pi**2
49    _double_min = np.finfo(float).min
50    _double_max = np.finfo(float).max
51except TypeError:
52    pass
53nxs = np.newaxis
54   
55################################################################################
56##### Hessian least-squares Levenberg-Marquardt routine
57################################################################################
58
59def pinv(a, rcond=1e-15 ):
60    '''
61    Compute the (Moore-Penrose) pseudo-inverse of a matrix.
62    Modified from numpy.linalg.pinv; assumes a is Hessian & returns no. zeros found
63    Calculate the generalized inverse of a matrix using its
64    singular-value decomposition (SVD) and including all
65    *large* singular values.
66
67    :param array a: (M, M) array_like - here assumed to be LS Hessian
68      Matrix to be pseudo-inverted.
69    :param float rcond: Cutoff for small singular values.
70      Singular values smaller (in modulus) than
71      `rcond` * largest_singular_value (again, in modulus)
72      are set to zero.
73
74    :returns: B : (M, M) ndarray
75      The pseudo-inverse of `a`
76
77    Raises: LinAlgError
78      If the SVD computation does not converge.
79
80    Notes:
81      The pseudo-inverse of a matrix A, denoted :math:`A^+`, is
82      defined as: "the matrix that 'solves' [the least-squares problem]
83      :math:`Ax = b`," i.e., if :math:`\\bar{x}` is said solution, then
84      :math:`A^+` is that matrix such that :math:`\\bar{x} = A^+b`.
85
86    It can be shown that if :math:`Q_1 \\Sigma Q_2^T = A` is the singular
87    value decomposition of A, then
88    :math:`A^+ = Q_2 \\Sigma^+ Q_1^T`, where :math:`Q_{1,2}` are
89    orthogonal matrices, :math:`\\Sigma` is a diagonal matrix consisting
90    of A's so-called singular values, (followed, typically, by
91    zeros), and then :math:`\\Sigma^+` is simply the diagonal matrix
92    consisting of the reciprocals of A's singular values
93    (again, followed by zeros). [1]
94
95    References:
96    .. [1] G. Strang, *Linear Algebra and Its Applications*, 2nd Ed., Orlando, FL, Academic Press, Inc., 1980, pp. 139-142.
97
98    '''
99    u, s, vt = nl.svd(a)
100    cutoff = rcond*np.maximum.reduce(s)
101    s = np.where(s>cutoff,1./s,0.)
102    nzero = s.shape[0]-np.count_nonzero(s)
103#    res = np.dot(np.transpose(vt), np.multiply(s[:, np.newaxis], np.transpose(u)))
104    res = np.dot(vt.T,s[:,nxs]*u.T)
105    return res,nzero
106
107def HessianLSQ(func,x0,Hess,args=(),ftol=1.49012e-8,xtol=1.e-6, maxcyc=0,lamda=-3,Print=False,refPlotUpdate=None):
108    '''
109    Minimize the sum of squares of a function (:math:`f`) evaluated on a series of
110    values (y): :math:`\\sum_{y=0}^{N_{obs}} f(y,{args})`   
111    where :math:`x = arg min(\\sum_{y=0}^{N_{obs}} (func(y)^2,axis=0))`
112
113    :param function func: callable method or function
114        should take at least one (possibly length N vector) argument and
115        returns M floating point numbers.
116    :param np.ndarray x0: The starting estimate for the minimization of length N
117    :param function Hess: callable method or function
118        A required function or method to compute the weighted vector and Hessian for func.
119        It must be a symmetric NxN array
120    :param tuple args: Any extra arguments to func are placed in this tuple.
121    :param float ftol: Relative error desired in the sum of squares.
122    :param float xtol: Relative tolerance of zeros in the SVD solution in nl.pinv.
123    :param int maxcyc: The maximum number of cycles of refinement to execute, if -1 refine
124        until other limits are met (ftol, xtol)
125    :param int lamda: initial Marquardt lambda=10**lamda
126    :param bool Print: True for printing results (residuals & times) by cycle
127
128    :returns: (x,cov_x,infodict) where
129
130      * x : ndarray
131        The solution (or the result of the last iteration for an unsuccessful
132        call).
133      * cov_x : ndarray
134        Uses the fjac and ipvt optional outputs to construct an
135        estimate of the jacobian around the solution.  ``None`` if a
136        singular matrix encountered (indicates very flat curvature in
137        some direction).  This matrix must be multiplied by the
138        residual standard deviation to get the covariance of the
139        parameter estimates -- see curve_fit.
140      * infodict : dict
141        a dictionary of optional outputs with the keys:
142
143         * 'fvec' : the function evaluated at the output
144         * 'num cyc':
145         * 'nfev':
146         * 'lamMax':
147         * 'psing':
148         * 'SVD0':
149           
150    '''
151               
152    ifConverged = False
153    deltaChi2 = -10.
154    x0 = np.array(x0, ndmin=1)      #might be redundant?
155    n = len(x0)
156    if type(args) != type(()):
157        args = (args,)
158       
159    icycle = 0
160    One = np.ones((n,n))
161    lam = 10.**lamda
162    lamMax = lam
163    nfev = 0
164    if Print:
165        G2fil.G2Print(' Hessian Levenberg-Marquardt SVD refinement on %d variables:'%(n))
166    Lam = np.zeros((n,n))
167    Xvec = np.zeros(len(x0))
168    chisq00 = None
169    while icycle < maxcyc:
170        time0 = time.time()
171        M = func(x0,*args)
172        Nobs = len(M)
173        nfev += 1
174        chisq0 = np.sum(M**2)
175        if chisq00 is None: chisq00 = chisq0
176        Yvec,Amat = Hess(x0,*args)
177        Adiag = np.sqrt(np.diag(Amat))
178        psing = np.where(np.abs(Adiag) < 1.e-14,True,False)
179        if np.any(psing):                #hard singularity in matrix
180            return [x0,None,{'num cyc':icycle,'fvec':M,'nfev':nfev,'lamMax':lamMax,'psing':psing,'SVD0':-1}]
181        Anorm = np.outer(Adiag,Adiag)
182        Yvec /= Adiag
183        Amat /= Anorm
184        if Print:
185            G2fil.G2Print('initial chi^2 %.5g on %d obs.'%(chisq0,Nobs))
186        chitol = ftol
187        while True:
188            Lam = np.eye(Amat.shape[0])*lam
189            Amatlam = Amat*(One+Lam)
190            try:
191                Ainv,Nzeros = pinv(Amatlam,xtol)    #do Moore-Penrose inversion (via SVD)
192            except nl.LinAlgError:
193                psing = list(np.where(np.diag(nl.qr(Amatlam)[1]) < 1.e-14)[0])
194                G2fil.G2Print('ouch #1 bad SVD inversion; change parameterization for ',psing, mode='error')
195                return [x0,None,{'num cyc':icycle,'fvec':M,'nfev':nfev,'lamMax':lamMax,'psing':psing,'SVD0':-1}]
196            Xvec = np.inner(Ainv,Yvec)      #solve
197            Xvec /= Adiag
198            M2 = func(x0+Xvec,*args)
199            nfev += 1
200            chisq1 = np.sum(M2**2)
201            if chisq1 > chisq0*(1.+chitol):     #TODO put Alan Coehlo's criteria for lambda here?
202                lam *= 10.
203                if Print:
204                    G2fil.G2Print('new chi^2 %.5g on %d obs., %d SVD zeros ; matrix modification needed; lambda now %.1e'  \
205                           %(chisq1,Nobs,Nzeros,lam))
206            else:
207                x0 += Xvec
208                lam /= 10.
209                break
210            if lam > 10.:
211                G2fil.G2Print('ouch #3 chisq1 %g.4 stuck > chisq0 %g.4'%(chisq1,chisq0), mode='warn')
212                break
213            chitol *= 2
214        lamMax = max(lamMax,lam)
215        deltaChi2 = (chisq0-chisq1)/chisq0
216        if Print:
217            G2fil.G2Print(' Cycle: %d, Time: %.2fs, Chi**2: %.5g for %d obs., Lambda: %.3g,  Delta: %.3g'%(
218                icycle,time.time()-time0,chisq1,Nobs,lamMax,deltaChi2))
219        Histograms = args[0][0]
220        if refPlotUpdate is not None: refPlotUpdate(Histograms,icycle)   # update plot
221        if deltaChi2 < ftol:
222            ifConverged = True
223            if Print: G2fil.G2Print("converged")
224            break
225        icycle += 1
226    M = func(x0,*args)
227    nfev += 1
228    Yvec,Amat = Hess(x0,*args)
229    Adiag = np.sqrt(np.diag(Amat))
230    Anorm = np.outer(Adiag,Adiag)
231    Lam = np.eye(Amat.shape[0])*lam
232    Amatlam = Amat/Anorm       
233    try:
234        Bmat,Nzero = pinv(Amatlam,xtol)    #Moore-Penrose inversion (via SVD) & count of zeros
235        if Print: G2fil.G2Print('Found %d SVD zeros'%(Nzero), mode='warn')
236        Bmat = Bmat/Anorm
237        return [x0,Bmat,{'num cyc':icycle,'fvec':M,'nfev':nfev,'lamMax':lamMax,'psing':[],
238            'SVD0':Nzero,'Converged': ifConverged, 'DelChi2':deltaChi2,'Xvec':Xvec, 'chisq0':chisq00}]
239    except nl.LinAlgError:
240        G2fil.G2Print('ouch #2 linear algebra error in making v-cov matrix', mode='error')
241        psing = []
242        if maxcyc:
243            psing = list(np.where(np.diag(nl.qr(Amat)[1]) < 1.e-14)[0])
244        return [x0,None,{'num cyc':icycle,'fvec':M,'nfev':nfev,'lamMax':lamMax,'psing':psing,'SVD0':-1,'Xvec':None, 'chisq0':chisq00}]
245           
246def HessianSVD(func,x0,Hess,args=(),ftol=1.49012e-8,xtol=1.e-6, maxcyc=0,lamda=-3,Print=False,refPlotUpdate=None):
247   
248    '''
249    Minimize the sum of squares of a function (:math:`f`) evaluated on a series of
250    values (y): :math:`\\sum_{y=0}^{N_{obs}} f(y,{args})`   
251    where :math:`x = arg min(\\sum_{y=0}^{N_{obs}} (func(y)^2,axis=0))`
252
253    :param function func: callable method or function
254        should take at least one (possibly length N vector) argument and
255        returns M floating point numbers.
256    :param np.ndarray x0: The starting estimate for the minimization of length N
257    :param function Hess: callable method or function
258        A required function or method to compute the weighted vector and Hessian for func.
259        It must be a symmetric NxN array
260    :param tuple args: Any extra arguments to func are placed in this tuple.
261    :param float ftol: Relative error desired in the sum of squares.
262    :param float xtol: Relative tolerance of zeros in the SVD solution in nl.pinv.
263    :param int maxcyc: The maximum number of cycles of refinement to execute, if -1 refine
264        until other limits are met (ftol, xtol)
265    :param bool Print: True for printing results (residuals & times) by cycle
266
267    :returns: (x,cov_x,infodict) where
268
269      * x : ndarray
270        The solution (or the result of the last iteration for an unsuccessful
271        call).
272      * cov_x : ndarray
273        Uses the fjac and ipvt optional outputs to construct an
274        estimate of the jacobian around the solution.  ``None`` if a
275        singular matrix encountered (indicates very flat curvature in
276        some direction).  This matrix must be multiplied by the
277        residual standard deviation to get the covariance of the
278        parameter estimates -- see curve_fit.
279      * infodict : dict
280        a dictionary of optional outputs with the keys:
281
282         * 'fvec' : the function evaluated at the output
283         * 'num cyc':
284         * 'nfev':
285         * 'lamMax':0.
286         * 'psing':
287         * 'SVD0':
288           
289    '''
290               
291    ifConverged = False
292    deltaChi2 = -10.
293    x0 = np.array(x0, ndmin=1)      #might be redundant?
294    n = len(x0)
295    if type(args) != type(()):
296        args = (args,)
297       
298    icycle = 0
299    nfev = 0
300    if Print:
301        G2fil.G2Print(' Hessian SVD refinement on %d variables:'%(n))
302    chisq00 = None
303    while icycle < maxcyc:
304        time0 = time.time()
305        M = func(x0,*args)
306        nfev += 1
307        chisq0 = np.sum(M**2)
308        if chisq00 is None: chisq00 = chisq0
309        Yvec,Amat = Hess(x0,*args)
310        Adiag = np.sqrt(np.diag(Amat))
311        psing = np.where(np.abs(Adiag) < 1.e-14,True,False)
312        if np.any(psing):                #hard singularity in matrix
313            return [x0,None,{'num cyc':icycle,'fvec':M,'nfev':nfev,'lamMax':0.,'psing':psing,'SVD0':-1}]
314        Anorm = np.outer(Adiag,Adiag)
315        Yvec /= Adiag
316        Amat /= Anorm
317        if Print:
318            G2fil.G2Print('initial chi^2 %.5g'%(chisq0))
319        try:
320            Ainv,Nzeros = pinv(Amat,xtol)    #do Moore-Penrose inversion (via SVD)
321        except nl.LinAlgError:
322            G2fil.G2Print('ouch #1 bad SVD inversion; change parameterization', mode='warn')
323            psing = list(np.where(np.diag(nl.qr(Amat)[1]) < 1.e-14)[0])
324            return [x0,None,{'num cyc':icycle,'fvec':M,'nfev':nfev,'lamMax':0.,'psing':psing,'SVD0':-1}]
325        Xvec = np.inner(Ainv,Yvec)      #solve
326        Xvec /= Adiag
327        M2 = func(x0+Xvec,*args)
328        nfev += 1
329        chisq1 = np.sum(M2**2)
330        deltaChi2 = (chisq0-chisq1)/chisq0
331        if Print:
332            G2fil.G2Print(' Cycle: %d, Time: %.2fs, Chi**2: %.5g, Delta: %.3g'%(
333                icycle,time.time()-time0,chisq1,deltaChi2))
334        Histograms = args[0][0]
335        if refPlotUpdate is not None: refPlotUpdate(Histograms,icycle)   # update plot
336        if deltaChi2 < ftol:
337            ifConverged = True
338            if Print: G2fil.G2Print("converged")
339            break
340        icycle += 1
341    M = func(x0,*args)
342    nfev += 1
343    Yvec,Amat = Hess(x0,*args)
344    Adiag = np.sqrt(np.diag(Amat))
345    Anorm = np.outer(Adiag,Adiag)
346    Amat = Amat/Anorm       
347    try:
348        Bmat,Nzero = pinv(Amat,xtol)    #Moore-Penrose inversion (via SVD) & count of zeros
349        G2fil.G2Print('Found %d SVD zeros'%(Nzero), mode='warn')
350#        Bmat = nl.inv(Amatlam); Nzeros = 0
351        Bmat = Bmat/Anorm
352        return [x0,Bmat,{'num cyc':icycle,'fvec':M,'nfev':nfev,'lamMax':0.,'psing':[],
353            'SVD0':Nzero,'Converged': ifConverged, 'DelChi2':deltaChi2,
354                             'chisq0':chisq00}]
355    except nl.LinAlgError:
356        G2fil.G2Print('ouch #2 linear algebra error in making v-cov matrix', mode='error')
357        psing = []
358        if maxcyc:
359            psing = list(np.where(np.diag(nl.qr(Amat)[1]) < 1.e-14)[0])
360        return [x0,None,{'num cyc':icycle,'fvec':M,'nfev':nfev,'lamMax':0.,'psing':psing,'SVD0':-1,
361                             'chisq0':chisq00}]
362           
363def getVCov(varyNames,varyList,covMatrix):
364    '''obtain variance-covariance terms for a set of variables. NB: the varyList
365    and covMatrix were saved by the last least squares refinement so they must match.
366   
367    :param list varyNames: variable names to find v-cov matric for
368    :param list varyList: full list of all variables in v-cov matrix
369    :param nparray covMatrix: full variance-covariance matrix from the last
370     least squares refinement
371   
372    :returns: nparray vcov: variance-covariance matrix for the variables given
373     in varyNames
374   
375    '''
376    vcov = np.zeros((len(varyNames),len(varyNames)))
377    for i1,name1 in enumerate(varyNames):
378        for i2,name2 in enumerate(varyNames):
379            try:
380                vcov[i1][i2] = covMatrix[varyList.index(name1)][varyList.index(name2)]
381            except ValueError:
382                vcov[i1][i2] = 0.0
383#                if i1 == i2:
384#                    vcov[i1][i2] = 1e-20
385#                else:
386#                    vcov[i1][i2] = 0.0
387    return vcov
388   
389################################################################################
390##### Atom manipulations
391################################################################################
392
393def FindMolecule(ind,generalData,atomData):                    #uses numpy & masks - very fast even for proteins!
394
395    def getNeighbors(atom,radius):
396        Dx = UAtoms-np.array(atom[cx:cx+3])
397        dist = ma.masked_less(np.sqrt(np.sum(np.inner(Amat,Dx)**2,axis=0)),0.5) #gets rid of disorder "bonds" < 0.5A
398        sumR = Radii+radius
399        return set(ma.nonzero(ma.masked_greater(dist-factor*sumR,0.))[0])                #get indices of bonded atoms
400
401    import numpy.ma as ma
402    indices = (-1,0,1)
403    Units = np.array([[h,k,l] for h in indices for k in indices for l in indices],dtype='f')
404    cx,ct,cs,ci = generalData['AtomPtrs']
405    DisAglCtls = generalData['DisAglCtls']
406    SGData = generalData['SGData']
407    Amat,Bmat = G2lat.cell2AB(generalData['Cell'][1:7])
408    radii = DisAglCtls['BondRadii']
409    atomTypes = DisAglCtls['AtomTypes']
410    factor = DisAglCtls['Factors'][0]
411    unit = np.zeros(3)
412    try:
413        indH = atomTypes.index('H')
414        radii[indH] = 0.5
415    except:
416        pass           
417    nAtom = len(atomData)
418    Indx = list(range(nAtom))
419    UAtoms = []
420    Radii = []
421    for atom in atomData:
422        UAtoms.append(np.array(atom[cx:cx+3]))
423        Radii.append(radii[atomTypes.index(atom[ct])])
424    UAtoms = np.array(UAtoms)
425    Radii = np.array(Radii)
426    for nOp,Op in enumerate(SGData['SGOps'][1:]):
427        UAtoms = np.concatenate((UAtoms,(np.inner(Op[0],UAtoms[:nAtom]).T+Op[1])))
428        Radii = np.concatenate((Radii,Radii[:nAtom]))
429        Indx += Indx[:nAtom]
430    for icen,cen in enumerate(SGData['SGCen'][1:]):
431        UAtoms = np.concatenate((UAtoms,(UAtoms+cen)))
432        Radii = np.concatenate((Radii,Radii))
433        Indx += Indx[:nAtom]
434    if SGData['SGInv']:
435        UAtoms = np.concatenate((UAtoms,-UAtoms))
436        Radii = np.concatenate((Radii,Radii))
437        Indx += Indx
438    UAtoms %= 1.
439    mAtoms = len(UAtoms)
440    for unit in Units:
441        if np.any(unit):    #skip origin cell
442            UAtoms = np.concatenate((UAtoms,UAtoms[:mAtoms]+unit))
443            Radii = np.concatenate((Radii,Radii[:mAtoms]))
444            Indx += Indx[:mAtoms]
445    UAtoms = np.array(UAtoms)
446    Radii = np.array(Radii)
447    newAtoms = [atomData[ind],]
448    atomData[ind] = None
449    radius = Radii[ind]
450    IndB = getNeighbors(newAtoms[-1],radius)
451    while True:
452        if not len(IndB):
453            break
454        indb = IndB.pop()
455        if atomData[Indx[indb]] == None:
456            continue
457        while True:
458            try:
459                jndb = IndB.index(indb)
460                IndB.remove(jndb)
461            except:
462                break
463        newAtom = copy.copy(atomData[Indx[indb]])
464        newAtom[cx:cx+3] = UAtoms[indb]     #NB: thermal Uij, etc. not transformed!
465        newAtoms.append(newAtom)
466        atomData[Indx[indb]] = None
467        IndB = set(list(IndB)+list(getNeighbors(newAtoms[-1],radius)))
468        if len(IndB) > nAtom:
469            return 'Assemble molecule cannot be used on extended structures'
470    for atom in atomData:
471        if atom != None:
472            newAtoms.append(atom)
473    return newAtoms
474       
475def FindAtomIndexByIDs(atomData,loc,IDs,Draw=True):
476    '''finds the set of atom array indices for a list of atom IDs. Will search
477    either the Atom table or the drawAtom table.
478   
479    :param list atomData: Atom or drawAtom table containting coordinates, etc.
480    :param int loc: location of atom id in atomData record
481    :param list IDs: atom IDs to be found
482    :param bool Draw: True if drawAtom table to be searched; False if Atom table
483      is searched
484   
485    :returns: list indx: atom (or drawAtom) indices
486   
487    '''
488    indx = []
489    for i,atom in enumerate(atomData):
490        if Draw and atom[loc] in IDs:
491            indx.append(i)
492        elif atom[loc] in IDs:
493            indx.append(i)
494    return indx
495
496def FillAtomLookUp(atomData,indx):
497    '''create a dictionary of atom indexes with atom IDs as keys
498   
499    :param list atomData: Atom table to be used
500    :param int  indx: pointer to position of atom id in atom record (typically cia+8)
501   
502    :returns: dict atomLookUp: dictionary of atom indexes with atom IDs as keys
503   
504    '''
505    return {atom[indx]:iatm for iatm,atom in enumerate(atomData)}
506
507def GetAtomsById(atomData,atomLookUp,IdList):
508    '''gets a list of atoms from Atom table that match a set of atom IDs
509   
510    :param list atomData: Atom table to be used
511    :param dict atomLookUp: dictionary of atom indexes with atom IDs as keys
512    :param list IdList: atom IDs to be found
513   
514    :returns: list atoms: list of atoms found
515   
516    '''
517    atoms = []
518    for Id in IdList:
519        atoms.append(atomData[atomLookUp[Id]])
520    return atoms
521   
522def GetAtomItemsById(atomData,atomLookUp,IdList,itemLoc,numItems=1):
523    '''gets atom parameters for atoms using atom IDs
524   
525    :param list atomData: Atom table to be used
526    :param dict atomLookUp: dictionary of atom indexes with atom IDs as keys
527    :param list IdList: atom IDs to be found
528    :param int itemLoc: pointer to desired 1st item in an atom table entry
529    :param int numItems: number of items to be retrieved
530   
531    :returns: type name: description
532   
533    '''
534    Items = []
535    if not isinstance(IdList,list):
536        IdList = [IdList,]
537    for Id in IdList:
538        if numItems == 1:
539            Items.append(atomData[atomLookUp[Id]][itemLoc])
540        else:
541            Items.append(atomData[atomLookUp[Id]][itemLoc:itemLoc+numItems])
542    return Items
543   
544def GetAtomCoordsByID(pId,parmDict,AtLookup,indx):
545    '''default doc string
546   
547    :param type name: description
548   
549    :returns: type name: description
550   
551    '''
552    pfx = [str(pId)+'::A'+i+':' for i in ['x','y','z']]
553    dpfx = [str(pId)+'::dA'+i+':' for i in ['x','y','z']]
554    XYZ = []
555    for ind in indx:
556        names = [pfx[i]+str(AtLookup[ind]) for i in range(3)]
557        dnames = [dpfx[i]+str(AtLookup[ind]) for i in range(3)]
558        XYZ.append([parmDict[name]+parmDict[dname] for name,dname in zip(names,dnames)])
559    return XYZ
560   
561def GetAtomFracByID(pId,parmDict,AtLookup,indx):
562    '''default doc string
563   
564    :param type name: description
565   
566    :returns: type name: description
567   
568    '''
569    pfx = str(pId)+'::Afrac:'
570    Frac = []
571    for ind in indx:
572        name = pfx+str(AtLookup[ind])
573        Frac.append(parmDict[name])
574    return Frac
575   
576#    for Atom in Atoms:
577#        XYZ = Atom[cx:cx+3]
578#        if 'A' in Atom[cia]:
579#            U6 = Atom[cia+2:cia+8]
580   
581
582def ApplySeqData(data,seqData):
583    '''Applies result from seq. refinement to drawing atom positions & Uijs
584    '''
585    generalData = data['General']
586    SGData = generalData['SGData']
587    cx,ct,cs,cia = generalData['AtomPtrs']
588    drawingData = data['Drawing']
589    dcx,dct,dcs,dci = drawingData['atomPtrs']
590    atoms = data['Atoms']
591    drawAtoms = drawingData['Atoms']
592    pId = data['pId']
593    pfx = '%d::'%(pId)
594    parmDict = seqData['parmDict']
595    for ia,atom in enumerate(atoms):
596        dxyz = np.array([parmDict[pfx+'dAx:'+str(ia)],parmDict[pfx+'dAy:'+str(ia)],parmDict[pfx+'dAz:'+str(ia)]])
597        if atom[cia] == 'A':
598            atuij = np.array([parmDict[pfx+'AU11:'+str(ia)],parmDict[pfx+'AU22:'+str(ia)],parmDict[pfx+'AU33:'+str(ia)],
599                parmDict[pfx+'AU12:'+str(ia)],parmDict[pfx+'AU13:'+str(ia)],parmDict[pfx+'AU23:'+str(ia)]])
600        else:
601            atuiso = parmDict[pfx+'AUiso:'+str(ia)]
602        atxyz = G2spc.MoveToUnitCell(np.array(atom[cx:cx+3])+dxyz)[0]
603        indx = FindAtomIndexByIDs(drawAtoms,dci,[atom[cia+8],],True)
604        for ind in indx:
605            drawatom = drawAtoms[ind]
606            opr = drawatom[dcs-1]
607            #how do I handle Sfrac? - fade the atoms?
608            if atom[cia] == 'A':                   
609                X,U = G2spc.ApplyStringOps(opr,SGData,atxyz,atuij)
610                drawatom[dcx:dcx+3] = X
611                drawatom[dci-6:dci] = U
612            else:
613                X = G2spc.ApplyStringOps(opr,SGData,atxyz)
614                drawatom[dcx:dcx+3] = X
615                drawatom[dci-7] = atuiso
616    return drawAtoms
617   
618def FindNeighbors(phase,FrstName,AtNames,notName=''):
619    General = phase['General']
620    cx,ct,cs,cia = General['AtomPtrs']
621    Atoms = phase['Atoms']
622    atNames = [atom[ct-1] for atom in Atoms]
623    Cell = General['Cell'][1:7]
624    Amat,Bmat = G2lat.cell2AB(Cell)
625    atTypes = General['AtomTypes']
626    Radii = np.array(General['BondRadii'])
627    try:
628        DisAglCtls = General['DisAglCtls']   
629        radiusFactor = DisAglCtls['Factors'][0]
630    except:
631        radiusFactor = 0.85
632    AtInfo = dict(zip(atTypes,Radii)) #or General['BondRadii']
633    Orig = atNames.index(FrstName)
634    OId = Atoms[Orig][cia+8]
635    OType = Atoms[Orig][ct]
636    XYZ = getAtomXYZ(Atoms,cx)       
637    Neigh = []
638    Ids = []
639    Dx = np.inner(Amat,XYZ-XYZ[Orig]).T
640    dist = np.sqrt(np.sum(Dx**2,axis=1))
641    sumR = np.array([AtInfo[OType]+AtInfo[atom[ct]] for atom in Atoms])
642    IndB = ma.nonzero(ma.masked_greater(dist-radiusFactor*sumR,0.))
643    for j in IndB[0]:
644        if j != Orig:
645            if AtNames[j] not in notName:
646                Neigh.append([AtNames[j],dist[j],True])
647                Ids.append(Atoms[j][cia+8])
648    return Neigh,[OId,Ids]
649
650def FindOctahedron(results):
651    Octahedron = np.array([[1.,0,0],[0,1.,0],[0,0,1.],[-1.,0,0],[0,-1.,0],[0,0,-1.]])
652    Polygon = np.array([result[3] for result in results])
653    Dists = np.array([np.sqrt(np.sum(axis**2)) for axis in Polygon])
654    bond = np.mean(Dists)
655    std = np.std(Dists)
656    Norms = Polygon/Dists[:,nxs]
657    Tilts = acosd(np.dot(Norms,Octahedron[0]))
658    iAng = np.argmin(Tilts)
659    Qavec = np.cross(Norms[iAng],Octahedron[0])
660    QA = AVdeg2Q(Tilts[iAng],Qavec)
661    newNorms = prodQVQ(QA,Norms)
662    Rots = acosd(np.dot(newNorms,Octahedron[1]))
663    jAng = np.argmin(Rots)
664    Qbvec = np.cross(Norms[jAng],Octahedron[1])
665    QB = AVdeg2Q(Rots[jAng],Qbvec)
666    QQ = prodQQ(QA,QB)   
667    newNorms = prodQVQ(QQ,Norms)
668    dispVecs = np.array([norm[:,nxs]-Octahedron.T for norm in newNorms])
669    disp = np.sqrt(np.sum(dispVecs**2,axis=1))
670    dispids = np.argmin(disp,axis=1)
671    vecDisp = np.array([dispVecs[i,:,dispid] for i,dispid in enumerate(dispids)])
672    Disps = np.array([disp[i,dispid] for i,dispid in enumerate(dispids)])
673    meanDisp = np.mean(Disps)
674    stdDisp = np.std(Disps)
675    A,V = Q2AVdeg(QQ)
676    return bond,std,meanDisp,stdDisp,A,V,vecDisp
677   
678def FindTetrahedron(results):
679    Tetrahedron = np.array([[1.,1,1],[1,-1,-1],[-1,1,-1],[-1,-1,1]])/np.sqrt(3.)
680    Polygon = np.array([result[3] for result in results])
681    Dists = np.array([np.sqrt(np.sum(axis**2)) for axis in Polygon])
682    bond = np.mean(Dists)
683    std = np.std(Dists)
684    Norms = Polygon/Dists[:,nxs]
685    Tilts = acosd(np.dot(Norms,Tetrahedron[0]))
686    iAng = np.argmin(Tilts)
687    Qavec = np.cross(Norms[iAng],Tetrahedron[0])
688    QA = AVdeg2Q(Tilts[iAng],Qavec)
689    newNorms = prodQVQ(QA,Norms)
690    Rots = acosd(np.dot(newNorms,Tetrahedron[1]))
691    jAng = np.argmin(Rots)
692    Qbvec = np.cross(Norms[jAng],Tetrahedron[1])
693    QB = AVdeg2Q(Rots[jAng],Qbvec)
694    QQ = prodQQ(QA,QB)   
695    newNorms = prodQVQ(QQ,Norms)
696    dispVecs = np.array([norm[:,nxs]-Tetrahedron.T for norm in newNorms])
697    disp = np.sqrt(np.sum(dispVecs**2,axis=1))
698    dispids = np.argmin(disp,axis=1)
699    vecDisp = np.array([dispVecs[i,:,dispid] for i,dispid in enumerate(dispids)])
700    Disps = np.array([disp[i,dispid] for i,dispid in enumerate(dispids)])
701    meanDisp = np.mean(Disps)
702    stdDisp = np.std(Disps)
703    A,V = Q2AVdeg(QQ)
704    return bond,std,meanDisp,stdDisp,A,V,vecDisp
705   
706def FindAllNeighbors(phase,FrstName,AtNames,notName='',Orig=None,Short=False):
707    General = phase['General']
708    cx,ct,cs,cia = General['AtomPtrs']
709    Atoms = phase['Atoms']
710    atNames = [atom[ct-1] for atom in Atoms]
711    atTypes = [atom[ct] for atom in Atoms]
712    Cell = General['Cell'][1:7]
713    Amat,Bmat = G2lat.cell2AB(Cell)
714    SGData = General['SGData']
715    indices = (-1,0,1)
716    Units = np.array([[h,k,l] for h in indices for k in indices for l in indices])
717    AtTypes = General['AtomTypes']
718    Radii = np.array(General['BondRadii'])
719    try:
720        DisAglCtls = General['DisAglCtls']   
721        radiusFactor = DisAglCtls['Factors'][0]
722    except:
723        radiusFactor = 0.85
724    AtInfo = dict(zip(AtTypes,Radii)) #or General['BondRadii']
725    if Orig is None:
726        Orig = atNames.index(FrstName)
727    OId = Atoms[Orig][cia+8]
728    OType = Atoms[Orig][ct]
729    XYZ = getAtomXYZ(Atoms,cx)       
730    Oxyz = XYZ[Orig]
731    Neigh = []
732    Ids = []
733    sumR = np.array([AtInfo[OType]+AtInfo[atom[ct]] for atom in Atoms])
734    sumR = np.reshape(np.tile(sumR,27),(27,-1))
735    results = []
736    for xyz in XYZ:
737        results.append(G2spc.GenAtom(xyz,SGData,False,Move=False))
738    for iA,result in enumerate(results):
739        if iA != Orig:               
740            for [Txyz,Top,Tunit,Spn] in result:
741                Dx = np.array([Txyz-Oxyz+unit for unit in Units])
742                dx = np.inner(Dx,Amat)
743                dist = np.sqrt(np.sum(dx**2,axis=1))
744                IndB = ma.nonzero(ma.masked_greater(dist-radiusFactor*sumR[:,iA],0.))
745                for iU in IndB[0]:
746                    if AtNames[iA%len(AtNames)] != notName:
747                        unit = Units[iU]
748                        if np.any(unit):
749                            Topstr = ' +(%4d)[%2d,%2d,%2d]'%(Top,unit[0],unit[1],unit[2])
750                        else:
751                            Topstr = ' +(%4d)'%(Top)
752                        if Short:
753                            Neigh.append([AtNames[iA%len(AtNames)],dist[iU],True])
754                        else:
755                            Neigh.append([AtNames[iA]+Topstr,atTypes[iA],dist[iU],dx[iU]])
756                        Ids.append(Atoms[iA][cia+8])
757    return Neigh,[OId,Ids]
758   
759def calcBond(A,Ax,Bx,MTCU):
760    cell = G2lat.A2cell(A)
761    Amat,Bmat = G2lat.cell2AB(cell)
762    M,T,C,U = MTCU
763    Btx = np.inner(M,Bx)+T+C+U
764    Dx = Btx-Ax
765    dist = np.sqrt(np.inner(Amat,Dx))
766    return dist
767   
768def AddHydrogens(AtLookUp,General,Atoms,AddHydId):
769   
770    def getTransMat(RXYZ,OXYZ,TXYZ,Amat):
771        Vec = np.inner(Amat,np.array([OXYZ-TXYZ[0],RXYZ-TXYZ[0]])).T           
772        Vec /= np.sqrt(np.sum(Vec**2,axis=1))[:,nxs]
773        Mat2 = np.cross(Vec[0],Vec[1])      #UxV
774        Mat2 /= np.sqrt(np.sum(Mat2**2))
775        Mat3 = np.cross(Mat2,Vec[0])        #(UxV)xU
776        return nl.inv(np.array([Vec[0],Mat2,Mat3]))       
777   
778    cx,ct,cs,cia = General['AtomPtrs']
779    Cell = General['Cell'][1:7]
780    Amat,Bmat = G2lat.cell2AB(Cell)
781    nBonds = AddHydId[-1]+len(AddHydId[1])
782    Oatom = GetAtomsById(Atoms,AtLookUp,[AddHydId[0],])[0]
783    OXYZ = np.array(Oatom[cx:cx+3])
784    if 'I' in Oatom[cia]:
785        Uiso = Oatom[cia+1]
786    else:
787        Uiso = (Oatom[cia+2]+Oatom[cia+3]+Oatom[cia+4])/3.0       #simple average
788    Uiso = max(Uiso,0.005)                      #set floor!
789    Tatoms = GetAtomsById(Atoms,AtLookUp,AddHydId[1])
790    TXYZ = np.array([tatom[cx:cx+3] for tatom in Tatoms]) #3 x xyz
791    if nBonds == 4:
792        if AddHydId[-1] == 1:
793            Vec = TXYZ-OXYZ
794            Len = np.sqrt(np.sum(np.inner(Amat,Vec).T**2,axis=0))
795            Vec = np.sum(Vec/Len,axis=0)
796            Len = np.sqrt(np.sum(Vec**2))
797            Hpos = OXYZ-0.98*np.inner(Bmat,Vec).T/Len
798            HU = 1.1*Uiso
799            return [Hpos,],[HU,]
800        elif AddHydId[-1] == 2:
801            Vec = np.inner(Amat,TXYZ-OXYZ).T
802            Vec[0] += Vec[1]            #U - along bisector
803            Vec /= np.sqrt(np.sum(Vec**2,axis=1))[:,nxs]
804            Mat2 = np.cross(Vec[0],Vec[1])      #UxV
805            Mat2 /= np.sqrt(np.sum(Mat2**2))
806            Mat3 = np.cross(Mat2,Vec[0])        #(UxV)xU
807            iMat = nl.inv(np.array([Vec[0],Mat2,Mat3]))
808            Hpos = np.array([[-0.97*cosd(54.75),0.97*sind(54.75),0.],
809                [-0.97*cosd(54.75),-0.97*sind(54.75),0.]])
810            HU = 1.2*Uiso*np.ones(2)
811            Hpos = np.inner(Bmat,np.inner(iMat,Hpos).T).T+OXYZ
812            return Hpos,HU
813        else:
814            Ratom = GetAtomsById(Atoms,AtLookUp,[AddHydId[2],])[0]
815            RXYZ = np.array(Ratom[cx:cx+3])
816            iMat = getTransMat(RXYZ,OXYZ,TXYZ,Amat)
817            a = 0.96*cosd(70.5)
818            b = 0.96*sind(70.5)
819            Hpos = np.array([[a,0.,-b],[a,-b*cosd(30.),0.5*b],[a,b*cosd(30.),0.5*b]])
820            Hpos = np.inner(Bmat,np.inner(iMat,Hpos).T).T+OXYZ
821            HU = 1.5*Uiso*np.ones(3)
822            return Hpos,HU         
823    elif nBonds == 3:
824        if AddHydId[-1] == 1:
825            Vec = np.sum(TXYZ-OXYZ,axis=0)               
826            Len = np.sqrt(np.sum(np.inner(Amat,Vec).T**2))
827            Vec = -0.93*Vec/Len
828            Hpos = OXYZ+Vec
829            HU = 1.1*Uiso
830            return [Hpos,],[HU,]
831        elif AddHydId[-1] == 2:
832            Ratom = GetAtomsById(Atoms,AtLookUp,[AddHydId[2],])[0]
833            RXYZ = np.array(Ratom[cx:cx+3])
834            iMat = getTransMat(RXYZ,OXYZ,TXYZ,Amat)
835            a = 0.93*cosd(60.)
836            b = 0.93*sind(60.)
837            Hpos = [[a,b,0],[a,-b,0]]
838            Hpos = np.inner(Bmat,np.inner(iMat,Hpos).T).T+OXYZ
839            HU = 1.2*Uiso*np.ones(2)
840            return Hpos,HU
841    else:   #2 bonds
842        if 'C' in Oatom[ct]:
843            Vec = TXYZ[0]-OXYZ
844            Len = np.sqrt(np.sum(np.inner(Amat,Vec).T**2))
845            Vec = -0.93*Vec/Len
846            Hpos = OXYZ+Vec
847            HU = 1.1*Uiso
848            return [Hpos,],[HU,]
849        elif 'O' in Oatom[ct]:
850            mapData = General['Map']
851            Ratom = GetAtomsById(Atoms,AtLookUp,[AddHydId[2],])[0]
852            RXYZ = np.array(Ratom[cx:cx+3])
853            iMat = getTransMat(RXYZ,OXYZ,TXYZ,Amat)
854            a = 0.82*cosd(70.5)
855            b = 0.82*sind(70.5)
856            azm = np.arange(0.,360.,5.)
857            Hpos = np.array([[a,b*cosd(x),b*sind(x)] for x in azm])
858            Hpos = np.inner(Bmat,np.inner(iMat,Hpos).T).T+OXYZ
859            Rhos = np.array([getRho(pos,mapData) for pos in Hpos])
860            imax = np.argmax(Rhos)
861            HU = 1.5*Uiso
862            return [Hpos[imax],],[HU,]
863    return [],[]
864       
865#def AtomUij2TLS(atomData,atPtrs,Amat,Bmat,rbObj):   #unfinished & not used
866#    '''default doc string
867#   
868#    :param type name: description
869#   
870#    :returns: type name: description
871#   
872#    '''
873#    for atom in atomData:
874#        XYZ = np.inner(Amat,atom[cx:cx+3])
875#        if atom[cia] == 'A':
876#            UIJ = atom[cia+2:cia+8]
877               
878def TLS2Uij(xyz,g,Amat,rbObj):    #not used anywhere, but could be?
879    '''default doc string
880   
881    :param type name: description
882   
883    :returns: type name: description
884   
885    '''
886    TLStype,TLS = rbObj['ThermalMotion'][:2]
887    Tmat = np.zeros((3,3))
888    Lmat = np.zeros((3,3))
889    Smat = np.zeros((3,3))
890    gvec = np.sqrt(np.array([g[0][0]**2,g[1][1]**2,g[2][2]**2,
891        g[0][0]*g[1][1],g[0][0]*g[2][2],g[1][1]*g[2][2]]))
892    if 'T' in TLStype:
893        Tmat = G2lat.U6toUij(TLS[:6])
894    if 'L' in TLStype:
895        Lmat = G2lat.U6toUij(TLS[6:12])
896    if 'S' in TLStype:
897        Smat = np.array([[TLS[18],TLS[12],TLS[13]],[TLS[14],TLS[19],TLS[15]],[TLS[16],TLS[17],0] ])
898    XYZ = np.inner(Amat,xyz)
899    Axyz = np.array([[ 0,XYZ[2],-XYZ[1]], [-XYZ[2],0,XYZ[0]], [XYZ[1],-XYZ[0],0]] )
900    Umat = Tmat+np.inner(Axyz,Smat)+np.inner(Smat.T,Axyz.T)+np.inner(np.inner(Axyz,Lmat),Axyz.T)
901    beta = np.inner(np.inner(g,Umat),g)
902    return G2lat.UijtoU6(beta)*gvec   
903       
904def AtomTLS2UIJ(atomData,atPtrs,Amat,rbObj):    #not used anywhere, but could be?
905    '''default doc string
906   
907    :param type name: description
908   
909    :returns: type name: description
910   
911    '''
912    cx,ct,cs,cia = atPtrs
913    TLStype,TLS = rbObj['ThermalMotion'][:2]
914    Tmat = np.zeros((3,3))
915    Lmat = np.zeros((3,3))
916    Smat = np.zeros((3,3))
917    G,g = G2lat.A2Gmat(Amat)
918    gvec = 1./np.sqrt(np.array([g[0][0],g[1][1],g[2][2],g[0][1],g[0][2],g[1][2]]))
919    if 'T' in TLStype:
920        Tmat = G2lat.U6toUij(TLS[:6])
921    if 'L' in TLStype:
922        Lmat = G2lat.U6toUij(TLS[6:12])
923    if 'S' in TLStype:
924        Smat = np.array([ [TLS[18],TLS[12],TLS[13]], [TLS[14],TLS[19],TLS[15]], [TLS[16],TLS[17],0] ])
925    for atom in atomData:
926        XYZ = np.inner(Amat,atom[cx:cx+3])
927        Axyz = np.array([ 0,XYZ[2],-XYZ[1], -XYZ[2],0,XYZ[0], XYZ[1],-XYZ[0],0],ndmin=2 )
928        if 'U' in TLStype:
929            atom[cia+1] = TLS[0]
930            atom[cia] = 'I'
931        else:
932            atom[cia] = 'A'
933            Umat = Tmat+np.inner(Axyz,Smat)+np.inner(Smat.T,Axyz.T)+np.inner(np.inner(Axyz,Lmat),Axyz.T)
934            beta = np.inner(np.inner(g,Umat),g)
935            atom[cia+2:cia+8] = G2spc.U2Uij(beta/gvec)
936
937def GetXYZDist(xyz,XYZ,Amat):
938    '''gets distance from position xyz to all XYZ, xyz & XYZ are np.array
939        and are in crystal coordinates; Amat is crystal to Cart matrix
940   
941    :param type name: description
942   
943    :returns: type name: description
944   
945    '''
946    return np.sqrt(np.sum(np.inner(Amat,XYZ-xyz)**2,axis=0))
947
948def getAtomXYZ(atoms,cx):
949    '''Create an array of fractional coordinates from the atoms list
950   
951    :param list atoms: atoms object as found in tree
952    :param int cx: offset to where coordinates are found
953   
954    :returns: np.array with shape (n,3)   
955    '''
956    XYZ = []
957    for atom in atoms:
958        XYZ.append(atom[cx:cx+3])
959    return np.array(XYZ)
960
961def getRBTransMat(X,Y):
962    '''Get transformation for Cartesian axes given 2 vectors
963    X will  be parallel to new X-axis; X cross Y will be new Z-axis &
964    (X cross Y) cross Y will be new Y-axis
965    Useful for rigid body axes definintion
966   
967    :param array X: normalized vector
968    :param array Y: normalized vector
969   
970    :returns: array M: transformation matrix
971   
972    use as XYZ' = np.inner(M,XYZ) where XYZ are Cartesian
973   
974    '''
975    Mat2 = np.cross(X,Y)      #UxV-->Z
976    Mat2 /= np.sqrt(np.sum(Mat2**2))
977    Mat3 = np.cross(Mat2,X)        #(UxV)xU-->Y
978    Mat3 /= np.sqrt(np.sum(Mat3**2))
979    return np.array([X,Mat3,Mat2])       
980               
981def RotateRBXYZ(Bmat,Cart,oriQ,symAxis=None):
982    '''rotate & transform cartesian coordinates to crystallographic ones
983    no translation applied. To be used for numerical derivatives
984   
985    :param array Bmat: Orthogonalization matrix, see :func:`GSASIIlattice.cell2AB`
986    :param array Cart: 2D array of coordinates
987    :param array Q: quaternion as an np.array
988    :param tuple symAxis: if not None (default), specifies the symmetry
989      axis of the rigid body, which will be aligned to the quaternion vector.
990    :returns: 2D array of fractional coordinates, without translation to origin
991    '''
992    if symAxis is None:
993        Q = oriQ
994    else:
995        a,v = Q2AV(oriQ)
996        symaxis = np.array(symAxis)
997        xformAng = np.arccos(np.dot(v,symaxis))
998        xformVec = np.cross(symaxis,v)
999        Q = prodQQ(oriQ,AV2Q(xformAng,xformVec))
1000    XYZ = np.zeros_like(Cart)
1001    for i,xyz in enumerate(Cart):
1002        XYZ[i] = np.inner(Bmat,prodQVQ(Q,xyz))
1003    return XYZ
1004
1005def UpdateRBXYZ(Bmat,RBObj,RBData,RBType):
1006    '''returns crystal coordinates for atoms described by RBObj.
1007    Note that RBObj['symAxis'], if present, determines the symmetry
1008    axis of the rigid body, which will be aligned to the
1009    quaternion direction.
1010   
1011    :param np.array Bmat: see :func:`GSASIIlattice.cell2AB`
1012    :param dict rbObj: rigid body selection/orientation information
1013    :param dict RBData: rigid body tree data structure
1014    :param str RBType: rigid body type, 'Vector' or 'Residue'
1015
1016    :returns: coordinates for rigid body as XYZ,Cart where XYZ is
1017       the location in crystal coordinates and Cart is in cartesian
1018    '''
1019    RBRes = RBData[RBType][RBObj['RBId']]
1020    if RBType == 'Vector':
1021        vecs = RBRes['rbVect']
1022        mags = RBRes['VectMag']
1023        Cart = np.zeros_like(vecs[0])
1024        for vec,mag in zip(vecs,mags):
1025            Cart += vec*mag
1026    elif RBType == 'Residue':
1027        Cart = np.array(RBRes['rbXYZ'])
1028        for tor,seq in zip(RBObj['Torsions'],RBRes['rbSeq']):
1029            QuatA = AVdeg2Q(tor[0],Cart[seq[0]]-Cart[seq[1]])
1030            Cart[seq[3]] = prodQVQ(QuatA,(Cart[seq[3]]-Cart[seq[1]]))+Cart[seq[1]]
1031    # if symmetry axis is defined, place symmetry axis along quaternion
1032    if RBObj.get('symAxis') is None:
1033        Q = RBObj['Orient'][0]
1034    else:
1035        a,v = Q2AV(RBObj['Orient'][0])
1036        symaxis = np.array(RBObj.get('symAxis'))
1037        xformAng = np.arccos(np.dot(v,symaxis))
1038        xformVec = np.cross(symaxis,v)
1039        Q = prodQQ(RBObj['Orient'][0],AV2Q(xformAng,xformVec))
1040    XYZ = np.zeros_like(Cart)
1041    for i,xyz in enumerate(Cart):
1042        XYZ[i] = np.inner(Bmat,prodQVQ(Q,xyz))+RBObj['Orig'][0]
1043    return XYZ,Cart
1044
1045def UpdateMCSAxyz(Bmat,MCSA):
1046    '''default doc string
1047   
1048    :param type name: description
1049   
1050    :returns: type name: description
1051   
1052    '''
1053    xyz = []
1054    atTypes = []
1055    iatm = 0
1056    for model in MCSA['Models'][1:]:        #skip the MD model
1057        if model['Type'] == 'Atom':
1058            xyz.append(model['Pos'][0])
1059            atTypes.append(model['atType'])
1060            iatm += 1
1061        else:
1062            RBRes = MCSA['rbData'][model['Type']][model['RBId']]
1063            Pos = np.array(model['Pos'][0])
1064            Ori = np.array(model['Ori'][0])
1065            Qori = AVdeg2Q(Ori[0],Ori[1:])
1066            if model['Type'] == 'Vector':
1067                vecs = RBRes['rbVect']
1068                mags = RBRes['VectMag']
1069                Cart = np.zeros_like(vecs[0])
1070                for vec,mag in zip(vecs,mags):
1071                    Cart += vec*mag
1072            elif model['Type'] == 'Residue':
1073                Cart = np.array(RBRes['rbXYZ'])
1074                for itor,seq in enumerate(RBRes['rbSeq']):
1075                    QuatA = AVdeg2Q(model['Tor'][0][itor],Cart[seq[0]]-Cart[seq[1]])
1076                    Cart[seq[3]] = prodQVQ(QuatA,(Cart[seq[3]]-Cart[seq[1]]))+Cart[seq[1]]
1077            if model['MolCent'][1]:
1078                Cart -= model['MolCent'][0]
1079            for i,x in enumerate(Cart):
1080                xyz.append(np.inner(Bmat,prodQVQ(Qori,x))+Pos)
1081                atType = RBRes['rbTypes'][i]
1082                atTypes.append(atType)
1083                iatm += 1
1084    return np.array(xyz),atTypes
1085   
1086def SetMolCent(model,RBData):
1087    '''default doc string
1088   
1089    :param type name: description
1090   
1091    :returns: type name: description
1092   
1093    '''
1094    rideList = []
1095    RBRes = RBData[model['Type']][model['RBId']]
1096    if model['Type'] == 'Vector':
1097        vecs = RBRes['rbVect']
1098        mags = RBRes['VectMag']
1099        Cart = np.zeros_like(vecs[0])
1100        for vec,mag in zip(vecs,mags):
1101            Cart += vec*mag
1102    elif model['Type'] == 'Residue':
1103        Cart = np.array(RBRes['rbXYZ'])
1104        for seq in RBRes['rbSeq']:
1105            rideList += seq[3]
1106    centList = set(range(len(Cart)))-set(rideList)
1107    cent = np.zeros(3)
1108    for i in centList:
1109        cent += Cart[i]
1110    model['MolCent'][0] = cent/len(centList)   
1111   
1112def UpdateRBUIJ(Bmat,Cart,RBObj):
1113    '''default doc string
1114   
1115    :param type name: description
1116   
1117    :returns: type name: description
1118   
1119    '''
1120    ''' returns atom I/A, Uiso or UIJ for atoms at XYZ as described by RBObj
1121    '''
1122    TLStype,TLS = RBObj['ThermalMotion'][:2]
1123    T = np.zeros(6)
1124    L = np.zeros(6)
1125    S = np.zeros(8)
1126    if 'T' in TLStype:
1127        T = TLS[:6]
1128    if 'L' in TLStype:
1129        L = np.array(TLS[6:12])*(np.pi/180.)**2
1130    if 'S' in TLStype:
1131        S = np.array(TLS[12:])*(np.pi/180.)
1132    g = nl.inv(np.inner(Bmat,Bmat))
1133    gvec = np.sqrt(np.array([g[0][0]**2,g[1][1]**2,g[2][2]**2,
1134        g[0][0]*g[1][1],g[0][0]*g[2][2],g[1][1]*g[2][2]]))
1135    Uout = []
1136    Q = RBObj['Orient'][0]
1137    for X in Cart:
1138        X = prodQVQ(Q,X)
1139        if 'U' in TLStype:
1140            Uout.append(['I',TLS[0],0,0,0,0,0,0])
1141        elif not 'N' in TLStype:
1142            U = [0,0,0,0,0,0]
1143            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])
1144            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])
1145            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])
1146            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]+  \
1147                S[4]*X[0]-S[5]*X[1]-(S[6]+S[7])*X[2]
1148            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]+  \
1149                S[3]*X[2]-S[2]*X[0]+S[6]*X[1]
1150            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]+  \
1151                S[0]*X[1]-S[1]*X[2]+S[7]*X[0]
1152            Umat = G2lat.U6toUij(U)
1153            beta = np.inner(np.inner(Bmat.T,Umat),Bmat)
1154            Uout.append(['A',0.0,]+list(G2lat.UijtoU6(beta)*gvec))
1155        else:
1156            Uout.append(['N',])
1157    return Uout
1158   
1159def GetSHCoeff(pId,parmDict,SHkeys):
1160    '''default doc string
1161   
1162    :param type name: description
1163   
1164    :returns: type name: description
1165   
1166    '''
1167    SHCoeff = {}
1168    for shkey in SHkeys:
1169        shname = str(pId)+'::'+shkey
1170        SHCoeff[shkey] = parmDict[shname]
1171    return SHCoeff
1172       
1173def getMass(generalData):
1174    '''Computes mass of unit cell contents
1175   
1176    :param dict generalData: The General dictionary in Phase
1177   
1178    :returns: float mass: Crystal unit cell mass in AMU.
1179   
1180    '''
1181    mass = 0.
1182    for i,elem in enumerate(generalData['AtomTypes']):
1183        mass += generalData['NoAtoms'][elem]*generalData['AtomMass'][i]
1184    return max(mass,1.0)   
1185
1186def getDensity(generalData):
1187    '''calculate crystal structure density
1188   
1189    :param dict generalData: The General dictionary in Phase
1190   
1191    :returns: float density: crystal density in gm/cm^3
1192   
1193    '''
1194    mass = getMass(generalData)
1195    Volume = generalData['Cell'][7]
1196    density = mass/(0.6022137*Volume)
1197    return density,Volume/mass
1198   
1199def getWave(Parms):
1200    '''returns wavelength from Instrument parameters dictionary
1201   
1202    :param dict Parms: Instrument parameters;
1203        must contain:
1204        Lam: single wavelength
1205        or
1206        Lam1: Ka1 radiation wavelength
1207   
1208    :returns: float wave: wavelength
1209   
1210    '''
1211    try:
1212        return Parms['Lam'][1]
1213    except KeyError:
1214        return Parms['Lam1'][1]
1215       
1216def getMeanWave(Parms):
1217    '''returns mean wavelength from Instrument parameters dictionary
1218   
1219    :param dict Parms: Instrument parameters;
1220        must contain:
1221        Lam: single wavelength
1222        or
1223        Lam1,Lam2: Ka1,Ka2 radiation wavelength
1224        I(L2)/I(L1): Ka2/Ka1 ratio
1225   
1226    :returns: float wave: mean wavelength
1227   
1228    '''
1229    try:
1230        return Parms['Lam'][1]
1231    except KeyError:
1232        meanLam = (Parms['Lam1'][1]+Parms['I(L2)/I(L1)'][1]*Parms['Lam2'][1])/   \
1233            (1.+Parms['I(L2)/I(L1)'][1])
1234        return meanLam
1235   
1236       
1237def El2Mass(Elements):
1238    '''compute molecular weight from Elements
1239   
1240    :param dict Elements: elements in molecular formula;
1241        each must contain
1242        Num: number of atoms in formula
1243        Mass: at. wt.
1244   
1245    :returns: float mass: molecular weight.
1246   
1247    '''
1248    mass = 0
1249    for El in Elements:
1250        mass += Elements[El]['Num']*Elements[El]['Mass']
1251    return mass
1252       
1253def Den2Vol(Elements,density):
1254    '''converts density to molecular volume
1255   
1256    :param dict Elements: elements in molecular formula;
1257        each must contain
1258        Num: number of atoms in formula
1259        Mass: at. wt.
1260    :param float density: material density in gm/cm^3
1261   
1262    :returns: float volume: molecular volume in A^3
1263   
1264    '''
1265    return El2Mass(Elements)/(density*0.6022137)
1266   
1267def Vol2Den(Elements,volume):
1268    '''converts volume to density
1269   
1270    :param dict Elements: elements in molecular formula;
1271        each must contain
1272        Num: number of atoms in formula
1273        Mass: at. wt.
1274    :param float volume: molecular volume in A^3
1275   
1276    :returns: float density: material density in gm/cm^3
1277   
1278    '''
1279    return El2Mass(Elements)/(volume*0.6022137)
1280   
1281def El2EstVol(Elements):
1282    '''Estimate volume from molecular formula; assumes atom volume = 10A^3
1283   
1284    :param dict Elements: elements in molecular formula;
1285        each must contain
1286        Num: number of atoms in formula
1287   
1288    :returns: float volume: estimate of molecular volume in A^3
1289   
1290    '''
1291    vol = 0
1292    for El in Elements:
1293        vol += 10.*Elements[El]['Num']
1294    return vol
1295   
1296def XScattDen(Elements,vol,wave=0.):
1297    '''Estimate X-ray scattering density from molecular formula & volume;
1298    ignores valence, but includes anomalous effects
1299   
1300    :param dict Elements: elements in molecular formula;
1301        each element must contain
1302        Num: number of atoms in formula
1303        Z: atomic number
1304    :param float vol: molecular volume in A^3
1305    :param float wave: optional wavelength in A
1306   
1307    :returns: float rho: scattering density in 10^10cm^-2;
1308        if wave > 0 the includes f' contribution
1309    :returns: float mu: if wave>0 absorption coeff in cm^-1 ; otherwise 0
1310    :returns: float fpp: if wave>0 f" in 10^10cm^-2; otherwise 0
1311   
1312    '''
1313    rho = 0
1314    mu = 0
1315    fpp = 0
1316    if wave: 
1317        Xanom = XAnomAbs(Elements,wave)
1318    for El in Elements:
1319        f0 = Elements[El]['Z']
1320        if wave:
1321            f0 += Xanom[El][0]
1322            fpp += Xanom[El][1]*Elements[El]['Num']
1323            mu += Xanom[El][2]*Elements[El]['Num']
1324        rho += Elements[El]['Num']*f0
1325    return 28.179*rho/vol,mu/vol,28.179*fpp/vol
1326   
1327def NCScattDen(Elements,vol,wave=0.):
1328    '''Estimate neutron scattering density from molecular formula & volume;
1329    ignores valence, but includes anomalous effects
1330   
1331    :param dict Elements: elements in molecular formula;
1332        each element must contain
1333        Num: number of atoms in formula
1334        Z: atomic number
1335    :param float vol: molecular volume in A^3
1336    :param float wave: optional wavelength in A
1337   
1338    :returns: float rho: scattering density in 10^10cm^-2;
1339        if wave > 0 the includes f' contribution
1340    :returns: float mu: if wave>0 absorption coeff in cm^-1 ; otherwise 0
1341    :returns: float fpp: if wave>0 f" in 10^10cm^-2; otherwise 0
1342   
1343    '''
1344    rho = 0
1345    mu = 0
1346    bpp = 0
1347    for El in Elements:
1348        isotope = Elements[El]['Isotope']
1349        b0 = Elements[El]['Isotopes'][isotope]['SL'][0]
1350        mu += Elements[El]['Isotopes'][isotope].get('SA',0.)*Elements[El]['Num']
1351        if wave and 'BW-LS' in Elements[El]['Isotopes'][isotope]:
1352            Re,Im,E0,gam,A,E1,B,E2 = Elements[El]['Isotopes'][isotope]['BW-LS'][1:]
1353            Emev = 81.80703/wave**2
1354            T0 = Emev-E0
1355            T1 = Emev-E1
1356            T2 = Emev-E2
1357            D0 = T0**2+gam**2
1358            D1 = T1**2+gam**2
1359            D2 = T2**2+gam**2
1360            b0 += Re*(T0/D0+A*T1/D1+B*T2/D2)
1361            bpp += Im*(1/D0+A/D1+B/D2)
1362        else:
1363            bpp += Elements[El]['Isotopes'][isotope]['SL'][1]
1364        rho += Elements[El]['Num']*b0
1365    if wave: mu *= wave
1366    return 100.*rho/vol,mu/vol,100.*bpp/vol
1367   
1368def wavekE(wavekE):
1369    '''Convert wavelength to energy & vise versa
1370   
1371    :param float waveKe:wavelength in A or energy in kE
1372   
1373    :returns float waveKe:the other one
1374   
1375    '''
1376    return 12.397639/wavekE
1377       
1378def XAnomAbs(Elements,wave):
1379    kE = wavekE(wave)
1380    Xanom = {}
1381    for El in Elements:
1382        Orbs = G2el.GetXsectionCoeff(El)
1383        Xanom[El] = G2el.FPcalc(Orbs, kE)
1384    return Xanom        #f',f", mu
1385   
1386################################################################################
1387#### Modulation math
1388################################################################################
1389
1390def makeWaves(waveTypes,FSSdata,XSSdata,USSdata,MSSdata,Mast):
1391    '''
1392    waveTypes: array nAtoms: 'Fourier','ZigZag' or 'Block'
1393    FSSdata: array 2 x atoms x waves    (sin,cos terms)
1394    XSSdata: array 2x3 x atoms X waves (sin,cos terms)
1395    USSdata: array 2x6 x atoms X waves (sin,cos terms)
1396    MSSdata: array 2x3 x atoms X waves (sin,cos terms)
1397   
1398    Mast: array orthogonalization matrix for Uij
1399    '''
1400    ngl = 36                    #selected for integer steps for 1/6,1/4,1/3...
1401    glTau,glWt = pwd.pygauleg(0.,1.,ngl)         #get Gauss-Legendre intervals & weights
1402    mglTau = np.arange(0.,1.,1./ngl)
1403    Ax = np.array(XSSdata[:3]).T   #atoms x waves x sin pos mods
1404    Bx = np.array(XSSdata[3:]).T   #...cos pos mods
1405    Af = np.array(FSSdata[0]).T    #sin frac mods x waves x atoms
1406    Bf = np.array(FSSdata[1]).T    #cos frac mods...
1407    Au = Mast*np.array(G2lat.U6toUij(USSdata[:6])).T   #atoms x waves x sin Uij mods as betaij
1408    Bu = Mast*np.array(G2lat.U6toUij(USSdata[6:])).T   #...cos Uij mods as betaij
1409    Am = np.array(MSSdata[:3]).T   #atoms x waves x sin pos mods
1410    Bm = np.array(MSSdata[3:]).T   #...cos pos mods
1411    nWaves = [Af.shape[1],Ax.shape[1],Au.shape[1],Am.shape[1]] 
1412    if nWaves[0]:
1413        tauF = np.arange(1.,nWaves[0]+1)[:,nxs]*glTau  #Fwaves x ngl
1414        FmodA = Af[:,:,nxs]*np.sin(twopi*tauF[nxs,:,:])   #atoms X Fwaves X ngl
1415        FmodB = Bf[:,:,nxs]*np.cos(twopi*tauF[nxs,:,:])
1416        Fmod = np.sum(1.0+FmodA+FmodB,axis=1)             #atoms X ngl; sum waves
1417    else:
1418        Fmod = 1.0           
1419    XmodZ = np.zeros((Ax.shape[0],Ax.shape[1],3,ngl))
1420    XmodA = np.zeros((Ax.shape[0],Ax.shape[1],3,ngl))
1421    XmodB = np.zeros((Ax.shape[0],Ax.shape[1],3,ngl))
1422    for iatm in range(Ax.shape[0]):
1423        nx = 0
1424        if 'ZigZag' in waveTypes[iatm]:
1425            nx = 1
1426            Tmm = Ax[iatm][0][:2]                       
1427            XYZmax = np.array([Ax[iatm][0][2],Bx[iatm][0][0],Bx[iatm][0][1]])
1428            XmodZ[iatm][0] += posZigZag(glTau,Tmm,XYZmax).T
1429        elif 'Block' in waveTypes[iatm]:
1430            nx = 1
1431            Tmm = Ax[iatm][0][:2]                       
1432            XYZmax = np.array([Ax[iatm][0][2],Bx[iatm][0][0],Bx[iatm][0][1]])
1433            XmodZ[iatm][0] += posBlock(glTau,Tmm,XYZmax).T
1434        tauX = np.arange(1.,nWaves[1]+1-nx)[:,nxs]*glTau  #Xwaves x ngl
1435        if nx:   
1436            XmodA[iatm][:-nx] = Ax[iatm,nx:,:,nxs]*np.sin(twopi*tauX[nxs,:,nxs,:]) #atoms X waves X 3 X ngl
1437            XmodB[iatm][:-nx] = Bx[iatm,nx:,:,nxs]*np.cos(twopi*tauX[nxs,:,nxs,:]) #ditto
1438        else:
1439            XmodA[iatm] = Ax[iatm,:,:,nxs]*np.sin(twopi*tauX[nxs,:,nxs,:]) #atoms X waves X 3 X ngl
1440            XmodB[iatm] = Bx[iatm,:,:,nxs]*np.cos(twopi*tauX[nxs,:,nxs,:]) #ditto
1441    Xmod = np.sum(XmodA+XmodB+XmodZ,axis=1)                #atoms X 3 X ngl; sum waves
1442    Xmod = np.swapaxes(Xmod,1,2)
1443    if nWaves[2]:
1444        tauU = np.arange(1.,nWaves[2]+1)[:,nxs]*glTau     #Uwaves x ngl
1445        UmodA = Au[:,:,:,:,nxs]*np.sin(twopi*tauU[nxs,:,nxs,nxs,:]) #atoms x waves x 3x3 x ngl
1446        UmodB = Bu[:,:,:,:,nxs]*np.cos(twopi*tauU[nxs,:,nxs,nxs,:]) #ditto
1447        Umod = np.swapaxes(np.sum(UmodA+UmodB,axis=1),1,3)      #atoms x 3x3 x ngl; sum waves
1448    else:
1449        Umod = 1.0
1450    if nWaves[3]:
1451        tauM = np.arange(1.,nWaves[3]+1-nx)[:,nxs]*mglTau  #Mwaves x ngl
1452        MmodA = Am[:,:,:,nxs]*np.sin(twopi*tauM[nxs,:,nxs,:]) #atoms X waves X 3 X ngl
1453        MmodB = Bm[:,:,:,nxs]*np.cos(twopi*tauM[nxs,:,nxs,:]) #ditto
1454        Mmod = np.sum(MmodA+MmodB,axis=1)
1455        Mmod = np.swapaxes(Mmod,1,2)    #Mxyz,Ntau,Natm
1456    else:
1457        Mmod = 1.0
1458    return ngl,nWaves,Fmod,Xmod,Umod,Mmod,glTau,glWt
1459
1460def MagMod(glTau,XYZ,modQ,MSSdata,SGData,SSGData):
1461    '''
1462    this needs to make magnetic moment modulations & magnitudes as
1463    fxn of gTau points; NB: this allows only 1 mag. wave fxn.
1464    '''
1465    Am = np.array(MSSdata[3:]).T[:,0,:]   #atoms x cos pos mods; only 1 wave used
1466    Bm = np.array(MSSdata[:3]).T[:,0,:]   #...sin pos mods
1467    SGMT = np.array([ops[0] for ops in SGData['SGOps']])        #not .T!!
1468    Sinv = np.array([nl.inv(ops[0]) for ops in SSGData['SSGOps']])
1469    SGT = np.array([ops[1] for ops in SSGData['SSGOps']])
1470    if SGData['SGInv']:
1471        SGMT = np.vstack((SGMT,-SGMT))
1472        Sinv = np.vstack((Sinv,-Sinv))
1473        SGT = np.vstack((SGT,-SGT))
1474    SGMT = np.vstack([SGMT for cen in SGData['SGCen']])
1475    Sinv = np.vstack([Sinv for cen in SGData['SGCen']])
1476    SGT = np.vstack([SGT+cen for cen in SSGData['SSGCen']])%1.
1477    if SGData['SGGray']:
1478        SGMT = np.vstack((SGMT,SGMT))
1479        Sinv = np.vstack((Sinv,Sinv))
1480        SGT = np.vstack((SGT,SGT+np.array([0.,0.,0.,.5])))%1.
1481    mst = Sinv[:,3,:3]
1482    epsinv = Sinv[:,3,3]
1483    phi = np.inner(XYZ,modQ).T
1484    TA = np.sum(mst[nxs,:,:]*(XYZ-SGT[:,:3][nxs,:,:]),axis=-1).T
1485    phase =  TA[nxs,:,:] + epsinv[nxs,:,nxs]*(glTau[:,nxs,nxs]-SGT[:,3][nxs,:,nxs]+phi[nxs,:,:])
1486    psin = np.sin(twopi*phase)
1487    pcos = np.cos(twopi*phase)
1488    MmodA = Am[nxs,nxs,:,:]*pcos[:,:,:,nxs]    #cos term
1489    MmodB = Bm[nxs,nxs,:,:]*psin[:,:,:,nxs]    #sin term
1490    MmodA = np.sum(SGMT[nxs,:,nxs,:,:]*MmodA[:,:,:,nxs,:],axis=-1)*SGData['SpnFlp'][nxs,:,nxs,nxs]
1491    MmodB = np.sum(SGMT[nxs,:,nxs,:,:]*MmodB[:,:,:,nxs,:],axis=-1)*SGData['SpnFlp'][nxs,:,nxs,nxs]
1492    return MmodA,MmodB    #Ntau,Nops,Natm,Mxyz; cos & sin parts; sum matches drawn atom moments
1493       
1494def Modulation(H,HP,nWaves,Fmod,Xmod,Umod,glTau,glWt):
1495    '''
1496    H: array nRefBlk x ops X hklt
1497    HP: array nRefBlk x ops X hklt proj to hkl
1498    nWaves: list number of waves for frac, pos, uij & mag
1499    Fmod: array 2 x atoms x waves    (sin,cos terms)
1500    Xmod: array atoms X 3 X ngl
1501    Umod: array atoms x 3x3 x ngl
1502    glTau,glWt: arrays Gauss-Lorentzian pos & wts
1503    '''
1504   
1505    if nWaves[2]:       #uij (adp) waves
1506        if len(HP.shape) > 2:
1507            HbH = np.exp(-np.sum(HP[:,:,nxs,nxs,:]*np.inner(HP,Umod),axis=-1)) # refBlk x ops x atoms x ngl add Overhauser corr.?
1508        else:
1509            HbH = np.exp(-np.sum(HP[:,nxs,nxs,:]*np.inner(HP,Umod),axis=-1)) # refBlk x ops x atoms x ngl add Overhauser corr.?
1510    else:
1511        HbH = 1.0
1512    HdotX = np.inner(HP,Xmod)                   #refBlk x ops x atoms X ngl
1513    if len(H.shape) > 2:
1514        D = H[:,:,3:]*glTau[nxs,nxs,:]              #m*e*tau; refBlk x ops X ngl
1515        HdotXD = twopi*(HdotX+D[:,:,nxs,:])
1516    else:
1517        D = H[:,3:]*glTau[nxs,:]              #m*e*tau; refBlk x ops X ngl
1518        HdotXD = twopi*(HdotX+D[:,nxs,:])
1519    cosHA = np.sum(Fmod*HbH*np.cos(HdotXD)*glWt,axis=-1)       #real part; refBlk X ops x atoms; sum for G-L integration
1520    sinHA = np.sum(Fmod*HbH*np.sin(HdotXD)*glWt,axis=-1)       #imag part; ditto
1521    return np.array([cosHA,sinHA])             # 2 x refBlk x SGops x atoms
1522   
1523def ModulationTw(H,HP,nWaves,Fmod,Xmod,Umod,glTau,glWt):
1524    '''
1525    H: array nRefBlk x tw x ops X hklt
1526    HP: array nRefBlk x tw x ops X hklt proj to hkl
1527    Fmod: array 2 x atoms x waves    (sin,cos terms)
1528    Xmod: array atoms X ngl X 3
1529    Umod: array atoms x ngl x 3x3
1530    glTau,glWt: arrays Gauss-Lorentzian pos & wts
1531    '''
1532   
1533    if nWaves[2]:
1534        if len(HP.shape) > 3:   #Blocks of reflections
1535            HbH = np.exp(-np.sum(HP[:,:,nxs,nxs,:]*np.inner(HP,Umod),axis=-1)) # refBlk x ops x atoms x ngl add Overhauser corr.?
1536        else:   #single reflections
1537            HbH = np.exp(-np.sum(HP[:,nxs,nxs,:]*np.inner(HP,Umod),axis=-1)) # refBlk x ops x atoms x ngl add Overhauser corr.?
1538    else:
1539        HbH = 1.0
1540    HdotX = np.inner(HP,Xmod)                   #refBlk x tw x ops x atoms X ngl
1541    if len(H.shape) > 3:
1542        D = glTau*H[:,:,:,3:,nxs]              #m*e*tau; refBlk x tw x ops X ngl
1543        HdotXD = twopi*(HdotX+D[:,:,:,nxs,:])
1544    else:
1545        D = H*glTau[nxs,:]              #m*e*tau; refBlk x ops X ngl
1546        HdotXD = twopi*(HdotX+D[:,nxs,:])
1547    cosHA = np.sum(Fmod*HbH*np.cos(HdotXD)*glWt,axis=-1)       #real part; refBlk X ops x atoms; sum for G-L integration
1548    sinHA = np.sum(Fmod*HbH*np.sin(HdotXD)*glWt,axis=-1)       #imag part; ditto
1549    return np.array([cosHA,sinHA])             # 2 x refBlk x SGops x atoms
1550   
1551def makeWavesDerv(ngl,waveTypes,FSSdata,XSSdata,USSdata,Mast):
1552    '''
1553    Only for Fourier waves for fraction, position & adp (probably not used for magnetism)
1554    FSSdata: array 2 x atoms x waves    (sin,cos terms)
1555    XSSdata: array 2x3 x atoms X waves (sin,cos terms)
1556    USSdata: array 2x6 x atoms X waves (sin,cos terms)
1557    Mast: array orthogonalization matrix for Uij
1558    '''
1559    glTau,glWt = pwd.pygauleg(0.,1.,ngl)         #get Gauss-Legendre intervals & weights
1560    waveShapes = [FSSdata.T.shape,XSSdata.T.shape,USSdata.T.shape]
1561    Af = np.array(FSSdata[0]).T    #sin frac mods x waves x atoms
1562    Bf = np.array(FSSdata[1]).T    #cos frac mods...
1563    Ax = np.array(XSSdata[:3]).T   #...cos pos mods x waves x atoms
1564    Bx = np.array(XSSdata[3:]).T   #...cos pos mods
1565    Au = Mast*np.array(G2lat.U6toUij(USSdata[:6])).T   #atoms x waves x sin Uij mods
1566    Bu = Mast*np.array(G2lat.U6toUij(USSdata[6:])).T   #...cos Uij mods
1567    nWaves = [Af.shape[1],Ax.shape[1],Au.shape[1]] 
1568    StauX = np.zeros((Ax.shape[0],Ax.shape[1],3,ngl))    #atoms x waves x 3 x ngl
1569    CtauX = np.zeros((Ax.shape[0],Ax.shape[1],3,ngl))
1570    ZtauXt = np.zeros((Ax.shape[0],2,3,ngl))               #atoms x Tminmax x 3 x ngl
1571    ZtauXx = np.zeros((Ax.shape[0],3,ngl))               #atoms x XYZmax x ngl
1572    for iatm in range(Ax.shape[0]):
1573        nx = 0
1574        if 'ZigZag' in waveTypes[iatm]:
1575            nx = 1
1576        elif 'Block' in waveTypes[iatm]:
1577            nx = 1
1578        tauX = np.arange(1.,nWaves[1]+1-nx)[:,nxs]*glTau  #Xwaves x ngl
1579        if nx:   
1580            StauX[iatm][nx:] = np.ones_like(Ax)[iatm,nx:,:,nxs]*np.sin(twopi*tauX)[nxs,:,nxs,:]   #atoms X waves X 3(xyz) X ngl
1581            CtauX[iatm][nx:] = np.ones_like(Bx)[iatm,nx:,:,nxs]*np.cos(twopi*tauX)[nxs,:,nxs,:]   #ditto
1582        else:
1583            StauX[iatm] = np.ones_like(Ax)[iatm,:,:,nxs]*np.sin(twopi*tauX)[nxs,:,nxs,:]   #atoms X waves X 3(xyz) X ngl
1584            CtauX[iatm] = np.ones_like(Bx)[iatm,:,:,nxs]*np.cos(twopi*tauX)[nxs,:,nxs,:]   #ditto
1585    if nWaves[0]:
1586        tauF = np.arange(1.,nWaves[0]+1)[:,nxs]*glTau  #Fwaves x ngl
1587        StauF = np.ones_like(Af)[:,:,nxs]*np.sin(twopi*tauF)[nxs,:,:] #also dFmod/dAf
1588        CtauF = np.ones_like(Bf)[:,:,nxs]*np.cos(twopi*tauF)[nxs,:,:] #also dFmod/dBf
1589    else:
1590        StauF = 1.0
1591        CtauF = 1.0
1592    if nWaves[2]:
1593        tauU = np.arange(1.,nWaves[2]+1)[:,nxs]*glTau     #Uwaves x ngl
1594        StauU = np.ones_like(Au)[:,:,:,:,nxs]*np.sin(twopi*tauU)[nxs,:,nxs,nxs,:]   #also dUmodA/dAu
1595        CtauU = np.ones_like(Bu)[:,:,:,:,nxs]*np.cos(twopi*tauU)[nxs,:,nxs,nxs,:]   #also dUmodB/dBu
1596        UmodA = Au[:,:,:,:,nxs]*StauU #atoms x waves x 3x3 x ngl
1597        UmodB = Bu[:,:,:,:,nxs]*CtauU #ditto
1598#derivs need to be ops x atoms x waves x 6uij; ops x atoms x waves x ngl x 6uij before sum
1599        StauU = np.rollaxis(np.rollaxis(np.swapaxes(StauU,2,4),-1),-1)
1600        CtauU = np.rollaxis(np.rollaxis(np.swapaxes(CtauU,2,4),-1),-1)
1601    else:
1602        StauU = 1.0
1603        CtauU = 1.0
1604        UmodA = 0.
1605        UmodB = 0.
1606    return waveShapes,[StauF,CtauF],[StauX,CtauX,ZtauXt,ZtauXx],[StauU,CtauU],UmodA+UmodB
1607   
1608def ModulationDerv(H,HP,Hij,nWaves,waveShapes,Fmod,Xmod,UmodAB,SCtauF,SCtauX,SCtauU,glTau,glWt):
1609    '''
1610    Compute Fourier modulation derivatives
1611    H: array ops X hklt proj to hkl
1612    HP: array ops X hklt proj to hkl
1613    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
1614    '''
1615   
1616    Mf = [H.shape[0],]+list(waveShapes[0])    #=[ops,atoms,waves,2] (sin+cos frac mods)
1617    dGdMfC = np.zeros(Mf)
1618    dGdMfS = np.zeros(Mf)
1619    Mx = [H.shape[0],]+list(waveShapes[1])   #=[ops,atoms,waves,6] (sin+cos pos mods)
1620    dGdMxC = np.zeros(Mx)
1621    dGdMxS = np.zeros(Mx)
1622    Mu = [H.shape[0],]+list(waveShapes[2])    #=[ops,atoms,waves,12] (sin+cos Uij mods)
1623    dGdMuC = np.zeros(Mu)
1624    dGdMuS = np.zeros(Mu)
1625   
1626    D = twopi*H[:,3][:,nxs]*glTau[nxs,:]              #m*e*tau; ops X ngl
1627    HdotX = twopi*np.inner(HP,Xmod)        #ops x atoms X ngl
1628    HdotXD = HdotX+D[:,nxs,:]
1629    if nWaves[2]:
1630        Umod = np.swapaxes((UmodAB),2,4)      #atoms x waves x ngl x 3x3 (symmetric so I can do this!)
1631        HuH = np.sum(HP[:,nxs,nxs,nxs]*np.inner(HP,Umod),axis=-1)    #ops x atoms x waves x ngl
1632        HuH = np.sum(HP[:,nxs,nxs,nxs]*np.inner(HP,Umod),axis=-1)    #ops x atoms x waves x ngl
1633        HbH = np.exp(-np.sum(HuH,axis=-2)) # ops x atoms x ngl; sum waves - OK vs Modulation version
1634#        part1 = -np.exp(-HuH)*Fmod[nxs,:,nxs,:]    #ops x atoms x waves x ngl
1635        part1 = -np.exp(-HuH)*Fmod    #ops x atoms x waves x ngl
1636        dUdAu = Hij[:,nxs,nxs,nxs,:]*np.rollaxis(G2lat.UijtoU6(SCtauU[0]),0,4)[nxs,:,:,:,:]    #ops x atoms x waves x ngl x 6sinUij
1637        dUdBu = Hij[:,nxs,nxs,nxs,:]*np.rollaxis(G2lat.UijtoU6(SCtauU[1]),0,4)[nxs,:,:,:,:]    #ops x atoms x waves x ngl x 6cosUij
1638        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
1639        dGdMuCb = np.sum(part1[:,:,:,:,nxs]*dUdBu*np.cos(HdotXD)[:,:,nxs,:,nxs]*glWt[nxs,nxs,nxs,:,nxs],axis=-2)
1640        dGdMuC = np.concatenate((dGdMuCa,dGdMuCb),axis=-1)   #ops x atoms x waves x 12uij
1641        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
1642        dGdMuSb = np.sum(part1[:,:,:,:,nxs]*dUdBu*np.sin(HdotXD)[:,:,nxs,:,nxs]*glWt[nxs,nxs,nxs,:,nxs],axis=-2)
1643        dGdMuS = np.concatenate((dGdMuSa,dGdMuSb),axis=-1)   #ops x atoms x waves x 12uij
1644    else:
1645        HbH = np.ones_like(HdotX)
1646    dHdXA = twopi*HP[:,nxs,nxs,nxs,:]*np.swapaxes(SCtauX[0],-1,-2)[nxs,:,:,:,:]    #ops x atoms x sine waves x ngl x xyz
1647    dHdXB = twopi*HP[:,nxs,nxs,nxs,:]*np.swapaxes(SCtauX[1],-1,-2)[nxs,:,:,:,:]    #ditto - cos waves
1648# ops x atoms x waves x 2xyz - real part - good
1649#    dGdMxCa = -np.sum((Fmod[nxs,:,:]*HbH)[:,:,nxs,:,nxs]*(dHdXA*np.sin(HdotXD)[:,:,nxs,:,nxs])*glWt[nxs,nxs,nxs,:,nxs],axis=-2)
1650#    dGdMxCb = -np.sum((Fmod[nxs,:,:]*HbH)[:,:,nxs,:,nxs]*(dHdXB*np.sin(HdotXD)[:,:,nxs,:,nxs])*glWt[nxs,nxs,nxs,:,nxs],axis=-2)
1651    dGdMxCa = -np.sum((Fmod*HbH)[:,:,nxs,:,nxs]*(dHdXA*np.sin(HdotXD)[:,:,nxs,:,nxs])*glWt[nxs,nxs,nxs,:,nxs],axis=-2)
1652    dGdMxCb = -np.sum((Fmod*HbH)[:,:,nxs,:,nxs]*(dHdXB*np.sin(HdotXD)[:,:,nxs,:,nxs])*glWt[nxs,nxs,nxs,:,nxs],axis=-2)
1653    dGdMxC = np.concatenate((dGdMxCa,dGdMxCb),axis=-1)
1654# ops x atoms x waves x 2xyz - imag part - good
1655#    dGdMxSa = np.sum((Fmod[nxs,:,:]*HbH)[:,:,nxs,:,nxs]*(dHdXA*np.cos(HdotXD)[:,:,nxs,:,nxs])*glWt[nxs,nxs,nxs,:,nxs],axis=-2)   
1656#    dGdMxSb = np.sum((Fmod[nxs,:,:]*HbH)[:,:,nxs,:,nxs]*(dHdXB*np.cos(HdotXD)[:,:,nxs,:,nxs])*glWt[nxs,nxs,nxs,:,nxs],axis=-2)   
1657    dGdMxSa = np.sum((Fmod*HbH)[:,:,nxs,:,nxs]*(dHdXA*np.cos(HdotXD)[:,:,nxs,:,nxs])*glWt[nxs,nxs,nxs,:,nxs],axis=-2)   
1658    dGdMxSb = np.sum((Fmod*HbH)[:,:,nxs,:,nxs]*(dHdXB*np.cos(HdotXD)[:,:,nxs,:,nxs])*glWt[nxs,nxs,nxs,:,nxs],axis=-2)   
1659    dGdMxS = np.concatenate((dGdMxSa,dGdMxSb),axis=-1)
1660    return [dGdMfC,dGdMfS],[dGdMxC,dGdMxS],[dGdMuC,dGdMuS]
1661       
1662def posFourier(tau,psin,pcos):
1663    A = np.array([ps[:,nxs]*np.sin(2*np.pi*(i+1)*tau) for i,ps in enumerate(psin)])
1664    B = np.array([pc[:,nxs]*np.cos(2*np.pi*(i+1)*tau) for i,pc in enumerate(pcos)])
1665    return np.sum(A,axis=0)+np.sum(B,axis=0)
1666   
1667def posZigZag(T,Tmm,Xmax):
1668    DT = Tmm[1]-Tmm[0]
1669    Su = 2.*Xmax/DT
1670    Sd = 2.*Xmax/(1.-DT)
1671    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])
1672    return A
1673   
1674#def posZigZagDerv(T,Tmm,Xmax):
1675#    DT = Tmm[1]-Tmm[0]
1676#    Su = 2.*Xmax/DT
1677#    Sd = 2.*Xmax/(1.-DT)
1678#    dAdT = np.zeros((2,3,len(T)))
1679#    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
1680#    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
1681#    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])
1682#    return dAdT,dAdX
1683
1684def posBlock(T,Tmm,Xmax):
1685    A = np.array([np.where(Tmm[0] < t%1. <= Tmm[1],-Xmax,Xmax) for t in T])
1686    return A
1687   
1688#def posBlockDerv(T,Tmm,Xmax):
1689#    dAdT = np.zeros((2,3,len(T)))
1690#    ind = np.searchsorted(T,Tmm)
1691#    dAdT[0,:,ind[0]] = -Xmax/len(T)
1692#    dAdT[1,:,ind[1]] = Xmax/len(T)
1693#    dAdX = np.ones(3)[:,nxs]*np.array([np.where(Tmm[0] < t <= Tmm[1],-1.,1.) for t in T])  #OK
1694#    return dAdT,dAdX
1695   
1696def fracCrenel(tau,Toff,Twid):
1697    Tau = (tau-Toff)%1.
1698    A = np.where(Tau<Twid,1.,0.)
1699    return A
1700   
1701def fracFourier(tau,fsin,fcos):
1702    if len(fsin) == 1:
1703        A = np.array([fsin[0]*np.sin(2.*np.pi*tau)])
1704        B = np.array([fcos[0]*np.cos(2.*np.pi*tau)])
1705    else:
1706        A = np.array([fs[:,nxs]*np.sin(2.*np.pi*(i+1)*tau) for i,fs in enumerate(fsin)])
1707        B = np.array([fc[:,nxs]*np.cos(2.*np.pi*(i+1)*tau) for i,fc in enumerate(fcos)])
1708    return np.sum(A,axis=0)+np.sum(B,axis=0)
1709
1710def ApplyModulation(data,tau):
1711    '''Applies modulation to drawing atom positions & Uijs for given tau
1712    '''
1713    generalData = data['General']
1714    cell = generalData['Cell'][1:7]
1715    G,g = G2lat.cell2Gmat(cell)
1716    SGData = generalData['SGData']
1717    SSGData = generalData['SSGData']
1718    cx,ct,cs,cia = generalData['AtomPtrs']
1719    drawingData = data['Drawing']
1720    modul = generalData['SuperVec'][0]
1721    dcx,dct,dcs,dci = drawingData['atomPtrs']
1722    atoms = data['Atoms']
1723    drawAtoms = drawingData['Atoms']
1724    Fade = np.ones(len(drawAtoms))
1725    for atom in atoms:
1726        atxyz = np.array(atom[cx:cx+3])
1727        atuij = np.array(atom[cia+2:cia+8])
1728        Sfrac = atom[-1]['SS1']['Sfrac']
1729        Spos = atom[-1]['SS1']['Spos']
1730        Sadp = atom[-1]['SS1']['Sadp']
1731        if generalData['Type'] == 'magnetic':
1732            Smag = atom[-1]['SS1']['Smag']
1733            atmom = np.array(atom[cx+4:cx+7])
1734        indx = FindAtomIndexByIDs(drawAtoms,dci,[atom[cia+8],],True)
1735        for ind in indx:
1736            drawatom = drawAtoms[ind]
1737            opr = drawatom[dcs-1]
1738            sop,ssop,icent,cent,unit = G2spc.OpsfromStringOps(opr,SGData,SSGData)
1739            drxyz = (np.inner(sop[0],atxyz)+sop[1]+cent)*icent+np.array(unit)
1740            tauT = G2spc.getTauT(tau,sop,ssop,drxyz,modul)[-1]
1741            tauT *= icent       #invert wave on -1
1742#            print(tau,tauT,opr,G2spc.MT2text(sop).replace(' ',''),G2spc.SSMT2text(ssop).replace(' ',''))
1743            wave = np.zeros(3)
1744            uwave = np.zeros(6)
1745            mom = np.zeros(3)
1746            if len(Sfrac):
1747                scof = []
1748                ccof = []
1749                waveType = Sfrac[0]
1750                for i,sfrac in enumerate(Sfrac[1:]):
1751                    if not i and 'Crenel' in waveType:
1752                        Fade[ind] += fracCrenel(tauT,sfrac[0][0],sfrac[0][1])
1753                    else:
1754                        scof.append(sfrac[0][0])
1755                        ccof.append(sfrac[0][1])
1756                    if len(scof):
1757                        Fade[ind] += np.sum(fracFourier(tauT,scof,ccof))                           
1758            if len(Spos):
1759                scof = []
1760                ccof = []
1761                waveType = Spos[0]
1762                for i,spos in enumerate(Spos[1:]):
1763                    if waveType in ['ZigZag','Block'] and not i:
1764                        Tminmax = spos[0][:2]
1765                        XYZmax = np.array(spos[0][2:5])
1766                        if waveType == 'Block':
1767                            wave = np.array(posBlock([tauT,],Tminmax,XYZmax))[0]
1768                        elif waveType == 'ZigZag':
1769                            wave = np.array(posZigZag([tauT,],Tminmax,XYZmax))[0]
1770                    else:
1771                        scof.append(spos[0][:3])
1772                        ccof.append(spos[0][3:])
1773                if len(scof):
1774                    wave += np.sum(posFourier(tauT,np.array(scof),np.array(ccof)),axis=1)
1775            if generalData['Type'] == 'magnetic' and len(Smag):
1776                scof = []
1777                ccof = []
1778                waveType = Smag[0]
1779                for i,spos in enumerate(Smag[1:]):
1780                    scof.append(spos[0][:3])
1781                    ccof.append(spos[0][3:])
1782                if len(scof):
1783                    mom += np.sum(posFourier(tauT,np.array(scof),np.array(ccof)),axis=1)
1784            if len(Sadp):
1785                scof = []
1786                ccof = []
1787                waveType = Sadp[0]
1788                for i,sadp in enumerate(Sadp[1:]):
1789                    scof.append(sadp[0][:6])
1790                    ccof.append(sadp[0][6:])
1791                ures = posFourier(tauT,np.array(scof),np.array(ccof))
1792                if np.any(ures):
1793                    uwave += np.sum(ures,axis=1)
1794            if atom[cia] == 'A':                   
1795                X,U = G2spc.ApplyStringOps(opr,SGData,atxyz+wave,atuij+uwave)
1796                drawatom[dcx:dcx+3] = X
1797                drawatom[dci-6:dci] = U
1798            else:
1799                X = G2spc.ApplyStringOps(opr,SGData,atxyz+wave)
1800                drawatom[dcx:dcx+3] = X
1801            if generalData['Type'] == 'magnetic':
1802                M = G2spc.ApplyStringOpsMom(opr,SGData,SSGData,atmom+mom)
1803                drawatom[dcx+3:dcx+6] = M
1804    return drawAtoms,Fade
1805   
1806# gauleg.py Gauss Legendre numerical quadrature, x and w computation
1807# integrate from a to b using n evaluations of the function f(x) 
1808# usage: from gauleg import gaulegf         
1809#        x,w = gaulegf( a, b, n)                               
1810#        area = 0.0                                           
1811#        for i in range(1,n+1):          #  yes, 1..n                   
1812#          area += w[i]*f(x[i])                                   
1813
1814def gaulegf(a, b, n):
1815    x = range(n+1) # x[0] unused
1816    w = range(n+1) # w[0] unused
1817    eps = 3.0E-14
1818    m = (n+1)/2
1819    xm = 0.5*(b+a)
1820    xl = 0.5*(b-a)
1821    for i in range(1,m+1):
1822        z = math.cos(3.141592654*(i-0.25)/(n+0.5))
1823        while True:
1824            p1 = 1.0
1825            p2 = 0.0
1826            for j in range(1,n+1):
1827                p3 = p2
1828                p2 = p1
1829                p1 = ((2.0*j-1.0)*z*p2-(j-1.0)*p3)/j
1830       
1831            pp = n*(z*p1-p2)/(z*z-1.0)
1832            z1 = z
1833            z = z1 - p1/pp
1834            if abs(z-z1) <= eps:
1835                break
1836
1837        x[i] = xm - xl*z
1838        x[n+1-i] = xm + xl*z
1839        w[i] = 2.0*xl/((1.0-z*z)*pp*pp)
1840        w[n+1-i] = w[i]
1841    return np.array(x), np.array(w)
1842# end gaulegf
1843   
1844   
1845def BessJn(nmax,x):
1846    ''' compute Bessel function J(n,x) from scipy routine & recurrance relation
1847    returns sequence of J(n,x) for n in range [-nmax...0...nmax]
1848   
1849    :param  integer nmax: maximul order for Jn(x)
1850    :param  float x: argument for Jn(x)
1851   
1852    :returns numpy array: [J(-nmax,x)...J(0,x)...J(nmax,x)]
1853   
1854    '''
1855    import scipy.special as sp
1856    bessJn = np.zeros(2*nmax+1)
1857    bessJn[nmax] = sp.j0(x)
1858    bessJn[nmax+1] = sp.j1(x)
1859    bessJn[nmax-1] = -bessJn[nmax+1]
1860    for i in range(2,nmax+1):
1861        bessJn[i+nmax] = 2*(i-1)*bessJn[nmax+i-1]/x-bessJn[nmax+i-2]
1862        bessJn[nmax-i] = bessJn[i+nmax]*(-1)**i
1863    return bessJn
1864   
1865def BessIn(nmax,x):
1866    ''' compute modified Bessel function I(n,x) from scipy routines & recurrance relation
1867    returns sequence of I(n,x) for n in range [-nmax...0...nmax]
1868   
1869    :param  integer nmax: maximul order for In(x)
1870    :param  float x: argument for In(x)
1871   
1872    :returns numpy array: [I(-nmax,x)...I(0,x)...I(nmax,x)]
1873   
1874    '''
1875    import scipy.special as sp
1876    bessIn = np.zeros(2*nmax+1)
1877    bessIn[nmax] = sp.i0(x)
1878    bessIn[nmax+1] = sp.i1(x)
1879    bessIn[nmax-1] = bessIn[nmax+1]
1880    for i in range(2,nmax+1):
1881        bessIn[i+nmax] = bessIn[nmax+i-2]-2*(i-1)*bessIn[nmax+i-1]/x
1882        bessIn[nmax-i] = bessIn[i+nmax]
1883    return bessIn
1884       
1885   
1886################################################################################
1887##### distance, angle, planes, torsion stuff
1888################################################################################
1889
1890def CalcDist(distance_dict, distance_atoms, parmDict):
1891    if not len(parmDict):
1892        return 0.
1893    pId = distance_dict['pId']
1894    A = [parmDict['%s::A%d'%(pId,i)] for i in range(6)]
1895    Amat = G2lat.cell2AB(G2lat.A2cell(A))[0]
1896    Oxyz = [parmDict['%s::A%s:%d'%(pId,x,distance_atoms[0])] for x in ['x','y','z']]
1897    Txyz = [parmDict['%s::A%s:%d'%(pId,x,distance_atoms[1])] for x in ['x','y','z']]
1898    inv = 1
1899    symNo = distance_dict['symNo']
1900    if symNo < 0:
1901        inv = -1
1902        symNo *= -1
1903    cen = symNo//100
1904    op = symNo%100-1
1905    M,T = distance_dict['SGData']['SGOps'][op]
1906    D = T*inv+distance_dict['SGData']['SGCen'][cen]
1907    D += distance_dict['cellNo']
1908    Txyz = np.inner(M*inv,Txyz)+D
1909    dist = np.sqrt(np.sum(np.inner(Amat,(Txyz-Oxyz))**2))
1910#    GSASIIpath.IPyBreak()
1911    return dist   
1912   
1913def CalcDistDeriv(distance_dict, distance_atoms, parmDict):
1914    if not len(parmDict):
1915        return None
1916    pId = distance_dict['pId']
1917    A = [parmDict['%s::A%d'%(pId,i)] for i in range(6)]
1918    Amat = G2lat.cell2AB(G2lat.A2cell(A))[0]
1919    Oxyz = [parmDict['%s::A%s:%d'%(pId,x,distance_atoms[0])] for x in ['x','y','z']]
1920    Txyz = [parmDict['%s::A%s:%d'%(pId,x,distance_atoms[1])] for x in ['x','y','z']]
1921    symNo = distance_dict['symNo']
1922    Tunit = distance_dict['cellNo']
1923    SGData = distance_dict['SGData']   
1924    deriv = getDistDerv(Oxyz,Txyz,Amat,Tunit,symNo,SGData)
1925    return deriv
1926   
1927def CalcAngle(angle_dict, angle_atoms, parmDict):
1928    if not len(parmDict):
1929        return 0.
1930    pId = angle_dict['pId']
1931    A = [parmDict['%s::A%d'%(pId,i)] for i in range(6)]
1932    Amat = G2lat.cell2AB(G2lat.A2cell(A))[0]
1933    Oxyz = [parmDict['%s::A%s:%d'%(pId,x,angle_atoms[0])] for x in ['x','y','z']]
1934    Axyz = [parmDict['%s::A%s:%d'%(pId,x,angle_atoms[1][0])] for x in ['x','y','z']]
1935    Bxyz = [parmDict['%s::A%s:%d'%(pId,x,angle_atoms[1][1])] for x in ['x','y','z']]
1936    ABxyz = [Axyz,Bxyz]
1937    symNo = angle_dict['symNo']
1938    vec = np.zeros((2,3))
1939    for i in range(2):
1940        inv = 1
1941        if symNo[i] < 0:
1942            inv = -1
1943        cen = inv*symNo[i]//100
1944        op = inv*symNo[i]%100-1
1945        M,T = angle_dict['SGData']['SGOps'][op]
1946        D = T*inv+angle_dict['SGData']['SGCen'][cen]
1947        D += angle_dict['cellNo'][i]
1948        ABxyz[i] = np.inner(M*inv,ABxyz[i])+D
1949        vec[i] = np.inner(Amat,(ABxyz[i]-Oxyz))
1950        dist = np.sqrt(np.sum(vec[i]**2))
1951        if not dist:
1952            return 0.
1953        vec[i] /= dist
1954    angle = acosd(np.sum(vec[0]*vec[1]))
1955    return angle
1956
1957def CalcAngleDeriv(angle_dict, angle_atoms, parmDict):
1958    if not len(parmDict):
1959        return None
1960    pId = angle_dict['pId']
1961    A = [parmDict['%s::A%d'%(pId,i)] for i in range(6)]
1962    Amat = G2lat.cell2AB(G2lat.A2cell(A))[0]
1963    Oxyz = [parmDict['%s::A%s:%d'%(pId,x,angle_atoms[0])] for x in ['x','y','z']]
1964    Axyz = [parmDict['%s::A%s:%d'%(pId,x,angle_atoms[1][0])] for x in ['x','y','z']]
1965    Bxyz = [parmDict['%s::A%s:%d'%(pId,x,angle_atoms[1][1])] for x in ['x','y','z']]
1966    symNo = angle_dict['symNo']
1967    Tunit = angle_dict['cellNo']
1968    SGData = angle_dict['SGData']   
1969    deriv = getAngleDerv(Oxyz,Axyz,Bxyz,Amat,Tunit,symNo,SGData)
1970    return deriv
1971
1972def getSyXYZ(XYZ,ops,SGData):
1973    '''default doc
1974   
1975    :param type name: description
1976   
1977    :returns: type name: description
1978   
1979    '''
1980    XYZout = np.zeros_like(XYZ)
1981    for i,[xyz,op] in enumerate(zip(XYZ,ops)):
1982        if op == '1':
1983            XYZout[i] = xyz
1984        else:
1985            oprs = op.split('+')
1986            unit = [0,0,0]
1987            if len(oprs)>1:
1988                unit = np.array(list(eval(oprs[1])))
1989            syop =int(oprs[0])
1990            inv = syop//abs(syop)
1991            syop *= inv
1992            cent = syop//100
1993            syop %= 100
1994            syop -= 1
1995            M,T = SGData['SGOps'][syop]
1996            XYZout[i] = (np.inner(M,xyz)+T)*inv+SGData['SGCen'][cent]+unit
1997    return XYZout
1998   
1999def getRestDist(XYZ,Amat):
2000    '''default doc string
2001   
2002    :param type name: description
2003   
2004    :returns: type name: description
2005   
2006    '''
2007    return np.sqrt(np.sum(np.inner(Amat,(XYZ[1]-XYZ[0]))**2))
2008   
2009def getRestDeriv(Func,XYZ,Amat,ops,SGData):
2010    '''default doc string
2011   
2012    :param type name: description
2013   
2014    :returns: type name: description
2015   
2016    '''
2017    deriv = np.zeros((len(XYZ),3))
2018    dx = 0.00001
2019    for j,xyz in enumerate(XYZ):
2020        for i,x in enumerate(np.array([[dx,0,0],[0,dx,0],[0,0,dx]])):
2021            XYZ[j] -= x
2022            d1 = Func(getSyXYZ(XYZ,ops,SGData),Amat)
2023            XYZ[j] += 2*x
2024            d2 = Func(getSyXYZ(XYZ,ops,SGData),Amat)
2025            XYZ[j] -= x
2026            deriv[j][i] = (d1-d2)/(2*dx)
2027    return deriv.flatten()
2028
2029def getRestAngle(XYZ,Amat):
2030    '''default doc string
2031   
2032    :param type name: description
2033   
2034    :returns: type name: description
2035   
2036    '''
2037   
2038    def calcVec(Ox,Tx,Amat):
2039        return np.inner(Amat,(Tx-Ox))
2040
2041    VecA = calcVec(XYZ[1],XYZ[0],Amat)
2042    VecA /= np.sqrt(np.sum(VecA**2))
2043    VecB = calcVec(XYZ[1],XYZ[2],Amat)
2044    VecB /= np.sqrt(np.sum(VecB**2))
2045    edge = VecB-VecA
2046    edge = np.sum(edge**2)
2047    angle = (2.-edge)/2.
2048    angle = max(angle,-1.)
2049    return acosd(angle)
2050   
2051def getRestPlane(XYZ,Amat):
2052    '''default doc string
2053   
2054    :param type name: description
2055   
2056    :returns: type name: description
2057   
2058    '''
2059    sumXYZ = np.zeros(3)
2060    for xyz in XYZ:
2061        sumXYZ += xyz
2062    sumXYZ /= len(XYZ)
2063    XYZ = np.array(XYZ)-sumXYZ
2064    XYZ = np.inner(Amat,XYZ).T
2065    Zmat = np.zeros((3,3))
2066    for i,xyz in enumerate(XYZ):
2067        Zmat += np.outer(xyz.T,xyz)
2068    Evec,Emat = nl.eig(Zmat)
2069    Evec = np.sqrt(Evec)/(len(XYZ)-3)
2070    Order = np.argsort(Evec)
2071    return Evec[Order[0]]
2072   
2073def getRestChiral(XYZ,Amat):   
2074    '''default doc string
2075   
2076    :param type name: description
2077   
2078    :returns: type name: description
2079   
2080    '''
2081    VecA = np.empty((3,3))   
2082    VecA[0] = np.inner(XYZ[1]-XYZ[0],Amat)
2083    VecA[1] = np.inner(XYZ[2]-XYZ[0],Amat)
2084    VecA[2] = np.inner(XYZ[3]-XYZ[0],Amat)
2085    return nl.det(VecA)
2086   
2087def getRestTorsion(XYZ,Amat):
2088    '''default doc string
2089   
2090    :param type name: description
2091   
2092    :returns: type name: description
2093   
2094    '''
2095    VecA = np.empty((3,3))
2096    VecA[0] = np.inner(XYZ[1]-XYZ[0],Amat)
2097    VecA[1] = np.inner(XYZ[2]-XYZ[1],Amat)
2098    VecA[2] = np.inner(XYZ[3]-XYZ[2],Amat)
2099    D = nl.det(VecA)
2100    Mag = np.sqrt(np.sum(VecA*VecA,axis=1))
2101    P12 = np.sum(VecA[0]*VecA[1])/(Mag[0]*Mag[1])
2102    P13 = np.sum(VecA[0]*VecA[2])/(Mag[0]*Mag[2])
2103    P23 = np.sum(VecA[1]*VecA[2])/(Mag[1]*Mag[2])
2104    Ang = 1.0
2105    if abs(P12) < 1.0 and abs(P23) < 1.0:
2106        Ang = (P12*P23-P13)/(np.sqrt(1.-P12**2)*np.sqrt(1.-P23**2))
2107    TOR = (acosd(Ang)*D/abs(D)+720.)%360.
2108    return TOR
2109   
2110def calcTorsionEnergy(TOR,Coeff=[]):
2111    '''default doc string
2112   
2113    :param type name: description
2114   
2115    :returns: type name: description
2116   
2117    '''
2118    sum = 0.
2119    if len(Coeff):
2120        cof = np.reshape(Coeff,(3,3)).T
2121        delt = TOR-cof[1]
2122        delt = np.where(delt<-180.,delt+360.,delt)
2123        delt = np.where(delt>180.,delt-360.,delt)
2124        term = -cof[2]*delt**2
2125        val = cof[0]*np.exp(term/1000.0)
2126        pMax = cof[0][np.argmin(val)]
2127        Eval = np.sum(val)
2128        sum = Eval-pMax
2129    return sum,Eval
2130
2131def getTorsionDeriv(XYZ,Amat,Coeff):
2132    '''default doc string
2133   
2134    :param type name: description
2135   
2136    :returns: type name: description
2137   
2138    '''
2139    deriv = np.zeros((len(XYZ),3))
2140    dx = 0.00001
2141    for j,xyz in enumerate(XYZ):
2142        for i,x in enumerate(np.array([[dx,0,0],[0,dx,0],[0,0,dx]])):
2143            XYZ[j] -= x
2144            tor = getRestTorsion(XYZ,Amat)
2145            p1,d1 = calcTorsionEnergy(tor,Coeff)
2146            XYZ[j] += 2*x
2147            tor = getRestTorsion(XYZ,Amat)
2148            p2,d2 = calcTorsionEnergy(tor,Coeff)           
2149            XYZ[j] -= x
2150            deriv[j][i] = (p2-p1)/(2*dx)
2151    return deriv.flatten()
2152
2153def getRestRama(XYZ,Amat):
2154    '''Computes a pair of torsion angles in a 5 atom string
2155   
2156    :param nparray XYZ: crystallographic coordinates of 5 atoms
2157    :param nparray Amat: crystal to cartesian transformation matrix
2158   
2159    :returns: list (phi,psi) two torsion angles in degrees
2160   
2161    '''
2162    phi = getRestTorsion(XYZ[:5],Amat)
2163    psi = getRestTorsion(XYZ[1:],Amat)
2164    return phi,psi
2165   
2166def calcRamaEnergy(phi,psi,Coeff=[]):
2167    '''Computes pseudo potential energy from a pair of torsion angles and a
2168    numerical description of the potential energy surface. Used to create
2169    penalty function in LS refinement:     
2170    :math:`Eval(\\phi,\\psi) = C[0]*exp(-V/1000)`
2171
2172    where :math:`V = -C[3] * (\\phi-C[1])^2 - C[4]*(\\psi-C[2])^2 - 2*(\\phi-C[1])*(\\psi-C[2])`
2173   
2174    :param float phi: first torsion angle (:math:`\\phi`)
2175    :param float psi: second torsion angle (:math:`\\psi`)
2176    :param list Coeff: pseudo potential coefficients
2177   
2178    :returns: list (sum,Eval): pseudo-potential difference from minimum & value;
2179      sum is used for penalty function.
2180   
2181    '''
2182    sum = 0.
2183    Eval = 0.
2184    if len(Coeff):
2185        cof = Coeff.T
2186        dPhi = phi-cof[1]
2187        dPhi = np.where(dPhi<-180.,dPhi+360.,dPhi)
2188        dPhi = np.where(dPhi>180.,dPhi-360.,dPhi)
2189        dPsi = psi-cof[2]
2190        dPsi = np.where(dPsi<-180.,dPsi+360.,dPsi)
2191        dPsi = np.where(dPsi>180.,dPsi-360.,dPsi)
2192        val = -cof[3]*dPhi**2-cof[4]*dPsi**2-2.0*cof[5]*dPhi*dPsi
2193        val = cof[0]*np.exp(val/1000.)
2194        pMax = cof[0][np.argmin(val)]
2195        Eval = np.sum(val)
2196        sum = Eval-pMax
2197    return sum,Eval
2198
2199def getRamaDeriv(XYZ,Amat,Coeff):
2200    '''Computes numerical derivatives of torsion angle pair pseudo potential
2201    with respect of crystallographic atom coordinates of the 5 atom sequence
2202   
2203    :param nparray XYZ: crystallographic coordinates of 5 atoms
2204    :param nparray Amat: crystal to cartesian transformation matrix
2205    :param list Coeff: pseudo potential coefficients
2206   
2207    :returns: list (deriv) derivatives of pseudopotential with respect to 5 atom
2208     crystallographic xyz coordinates.
2209   
2210    '''
2211    deriv = np.zeros((len(XYZ),3))
2212    dx = 0.00001
2213    for j,xyz in enumerate(XYZ):
2214        for i,x in enumerate(np.array([[dx,0,0],[0,dx,0],[0,0,dx]])):
2215            XYZ[j] -= x
2216            phi,psi = getRestRama(XYZ,Amat)
2217            p1,d1 = calcRamaEnergy(phi,psi,Coeff)
2218            XYZ[j] += 2*x
2219            phi,psi = getRestRama(XYZ,Amat)
2220            p2,d2 = calcRamaEnergy(phi,psi,Coeff)
2221            XYZ[j] -= x
2222            deriv[j][i] = (p2-p1)/(2*dx)
2223    return deriv.flatten()
2224
2225def getRestPolefig(ODFln,SamSym,Grid):
2226    '''default doc string
2227   
2228    :param type name: description
2229   
2230    :returns: type name: description
2231   
2232    '''
2233    X,Y = np.meshgrid(np.linspace(1.,-1.,Grid),np.linspace(-1.,1.,Grid))
2234    R,P = np.sqrt(X**2+Y**2).flatten(),atan2d(Y,X).flatten()
2235    R = np.where(R <= 1.,2.*atand(R),0.0)
2236    Z = np.zeros_like(R)
2237    Z = G2lat.polfcal(ODFln,SamSym,R,P)
2238    Z = np.reshape(Z,(Grid,Grid))
2239    return np.reshape(R,(Grid,Grid)),np.reshape(P,(Grid,Grid)),Z
2240
2241def getRestPolefigDerv(HKL,Grid,SHCoeff):
2242    '''default doc string
2243   
2244    :param type name: description
2245   
2246    :returns: type name: description
2247   
2248    '''
2249    pass
2250       
2251def getDistDerv(Oxyz,Txyz,Amat,Tunit,Top,SGData):
2252    '''default doc string
2253   
2254    :param type name: description
2255   
2256    :returns: type name: description
2257   
2258    '''
2259    def calcDist(Ox,Tx,U,inv,C,M,T,Amat):
2260        TxT = inv*(np.inner(M,Tx)+T)+C+U
2261        return np.sqrt(np.sum(np.inner(Amat,(TxT-Ox))**2))
2262       
2263    inv = Top/abs(Top)
2264    cent = abs(Top)//100
2265    op = abs(Top)%100-1
2266    M,T = SGData['SGOps'][op]
2267    C = SGData['SGCen'][cent]
2268    dx = .00001
2269    deriv = np.zeros(6)
2270    for i in [0,1,2]:
2271        Oxyz[i] -= dx
2272        d0 = calcDist(Oxyz,Txyz,Tunit,inv,C,M,T,Amat)
2273        Oxyz[i] += 2*dx
2274        deriv[i] = (calcDist(Oxyz,Txyz,Tunit,inv,C,M,T,Amat)-d0)/(2.*dx)
2275        Oxyz[i] -= dx
2276        Txyz[i] -= dx
2277        d0 = calcDist(Oxyz,Txyz,Tunit,inv,C,M,T,Amat)
2278        Txyz[i] += 2*dx
2279        deriv[i+3] = (calcDist(Oxyz,Txyz,Tunit,inv,C,M,T,Amat)-d0)/(2.*dx)
2280        Txyz[i] -= dx
2281    return deriv
2282   
2283def getAngleDerv(Oxyz,Axyz,Bxyz,Amat,Tunit,symNo,SGData):
2284   
2285    def calcAngle(Oxyz,ABxyz,Amat,Tunit,symNo,SGData):
2286        vec = np.zeros((2,3))
2287        for i in range(2):
2288            inv = 1
2289            if symNo[i] < 0:
2290                inv = -1
2291            cen = inv*symNo[i]//100
2292            op = inv*symNo[i]%100-1
2293            M,T = SGData['SGOps'][op]
2294            D = T*inv+SGData['SGCen'][cen]
2295            D += Tunit[i]
2296            ABxyz[i] = np.inner(M*inv,ABxyz[i])+D
2297            vec[i] = np.inner(Amat,(ABxyz[i]-Oxyz))
2298            dist = np.sqrt(np.sum(vec[i]**2))
2299            if not dist:
2300                return 0.
2301            vec[i] /= dist
2302        angle = acosd(np.sum(vec[0]*vec[1]))
2303    #    GSASIIpath.IPyBreak()
2304        return angle
2305       
2306    dx = .00001
2307    deriv = np.zeros(9)
2308    for i in [0,1,2]:
2309        Oxyz[i] -= dx
2310        a0 = calcAngle(Oxyz,[Axyz,Bxyz],Amat,Tunit,symNo,SGData)
2311        Oxyz[i] += 2*dx
2312        deriv[i] = (calcAngle(Oxyz,[Axyz,Bxyz],Amat,Tunit,symNo,SGData)-a0)/(2.*dx)
2313        Oxyz[i] -= dx
2314        Axyz[i] -= dx
2315        a0 = calcAngle(Oxyz,[Axyz,Bxyz],Amat,Tunit,symNo,SGData)
2316        Axyz[i] += 2*dx
2317        deriv[i+3] = (calcAngle(Oxyz,[Axyz,Bxyz],Amat,Tunit,symNo,SGData)-a0)/(2.*dx)
2318        Axyz[i] -= dx
2319        Bxyz[i] -= dx
2320        a0 = calcAngle(Oxyz,[Axyz,Bxyz],Amat,Tunit,symNo,SGData)
2321        Bxyz[i] += 2*dx
2322        deriv[i+6] = (calcAngle(Oxyz,[Axyz,Bxyz],Amat,Tunit,symNo,SGData)-a0)/(2.*dx)
2323        Bxyz[i] -= dx
2324    return deriv
2325   
2326def getAngSig(VA,VB,Amat,SGData,covData={}):
2327    '''default doc string
2328   
2329    :param type name: description
2330   
2331    :returns: type name: description
2332   
2333    '''
2334    def calcVec(Ox,Tx,U,inv,C,M,T,Amat):
2335        TxT = inv*(np.inner(M,Tx)+T)+C+U
2336        return np.inner(Amat,(TxT-Ox))
2337       
2338    def calcAngle(Ox,TxA,TxB,unitA,unitB,invA,CA,MA,TA,invB,CB,MB,TB,Amat):
2339        VecA = calcVec(Ox,TxA,unitA,invA,CA,MA,TA,Amat)
2340        VecA /= np.sqrt(np.sum(VecA**2))
2341        VecB = calcVec(Ox,TxB,unitB,invB,CB,MB,TB,Amat)
2342        VecB /= np.sqrt(np.sum(VecB**2))
2343        edge = VecB-VecA
2344        edge = np.sum(edge**2)
2345        angle = (2.-edge)/2.
2346        angle = max(angle,-1.)
2347        return acosd(angle)
2348       
2349    OxAN,OxA,TxAN,TxA,unitA,TopA = VA
2350    OxBN,OxB,TxBN,TxB,unitB,TopB = VB
2351    invA = invB = 1
2352    invA = TopA//abs(TopA)
2353    invB = TopB//abs(TopB)
2354    centA = abs(TopA)//100
2355    centB = abs(TopB)//100
2356    opA = abs(TopA)%100-1
2357    opB = abs(TopB)%100-1
2358    MA,TA = SGData['SGOps'][opA]
2359    MB,TB = SGData['SGOps'][opB]
2360    CA = SGData['SGCen'][centA]
2361    CB = SGData['SGCen'][centB]
2362    if 'covMatrix' in covData:
2363        covMatrix = covData['covMatrix']
2364        varyList = covData['varyList']
2365        AngVcov = getVCov(OxAN+TxAN+TxBN,varyList,covMatrix)
2366        dx = .00001
2367        dadx = np.zeros(9)
2368        Ang = calcAngle(OxA,TxA,TxB,unitA,unitB,invA,CA,MA,TA,invB,CB,MB,TB,Amat)
2369        for i in [0,1,2]:
2370            OxA[i] -= dx
2371            a0 = calcAngle(OxA,TxA,TxB,unitA,unitB,invA,CA,MA,TA,invB,CB,MB,TB,Amat)
2372            OxA[i] += 2*dx
2373            dadx[i] = (calcAngle(OxA,TxA,TxB,unitA,unitB,invA,CA,MA,TA,invB,CB,MB,TB,Amat)-a0)/(2*dx)
2374            OxA[i] -= dx
2375           
2376            TxA[i] -= dx
2377            a0 = calcAngle(OxA,TxA,TxB,unitA,unitB,invA,CA,MA,TA,invB,CB,MB,TB,Amat)
2378            TxA[i] += 2*dx
2379            dadx[i+3] = (calcAngle(OxA,TxA,TxB,unitA,unitB,invA,CA,MA,TA,invB,CB,MB,TB,Amat)-a0)/(2*dx)
2380            TxA[i] -= dx
2381           
2382            TxB[i] -= dx
2383            a0 = calcAngle(OxA,TxA,TxB,unitA,unitB,invA,CA,MA,TA,invB,CB,MB,TB,Amat)
2384            TxB[i] += 2*dx
2385            dadx[i+6] = (calcAngle(OxA,TxA,TxB,unitA,unitB,invA,CA,MA,TA,invB,CB,MB,TB,Amat)-a0)/(2*dx)
2386            TxB[i] -= dx
2387           
2388        sigAng = np.sqrt(np.inner(dadx,np.inner(AngVcov,dadx)))
2389        if sigAng < 0.01:
2390            sigAng = 0.0
2391        return Ang,sigAng
2392    else:
2393        return calcAngle(OxA,TxA,TxB,unitA,unitB,invA,CA,MA,TA,invB,CB,MB,TB,Amat),0.0
2394
2395def GetDistSig(Oatoms,Atoms,Amat,SGData,covData={}):
2396    '''default doc string
2397   
2398    :param type name: description
2399   
2400    :returns: type name: description
2401   
2402    '''
2403    def calcDist(Atoms,SyOps,Amat):
2404        XYZ = []
2405        for i,atom in enumerate(Atoms):
2406            Inv,M,T,C,U = SyOps[i]
2407            XYZ.append(np.array(atom[1:4]))
2408            XYZ[-1] = Inv*(np.inner(M,np.array(XYZ[-1]))+T)+C+U
2409            XYZ[-1] = np.inner(Amat,XYZ[-1]).T
2410        V1 = XYZ[1]-XYZ[0]
2411        return np.sqrt(np.sum(V1**2))
2412       
2413    SyOps = []
2414    names = []
2415    for i,atom in enumerate(Oatoms):
2416        names += atom[-1]
2417        Op,unit = Atoms[i][-1]
2418        inv = Op//abs(Op)
2419        m,t = SGData['SGOps'][abs(Op)%100-1]
2420        c = SGData['SGCen'][abs(Op)//100]
2421        SyOps.append([inv,m,t,c,unit])
2422    Dist = calcDist(Oatoms,SyOps,Amat)
2423   
2424    sig = -0.001
2425    if 'covMatrix' in covData:
2426        dx = .00001
2427        dadx = np.zeros(6)
2428        for i in range(6):
2429            ia = i//3
2430            ix = i%3
2431            Oatoms[ia][ix+1] += dx
2432            a0 = calcDist(Oatoms,SyOps,Amat)
2433            Oatoms[ia][ix+1] -= 2*dx
2434            dadx[i] = (calcDist(Oatoms,SyOps,Amat)-a0)/(2.*dx)
2435        covMatrix = covData['covMatrix']
2436        varyList = covData['varyList']
2437        DistVcov = getVCov(names,varyList,covMatrix)
2438        sig = np.sqrt(np.inner(dadx,np.inner(DistVcov,dadx)))
2439        if sig < 0.001:
2440            sig = -0.001
2441   
2442    return Dist,sig
2443
2444def GetAngleSig(Oatoms,Atoms,Amat,SGData,covData={}):
2445    '''default doc string
2446   
2447    :param type name: description
2448   
2449    :returns: type name: description
2450   
2451    '''
2452
2453    def calcAngle(Atoms,SyOps,Amat):
2454        XYZ = []
2455        for i,atom in enumerate(Atoms):
2456            Inv,M,T,C,U = SyOps[i]
2457            XYZ.append(np.array(atom[1:4]))
2458            XYZ[-1] = Inv*(np.inner(M,np.array(XYZ[-1]))+T)+C+U
2459            XYZ[-1] = np.inner(Amat,XYZ[-1]).T
2460        V1 = XYZ[1]-XYZ[0]
2461        V1 /= np.sqrt(np.sum(V1**2))
2462        V2 = XYZ[1]-XYZ[2]
2463        V2 /= np.sqrt(np.sum(V2**2))
2464        V3 = V2-V1
2465        cang = min(1.,max((2.-np.sum(V3**2))/2.,-1.))
2466        return acosd(cang)
2467
2468    SyOps = []
2469    names = []
2470    for i,atom in enumerate(Oatoms):
2471        names += atom[-1]
2472        Op,unit = Atoms[i][-1]
2473        inv = Op//abs(Op)
2474        m,t = SGData['SGOps'][abs(Op)%100-1]
2475        c = SGData['SGCen'][abs(Op)//100]
2476        SyOps.append([inv,m,t,c,unit])
2477    Angle = calcAngle(Oatoms,SyOps,Amat)
2478   
2479    sig = -0.01
2480    if 'covMatrix' in covData:
2481        dx = .00001
2482        dadx = np.zeros(9)
2483        for i in range(9):
2484            ia = i//3
2485            ix = i%3
2486            Oatoms[ia][ix+1] += dx
2487            a0 = calcAngle(Oatoms,SyOps,Amat)
2488            Oatoms[ia][ix+1] -= 2*dx
2489            dadx[i] = (calcAngle(Oatoms,SyOps,Amat)-a0)/(2.*dx)
2490        covMatrix = covData['covMatrix']
2491        varyList = covData['varyList']
2492        AngVcov = getVCov(names,varyList,covMatrix)
2493        sig = np.sqrt(np.inner(dadx,np.inner(AngVcov,dadx)))
2494        if sig < 0.01:
2495            sig = -0.01
2496   
2497    return Angle,sig
2498
2499def GetTorsionSig(Oatoms,Atoms,Amat,SGData,covData={}):
2500    '''default doc string
2501   
2502    :param type name: description
2503   
2504    :returns: type name: description
2505   
2506    '''
2507
2508    def calcTorsion(Atoms,SyOps,Amat):
2509       
2510        XYZ = []
2511        for i,atom in enumerate(Atoms):
2512            Inv,M,T,C,U = SyOps[i]
2513            XYZ.append(np.array(atom[1:4]))
2514            XYZ[-1] = Inv*(np.inner(M,np.array(XYZ[-1]))+T)+C+U
2515            XYZ[-1] = np.inner(Amat,XYZ[-1]).T
2516        V1 = XYZ[1]-XYZ[0]
2517        V2 = XYZ[2]-XYZ[1]
2518        V3 = XYZ[3]-XYZ[2]
2519        V1 /= np.sqrt(np.sum(V1**2))
2520        V2 /= np.sqrt(np.sum(V2**2))
2521        V3 /= np.sqrt(np.sum(V3**2))
2522        M = np.array([V1,V2,V3])
2523        D = nl.det(M)
2524        P12 = np.dot(V1,V2)
2525        P13 = np.dot(V1,V3)
2526        P23 = np.dot(V2,V3)
2527        Tors = acosd((P12*P23-P13)/(np.sqrt(1.-P12**2)*np.sqrt(1.-P23**2)))*D/abs(D)
2528        return Tors
2529           
2530    SyOps = []
2531    names = []
2532    for i,atom in enumerate(Oatoms):
2533        names += atom[-1]
2534        Op,unit = Atoms[i][-1]
2535        inv = Op//abs(Op)
2536        m,t = SGData['SGOps'][abs(Op)%100-1]
2537        c = SGData['SGCen'][abs(Op)//100]
2538        SyOps.append([inv,m,t,c,unit])
2539    Tors = calcTorsion(Oatoms,SyOps,Amat)
2540   
2541    sig = -0.01
2542    if 'covMatrix' in covData:
2543        dx = .00001
2544        dadx = np.zeros(12)
2545        for i in range(12):
2546            ia = i//3
2547            ix = i%3
2548            Oatoms[ia][ix+1] -= dx
2549            a0 = calcTorsion(Oatoms,SyOps,Amat)
2550            Oatoms[ia][ix+1] += 2*dx
2551            dadx[i] = (calcTorsion(Oatoms,SyOps,Amat)-a0)/(2.*dx)
2552            Oatoms[ia][ix+1] -= dx           
2553        covMatrix = covData['covMatrix']
2554        varyList = covData['varyList']
2555        TorVcov = getVCov(names,varyList,covMatrix)
2556        sig = np.sqrt(np.inner(dadx,np.inner(TorVcov,dadx)))
2557        if sig < 0.01:
2558            sig = -0.01
2559   
2560    return Tors,sig
2561       
2562def GetDATSig(Oatoms,Atoms,Amat,SGData,covData={}):
2563    '''default doc string
2564   
2565    :param type name: description
2566   
2567    :returns: type name: description
2568   
2569    '''
2570
2571    def calcDist(Atoms,SyOps,Amat):
2572        XYZ = []
2573        for i,atom in enumerate(Atoms):
2574            Inv,M,T,C,U = SyOps[i]
2575            XYZ.append(np.array(atom[1:4]))
2576            XYZ[-1] = Inv*(np.inner(M,np.array(XYZ[-1]))+T)+C+U
2577            XYZ[-1] = np.inner(Amat,XYZ[-1]).T
2578        V1 = XYZ[1]-XYZ[0]
2579        return np.sqrt(np.sum(V1**2))
2580       
2581    def calcAngle(Atoms,SyOps,Amat):
2582        XYZ = []
2583        for i,atom in enumerate(Atoms):
2584            Inv,M,T,C,U = SyOps[i]
2585            XYZ.append(np.array(atom[1:4]))
2586            XYZ[-1] = Inv*(np.inner(M,np.array(XYZ[-1]))+T)+C+U
2587            XYZ[-1] = np.inner(Amat,XYZ[-1]).T
2588        V1 = XYZ[1]-XYZ[0]
2589        V1 /= np.sqrt(np.sum(V1**2))
2590        V2 = XYZ[1]-XYZ[2]
2591        V2 /= np.sqrt(np.sum(V2**2))
2592        V3 = V2-V1
2593        cang = min(1.,max((2.-np.sum(V3**2))/2.,-1.))
2594        return acosd(cang)
2595
2596    def calcTorsion(Atoms,SyOps,Amat):
2597       
2598        XYZ = []
2599        for i,atom in enumerate(Atoms):
2600            Inv,M,T,C,U = SyOps[i]
2601            XYZ.append(np.array(atom[1:4]))
2602            XYZ[-1] = Inv*(np.inner(M,np.array(XYZ[-1]))+T)+C+U
2603            XYZ[-1] = np.inner(Amat,XYZ[-1]).T
2604        V1 = XYZ[1]-XYZ[0]
2605        V2 = XYZ[2]-XYZ[1]
2606        V3 = XYZ[3]-XYZ[2]
2607        V1 /= np.sqrt(np.sum(V1**2))
2608        V2 /= np.sqrt(np.sum(V2**2))
2609        V3 /= np.sqrt(np.sum(V3**2))
2610        M = np.array([V1,V2,V3])
2611        D = nl.det(M)
2612        P12 = np.dot(V1,V2)
2613        P13 = np.dot(V1,V3)
2614        P23 = np.dot(V2,V3)
2615        Tors = acosd((P12*P23-P13)/(np.sqrt(1.-P12**2)*np.sqrt(1.-P23**2)))*D/abs(D)
2616        return Tors
2617           
2618    SyOps = []
2619    names = []
2620    for i,atom in enumerate(Oatoms):
2621        names += atom[-1]
2622        Op,unit = Atoms[i][-1]
2623        inv = Op//abs(Op)
2624        m,t = SGData['SGOps'][abs(Op)%100-1]
2625        c = SGData['SGCen'][abs(Op)//100]
2626        SyOps.append([inv,m,t,c,unit])
2627    M = len(Oatoms)
2628    if M == 2:
2629        Val = calcDist(Oatoms,SyOps,Amat)
2630    elif M == 3:
2631        Val = calcAngle(Oatoms,SyOps,Amat)
2632    else:
2633        Val = calcTorsion(Oatoms,SyOps,Amat)
2634   
2635    sigVals = [-0.001,-0.01,-0.01]
2636    sig = sigVals[M-3]
2637    if 'covMatrix' in covData:
2638        dx = .00001
2639        N = M*3
2640        dadx = np.zeros(N)
2641        for i in range(N):
2642            ia = i//3
2643            ix = i%3
2644            Oatoms[ia][ix+1] += dx
2645            if M == 2:
2646                a0 = calcDist(Oatoms,SyOps,Amat)
2647            elif M == 3:
2648                a0 = calcAngle(Oatoms,SyOps,Amat)
2649            else:
2650                a0 = calcTorsion(Oatoms,SyOps,Amat)
2651            Oatoms[ia][ix+1] -= 2*dx
2652            if M == 2:
2653                dadx[i] = (calcDist(Oatoms,SyOps,Amat)-a0)/(2.*dx)               
2654            elif M == 3:
2655                dadx[i] = (calcAngle(Oatoms,SyOps,Amat)-a0)/(2.*dx)               
2656            else:
2657                dadx[i] = (calcTorsion(Oatoms,SyOps,Amat)-a0)/(2.*dx)
2658        covMatrix = covData['covMatrix']
2659        varyList = covData['varyList']
2660        Vcov = getVCov(names,varyList,covMatrix)
2661        sig = np.sqrt(np.inner(dadx,np.inner(Vcov,dadx)))
2662        if sig < sigVals[M-3]:
2663            sig = sigVals[M-3]
2664   
2665    return Val,sig
2666       
2667def ValEsd(value,esd=0,nTZ=False):
2668    '''Format a floating point number with a given level of precision or
2669    with in crystallographic format with a "esd", as value(esd). If esd is
2670    negative the number is formatted with the level of significant figures
2671    appropriate if abs(esd) were the esd, but the esd is not included.
2672    if the esd is zero, approximately 6 significant figures are printed.
2673    nTZ=True causes "extra" zeros to be removed after the decimal place.
2674    for example:
2675
2676      * "1.235(3)" for value=1.2346 & esd=0.003
2677      * "1.235(3)e4" for value=12346. & esd=30
2678      * "1.235(3)e6" for value=0.12346e7 & esd=3000
2679      * "1.235" for value=1.2346 & esd=-0.003
2680      * "1.240" for value=1.2395 & esd=-0.003
2681      * "1.24" for value=1.2395 & esd=-0.003 with nTZ=True
2682      * "1.23460" for value=1.2346 & esd=0.0
2683
2684    :param float value: number to be formatted
2685    :param float esd: uncertainty or if esd < 0, specifies level of
2686      precision to be shown e.g. esd=-0.01 gives 2 places beyond decimal
2687    :param bool nTZ: True to remove trailing zeros (default is False)
2688    :returns: value(esd) or value as a string
2689
2690    '''
2691    # Note: this routine is Python 3 compatible -- I think
2692    cutoff = 3.16228    #=(sqrt(10); same as old GSAS   was 1.95
2693    if math.isnan(value): # invalid value, bail out
2694        return '?'
2695    if math.isnan(esd): # invalid esd, treat as zero
2696        esd = 0
2697        esdoff = 5
2698#    if esd < 1.e-5:
2699#        esd = 0
2700#        esdoff = 5
2701    elif esd != 0:
2702        # transform the esd to a one or two digit integer
2703        l = math.log10(abs(esd)) % 1.
2704        if l < math.log10(cutoff): l+= 1.       
2705        intesd = int(round(10**l)) # esd as integer
2706        # determine the number of digits offset for the esd
2707        esdoff = int(round(math.log10(intesd*1./abs(esd))))
2708    else:
2709        esdoff = 5
2710    valoff = 0
2711    if abs(value) < abs(esdoff): # value is effectively zero
2712        pass
2713    elif esdoff < 0 or abs(value) > 1.0e6 or abs(value) < 1.0e-4: # use scientific notation
2714        # where the digit offset is to the left of the decimal place or where too many
2715        # digits are needed
2716        if abs(value) > 1:
2717            valoff = int(math.log10(abs(value)))
2718        elif abs(value) > 0:
2719            valoff = int(math.log10(abs(value))-0.9999999)
2720        else:
2721            valoff = 0
2722    if esd != 0:
2723        if valoff+esdoff < 0:
2724            valoff = esdoff = 0
2725        out = ("{:."+str(valoff+esdoff)+"f}").format(value/10**valoff) # format the value
2726    elif valoff != 0: # esd = 0; exponential notation ==> esdoff decimal places
2727        out = ("{:."+str(esdoff)+"f}").format(value/10**valoff) # format the value
2728    else: # esd = 0; non-exponential notation ==> esdoff+1 significant digits
2729        if abs(value) > 0:           
2730            extra = -math.log10(abs(value))
2731        else:
2732            extra = 0
2733        if extra > 0: extra += 1
2734        out = ("{:."+str(max(0,esdoff+int(extra)))+"f}").format(value) # format the value
2735    if esd > 0:
2736        out += ("({:d})").format(intesd)  # add the esd
2737    elif nTZ and '.' in out:
2738        out = out.rstrip('0')  # strip zeros to right of decimal
2739        out = out.rstrip('.')  # and decimal place when not needed
2740    if valoff != 0:
2741        out += ("e{:d}").format(valoff) # add an exponent, when needed
2742    return out
2743   
2744###############################################################################
2745##### Protein validation - "ERRATV2" analysis
2746###############################################################################
2747
2748def validProtein(Phase,old):
2749   
2750    def sumintact(intact):
2751        return {'CC':intact['CC'],'NN':intact['NN'],'OO':intact['OO'],
2752        'CN':(intact['CN']+intact['NC']),'CO':(intact['CO']+intact['OC']),
2753        'NO':(intact['NO']+intact['ON'])}
2754       
2755    resNames = ['ALA','ARG','ASN','ASP','CYS','GLN','GLU','GLY','HIS','ILE',
2756        'LEU','LYS','MET','PHE','PRO','SER','THR','TRP','TYR','VAL','MSE']
2757# data from errat.f
2758    b1_old = np.array([ 
2759        [1154.343,  600.213, 1051.018, 1132.885,  960.738],
2760        [600.213, 1286.818, 1282.042,  957.156,  612.789],
2761        [1051.018, 1282.042, 3519.471,  991.974, 1226.491],
2762        [1132.885,  957.156,  991.974, 1798.672,  820.355],
2763        [960.738,  612.789, 1226.491,  820.355, 2428.966]
2764        ])
2765    avg_old = np.array([ 0.225, 0.281, 0.071, 0.237, 0.044])    #Table 1 3.5A Obsd. Fr. p 1513
2766# data taken from erratv2.ccp
2767    b1 = np.array([
2768          [5040.279078850848200,        3408.805141583649400,   4152.904423767300600,   4236.200004171890200,   5054.781210204625500], 
2769          [3408.805141583648900,        8491.906094010220800,   5958.881777877950300,   1521.387352718486200,   4304.078200827221700], 
2770          [4152.904423767301500,        5958.881777877952100,   7637.167089335050100,   6620.715738223072500,   5287.691183798410700], 
2771          [4236.200004171890200,        1521.387352718486200,   6620.715738223072500,   18368.343774298410000,  4050.797811118806700], 
2772          [5054.781210204625500,        4304.078200827220800,   5287.691183798409800,   4050.797811118806700,   6666.856740479164700]])
2773    avg = np.array([0.192765509919262, 0.195575208778518, 0.275322406824210, 0.059102357035642, 0.233154192767480])
2774    General = Phase['General']
2775    Amat,Bmat = G2lat.cell2AB(General['Cell'][1:7])
2776    cx,ct,cs,cia = General['AtomPtrs']
2777    Atoms = Phase['Atoms']
2778    cartAtoms = []
2779    xyzmin = 999.*np.ones(3)
2780    xyzmax = -999.*np.ones(3)
2781    #select residue atoms,S,Se --> O make cartesian
2782    for atom in Atoms:
2783        if atom[1] in resNames:
2784            cartAtoms.append(atom[:cx+3])
2785            if atom[4].strip() in ['S','Se']:
2786                if not old:
2787                    continue        #S,Se skipped for erratv2?
2788                cartAtoms[-1][3] = 'Os'
2789                cartAtoms[-1][4] = 'O'
2790            cartAtoms[-1][cx:cx+3] = np.inner(Amat,cartAtoms[-1][cx:cx+3])
2791            cartAtoms[-1].append(atom[cia+8])
2792    XYZ = np.array([atom[cx:cx+3] for atom in cartAtoms])
2793    xyzmin = np.array([np.min(XYZ.T[i]) for i in [0,1,2]])
2794    xyzmax = np.array([np.max(XYZ.T[i]) for i in [0,1,2]])
2795    nbox = list(np.array(np.ceil((xyzmax-xyzmin)/4.),dtype=int))+[15,]
2796    Boxes = np.zeros(nbox,dtype=int)
2797    iBox = np.array([np.trunc((XYZ.T[i]-xyzmin[i])/4.) for i in [0,1,2]],dtype=int).T
2798    for ib,box in enumerate(iBox):  #put in a try for too many atoms in box (IndexError)?
2799        try:
2800            Boxes[box[0],box[1],box[2],0] += 1
2801            Boxes[box[0],box[1],box[2],Boxes[box[0],box[1],box[2],0]] = ib
2802        except IndexError:
2803            G2fil.G2Print('Error: too many atoms in box' )
2804            continue
2805    #Box content checks with errat.f $ erratv2.cpp ibox1 arrays
2806    indices = (-1,0,1)
2807    Units = np.array([[h,k,l] for h in indices for k in indices for l in indices]) 
2808    dsmax = 3.75**2
2809    if old:
2810        dsmax = 3.5**2
2811    chains = []
2812    resIntAct = []
2813    chainIntAct = []
2814    res = []
2815    resNames = []
2816    resIDs = {}
2817    resname = []
2818    resID = {}
2819    newChain = True
2820    intact = {'CC':0,'CN':0,'CO':0,'NN':0,'NO':0,'OO':0,'NC':0,'OC':0,'ON':0}
2821    for ia,atom in enumerate(cartAtoms):
2822        jntact = {'CC':0,'CN':0,'CO':0,'NN':0,'NO':0,'OO':0,'NC':0,'OC':0,'ON':0}
2823        if atom[2] not in chains:   #get chain id & save residue sequence from last chain
2824            chains.append(atom[2])
2825            if len(resIntAct):
2826                resIntAct.append(sumintact(intact))
2827                chainIntAct.append(resIntAct)
2828                resNames += resname
2829                resIDs.update(resID)
2830                res = []
2831                resname = []
2832                resID = {}
2833                resIntAct = []
2834                intact = {'CC':0,'CN':0,'CO':0,'NN':0,'NO':0,'OO':0,'NC':0,'OC':0,'ON':0}
2835                newChain = True
2836        if atom[0] not in res:  #new residue, get residue no.
2837            if res and int(res[-1]) != int(atom[0])-1:  #a gap in chain - not new chain
2838                intact = {'CC':0,'CN':0,'CO':0,'NN':0,'NO':0,'OO':0,'NC':0,'OC':0,'ON':0}
2839                ires = int(res[-1])
2840                for i in range(int(atom[0])-ires-1):
2841                    res.append(str(ires+i+1))
2842                    resname.append('')
2843                    resIntAct.append(sumintact(intact))
2844            res.append(atom[0])
2845            name = '%s-%s%s'%(atom[2],atom[0],atom[1])
2846            resname.append(name)
2847            resID[name] = atom[-1]
2848            if not newChain:
2849                resIntAct.append(sumintact(intact))
2850            intact = {'CC':0,'CN':0,'CO':0,'NN':0,'NO':0,'OO':0,'NC':0,'OC':0,'ON':0}
2851            newChain = False
2852        ibox = iBox[ia]         #box location of atom
2853        tgts = []
2854        for unit in Units:      #assemble list of all possible target atoms
2855            jbox = ibox+unit
2856            if np.all(jbox>=0) and np.all((jbox-nbox[:3])<0):               
2857                tgts += list(Boxes[jbox[0],jbox[1],jbox[2]])
2858        tgts = list(set(tgts))
2859        tgts = [tgt for tgt in tgts if atom[:3] != cartAtoms[tgt][:3]]    #exclude same residue
2860        tgts = [tgt for tgt in tgts if np.sum((XYZ[ia]-XYZ[tgt])**2) < dsmax]
2861        ires = int(atom[0])
2862        if old:
2863            if atom[3].strip() == 'C':
2864                tgts = [tgt for tgt in tgts if not (cartAtoms[tgt][3].strip() == 'N' and int(cartAtoms[tgt][0]) in [ires-1,ires+1])]
2865            elif atom[3].strip() == 'N':
2866                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])]
2867            elif atom[3].strip() == 'CA':
2868                tgts = [tgt for tgt in tgts if not (cartAtoms[tgt][3].strip() == 'N' and int(cartAtoms[tgt][0]) in [ires-1,ires+1])]
2869        else:
2870            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]]
2871            if atom[3].strip() == 'C':
2872                tgts = [tgt for tgt in tgts if not (cartAtoms[tgt][3].strip() == 'N' and int(cartAtoms[tgt][0]) == ires+1)]
2873            elif atom[3].strip() == 'N':
2874                tgts = [tgt for tgt in tgts if not (cartAtoms[tgt][3].strip() == 'C' and int(cartAtoms[tgt][0]) == ires-1)]
2875        for tgt in tgts:
2876            dsqt = np.sqrt(np.sum((XYZ[ia]-XYZ[tgt])**2))
2877            mult = 1.0
2878            if dsqt > 3.25 and not old:
2879                mult = 2.*(3.75-dsqt)
2880            intype = atom[4].strip()+cartAtoms[tgt][4].strip()
2881            if 'S' not in intype:
2882                intact[intype] += mult
2883                jntact[intype] += mult
2884#        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']
2885    resNames += resname
2886    resIDs.update(resID)
2887    resIntAct.append(sumintact(intact))
2888    chainIntAct.append(resIntAct)
2889    chainProb = []
2890    for ich,chn in enumerate(chains):
2891        IntAct = chainIntAct[ich]
2892        nRes = len(IntAct)
2893        Probs = [0.,0.,0.,0.]   #skip 1st 4 residues in chain
2894        for i in range(4,nRes-4):
2895            if resNames[i]:
2896                mtrx = np.zeros(5)
2897                summ = 0.
2898                for j in range(i-4,i+5):
2899                    summ += np.sum(np.array(list(IntAct[j].values())))
2900                    if old:
2901                        mtrx[0] += IntAct[j]['CC']
2902                        mtrx[1] += IntAct[j]['CO']
2903                        mtrx[2] += IntAct[j]['NN']
2904                        mtrx[3] += IntAct[j]['NO']
2905                        mtrx[4] += IntAct[j]['OO']
2906                    else:
2907                        mtrx[0] += IntAct[j]['CC']
2908                        mtrx[1] += IntAct[j]['CN']
2909                        mtrx[2] += IntAct[j]['CO']
2910                        mtrx[3] += IntAct[j]['NN']
2911                        mtrx[4] += IntAct[j]['NO']
2912                mtrx /= summ
2913    #            print i+1,mtrx*summ
2914                if old:
2915                    mtrx -= avg_old
2916                    prob = np.inner(np.inner(mtrx,b1_old),mtrx)
2917                else:
2918                    mtrx -= avg
2919                    prob = np.inner(np.inner(mtrx,b1),mtrx)
2920            else:       #skip the gaps
2921                prob = 0.0
2922            Probs.append(prob)
2923        Probs += 4*[0.,]        #skip last 4 residues in chain
2924        chainProb += Probs
2925    return resNames,chainProb,resIDs
2926   
2927################################################################################
2928##### Texture fitting stuff
2929################################################################################
2930
2931def FitTexture(General,Gangls,refData,keyList,pgbar):
2932    import pytexture as ptx
2933    ptx.pyqlmninit()            #initialize fortran arrays for spherical harmonics
2934   
2935    def printSpHarm(textureData,SHtextureSig):
2936        Tindx = 1.0
2937        Tvar = 0.0
2938        print ('\n Spherical harmonics texture: Order:' + str(textureData['Order']))
2939        names = ['omega','chi','phi']
2940        namstr = '  names :'
2941        ptstr =  '  values:'
2942        sigstr = '  esds  :'
2943        for name in names:
2944            namstr += '%12s'%('Sample '+name)
2945            ptstr += '%12.3f'%(textureData['Sample '+name][1])
2946            if 'Sample '+name in SHtextureSig:
2947                sigstr += '%12.3f'%(SHtextureSig['Sample '+name])
2948            else:
2949                sigstr += 12*' '
2950        print (namstr)
2951        print (ptstr)
2952        print (sigstr)
2953        print ('\n Texture coefficients:')
2954        SHcoeff = textureData['SH Coeff'][1]
2955        SHkeys = list(SHcoeff.keys())
2956        nCoeff = len(SHcoeff)
2957        nBlock = nCoeff//10+1
2958        iBeg = 0
2959        iFin = min(iBeg+10,nCoeff)
2960        for block in range(nBlock):
2961            namstr = '  names :'
2962            ptstr =  '  values:'
2963            sigstr = '  esds  :'
2964            for name in SHkeys[iBeg:iFin]:
2965                if 'C' in name:
2966                    l = 2.0*eval(name.strip('C'))[0]+1
2967                    Tindx += SHcoeff[name]**2/l
2968                    namstr += '%12s'%(name)
2969                    ptstr += '%12.3f'%(SHcoeff[name])
2970                    if name in SHtextureSig:
2971                        Tvar += (2.*SHcoeff[name]*SHtextureSig[name]/l)**2
2972                        sigstr += '%12.3f'%(SHtextureSig[name])
2973                    else:
2974                        sigstr += 12*' '
2975            print (namstr)
2976            print (ptstr)
2977            print (sigstr)
2978            iBeg += 10
2979            iFin = min(iBeg+10,nCoeff)
2980        print(' Texture index J = %.3f(%d)'%(Tindx,int(1000*np.sqrt(Tvar))))
2981           
2982    def Dict2Values(parmdict, varylist):
2983        '''Use before call to leastsq to setup list of values for the parameters
2984        in parmdict, as selected by key in varylist'''
2985        return [parmdict[key] for key in varylist] 
2986       
2987    def Values2Dict(parmdict, varylist, values):
2988        ''' Use after call to leastsq to update the parameter dictionary with
2989        values corresponding to keys in varylist'''
2990        parmdict.update(list(zip(varylist,values)))
2991       
2992    def errSpHarm(values,SGData,cell,Gangls,shModel,refData,parmDict,varyList,pgbar):
2993        parmDict.update(list(zip(varyList,values)))
2994        Mat = np.empty(0)
2995        sumObs = 0
2996        Sangls = [parmDict['Sample '+'omega'],parmDict['Sample '+'chi'],parmDict['Sample '+'phi']]
2997        for hist in Gangls.keys():
2998            Refs = refData[hist]
2999            Refs[:,5] = np.where(Refs[:,5]>0.,Refs[:,5],0.)
3000            wt = 1./np.sqrt(np.fmax(Refs[:,4],.25))
3001#            wt = 1./np.max(Refs[:,4],.25)
3002            sumObs += np.sum(wt*Refs[:,5])
3003            Refs[:,6] = 1.
3004            H = Refs[:,:3]
3005            phi,beta = G2lat.CrsAng(H,cell,SGData)
3006            psi,gam,x,x = G2lat.SamAng(Refs[:,3]/2.,Gangls[hist],Sangls,False) #assume not Bragg-Brentano!
3007            for item in parmDict:
3008                if 'C' in item:
3009                    L,M,N = eval(item.strip('C'))
3010                    Kcl = G2lat.GetKcl(L,N,SGData['SGLaue'],phi,beta)
3011                    Ksl,x,x = G2lat.GetKsl(L,M,shModel,psi,gam)
3012                    Lnorm = G2lat.Lnorm(L)
3013                    Refs[:,6] += parmDict[item]*Lnorm*Kcl*Ksl
3014            mat = wt*(Refs[:,5]-Refs[:,6])
3015            Mat = np.concatenate((Mat,mat))
3016        sumD = np.sum(np.abs(Mat))
3017        R = min(100.,100.*sumD/sumObs)
3018        pgbar.Raise()
3019        pgbar.Update(R,newmsg='Residual = %5.2f'%(R))
3020        print (' Residual: %.3f%%'%(R))
3021        return Mat
3022       
3023    def dervSpHarm(values,SGData,cell,Gangls,shModel,refData,parmDict,varyList,pgbar):
3024        Mat = np.empty(0)
3025        Sangls = [parmDict['Sample omega'],parmDict['Sample chi'],parmDict['Sample phi']]
3026        for hist in Gangls.keys():
3027            mat = np.zeros((len(varyList),len(refData[hist])))
3028            Refs = refData[hist]
3029            H = Refs[:,:3]
3030            wt = 1./np.sqrt(np.fmax(Refs[:,4],.25))
3031#            wt = 1./np.max(Refs[:,4],.25)
3032            phi,beta = G2lat.CrsAng(H,cell,SGData)
3033            psi,gam,dPdA,dGdA = G2lat.SamAng(Refs[:,3]/2.,Gangls[hist],Sangls,False) #assume not Bragg-Brentano!
3034            for j,item in enumerate(varyList):
3035                if 'C' in item:
3036                    L,M,N = eval(item.strip('C'))
3037                    Kcl = G2lat.GetKcl(L,N,SGData['SGLaue'],phi,beta)
3038                    Ksl,dKdp,dKdg = G2lat.GetKsl(L,M,shModel,psi,gam)
3039                    Lnorm = G2lat.Lnorm(L)
3040                    mat[j] = -wt*Lnorm*Kcl*Ksl
3041                    for k,itema in enumerate(['Sample omega','Sample chi','Sample phi']):
3042                        try:
3043                            l = varyList.index(itema)
3044                            mat[l] -= parmDict[item]*wt*Lnorm*Kcl*(dKdp*dPdA[k]+dKdg*dGdA[k])
3045                        except ValueError:
3046                            pass
3047            if len(Mat):
3048                Mat = np.concatenate((Mat,mat.T))
3049            else:
3050                Mat = mat.T
3051        print ('deriv')
3052        return Mat
3053
3054    print (' Fit texture for '+General['Name'])
3055    SGData = General['SGData']
3056    cell = General['Cell'][1:7]
3057    Texture = General['SH Texture']
3058    if not Texture['Order']:
3059        return 'No spherical harmonics coefficients'
3060    varyList = []
3061    parmDict = copy.copy(Texture['SH Coeff'][1])
3062    for item in ['Sample omega','Sample chi','Sample phi']:
3063        parmDict[item] = Texture[item][1]
3064        if Texture[item][0]:
3065            varyList.append(item)
3066    if Texture['SH Coeff'][0]:
3067        varyList += list(Texture['SH Coeff'][1].keys())
3068    while True:
3069        begin = time.time()
3070        values =  np.array(Dict2Values(parmDict, varyList))
3071        result = so.leastsq(errSpHarm,values,Dfun=dervSpHarm,full_output=True,ftol=1.e-6,
3072            args=(SGData,cell,Gangls,Texture['Model'],refData,parmDict,varyList,pgbar))
3073        ncyc = int(result[2]['nfev']//2)
3074        if ncyc:
3075            runtime = time.time()-begin   
3076            chisq = np.sum(result[2]['fvec']**2)
3077            Values2Dict(parmDict, varyList, result[0])
3078            GOF = chisq/(len(result[2]['fvec'])-len(varyList))       #reduced chi^2
3079            G2fil.G2Print ('Number of function calls: %d Number of observations: %d Number of parameters: %d'%(result[2]['nfev'],len(result[2]['fvec']),len(varyList)))
3080            G2fil.G2Print ('refinement time = %8.3fs, %8.3fs/cycle'%(runtime,runtime/ncyc))
3081            try:
3082                sig = np.sqrt(np.diag(result[1])*GOF)
3083                if np.any(np.isnan(sig)):
3084                    G2fil.G2Print ('*** Least squares aborted - some invalid esds possible ***', mode='error')
3085                break                   #refinement succeeded - finish up!
3086            except ValueError:          #result[1] is None on singular matrix
3087                G2fil.G2Print ('**** Refinement failed - singular matrix ****', mode='error')
3088                return None
3089        else:
3090            break
3091   
3092    if ncyc:
3093        for parm in parmDict:
3094            if 'C' in parm:
3095                Texture['SH Coeff'][1][parm] = parmDict[parm]
3096            else:
3097                Texture[parm][1] = parmDict[parm] 
3098        sigDict = dict(zip(varyList,sig))
3099        printSpHarm(Texture,sigDict)
3100       
3101    return None
3102   
3103################################################################################
3104##### Fourier & charge flip stuff
3105################################################################################
3106
3107def adjHKLmax(SGData,Hmax):
3108    '''default doc string
3109   
3110    :param type name: description
3111   
3112    :returns: type name: description
3113   
3114    '''
3115    if SGData['SGLaue'] in ['3','3m1','31m','6/m','6/mmm']:
3116        Hmax[0] = int(math.ceil(Hmax[0]/6.))*6
3117        Hmax[1] = int(math.ceil(Hmax[1]/6.))*6
3118        Hmax[2] = int(math.ceil(Hmax[2]/4.))*4
3119    else:
3120        Hmax[0] = int(math.ceil(Hmax[0]/4.))*4
3121        Hmax[1] = int(math.ceil(Hmax[1]/4.))*4
3122        Hmax[2] = int(math.ceil(Hmax[2]/4.))*4
3123
3124def OmitMap(data,reflDict,pgbar=None):
3125    '''default doc string
3126   
3127    :param type name: description
3128   
3129    :returns: type name: description
3130   
3131    '''
3132    generalData = data['General']
3133    if not generalData['Map']['MapType']:
3134        G2fil.G2Print ('**** ERROR - Fourier map not defined')
3135        return
3136    mapData = generalData['Map']
3137    dmin = mapData['GridStep']*2.
3138    SGData = generalData['SGData']
3139    SGMT = np.array([ops[0].T for ops in SGData['SGOps']])
3140    SGT = np.array([ops[1] for ops in SGData['SGOps']])
3141    cell = generalData['Cell'][1:8]       
3142    A = G2lat.cell2A(cell[:6])
3143    Hmax = np.asarray(G2lat.getHKLmax(dmin,SGData,A),dtype='i')+1
3144    adjHKLmax(SGData,Hmax)
3145    Fhkl = np.zeros(shape=2*Hmax,dtype='c16')
3146    time0 = time.time()
3147    for iref,ref in enumerate(reflDict['RefList']):
3148        if ref[4] >= dmin:
3149            Fosq,Fcsq,ph = ref[8:11]
3150            Uniq = np.inner(ref[:3],SGMT)
3151            Phi = np.inner(ref[:3],SGT)
3152            for i,hkl in enumerate(Uniq):        #uses uniq
3153                hkl = np.asarray(hkl,dtype='i')
3154                dp = 360.*Phi[i]                #and phi
3155                a = cosd(ph+dp)
3156                b = sind(ph+dp)
3157                phasep = complex(a,b)
3158                phasem = complex(a,-b)
3159                if '2Fo-Fc' in mapData['MapType']:
3160                    F = 2.*np.sqrt(Fosq)-np.sqrt(Fcsq)
3161                else:
3162                    F = np.sqrt(Fosq)
3163                h,k,l = hkl+Hmax
3164                Fhkl[h,k,l] = F*phasep
3165                h,k,l = -hkl+Hmax
3166                Fhkl[h,k,l] = F*phasem
3167    rho0 = fft.fftn(fft.fftshift(Fhkl))/cell[6]
3168    M = np.mgrid[0:4,0:4,0:4]
3169    blkIds = np.array(list(zip(M[0].flatten(),M[1].flatten(),M[2].flatten())))
3170    iBeg = blkIds*rho0.shape//4
3171    iFin = (blkIds+1)*rho0.shape//4
3172    rho_omit = np.zeros_like(rho0)
3173    nBlk = 0
3174    for iB,iF in zip(iBeg,iFin):
3175        rho1 = np.copy(rho0)
3176        rho1[iB[0]:iF[0],iB[1]:iF[1],iB[2]:iF[2]] = 0.
3177        Fnew = fft.ifftshift(fft.ifftn(rho1))
3178        Fnew = np.where(Fnew,Fnew,1.0)           #avoid divide by zero
3179        phase = Fnew/np.absolute(Fnew)
3180        OFhkl = np.absolute(Fhkl)*phase
3181        rho1 = np.real(fft.fftn(fft.fftshift(OFhkl)))*(1.+0j)
3182        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]])
3183        nBlk += 1
3184        pgbar.Raise()
3185        pgbar.Update(nBlk)
3186    mapData['rho'] = np.real(rho_omit)/cell[6]
3187    mapData['rhoMax'] = max(np.max(mapData['rho']),-np.min(mapData['rho']))
3188    mapData['minmax'] = [np.max(mapData['rho']),np.min(mapData['rho'])]
3189    G2fil.G2Print ('Omit map time: %.4f no. elements: %d dimensions: %s'%(time.time()-time0,Fhkl.size,str(Fhkl.shape)))
3190    return mapData
3191   
3192def FourierMap(data,reflDict):
3193    '''default doc string
3194   
3195    :param type name: description
3196   
3197    :returns: type name: description
3198   
3199    '''
3200    generalData = data['General']
3201    mapData = generalData['Map']
3202    dmin = mapData['GridStep']*2.
3203    SGData = generalData['SGData']
3204    SGMT = np.array([ops[0].T for ops in SGData['SGOps']])
3205    SGT = np.array([ops[1] for ops in SGData['SGOps']])
3206    cell = generalData['Cell'][1:8]       
3207    A = G2lat.cell2A(cell[:6])
3208    Hmax = np.asarray(G2lat.getHKLmax(dmin,SGData,A),dtype='i')+1
3209    adjHKLmax(SGData,Hmax)
3210    Fhkl = np.zeros(shape=2*Hmax,dtype='c16')
3211#    Fhkl[0,0,0] = generalData['F000X']
3212    time0 = time.time()
3213    for iref,ref in enumerate(reflDict['RefList']):
3214        if ref[4] > dmin:
3215            Fosq,Fcsq,ph = ref[8:11]
3216            Uniq = np.inner(ref[:3],SGMT)
3217            Phi = np.inner(ref[:3],SGT)
3218            for i,hkl in enumerate(Uniq):        #uses uniq
3219                hkl = np.asarray(hkl,dtype='i')
3220                dp = 360.*Phi[i]                #and phi
3221                a = cosd(ph+dp)
3222                b = sind(ph+dp)
3223                phasep = complex(a,b)
3224                phasem = complex(a,-b)
3225                if 'Fobs' in mapData['MapType']:
3226                    F = np.where(Fosq>0.,np.sqrt(Fosq),0.)
3227                    h,k,l = hkl+Hmax
3228                    Fhkl[h,k,l] = F*phasep
3229                    h,k,l = -hkl+Hmax
3230                    Fhkl[h,k,l] = F*phasem
3231                elif 'Fcalc' in mapData['MapType']:
3232                    F = np.sqrt(Fcsq)
3233                    h,k,l = hkl+Hmax
3234                    Fhkl[h,k,l] = F*phasep
3235                    h,k,l = -hkl+Hmax
3236                    Fhkl[h,k,l] = F*phasem
3237                elif 'delt-F' in mapData['MapType']:
3238                    dF = np.where(Fosq>0.,np.sqrt(Fosq),0.)-np.sqrt(Fcsq)
3239                    h,k,l = hkl+Hmax
3240                    Fhkl[h,k,l] = dF*phasep
3241                    h,k,l = -hkl+Hmax
3242                    Fhkl[h,k,l] = dF*phasem
3243                elif '2*Fo-Fc' in mapData['MapType']:
3244                    F = 2.*np.where(Fosq>0.,np.sqrt(Fosq),0.)-np.sqrt(Fcsq)
3245                    h,k,l = hkl+Hmax
3246                    Fhkl[h,k,l] = F*phasep
3247                    h,k,l = -hkl+Hmax
3248                    Fhkl[h,k,l] = F*phasem
3249                elif 'Patterson' in mapData['MapType']:
3250                    h,k,l = hkl+Hmax
3251                    Fhkl[h,k,l] = complex(Fosq,0.)
3252                    h,k,l = -hkl+Hmax
3253                    Fhkl[h,k,l] = complex(Fosq,0.)
3254    rho = fft.fftn(fft.fftshift(Fhkl))/cell[6]
3255    G2fil.G2Print ('Fourier map time: %.4f no. elements: %d dimensions: %s'%(time.time()-time0,Fhkl.size,str(Fhkl.shape)))
3256    mapData['Type'] = reflDict['Type']
3257    mapData['rho'] = np.real(rho)
3258    mapData['rhoMax'] = max(np.max(mapData['rho']),-np.min(mapData['rho']))
3259    mapData['minmax'] = [np.max(mapData['rho']),np.min(mapData['rho'])]
3260   
3261def Fourier4DMap(data,reflDict):
3262    '''default doc string
3263   
3264    :param type name: description
3265   
3266    :returns: type name: description
3267   
3268    '''
3269    generalData = data['General']
3270    map4DData = generalData['4DmapData']
3271    mapData = generalData['Map']
3272    dmin = mapData['GridStep']*2.
3273    SGData = generalData['SGData']
3274    SSGData = generalData['SSGData']
3275    SSGMT = np.array([ops[0].T for ops in SSGData['SSGOps']])
3276    SSGT = np.array([ops[1] for ops in SSGData['SSGOps']])
3277    cell = generalData['Cell'][1:8]       
3278    A = G2lat.cell2A(cell[:6])
3279    maxM = 4
3280    Hmax = G2lat.getHKLmax(dmin,SGData,A)+[maxM,]
3281    adjHKLmax(SGData,Hmax)
3282    Hmax = np.asarray(Hmax,dtype='i')+1
3283    Fhkl = np.zeros(shape=2*Hmax,dtype='c16')
3284    time0 = time.time()
3285    for iref,ref in enumerate(reflDict['RefList']):
3286        if ref[5] > dmin:
3287            Fosq,Fcsq,ph = ref[9:12]
3288            Fosq = np.where(Fosq>0.,Fosq,0.)    #can't use Fo^2 < 0
3289            Uniq = np.inner(ref[:4],SSGMT)
3290            Phi = np.inner(ref[:4],SSGT)
3291            for i,hkl in enumerate(Uniq):        #uses uniq
3292                hkl = np.asarray(hkl,dtype='i')
3293                dp = 360.*Phi[i]                #and phi
3294                a = cosd(ph+dp)
3295                b = sind(ph+dp)
3296                phasep = complex(a,b)
3297                phasem = complex(a,-b)
3298                if 'Fobs' in mapData['MapType']:
3299                    F = np.sqrt(Fosq)
3300                    h,k,l,m = hkl+Hmax
3301                    Fhkl[h,k,l,m] = F*phasep
3302                    h,k,l,m = -hkl+Hmax
3303                    Fhkl[h,k,l,m] = F*phasem
3304                elif 'Fcalc' in mapData['MapType']:
3305                    F = np.sqrt(Fcsq)
3306                    h,k,l,m = hkl+Hmax
3307                    Fhkl[h,k,l,m] = F*phasep
3308                    h,k,l,m = -hkl+Hmax
3309                    Fhkl[h,k,l,m] = F*phasem                   
3310                elif 'delt-F' in mapData['MapType']:
3311                    dF = np.sqrt(Fosq)-np.sqrt(Fcsq)
3312                    h,k,l,m = hkl+Hmax
3313                    Fhkl[h,k,l,m] = dF*phasep
3314                    h,k,l,m = -hkl+Hmax
3315                    Fhkl[h,k,l,m] = dF*phasem
3316    SSrho = fft.fftn(fft.fftshift(Fhkl))/cell[6]          #4D map
3317    rho = fft.fftn(fft.fftshift(Fhkl[:,:,:,maxM+1]))/cell[6]    #3D map
3318    map4DData['rho'] = np.real(SSrho)
3319    map4DData['rhoMax'] = max(np.max(map4DData['rho']),-np.min(map4DData['rho']))
3320    map4DData['minmax'] = [np.max(map4DData['rho']),np.min(map4DData['rho'])]
3321    map4DData['Type'] = reflDict['Type']
3322    mapData['Type'] = reflDict['Type']
3323    mapData['rho'] = np.real(rho)
3324    mapData['rhoMax'] = max(np.max(mapData['rho']),-np.min(mapData['rho']))
3325    mapData['minmax'] = [np.max(mapData['rho']),np.min(mapData['rho'])]
3326    G2fil.G2Print ('Fourier map time: %.4f no. elements: %d dimensions: %s'%(time.time()-time0,Fhkl.size,str(Fhkl.shape)))
3327
3328# map printing for testing purposes
3329def printRho(SGLaue,rho,rhoMax):                         
3330    '''default doc string
3331   
3332    :param type name: description
3333   
3334    :returns: type name: description
3335   
3336    '''
3337    dim = len(rho.shape)
3338    if dim == 2:
3339        ix,jy = rho.shape
3340        for j in range(jy):
3341            line = ''
3342            if SGLaue in ['3','3m1','31m','6/m','6/mmm']:
3343                line += (jy-j)*'  '
3344            for i in range(ix):
3345                r = int(100*rho[i,j]/rhoMax)
3346                line += '%4d'%(r)
3347            print (line+'\n')
3348    else:
3349        ix,jy,kz = rho.shape
3350        for k in range(kz):
3351            print ('k = %d'%k)
3352            for j in range(jy):
3353                line = ''
3354                if SGLaue in ['3','3m1','31m','6/m','6/mmm']:
3355                    line += (jy-j)*'  '
3356                for i in range(ix):
3357                    r = int(100*rho[i,j,k]/rhoMax)
3358                    line += '%4d'%(r)
3359                print (line+'\n')
3360## keep this
3361               
3362def findOffset(SGData,A,Fhkl):   
3363    '''default doc string
3364   
3365    :param type name: description
3366   
3367    :returns: type name: description
3368   
3369    '''
3370    if SGData['SpGrp'] == 'P 1':
3371        return [0,0,0]   
3372    hklShape = Fhkl.shape
3373    hklHalf = np.array(hklShape)/2
3374    sortHKL = np.argsort(Fhkl.flatten())
3375    Fdict = {}
3376    for hkl in sortHKL:
3377        HKL = np.unravel_index(hkl,hklShape)
3378        F = Fhkl[HKL[0]][HKL[1]][HKL[2]]
3379        if F == 0.:
3380            break
3381        Fdict['%.6f'%(np.absolute(F))] = hkl
3382    Flist = np.flipud(np.sort(list(Fdict.keys())))
3383    F = str(1.e6)
3384    i = 0
3385    DH = []
3386    Dphi = []
3387    Hmax = 2*np.asarray(G2lat.getHKLmax(3.5,SGData,A),dtype='i')
3388    for F in Flist:
3389        hkl = np.unravel_index(Fdict[F],hklShape)
3390        if np.any(np.abs(hkl-hklHalf)-Hmax > 0):
3391            continue
3392        iabsnt,mulp,Uniq,Phi = G2spc.GenHKLf(list(hkl-hklHalf),SGData)
3393        Uniq = np.array(Uniq,dtype='i')
3394        Phi = np.array(Phi)
3395        Uniq = np.concatenate((Uniq,-Uniq))+hklHalf         # put in Friedel pairs & make as index to Farray
3396        Phi = np.concatenate((Phi,-Phi))                      # and their phase shifts
3397        Fh0 = Fhkl[hkl[0],hkl[1],hkl[2]]
3398        ang0 = np.angle(Fh0,deg=True)/360.
3399        for H,phi in list(zip(Uniq,Phi))[1:]:
3400            ang = (np.angle(Fhkl[int(H[0]),int(H[1]),int(H[2])],deg=True)/360.-phi)
3401            dH = H-hkl
3402            dang = ang-ang0
3403            DH.append(dH)
3404            Dphi.append((dang+.5) % 1.0)
3405        if i > 20 or len(DH) > 30:
3406            break
3407        i += 1
3408    DH = np.array(DH)
3409    G2fil.G2Print (' map offset no.of terms: %d from %d reflections'%(len(DH),len(Flist)))
3410    Dphi = np.array(Dphi)
3411    steps = np.array(hklShape)
3412    X,Y,Z = np.mgrid[0:1:1./steps[0],0:1:1./steps[1],0:1:1./steps[2]]
3413    XYZ = np.array(list(zip(X.flatten(),Y.flatten(),Z.flatten())))
3414    Dang = (np.dot(XYZ,DH.T)+.5)%1.-Dphi
3415    Mmap = np.reshape(np.sum((Dang)**2,axis=1),newshape=steps)/len(DH)
3416    hist,bins = np.histogram(Mmap,bins=1000)
3417    chisq = np.min(Mmap)
3418    DX = -np.array(np.unravel_index(np.argmin(Mmap),Mmap.shape))
3419    ptext = ' map offset chi**2: %.3f, map offset: %d %d %d'%(chisq,DX[0],DX[1],DX[2])
3420    G2fil.G2Print(ptext)
3421    return DX,ptext
3422   
3423def ChargeFlip(data,reflDict,pgbar):
3424    '''default doc string
3425   
3426    :param type name: description
3427   
3428    :returns: type name: description
3429   
3430    '''
3431    generalData = data['General']
3432    mapData = generalData['Map']
3433    flipData = generalData['Flip']
3434    FFtable = {}
3435    if 'None' not in flipData['Norm element']:
3436        normElem = flipData['Norm element'].upper()
3437        FFs = G2el.GetFormFactorCoeff(normElem.split('+')[0].split('-')[0])
3438        for ff in FFs:
3439            if ff['Symbol'] == normElem:
3440                FFtable.update(ff)
3441    dmin = flipData['GridStep']*2.
3442    SGData = generalData['SGData']
3443    SGMT = np.array([ops[0].T for ops in SGData['SGOps']])
3444    SGT = np.array([ops[1] for ops in SGData['SGOps']])
3445    cell = generalData['Cell'][1:8]       
3446    A = G2lat.cell2A(cell[:6])
3447    Vol = cell[6]
3448    im = 0
3449    if generalData['Modulated'] == True:
3450        im = 1
3451    Hmax = np.asarray(G2lat.getHKLmax(dmin,SGData,A),dtype='i')+1
3452    adjHKLmax(SGData,Hmax)
3453    Ehkl = np.zeros(shape=2*Hmax,dtype='c16')       #2X64bits per complex no.
3454    time0 = time.time()
3455    for iref,ref in enumerate(reflDict['RefList']):
3456        dsp = ref[4+im]
3457        if im and ref[3]:   #skip super lattice reflections - result is 3D projection
3458            continue
3459        if dsp > dmin:
3460            ff = 0.1*Vol    #est. no. atoms for ~10A**3/atom
3461            if FFtable:
3462                SQ = 0.25/dsp**2
3463                ff *= G2el.ScatFac(FFtable,SQ)[0]
3464            if ref[8+im] > 0.:         #use only +ve Fobs**2
3465                E = np.sqrt(ref[8+im])/ff
3466            else:
3467                E = 0.
3468            ph = ref[10]
3469            ph = rn.uniform(0.,360.)
3470            Uniq = np.inner(ref[:3],SGMT)
3471            Phi = np.inner(ref[:3],SGT)
3472            for i,hkl in enumerate(Uniq):        #uses uniq
3473                hkl = np.asarray(hkl,dtype='i')
3474                dp = 360.*Phi[i]                #and phi
3475                a = cosd(ph+dp)
3476                b = sind(ph+dp)
3477                phasep = complex(a,b)
3478                phasem = complex(a,-b)
3479                h,k,l = hkl+Hmax
3480                Ehkl[h,k,l] = E*phasep
3481                h,k,l = -hkl+Hmax
3482                Ehkl[h,k,l] = E*phasem
3483#    Ehkl[Hmax] = 0.00001           #this to preserve F[0,0,0]
3484    testHKL = np.array(flipData['testHKL'])+Hmax
3485    CEhkl = copy.copy(Ehkl)
3486    MEhkl = ma.array(Ehkl,mask=(Ehkl==0.0))
3487    Emask = ma.getmask(MEhkl)
3488    sumE = np.sum(ma.array(np.absolute(CEhkl),mask=Emask))
3489    Ncyc = 0
3490    old = np.seterr(all='raise')
3491    twophases = []
3492    while True:       
3493        CErho = np.real(fft.fftn(fft.fftshift(CEhkl)))*(1.+0j)
3494        CEsig = np.std(CErho)
3495        CFrho = np.where(np.real(CErho) >= flipData['k-factor']*CEsig,CErho,-CErho)
3496        CFrho = np.where(np.real(CErho) <= flipData['k-Max']*CEsig,CFrho,-CFrho)      #solves U atom problem!
3497        CFhkl = fft.ifftshift(fft.ifftn(CFrho))
3498        CFhkl = np.where(CFhkl,CFhkl,1.0)           #avoid divide by zero
3499        phase = CFhkl/np.absolute(CFhkl)
3500        twophases.append([np.angle(phase[h,k,l]) for h,k,l in testHKL])
3501        CEhkl = np.absolute(Ehkl)*phase
3502        Ncyc += 1
3503        sumCF = np.sum(ma.array(np.absolute(CFhkl),mask=Emask))
3504        DEhkl = np.absolute(np.absolute(Ehkl)/sumE-np.absolute(CFhkl)/sumCF)
3505        Rcf = min(100.,np.sum(ma.array(DEhkl,mask=Emask)*100.))
3506        if Rcf < 5.:
3507            break
3508        GoOn = pgbar.Update(Rcf,newmsg='%s%8.3f%s\n%s %d'%('Residual Rcf =',Rcf,'%','No.cycles = ',Ncyc))[0]
3509        if not GoOn or Ncyc > 10000:
3510            break
3511    np.seterr(**old)
3512    G2fil.G2Print (' Charge flip time: %.4f'%(time.time()-time0),'no. elements: %d'%(Ehkl.size))
3513    CErho = np.real(fft.fftn(fft.fftshift(CEhkl)))/10.  #? to get on same scale as e-map
3514    ctext = ' No.cycles = %d Residual Rcf =%8.3f%s Map size: %s'%(Ncyc,Rcf,'%',str(CErho.shape))
3515    G2fil.G2Print (ctext)
3516    roll,ptext = findOffset(SGData,A,CEhkl)               #CEhkl needs to be just the observed set, not the full set!
3517       
3518    mapData['Rcf'] = Rcf
3519    mapData['rho'] = np.roll(np.roll(np.roll(CErho,roll[0],axis=0),roll[1],axis=1),roll[2],axis=2)
3520    mapData['rhoMax'] = max(np.max(mapData['rho']),-np.min(mapData['rho']))
3521    mapData['minmax'] = [np.max(mapData['rho']),np.min(mapData['rho'])]
3522    mapData['Type'] = reflDict['Type']
3523    return mapData,twophases,ptext,ctext
3524   
3525def findSSOffset(SGData,SSGData,A,Fhklm):   
3526    '''default doc string
3527   
3528    :param type name: description
3529   
3530    :returns: type name: description
3531   
3532    '''
3533    if SGData['SpGrp'] == 'P 1':
3534        return [0,0,0,0]   
3535    hklmShape = Fhklm.shape
3536    hklmHalf = np.array(hklmShape)/2
3537    sortHKLM = np.argsort(Fhklm.flatten())
3538    Fdict = {}
3539    for hklm in sortHKLM:
3540        HKLM = np.unravel_index(hklm,hklmShape)
3541        F = Fhklm[HKLM[0]][HKLM[1]][HKLM[2]][HKLM[3]]
3542        if F == 0.:
3543            break
3544        Fdict['%.6f'%(np.absolute(F))] = hklm
3545    Flist = np.flipud(np.sort(list(Fdict.keys())))
3546    F = str(1.e6)
3547    i = 0
3548    DH = []
3549    Dphi = []
3550    SSGMT = np.array([ops[0].T for ops in SSGData['SSGOps']])
3551    SSGT = np.array([ops[1] for ops in SSGData['SSGOps']])
3552    Hmax = 2*np.asarray(G2lat.getHKLmax(3.5,SGData,A),dtype='i')
3553    for F in Flist:
3554        hklm = np.unravel_index(Fdict[F],hklmShape)
3555        if np.any(np.abs(hklm-hklmHalf)[:3]-Hmax > 0):
3556            continue
3557        Uniq = np.inner(hklm-hklmHalf,SSGMT)
3558        Phi = np.inner(hklm-hklmHalf,SSGT)
3559        Uniq = np.concatenate((Uniq,-Uniq))+hklmHalf         # put in Friedel pairs & make as index to Farray
3560        Phi = np.concatenate((Phi,-Phi))                      # and their phase shifts
3561        Fh0 = Fhklm[hklm[0],hklm[1],hklm[2],hklm[3]]
3562        ang0 = np.angle(Fh0,deg=True)/360.
3563        for H,phi in list(zip(Uniq,Phi))[1:]:
3564            H = np.array(H,dtype=int)
3565            ang = (np.angle(Fhklm[H[0],H[1],H[2],H[3]],deg=True)/360.-phi)
3566            dH = H-hklm
3567            dang = ang-ang0
3568            DH.append(dH)
3569            Dphi.append((dang+.5) % 1.0)
3570        if i > 20 or len(DH) > 30:
3571            break
3572        i += 1
3573    DH = np.array(DH)
3574    G2fil.G2Print (' map offset no.of terms: %d from %d reflections'%(len(DH),len(Flist)))
3575    Dphi = np.array(Dphi)
3576    steps = np.array(hklmShape)
3577    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]]
3578    XYZT = np.array(list(zip(X.flatten(),Y.flatten(),Z.flatten(),T.flatten())))
3579    Dang = (np.dot(XYZT,DH.T)+.5)%1.-Dphi
3580    Mmap = np.reshape(np.sum((Dang)**2,axis=1),newshape=steps)/len(DH)
3581    hist,bins = np.histogram(Mmap,bins=1000)
3582    chisq = np.min(Mmap)
3583    DX = -np.array(np.unravel_index(np.argmin(Mmap),Mmap.shape))
3584    ptext = ' map offset chi**2: %.3f, map offset: %d %d %d %d'%(chisq,DX[0],DX[1],DX[2],DX[3])
3585    G2fil.G2Print(ptext)
3586    return DX,ptext
3587   
3588def SSChargeFlip(data,reflDict,pgbar):
3589    '''default doc string
3590   
3591    :param type name: description
3592   
3593    :returns: type name: description
3594   
3595    '''
3596    generalData = data['General']
3597    mapData = generalData['Map']
3598    map4DData = {}
3599    flipData = generalData['Flip']
3600    FFtable = {}
3601    if 'None' not in flipData['Norm element']:
3602        normElem = flipData['Norm element'].upper()
3603        FFs = G2el.GetFormFactorCoeff(normElem.split('+')[0].split('-')[0])
3604        for ff in FFs:
3605            if ff['Symbol'] == normElem:
3606                FFtable.update(ff)
3607    dmin = flipData['GridStep']*2.
3608    SGData = generalData['SGData']
3609    SSGData = generalData['SSGData']
3610    SSGMT = np.array([ops[0].T for ops in SSGData['SSGOps']])
3611    SSGT = np.array([ops[1] for ops in SSGData['SSGOps']])
3612    cell = generalData['Cell'][1:8]       
3613    A = G2lat.cell2A(cell[:6])
3614    Vol = cell[6]
3615    maxM = 4
3616    Hmax = np.asarray(G2lat.getHKLmax(dmin,SGData,A)+[maxM,],dtype='i')+1
3617    adjHKLmax(SGData,Hmax)
3618    Ehkl = np.zeros(shape=2*Hmax,dtype='c16')       #2X64bits per complex no.
3619    time0 = time.time()
3620    for iref,ref in enumerate(reflDict['RefList']):
3621        dsp = ref[5]
3622        if dsp > dmin:
3623            ff = 0.1*Vol    #est. no. atoms for ~10A**3/atom
3624            if FFtable:
3625                SQ = 0.25/dsp**2
3626                ff *= G2el.ScatFac(FFtable,SQ)[0]
3627            if ref[9] > 0.:         #use only +ve Fobs**2
3628                E = np.sqrt(ref[9])/ff
3629            else:
3630                E = 0.
3631            ph = ref[11]
3632            ph = rn.uniform(0.,360.)
3633            Uniq = np.inner(ref[:4],SSGMT)
3634            Phi = np.inner(ref[:4],SSGT)
3635            for i,hklm in enumerate(Uniq):        #uses uniq
3636                hklm = np.asarray(hklm,dtype='i')
3637                dp = 360.*Phi[i]                #and phi
3638                a = cosd(ph+dp)
3639                b = sind(ph+dp)
3640                phasep = complex(a,b)
3641                phasem = complex(a,-b)
3642                h,k,l,m = hklm+Hmax
3643                Ehkl[h,k,l,m] = E*phasep
3644                h,k,l,m = -hklm+Hmax       #Friedel pair refl.
3645                Ehkl[h,k,l,m] = E*phasem
3646#    Ehkl[Hmax] = 0.00001           #this to preserve F[0,0,0]
3647    CEhkl = copy.copy(Ehkl)
3648    MEhkl = ma.array(Ehkl,mask=(Ehkl==0.0))
3649    Emask = ma.getmask(MEhkl)
3650    sumE = np.sum(ma.array(np.absolute(CEhkl),mask=Emask))
3651    Ncyc = 0
3652    old = np.seterr(all='raise')
3653    while True:       
3654        CErho = np.real(fft.fftn(fft.fftshift(CEhkl)))*(1.+0j)
3655        CEsig = np.std(CErho)
3656        CFrho = np.where(np.real(CErho) >= flipData['k-factor']*CEsig,CErho,-CErho)
3657        CFrho = np.where(np.real(CErho) <= flipData['k-Max']*CEsig,CFrho,-CFrho)      #solves U atom problem!
3658        CFhkl = fft.ifftshift(fft.ifftn(CFrho))
3659        CFhkl = np.where(CFhkl,CFhkl,1.0)           #avoid divide by zero
3660        phase = CFhkl/np.absolute(CFhkl)
3661        CEhkl = np.absolute(Ehkl)*phase
3662        Ncyc += 1
3663        sumCF = np.sum(ma.array(np.absolute(CFhkl),mask=Emask))
3664        DEhkl = np.absolute(np.absolute(Ehkl)/sumE-np.absolute(CFhkl)/sumCF)
3665        Rcf = min(100.,np.sum(ma.array(DEhkl,mask=Emask)*100.))
3666        if Rcf < 5.:
3667            break
3668        GoOn = pgbar.Update(Rcf,newmsg='%s%8.3f%s\n%s %d'%('Residual Rcf =',Rcf,'%','No.cycles = ',Ncyc))[0]
3669        if not GoOn or Ncyc > 10000:
3670            break
3671    np.seterr(**old)
3672    G2fil.G2Print (' Charge flip time: %.4f no. elements: %d'%(time.time()-time0,Ehkl.size))
3673    CErho = np.real(fft.fftn(fft.fftshift(CEhkl[:,:,:,maxM+1])))/10.    #? to get on same scale as e-map
3674    SSrho = np.real(fft.fftn(fft.fftshift(CEhkl)))/10.                  #? ditto
3675    ctext = ' No.cycles = %d Residual Rcf =%8.3f%s Map size: %s'%(Ncyc,Rcf,'%',str(CErho.shape))
3676    G2fil.G2Print (ctext)
3677    roll,ptext = findSSOffset(SGData,SSGData,A,CEhkl)               #CEhkl needs to be just the observed set, not the full set!
3678
3679    mapData['Rcf'] = Rcf
3680    mapData['rho'] = np.roll(np.roll(np.roll(CErho,roll[0],axis=0),roll[1],axis=1),roll[2],axis=2)
3681    mapData['rhoMax'] = max(np.max(mapData['rho']),-np.min(mapData['rho']))
3682    mapData['minmax'] = [np.max(mapData['rho']),np.min(mapData['rho'])]
3683    mapData['Type'] = reflDict['Type']
3684
3685    map4DData['Rcf'] = Rcf
3686    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))
3687    map4DData['rhoMax'] = max(np.max(map4DData['rho']),-np.min(map4DData['rho']))
3688    map4DData['minmax'] = [np.max(map4DData['rho']),np.min(map4DData['rho'])]
3689    map4DData['Type'] = reflDict['Type']
3690    return mapData,map4DData,ptext,ctext
3691   
3692def getRho(xyz,mapData):
3693    ''' get scattering density at a point by 8-point interpolation
3694    param xyz: coordinate to be probed
3695    param: mapData: dict of map data
3696   
3697    :returns: density at xyz
3698    '''
3699    rollMap = lambda rho,roll: np.roll(np.roll(np.roll(rho,roll[0],axis=0),roll[1],axis=1),roll[2],axis=2)
3700    if not len(mapData):
3701        return 0.0
3702    rho = copy.copy(mapData['rho'])     #don't mess up original
3703    if not len(rho):
3704        return 0.0
3705    mapShape = np.array(rho.shape)
3706    mapStep = 1./mapShape
3707    X = np.array(xyz)%1.    #get into unit cell
3708    I = np.array(X*mapShape,dtype='int')
3709    D = X-I*mapStep         #position inside map cell
3710    D12 = D[0]*D[1]
3711    D13 = D[0]*D[2]
3712    D23 = D[1]*D[2]
3713    D123 = np.prod(D)
3714    Rho = rollMap(rho,-I)    #shifts map so point is in corner
3715    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]+  \
3716        Rho[1,1,1]*D123+Rho[0,1,1]*(D23-D123)+Rho[1,0,1]*(D13-D123)+Rho[1,1,0]*(D12-D123)+  \
3717        Rho[0,0,0]*(D12+D13+D23-D123)-Rho[0,0,1]*(D13+D23-D123)-    \
3718        Rho[0,1,0]*(D23+D12-D123)-Rho[1,0,0]*(D13+D12-D123)   
3719    return R
3720       
3721def getRhos(XYZ,rho):
3722    ''' get scattering density at an array of point by 8-point interpolation
3723    this is faster than gerRho which is only used for single points. However, getRhos is
3724    replaced by scipy.ndimage.interpolation.map_coordinates which does a better job &  is just as fast.
3725    Thus, getRhos is unused in GSAS-II at this time.
3726    param xyz:  array coordinates to be probed Nx3
3727    param: rho: array copy of map (NB: don't use original!)
3728   
3729    :returns: density at xyz
3730    '''
3731    def getBoxes(rho,I):
3732        Rhos = np.zeros((2,2,2))
3733        Mx,My,Mz = rho.shape
3734        Ix,Iy,Iz = I
3735        Rhos = np.array([[[rho[Ix%Mx,Iy%My,Iz%Mz],rho[Ix%Mx,Iy%My,(Iz+1)%Mz]],
3736                          [rho[Ix%Mx,(Iy+1)%My,Iz%Mz],rho[Ix%Mx,(Iy+1)%My,(Iz+1)%Mz]]],
3737                        [[rho[(Ix+1)%Mx,Iy%My,Iz%Mz],rho[(Ix+1)%Mx,Iy%My,(Iz+1)%Mz]],
3738                          [rho[(Ix+1)%Mx,(Iy+1)%My,Iz%Mz],rho[(Ix+1)%Mx,(Iy+1)%My,(Iz+1)%Mz]]]])
3739        return Rhos
3740       
3741    Blk = 400     #400 doesn't seem to matter
3742    nBlk = len(XYZ)//Blk        #select Blk so this is an exact divide
3743    mapShape = np.array(rho.shape)
3744    mapStep = 1./mapShape
3745    X = XYZ%1.    #get into unit cell
3746    iBeg = 0
3747    R = np.zeros(len(XYZ))
3748#actually a lot faster!
3749    for iblk in range(nBlk):
3750        iFin = iBeg+Blk
3751        Xs = X[iBeg:iFin]
3752        I = np.array(np.rint(Xs*mapShape),dtype='int')
3753        Rhos = np.array([getBoxes(rho,i) for i in I])
3754        Ds = Xs-I*mapStep
3755        RIJs = Rhos[:,0,:2,:2]*(1.-Ds[:,0][:,nxs,nxs])
3756        RIs = RIJs[:,0]*(1.-Ds[:,1][:,nxs])+RIJs[:,1]*Ds[:,1][:,nxs]
3757        R[iBeg:iFin] = RIs[:,0]*(1.-Ds[:,2])+RIs[:,1]*Ds[:,2]
3758        iBeg += Blk
3759    return R
3760       
3761def SearchMap(generalData,drawingData,Neg=False):
3762    '''Does a search of a density map for peaks meeting the criterion of peak
3763    height is greater than mapData['cutOff']/100 of mapData['rhoMax'] where
3764    mapData is data['General']['mapData']; the map is also in mapData.
3765
3766    :param generalData: the phase data structure; includes the map
3767    :param drawingData: the drawing data structure
3768    :param Neg:  if True then search for negative peaks (i.e. H-atoms & neutron data)
3769
3770    :returns: (peaks,mags,dzeros) where
3771
3772        * peaks : ndarray
3773          x,y,z positions of the peaks found in the map
3774        * mags : ndarray
3775          the magnitudes of the peaks
3776        * dzeros : ndarray
3777          the distance of the peaks from  the unit cell origin
3778        * dcent : ndarray
3779          the distance of the peaks from  the unit cell center
3780
3781    '''       
3782    rollMap = lambda rho,roll: np.roll(np.roll(np.roll(rho,roll[0],axis=0),roll[1],axis=1),roll[2],axis=2)
3783   
3784    norm = 1./(np.sqrt(3.)*np.sqrt(2.*np.pi)**3)
3785   
3786    def fixSpecialPos(xyz,SGData,Amat):
3787        equivs = G2spc.GenAtom(xyz,SGData,Move=True)
3788        X = []
3789        xyzs = [equiv[0] for equiv in equivs]
3790        for x in xyzs:
3791            if np.sqrt(np.sum(np.inner(Amat,xyz-x)**2,axis=0))<0.5:
3792                X.append(x)
3793        if len(X) > 1:
3794            return np.average(X,axis=0)
3795        else:
3796            return xyz
3797       
3798    def rhoCalc(parms,rX,rY,rZ,res,SGLaue):
3799        Mag,x0,y0,z0,sig = parms
3800        z = -((x0-rX)**2+(y0-rY)**2+(z0-rZ)**2)/(2.*sig**2)
3801#        return norm*Mag*np.exp(z)/(sig*res**3)     #not slower but some faults in LS
3802        return norm*Mag*(1.+z+z**2/2.)/(sig*res**3)
3803       
3804    def peakFunc(parms,rX,rY,rZ,rho,res,SGLaue):
3805        Mag,x0,y0,z0,sig = parms
3806        M = rho-rhoCalc(parms,rX,rY,rZ,res,SGLaue)
3807        return M
3808       
3809    def peakHess(parms,rX,rY,rZ,rho,res,SGLaue):
3810        Mag,x0,y0,z0,sig = parms
3811        dMdv = np.zeros(([5,]+list(rX.shape)))
3812        delt = .01
3813        for i in range(5):
3814            parms[i] -= delt
3815            rhoCm = rhoCalc(parms,rX,rY,rZ,res,SGLaue)
3816            parms[i] += 2.*delt
3817            rhoCp = rhoCalc(parms,rX,rY,rZ,res,SGLaue)
3818            parms[i] -= delt
3819            dMdv[i] = (rhoCp-rhoCm)/(2.*delt)
3820        rhoC = rhoCalc(parms,rX,rY,rZ,res,SGLaue)
3821        Vec = np.sum(np.sum(np.sum(dMdv*(rho-rhoC),axis=3),axis=2),axis=1)
3822        dMdv = np.reshape(dMdv,(5,rX.size))
3823        Hess = np.inner(dMdv,dMdv)
3824       
3825        return Vec,Hess
3826       
3827    SGData = generalData['SGData']
3828    Amat,Bmat = G2lat.cell2AB(generalData['Cell'][1:7])
3829    peaks = []
3830    mags = []
3831    dzeros = []
3832    dcent = []
3833    try:
3834        mapData = generalData['Map']
3835        contLevel = mapData['cutOff']*mapData['rhoMax']/100.
3836        if Neg:
3837            rho = -copy.copy(mapData['rho'])     #flip +/-
3838        else:
3839            rho = copy.copy(mapData['rho'])     #don't mess up original
3840        mapHalf = np.array(rho.shape)/2
3841        res = mapData['GridStep']*2.
3842        incre = np.array(rho.shape,dtype=np.float)
3843        step = max(1.0,1./res)+1
3844        steps = np.array((3*[step,]),dtype='int32')
3845    except KeyError:
3846        G2fil.G2Print ('**** ERROR - Fourier map not defined')
3847        return peaks,mags
3848    rhoMask = ma.array(rho,mask=(rho<contLevel))
3849    indices = (-1,0,1)
3850    rolls = np.array([[h,k,l] for h in indices for k in indices for l in indices])
3851    for roll in rolls:
3852        if np.any(roll):
3853            rhoMask = ma.array(rhoMask,mask=(rhoMask-rollMap(rho,roll)<=0.))
3854    indx = np.transpose(rhoMask.nonzero())
3855    peaks = indx/incre
3856    mags = rhoMask[rhoMask.nonzero()]
3857    for i,[ind,peak,mag] in enumerate(zip(indx,peaks,mags)):
3858        rho = rollMap(rho,ind)
3859        rMM = mapHalf-steps
3860        rMP = mapHalf+steps+1
3861        rhoPeak = rho[int(rMM[0]):int(rMP[0]),int(rMM[1]):int(rMP[1]),int(rMM[2]):int(rMP[2])]
3862        peakInt = np.sum(rhoPeak)*res**3
3863        rX,rY,rZ = np.mgrid[int(rMM[0]):int(rMP[0]),int(rMM[1]):int(rMP[1]),int(rMM[2]):int(rMP[2])]
3864        x0 = [peakInt,mapHalf[0],mapHalf[1],mapHalf[2],2.0]          #magnitude, position & width(sig)
3865        result = HessianLSQ(peakFunc,x0,Hess=peakHess,
3866            args=(rX,rY,rZ,rhoPeak,res,SGData['SGLaue']),ftol=.01,maxcyc=10)
3867        x1 = result[0]
3868        if not np.any(x1 < 0):
3869            peak = (np.array(x1[1:4])-ind)/incre
3870        peak = fixSpecialPos(peak,SGData,Amat)
3871        rho = rollMap(rho,-ind)
3872    cent = np.ones(3)*.5     
3873    dzeros = np.sqrt(np.sum(np.inner(Amat,peaks)**2,axis=0))
3874    dcent = np.sqrt(np.sum(np.inner(Amat,peaks-cent)**2,axis=0))
3875    if Neg:     #want negative magnitudes for negative peaks
3876        return np.array(peaks),-np.array([mags,]).T,np.array([dzeros,]).T,np.array([dcent,]).T
3877    else:
3878        return np.array(peaks),np.array([mags,]).T,np.array([dzeros,]).T,np.array([dcent,]).T
3879   
3880def sortArray(data,pos,reverse=False):
3881    '''data is a list of items
3882    sort by pos in list; reverse if True
3883    '''
3884    T = []
3885    for i,M in enumerate(data):
3886        try:
3887            T.append((M[pos],i))
3888        except IndexError:
3889            return data
3890    D = dict(zip(T,data))
3891    T.sort()
3892    if reverse:
3893        T.reverse()
3894    X = []
3895    for key in T:
3896        X.append(D[key])
3897    return X
3898
3899def PeaksEquiv(data,Ind):
3900    '''Find the equivalent map peaks for those selected. Works on the
3901    contents of data['Map Peaks'].
3902
3903    :param data: the phase data structure
3904    :param list Ind: list of selected peak indices
3905    :returns: augmented list of peaks including those related by symmetry to the
3906      ones in Ind
3907
3908    '''       
3909    def Duplicate(xyz,peaks,Amat):
3910        if True in [np.allclose(np.inner(Amat,xyz),np.inner(Amat,peak),atol=0.5) for peak in peaks]:
3911            return True
3912        return False
3913                           
3914    generalData = data['General']
3915    Amat,Bmat = G2lat.cell2AB(generalData['Cell'][1:7])
3916    SGData = generalData['SGData']
3917    mapPeaks = data['Map Peaks']
3918    XYZ = np.array([xyz[1:4] for xyz in mapPeaks])
3919    Indx = {}
3920    for ind in Ind:
3921        xyz = np.array(mapPeaks[ind][1:4])
3922        xyzs = np.array([equiv[0] for equiv in G2spc.GenAtom(xyz,SGData,Move=True)])
3923        for jnd,xyz in enumerate(XYZ):       
3924            Indx[jnd] = Duplicate(xyz,xyzs,Amat)
3925    Ind = []
3926    for ind in Indx:
3927        if Indx[ind]:
3928            Ind.append(ind)
3929    return Ind
3930               
3931def PeaksUnique(data,Ind,Sel):
3932    '''Finds the symmetry unique set of peaks from those selected. Selects
3933    the one closest to the center of the unit cell.
3934    Works on the contents of data['Map Peaks']. Called from OnPeaksUnique in
3935    GSASIIphsGUI.py,
3936
3937    :param data: the phase data structure
3938    :param list Ind: list of selected peak indices
3939    :param int Sel: selected column to find peaks closest to
3940
3941    :returns: the list of symmetry unique peaks from among those given in Ind
3942    '''       
3943#    XYZE = np.array([[equiv[0] for equiv in G2spc.GenAtom(xyz[1:4],SGData,Move=True)] for xyz in mapPeaks]) #keep this!!
3944
3945    def noDuplicate(xyz,peaks,Amat):
3946        if True in [np.allclose(np.inner(Amat,xyz),np.inner(Amat,peak),atol=0.5) for peak in peaks]:
3947            return False
3948        return True
3949                           
3950    generalData = data['General']
3951    Amat,Bmat = G2lat.cell2AB(generalData['Cell'][1:7])
3952    SGData = generalData['SGData']
3953    mapPeaks = data['Map Peaks']   
3954    XYZ = {ind:np.array(mapPeaks[ind][1:4]) for ind in Ind}
3955    Indx = [True for ind in Ind]
3956    Unique = []
3957    # scan through peaks, finding all peaks equivalent to peak ind
3958    for ind in Ind:
3959        if Indx[ind]:
3960            xyz = XYZ[ind]
3961            dups = []
3962            for jnd in Ind:
3963                # only consider peaks we have not looked at before
3964                # and were not already found to be equivalent
3965                if jnd > ind and Indx[jnd]:                       
3966                    Equiv = G2spc.GenAtom(XYZ[jnd],SGData,Move=True)
3967                    xyzs = np.array([equiv[0] for equiv in Equiv])
3968                    if not noDuplicate(xyz,xyzs,Amat):
3969                        Indx[jnd] = False
3970                        dups.append(jnd)
3971            # select the unique peak closest to cell center
3972            cntr = mapPeaks[ind][Sel]
3973            icntr = ind
3974            for jnd in dups:
3975                if mapPeaks[jnd][Sel] < cntr:
3976                    cntr = mapPeaks[jnd][Sel]
3977                    icntr = jnd
3978            Unique.append(icntr)
3979    return Unique
3980
3981################################################################################
3982##### Dysnomia setup & return stuff
3983################################################################################
3984   
3985
3986   
3987################################################################################
3988##### single peak fitting profile fxn stuff
3989################################################################################
3990
3991def getCWsig(ins,pos):
3992    '''get CW peak profile sigma^2
3993   
3994    :param dict ins: instrument parameters with at least 'U', 'V', & 'W'
3995      as values only
3996    :param float pos: 2-theta of peak
3997    :returns: float getCWsig: peak sigma^2
3998   
3999    '''
4000    tp = tand(pos/2.0)
4001    return ins['U']*tp**2+ins['V']*tp+ins['W']
4002   
4003def getCWsigDeriv(pos):
4004    '''get derivatives of CW peak profile sigma^2 wrt U,V, & W
4005   
4006    :param float pos: 2-theta of peak
4007   
4008    :returns: list getCWsigDeriv: d(sig^2)/dU, d(sig)/dV & d(sig)/dW
4009   
4010    '''
4011    tp = tand(pos/2.0)
4012    return tp**2,tp,1.0
4013   
4014def getCWgam(ins,pos):
4015    '''get CW peak profile gamma
4016   
4017    :param dict ins: instrument parameters with at least 'X', 'Y' & 'Z'
4018      as values only
4019    :param float pos: 2-theta of peak
4020    :returns: float getCWgam: peak gamma
4021   
4022    '''
4023    return ins['X']/cosd(pos/2.0)+ins['Y']*tand(pos/2.0)+ins.get('Z',0.0)
4024   
4025def getCWgamDeriv(pos):
4026    '''get derivatives of CW peak profile gamma wrt X, Y & Z
4027   
4028    :param float pos: 2-theta of peak
4029   
4030    :returns: list getCWgamDeriv: d(gam)/dX & d(gam)/dY
4031   
4032    '''
4033    return 1./cosd(pos/2.0),tand(pos/2.0),1.0
4034   
4035def getTOFsig(ins,dsp):
4036    '''get TOF peak profile sigma^2
4037   
4038    :param dict ins: instrument parameters with at least 'sig-0', 'sig-1' & 'sig-q'
4039      as values only
4040    :param float dsp: d-spacing of peak
4041   
4042    :returns: float getTOFsig: peak sigma^2
4043   
4044    '''
4045    return ins['sig-0']+ins['sig-1']*dsp**2+ins['sig-2']*dsp**4+ins['sig-q']*dsp
4046   
4047def getTOFsigDeriv(dsp):
4048    '''get derivatives of TOF peak profile sigma^2 wrt sig-0, sig-1, & sig-q
4049   
4050    :param float dsp: d-spacing of peak
4051   
4052    :returns: list getTOFsigDeriv: d(sig0/d(sig-0), d(sig)/d(sig-1) & d(sig)/d(sig-q)
4053   
4054    '''
4055    return 1.0,dsp**2,dsp**4,dsp
4056   
4057def getTOFgamma(ins,dsp):
4058    '''get TOF peak profile gamma
4059   
4060    :param dict ins: instrument parameters with at least 'X', 'Y' & 'Z'
4061      as values only
4062    :param float dsp: d-spacing of peak
4063   
4064    :returns: float getTOFgamma: peak gamma
4065   
4066    '''
4067    return ins['Z']+ins['X']*dsp+ins['Y']*dsp**2
4068   
4069def getTOFgammaDeriv(dsp):
4070    '''get derivatives of TOF peak profile gamma wrt X, Y & Z
4071   
4072    :param float dsp: d-spacing of peak
4073   
4074    :returns: list getTOFgammaDeriv: d(gam)/dX & d(gam)/dY
4075   
4076    '''
4077    return dsp,dsp**2,1.0
4078   
4079def getTOFbeta(ins,dsp):
4080    '''get TOF peak profile beta
4081   
4082    :param dict ins: instrument parameters with at least 'beat-0', 'beta-1' & 'beta-q'
4083      as values only
4084    :param float dsp: d-spacing of peak
4085   
4086    :returns: float getTOFbeta: peak beat
4087   
4088    '''
4089    return ins['beta-0']+ins['beta-1']/dsp**4+ins['beta-q']/dsp**2
4090   
4091def getTOFbetaDeriv(dsp):
4092    '''get derivatives of TOF peak profile beta wrt beta-0, beta-1, & beat-q
4093   
4094    :param float dsp: d-spacing of peak
4095   
4096    :returns: list getTOFbetaDeriv: d(beta)/d(beat-0), d(beta)/d(beta-1) & d(beta)/d(beta-q)
4097   
4098    '''
4099    return 1.0,1./dsp**4,1./dsp**2
4100   
4101def getTOFalpha(ins,dsp):
4102    '''get TOF peak profile alpha
4103   
4104    :param dict ins: instrument parameters with at least 'alpha'
4105      as values only
4106    :param float dsp: d-spacing of peak
4107   
4108    :returns: flaot getTOFalpha: peak alpha
4109   
4110    '''
4111    return ins['alpha']/dsp
4112   
4113def getTOFalphaDeriv(dsp):
4114    '''get derivatives of TOF peak profile beta wrt alpha
4115   
4116    :param float dsp: d-spacing of peak
4117   
4118    :returns: float getTOFalphaDeriv: d(alp)/d(alpha)
4119   
4120    '''
4121    return 1./dsp
4122   
4123def getPinkalpha(ins,tth):
4124    '''get TOF peak profile alpha
4125   
4126    :param dict ins: instrument parameters with at least 'alpha'
4127      as values only
4128    :param float tth: 2-theta of peak
4129   
4130    :returns: flaot getPinkalpha: peak alpha
4131   
4132    '''
4133    return ins['alpha-0']+ ins['alpha-1']*tand(tth/2.)
4134   
4135def getPinkalphaDeriv(tth):
4136    '''get derivatives of TOF peak profile beta wrt alpha
4137   
4138    :param float dsp: d-spacing of peak
4139   
4140    :returns: float getTOFalphaDeriv: d(alp)/d(alpha-0), d(alp)/d(alpha-1)
4141   
4142    '''
4143    return 1.0,tand(tth/2.)
4144   
4145def getPinkbeta(ins,tth):
4146    '''get TOF peak profile beta
4147   
4148    :param dict ins: instrument parameters with at least 'beat-0' & 'beta-1'
4149      as values only
4150    :param float tth: 2-theta of peak
4151   
4152    :returns: float getaPinkbeta: peak beta
4153   
4154    '''
4155    return ins['beta-0']+ins['beta-1']*tand(tth/2.)
4156   
4157def getPinkbetaDeriv(tth):
4158    '''get derivatives of TOF peak profile beta wrt beta-0 & beta-1
4159   
4160    :param float dsp: d-spacing of peak
4161   
4162    :returns: list getTOFbetaDeriv: d(beta)/d(beta-0) & d(beta)/d(beta-1)
4163   
4164    '''
4165    return 1.0,tand(tth/2.)
4166   
4167def setPeakparms(Parms,Parms2,pos,mag,ifQ=False,useFit=False):
4168    '''set starting peak parameters for single peak fits from plot selection or auto selection
4169   
4170    :param dict Parms: instrument parameters dictionary
4171    :param dict Parms2: table lookup for TOF profile coefficients
4172    :param float pos: peak position in 2-theta, TOF or Q (ifQ=True)
4173    :param float mag: peak top magnitude from pick
4174    :param bool ifQ: True if pos in Q
4175    :param bool useFit: True if use fitted CW Parms values (not defaults)
4176   
4177    :returns: list XY: peak list entry:
4178        for CW: [pos,0,mag,1,sig,0,gam,0]
4179        for TOF: [pos,0,mag,1,alp,0,bet,0,sig,0,gam,0]
4180        for Pink: [pos,0,mag,1,alp,0,bet,0,sig,0,gam,0]
4181        NB: mag refinement set by default, all others off
4182   
4183    '''
4184    ind = 0
4185    if useFit:
4186        ind = 1
4187    ins = {}
4188    if 'T' in Parms['Type'][0]:
4189        if ifQ:
4190            dsp = 2.*np.pi/pos
4191            pos = Parms['difC']*dsp
4192        else:
4193            dsp = pos/Parms['difC'][1]
4194        if 'Pdabc' in Parms2:
4195            for x in ['sig-0','sig-1','sig-2','sig-q','X','Y','Z']:
4196                ins[x] = Parms.get(x,[0.0,0.0])[ind]
4197            Pdabc = Parms2['Pdabc'].T
4198            alp = np.interp(dsp,Pdabc[0],Pdabc[1])
4199            bet = np.interp(dsp,Pdabc[0],Pdabc[2])
4200        else:
4201            for x in ['alpha','beta-0','beta-1','beta-q','sig-0','sig-1','sig-2','sig-q','X','Y','Z']:
4202                ins[x] = Parms.get(x,[0.0,0.0])[ind]
4203            alp = getTOFalpha(ins,dsp)
4204            bet = getTOFbeta(ins,dsp)
4205        sig = getTOFsig(ins,dsp)
4206        gam = getTOFgamma(ins,dsp)
4207        XY = [pos,0,mag,1,alp,0,bet,0,sig,0,gam,0]
4208    elif 'C' in Parms['Type'][0]:                            #CW data - TOF later in an else
4209        for x in ['U','V','W','X','Y','Z']:
4210            ins[x] = Parms.get(x,[0.0,0.0])[ind]
4211        if ifQ:                              #qplot - convert back to 2-theta
4212            pos = 2.0*asind(pos*getWave(Parms)/(4*math.pi))
4213        sig = getCWsig(ins,pos)
4214        gam = getCWgam(ins,pos)           
4215        XY = [pos,0, mag,1, sig,0, gam,0]       #default refine intensity 1st
4216    elif 'B' in Parms['Type'][0]:
4217        for x in ['U','V','W','X','Y','Z','alpha-0','alpha-1','beta-0','beta-1']:
4218            ins[x] = Parms.get(x,[0.0,0.0])[ind]
4219        if ifQ:                              #qplot - convert back to 2-theta
4220            pos = 2.0*asind(pos*getWave(Parms)/(4*math.pi))
4221        alp = getPinkalpha(ins,pos)
4222        bet = getPinkbeta(ins,pos)
4223        sig = getCWsig(ins,pos)
4224        gam = getCWgam(ins,pos)           
4225        XY = [pos,0,mag,1,alp,0,bet,0,sig,0,gam,0]       #default refine intensity 1st
4226    return XY
4227   
4228################################################################################
4229##### MC/SA stuff
4230################################################################################
4231
4232#scipy/optimize/anneal.py code modified by R. Von Dreele 2013
4233# Original Author: Travis Oliphant 2002
4234# Bug-fixes in 2006 by Tim Leslie
4235
4236
4237import numpy
4238from numpy import asarray, exp, squeeze, sign, \
4239     all, shape, array, where
4240from numpy import random
4241
4242#__all__ = ['anneal']
4243
4244class base_schedule(object):
4245    def __init__(self):
4246        self.dwell = 20
4247        self.lower = 0.
4248        self.upper = 1.
4249        self.Ninit = 50
4250        self.accepted = 0
4251        self.tests = 0
4252        self.feval = 0
4253        self.k = 0
4254        self.T = None
4255
4256    def init(self, **options):
4257        self.__dict__.update(options)
4258        self.lower = asarray(self.lower)
4259        self.lower = where(self.lower == numpy.NINF, -_double_max, self.lower)
4260        self.upper = asarray(self.upper)
4261        self.upper = where(self.upper == numpy.PINF, _double_max, self.upper)
4262        self.k = 0
4263        self.accepted = 0
4264        self.feval = 0
4265        self.tests = 0
4266
4267    def getstart_temp(self, best_state):
4268        ''' Find a matching starting temperature and starting parameters vector
4269        i.e. find x0 such that func(x0) = T0.
4270
4271        :param best_state: _state
4272            A _state object to store the function value and x0 found.
4273
4274        :returns: x0 : array
4275            The starting parameters vector.
4276        '''
4277
4278        assert(not self.dims is None)
4279        lrange = self.lower
4280        urange = self.upper
4281        fmax = _double_min
4282        fmin = _double_max
4283        for _ in range(self.Ninit):
4284            x0 = random.uniform(size=self.dims)*(urange-lrange) + lrange
4285            fval = self.func(x0, *self.args)
4286            self.feval += 1
4287            if fval > fmax:
4288                fmax = fval
4289            if fval < fmin:
4290                fmin = fval
4291                best_state.cost = fval
4292                best_state.x = array(x0)
4293
4294        self.T0 = (fmax-fmin)*1.5
4295        return best_state.x
4296       
4297    def set_range(self,x0,frac):
4298        delrange = frac*(self.upper-self.lower)
4299        self.upper = x0+delrange
4300        self.lower = x0-delrange
4301
4302    def accept_test(self, dE):
4303        T = self.T
4304        self.tests += 1
4305        if dE < 0:
4306            self.accepted += 1
4307            return 1
4308        p = exp(-dE*1.0/T)
4309        if (p > random.uniform(0.0, 1.0)):
4310            self.accepted += 1
4311            return 1
4312        return 0
4313
4314    def update_guess(self, x0):
4315        return np.squeeze(np.random.uniform(0.,1.,size=self.dims))*(self.upper-self.lower)+self.lower
4316
4317    def update_temp(self, x0):
4318        pass
4319
4320class fast_sa(base_schedule):
4321    def init(self, **options):
4322        self.__dict__.update(options)
4323
4324    def update_guess(self, x0):
4325        x0 = asarray(x0)
4326        u = squeeze(random.uniform(0.0, 1.0, size=self.dims))
4327        T = self.T
4328        xc = (sign(u-0.5)*T*((1+1.0/T)**abs(2*u-1)-1.0)+1.0)/2.0
4329        xnew = xc*(self.upper - self.lower)+self.lower
4330        return xnew
4331
4332    def update_temp(self):
4333        self.T = self.T0*exp(-self.c * self.k**(self.quench))
4334        self.k += 1
4335        return
4336
4337class log_sa(base_schedule):        #OK
4338
4339    def init(self,**options):
4340        self.__dict__.update(options)
4341       
4342    def update_guess(self,x0):     #same as default #TODO - is this a reasonable update procedure?
4343        u = squeeze(random.uniform(0.0, 1.0, size=self.dims))
4344        T = self.T
4345        xc = (sign(u-0.5)*T*((1+1.0/T)**abs(2*u-1)-1.0)+1.0)/2.0
4346        xnew = xc*(self.upper - self.lower)+self.lower
4347        return xnew
4348       
4349    def update_temp(self):
4350        self.k += 1
4351        self.T = self.T0*self.slope**self.k
4352       
4353class _state(object):
4354    def __init__(self):
4355        self.x = None
4356        self.cost = None
4357
4358def makeTsched(data):
4359    if data['Algorithm'] == 'fast':
4360        sched = fast_sa()
4361        sched.quench = data['fast parms'][0]
4362        sched.c = data['fast parms'][1]
4363    elif data['Algorithm'] == 'log':
4364        sched = log_sa()
4365        sched.slope = data['log slope']
4366    sched.T0 = data['Annealing'][0]
4367    if not sched.T0:
4368        sched.T0 = 50.
4369    Tf = data['Annealing'][1]
4370    if not Tf:
4371        Tf = 0.001
4372    Tsched = [sched.T0,]
4373    while Tsched[-1] > Tf:
4374        sched.update_temp()
4375        Tsched.append(sched.T)
4376    return Tsched[1:]
4377   
4378def anneal(func, x0, args=(), schedule='fast', 
4379           T0=None, Tf=1e-12, maxeval=None, maxaccept=None, maxiter=400,
4380           feps=1e-6, quench=1.0, c=1.0,
4381           lower=-100, upper=100, dwell=50, slope=0.9,ranStart=False,
4382           ranRange=0.10,autoRan=False,dlg=None):
4383    '''Minimize a function using simulated annealing.
4384
4385    Schedule is a schedule class implementing the annealing schedule.
4386    Available ones are 'fast', 'cauchy', 'boltzmann'
4387
4388    :param callable func: f(x, \\*args)
4389        Function to be optimized.
4390    :param ndarray x0:
4391        Initial guess.
4392    :param tuple args:
4393        Extra parameters to `func`.
4394    :param base_schedule schedule:
4395        Annealing schedule to use (a class).
4396    :param float T0:
4397        Initial Temperature (estimated as 1.2 times the largest
4398        cost-function deviation over random points in the range).
4399    :param float Tf:
4400        Final goal temperature.
4401    :param int maxeval:
4402        Maximum function evaluations.
4403    :param int maxaccept:
4404        Maximum changes to accept.
4405    :param int maxiter:
4406        Maximum cooling iterations.
4407    :param float feps:
4408        Stopping relative error tolerance for the function value in
4409        last four coolings.
4410    :param float quench,c:
4411        Parameters to alter fast_sa schedule.
4412    :param float/ndarray lower,upper:
4413        Lower and upper bounds on `x`.
4414    :param int dwell:
4415        The number of times to search the space at each temperature.
4416    :param float slope:
4417        Parameter for log schedule
4418    :param bool ranStart=False:
4419        True for set 10% of ranges about x
4420
4421    :returns: (xmin, Jmin, T, feval, iters, accept, retval) where
4422
4423     * xmin (ndarray): Point giving smallest value found.
4424     * Jmin (float): Minimum value of function found.
4425     * T (float): Final temperature.
4426     * feval (int): Number of function evaluations.
4427     * iters (int): Number of cooling iterations.
4428     * accept (int): Number of tests accepted.
4429     * retval (int): Flag indicating stopping condition:
4430
4431              *  0: Points no longer changing
4432              *  1: Cooled to final temperature
4433              *  2: Maximum function evaluations
4434              *  3: Maximum cooling iterations reached
4435              *  4: Maximum accepted query locations reached
4436              *  5: Final point not the minimum amongst encountered points
4437
4438    *Notes*:
4439    Simulated annealing is a random algorithm which uses no derivative
4440    information from the function being optimized. In practice it has
4441    been more useful in discrete optimization than continuous
4442    optimization, as there are usually better algorithms for continuous
4443    optimization problems.
4444
4445    Some experimentation by trying the difference temperature
4446    schedules and altering their parameters is likely required to
4447    obtain good performance.
4448
4449    The randomness in the algorithm comes from random sampling in numpy.
4450    To obtain the same results you can call numpy.random.seed with the
4451    same seed immediately before calling scipy.optimize.anneal.
4452
4453    We give a brief description of how the three temperature schedules
4454    generate new points and vary their temperature. Temperatures are
4455    only updated with iterations in the outer loop. The inner loop is
4456    over range(dwell), and new points are generated for
4457    every iteration in the inner loop. (Though whether the proposed
4458    new points are accepted is probabilistic.)
4459
4460    For readability, let d denote the dimension of the inputs to func.
4461    Also, let x_old denote the previous state, and k denote the
4462    iteration number of the outer loop. All other variables not
4463    defined below are input variables to scipy.optimize.anneal itself.
4464
4465    In the 'fast' schedule the updates are ::
4466
4467        u ~ Uniform(0, 1, size=d)
4468        y = sgn(u - 0.5) * T * ((1+ 1/T)**abs(2u-1) -1.0)
4469        xc = y * (upper - lower)
4470        x_new = x_old + xc
4471
4472        T_new = T0 * exp(-c * k**quench)
4473
4474    '''
4475   
4476    ''' Scipy license:
4477        Copyright (c) 2001, 2002 Enthought, Inc.
4478    All rights reserved.
4479   
4480    Copyright (c) 2003-2016 SciPy Developers.
4481    All rights reserved.
4482   
4483    Redistribution and use in source and binary forms, with or without
4484    modification, are permitted provided that the following conditions are met:
4485   
4486      a. Redistributions of source code must retain the above copyright notice,
4487         this list of conditions and the following disclaimer.
4488      b. Redistributions in binary form must reproduce the above copyright
4489         notice, this list of conditions and the following disclaimer in the
4490         documentation and/or other materials provided with the distribution.
4491      c. Neither the name of Enthought nor the names of the SciPy Developers
4492         may be used to endorse or promote products derived from this software
4493         without specific prior written permission.
4494    '''
4495    x0 = asarray(x0)
4496    lower = asarray(lower)
4497    upper = asarray(upper)
4498
4499    schedule = eval(schedule+'_sa()')
4500    #   initialize the schedule
4501    schedule.init(dims=shape(x0),func=func,args=args,T0=T0,lower=lower, upper=upper,
4502        c=c, quench=quench, dwell=dwell, slope=slope)
4503
4504    current_state, last_state, best_state = _state(), _state(), _state()
4505    if ranStart:
4506        schedule.set_range(x0,ranRange)
4507    if T0 is None:
4508        x0 = schedule.getstart_temp(best_state)
4509    else:
4510        x0 = random.uniform(size=len(x0))*(upper-lower) + lower
4511        best_state.x = None
4512        best_state.cost = numpy.Inf
4513
4514    last_state.x = asarray(x0).copy()
4515    fval = func(x0,*args)
4516    schedule.feval += 1
4517    last_state.cost = fval
4518    if last_state.cost < best_state.cost:
4519        best_state.cost = fval
4520        best_state.x = asarray(x0).copy()
4521    schedule.T = schedule.T0
4522    fqueue = [100, 300, 500, 700]
4523    iters = 1
4524    keepGoing = True
4525    bestn = 0
4526    while keepGoing:
4527        retval = 0
4528        for n in range(dwell):
4529            current_state.x = schedule.update_guess(last_state.x)
4530            current_state.cost = func(current_state.x,*args)
4531            schedule.feval += 1
4532
4533            dE = current_state.cost - last_state.cost
4534            if schedule.accept_test(dE):
4535                last_state.x = current_state.x.copy()
4536                last_state.cost = current_state.cost
4537                if last_state.cost < best_state.cost:
4538                    best_state.x = last_state.x.copy()
4539                    best_state.cost = last_state.cost
4540                    bestn = n
4541                    if best_state.cost < 1.0 and autoRan:
4542                        schedule.set_range(x0,best_state.cost/2.)                       
4543        if dlg:
4544            GoOn = dlg.Update(min(100.,best_state.cost*100),
4545                newmsg='%s%8.5f, %s%d\n%s%8.4f%s'%('Temperature =',schedule.T, \
4546                    'Best trial:',bestn,  \
4547                    'MC/SA Residual:',best_state.cost*100,'%', \
4548                    ))[0]
4549            if not GoOn:
4550                best_state.x = last_state.x.copy()
4551                best_state.cost = last_state.cost
4552                retval = 5
4553        schedule.update_temp()
4554        iters += 1
4555        # Stopping conditions
4556        # 0) last saved values of f from each cooling step
4557        #     are all very similar (effectively cooled)
4558        # 1) Tf is set and we are below it
4559        # 2) maxeval is set and we are past it
4560        # 3) maxiter is set and we are past it
4561        # 4) maxaccept is set and we are past it
4562        # 5) user canceled run via progress bar
4563
4564        fqueue.append(squeeze(last_state.cost))
4565        fqueue.pop(0)
4566        af = asarray(fqueue)*1.0
4567        if retval == 5:
4568            G2fil.G2Print ('Error: User terminated run; incomplete MC/SA')
4569            keepGoing = False
4570            break
4571        if all(abs((af-af[0])/af[0]) < feps):
4572            retval = 0
4573            if abs(af[-1]-best_state.cost) > feps*10:
4574                retval = 5
4575                G2fil.G2Print (" Warning: Cooled to %.4f > selected Tmin %.4f in %d steps"%(squeeze(last_state.cost),Tf,iters-1))
4576            break
4577        if (Tf is not None) and (schedule.T < Tf):
4578#            print ' Minimum T reached in %d steps'%(iters-1)
4579            retval = 1
4580            break
4581        if (maxeval is not None) and (schedule.feval > maxeval):
4582            retval = 2
4583            break
4584        if (iters > maxiter):
4585            G2fil.G2Print  (" Warning: Maximum number of iterations exceeded.")
4586            retval = 3
4587            break
4588        if (maxaccept is not None) and (schedule.accepted > maxaccept):
4589            retval = 4
4590            break
4591
4592    return best_state.x, best_state.cost, schedule.T, \
4593           schedule.feval, iters, schedule.accepted, retval
4594
4595def worker(iCyc,data,RBdata,reflType,reflData,covData,out_q,out_t,out_n,nprocess=-1):
4596    outlist = []
4597    timelist = []
4598    nsflist = []
4599    random.seed(int(time.time())%100000+nprocess)   #make sure each process has a different random start
4600    for n in range(iCyc):
4601        result = mcsaSearch(data,RBdata,reflType,reflData,covData,None,False)         #mcsa result,time,rcov
4602        outlist.append(result[0])
4603        timelist.append(result[1])
4604        nsflist.append(result[2])
4605        G2fil.G2Print (' MC/SA final fit: %.3f%% structure factor time: %.3f'%(100*result[0][2],result[1]))
4606    out_q.put(outlist)
4607    out_t.put(timelist)
4608    out_n.put(nsflist)
4609
4610def MPmcsaSearch(nCyc,data,RBdata,reflType,reflData,covData,nprocs):
4611    import multiprocessing as mp
4612   
4613    out_q = mp.Queue()
4614    out_t = mp.Queue()
4615    out_n = mp.Queue()
4616    procs = []
4617    totsftime = 0.
4618    totnsf = 0
4619    iCyc = np.zeros(nprocs)
4620    for i in range(nCyc):
4621        iCyc[i%nprocs] += 1
4622    for i in range(nprocs):
4623        p = mp.Process(target=worker,args=(int(iCyc[i]),data,RBdata,reflType,reflData,covData,out_q,out_t,out_n,i))
4624        procs.append(p)
4625        p.start()
4626    resultlist = []
4627    for i in range(nprocs):
4628        resultlist += out_q.get()
4629        totsftime += np.sum(out_t.get())
4630        totnsf += np.sum(out_n.get())
4631    for p in procs:
4632        p.join()
4633    return resultlist,totsftime,totnsf
4634
4635def mcsaSearch(data,RBdata,reflType,reflData,covData,pgbar,start=True):
4636    '''default doc string
4637   
4638    :param type name: description
4639   
4640    :returns: type name: description
4641    '''
4642   
4643    class RandomDisplacementBounds(object):
4644        '''random displacement with bounds'''
4645        def __init__(self, xmin, xmax, stepsize=0.5):
4646            self.xmin = xmin
4647            self.xmax = xmax
4648            self.stepsize = stepsize
4649   
4650        def __call__(self, x):
4651            '''take a random step but ensure the new position is within the bounds'''
4652            while True:
4653                # this could be done in a much more clever way, but it will work for example purposes
4654                steps = self.xmax-self.xmin
4655                xnew = x + np.random.uniform(-self.stepsize*steps, self.stepsize*steps, np.shape(x))
4656                if np.all(xnew < self.xmax) and np.all(xnew > self.xmin):
4657                    break
4658            return xnew
4659   
4660    global tsum,nsum
4661    tsum = 0.
4662    nsum = 0
4663   
4664    def getMDparms(item,pfx,parmDict,varyList):
4665        parmDict[pfx+'MDaxis'] = item['axis']
4666        parmDict[pfx+'MDval'] = item['Coef'][0]
4667        if item['Coef'][1]:
4668            varyList += [pfx+'MDval',]
4669            limits = item['Coef'][2]
4670            lower.append(limits[0])
4671            upper.append(limits[1])
4672                       
4673    def getAtomparms(item,pfx,aTypes,SGData,parmDict,varyList):
4674        parmDict[pfx+'Atype'] = item['atType']
4675        aTypes |= set([item['atType'],]) 
4676        pstr = ['Ax','Ay','Az']
4677        XYZ = [0,0,0]
4678        for i in range(3):
4679            name = pfx+pstr[i]
4680            parmDict[name] = item['Pos'][0][i]
4681            XYZ[i] = parmDict[name]
4682            if item['Pos'][1][i]:
4683                varyList += [name,]
4684                limits = item['Pos'][2][i]
4685                lower.append(limits[0])
4686                upper.append(limits[1])
4687        parmDict[pfx+'Amul'] = len(list(G2spc.GenAtom(XYZ,SGData)))
4688           
4689    def getRBparms(item,mfx,aTypes,RBdata,SGData,atNo,parmDict,varyList):
4690        parmDict[mfx+'MolCent'] = item['MolCent']
4691        parmDict[mfx+'RBId'] = item['RBId']
4692        pstr = ['Px','Py','Pz']
4693        ostr = ['Qa','Qi','Qj','Qk']    #angle,vector not quaternion
4694        for i in range(3):
4695            name = mfx+pstr[i]
4696            parmDict[name] = item['Pos'][0][i]
4697            if item['Pos'][1][i]:
4698                varyList += [name,]
4699                limits = item['Pos'][2][i]
4700                lower.append(limits[0])
4701                upper.append(limits[1])
4702        AV = item['Ori'][0]
4703        A = AV[0]
4704        V = AV[1:]
4705        for i in range(4):
4706            name = mfx+ostr[i]
4707            if i:
4708                parmDict[name] = V[i-1]
4709            else:
4710                parmDict[name] = A
4711            if item['Ovar'] == 'AV':
4712                varyList += [name,]
4713                limits = item['Ori'][2][i]
4714                lower.append(limits[0])
4715                upper.append(limits[1])
4716            elif item['Ovar'] == 'A' and not i:
4717                varyList += [name,]
4718                limits = item['Ori'][2][i]
4719                lower.append(limits[0])
4720                upper.append(limits[1])
4721        if 'Tor' in item:      #'Tor' not there for 'Vector' RBs
4722            for i in range(len(item['Tor'][0])):
4723                name = mfx+'Tor'+str(i)
4724                parmDict[name] = item['Tor'][0][i]
4725                if item['Tor'][1][i]:
4726                    varyList += [name,]
4727                    limits = item['Tor'][2][i]
4728                    lower.append(limits[0])
4729                    upper.append(limits[1])
4730        atypes = RBdata[item['Type']][item['RBId']]['rbTypes']
4731        aTypes |= set(atypes)
4732        atNo += len(atypes)
4733        return atNo
4734               
4735    def GetAtomM(Xdata,SGData):
4736        Mdata = []
4737        for xyz in Xdata:
4738            Mdata.append(float(len(list(G2spc.GenAtom(xyz,SGData)))))
4739        return np.array(Mdata)
4740       
4741    def GetAtomT(RBdata,parmDict):
4742        'Needs a doc string'
4743        atNo = parmDict['atNo']
4744        nfixAt = parmDict['nfixAt']
4745        Tdata = atNo*[' ',]
4746        for iatm in range(nfixAt):
4747            parm = ':'+str(iatm)+':Atype'
4748            if parm in parmDict:
4749                Tdata[iatm] = aTypes.index(parmDict[parm])
4750        iatm = nfixAt
4751        for iObj in range(parmDict['nObj']):
4752            pfx = str(iObj)+':'
4753            if parmDict[pfx+'Type'] in ['Vector','Residue']:
4754                if parmDict[pfx+'Type'] == 'Vector':
4755                    RBRes = RBdata['Vector'][parmDict[pfx+'RBId']]
4756                    nAtm = len(RBRes['rbVect'][0])
4757                else:       #Residue
4758                    RBRes = RBdata['Residue'][parmDict[pfx+'RBId']]
4759                    nAtm = len(RBRes['rbXYZ'])
4760                for i in range(nAtm):
4761                    Tdata[iatm] = aTypes.index(RBRes['rbTypes'][i])
4762                    iatm += 1
4763            elif parmDict[pfx+'Type'] == 'Atom':
4764                atNo = parmDict[pfx+'atNo']
4765                parm = pfx+'Atype'              #remove extra ':'
4766                if parm in parmDict:
4767                    Tdata[atNo] = aTypes.index(parmDict[parm])
4768                iatm += 1
4769            else:
4770                continue        #skips March Dollase
4771        return Tdata
4772       
4773    def GetAtomX(RBdata,parmDict):
4774        'Needs a doc string'
4775        Bmat = parmDict['Bmat']
4776        atNo = parmDict['atNo']
4777        nfixAt = parmDict['nfixAt']
4778        Xdata = np.zeros((3,atNo))
4779        keys = {':Ax':Xdata[0],':Ay':Xdata[1],':Az':Xdata[2]}
4780        for iatm in range(nfixAt):
4781            for key in keys:
4782                parm = ':'+str(iatm)+key
4783                if parm in parmDict:
4784                    keys[key][iatm] = parmDict[parm]
4785        iatm = nfixAt
4786        for iObj in range(parmDict['nObj']):
4787            pfx = str(iObj)+':'
4788            if parmDict[pfx+'Type'] in ['Vector','Residue']:
4789                if parmDict[pfx+'Type'] == 'Vector':
4790                    RBRes = RBdata['Vector'][parmDict[pfx+'RBId']]
4791                    vecs = RBRes['rbVect']
4792                    mags = RBRes['VectMag']
4793                    Cart = np.zeros_like(vecs[0])
4794                    for vec,mag in zip(vecs,mags):
4795                        Cart += vec*mag
4796                elif parmDict[pfx+'Type'] == 'Residue':
4797                    RBRes = RBdata['Residue'][parmDict[pfx+'RBId']]
4798                    Cart = np.array(RBRes['rbXYZ'])
4799                    for itor,seq in enumerate(RBRes['rbSeq']):
4800                        QuatA = AVdeg2Q(parmDict[pfx+'Tor'+str(itor)],Cart[seq[0]]-Cart[seq[1]])
4801                        Cart[seq[3]] = prodQVQ(QuatA,Cart[seq[3]]-Cart[seq[1]])+Cart[seq[1]]
4802                if parmDict[pfx+'MolCent'][1]:
4803                    Cart -= parmDict[pfx+'MolCent'][0]
4804                Qori = AVdeg2Q(parmDict[pfx+'Qa'],[parmDict[pfx+'Qi'],parmDict[pfx+'Qj'],parmDict[pfx+'Qk']])
4805                Pos = np.array([parmDict[pfx+'Px'],parmDict[pfx+'Py'],parmDict[pfx+'Pz']])
4806                Xdata.T[iatm:iatm+len(Cart)] = np.inner(Bmat,prodQVQ(Qori,Cart)).T+Pos
4807                iatm += len(Cart)
4808            elif parmDict[pfx+'Type'] == 'Atom':
4809                atNo = parmDict[pfx+'atNo']
4810                for key in keys:
4811                    parm = pfx+key[1:]              #remove extra ':'
4812                    if parm in parmDict:
4813                        keys[key][atNo] = parmDict[parm]
4814                iatm += 1
4815            else:
4816                continue        #skips March Dollase
4817        return Xdata.T
4818       
4819    def getAllTX(Tdata,Mdata,Xdata,SGM,SGT):
4820        allX = np.inner(Xdata,SGM)+SGT
4821        allT = np.repeat(Tdata,allX.shape[1])
4822        allM = np.repeat(Mdata,allX.shape[1])
4823        allX = np.reshape(allX,(-1,3))
4824        return allT,allM,allX
4825
4826    def getAllX(Xdata,SGM,SGT):
4827        allX = np.inner(Xdata,SGM)+SGT
4828        allX = np.reshape(allX,(-1,3))
4829        return allX
4830       
4831    def normQuaternions(RBdata,parmDict,varyList,values):
4832        for iObj in range(parmDict['nObj']):
4833            pfx = str(iObj)+':'
4834            if parmDict[pfx+'Type'] in ['Vector','Residue']:
4835                Qori = AVdeg2Q(parmDict[pfx+'Qa'],[parmDict[pfx+'Qi'],parmDict[pfx+'Qj'],parmDict[pfx+'Qk']])
4836                A,V = Q2AVdeg(Qori)
4837                for i,name in enumerate(['Qa','Qi','Qj','Qk']):
4838                    if i:
4839                        parmDict[pfx+name] = V[i-1]
4840                    else:
4841                        parmDict[pfx+name] = A
4842       
4843    def mcsaCalc(values,refList,rcov,cosTable,ifInv,allFF,RBdata,varyList,parmDict):
4844        ''' Compute structure factors for all h,k,l for phase
4845        input:
4846            refList: [ref] where each ref = h,k,l,m,d,...
4847            rcov:   array[nref,nref] covariance terms between Fo^2 values
4848            ifInv:  bool True if centrosymmetric
4849            allFF: array[nref,natoms] each value is mult*FF(H)/max(mult)
4850            RBdata: [dict] rigid body dictionary
4851            varyList: [list] names of varied parameters in MC/SA (not used here)           
4852            ParmDict: [dict] problem parameters
4853        puts result F^2 in each ref[5] in refList
4854        returns:
4855            delt-F*rcov*delt-F/sum(Fo^2)
4856        '''   
4857           
4858        global tsum,nsum
4859        t0 = time.time()
4860        parmDict.update(dict(zip(varyList,values)))             #update parameter tables
4861        Xdata = GetAtomX(RBdata,parmDict)                       #get new atom coords from RB
4862        allX = getAllX(Xdata,SGM,SGT)                           #fill unit cell - dups. OK
4863        MDval = parmDict['0:MDval']                             #get March-Dollase coeff
4864        HX2pi = 2.*np.pi*np.inner(allX,refList[:3].T)           #form 2piHX for every H,X pair
4865        Aterm = refList[3]*np.sum(allFF*np.cos(HX2pi),axis=0)**2    #compute real part for all H
4866        refList[5] = Aterm
4867        if not ifInv:
4868            refList[5] += refList[3]*np.sum(allFF*np.sin(HX2pi),axis=0)**2    #imaginary part for all H
4869        if len(cosTable):        #apply MD correction
4870            refList[5] *= np.sum(np.sqrt((MDval/(cosTable*(MDval**3-1.)+1.))**3),axis=1)/cosTable.shape[1]
4871        sumFcsq = np.sum(refList[5])
4872        scale = parmDict['sumFosq']/sumFcsq
4873        refList[5] *= scale
4874        refList[6] = refList[4]-refList[5]
4875        M = np.inner(refList[6],np.inner(rcov,refList[6]))
4876        tsum += (time.time()-t0)
4877        nsum += 1
4878        return np.sqrt(M/np.sum(refList[4]**2))
4879   
4880    def MCSAcallback(x, f,accept):
4881        return not pgbar.Update(min(100.,f*100),
4882            newmsg='%s%8.4f%s'%('MC/SA Residual:',f*100,'%'))[0]
4883
4884    sq2pi = np.sqrt(2*np.pi)
4885    sq4pi = np.sqrt(4*np.pi)
4886    generalData = data['General']
4887    Amat,Bmat = G2lat.cell2AB(generalData['Cell'][1:7])
4888    Gmat,gmat = G2lat.cell2Gmat(generalData['Cell'][1:7])
4889    SGData = generalData['SGData']
4890    SGM = np.array([SGData['SGOps'][i][0] for i in range(len(SGData['SGOps']))])
4891    SGMT = np.array([SGData['SGOps'][i][0].T for i in range(len(SGData['SGOps']))])
4892    SGT = np.array([SGData['SGOps'][i][1] for i in range(len(SGData['SGOps']))])
4893    fixAtoms = data['Atoms']                       #if any
4894    cx,ct,cs = generalData['AtomPtrs'][:3]
4895    aTypes = set([])
4896    parmDict = {'Bmat':Bmat,'Gmat':Gmat}
4897    varyList = []
4898    atNo = 0
4899    for atm in fixAtoms:
4900        pfx = ':'+str(atNo)+':'
4901        parmDict[pfx+'Atype'] = atm[ct]
4902        aTypes |= set([atm[ct],])
4903        pstr = ['Ax','Ay','Az']
4904        parmDict[pfx+'Amul'] = atm[cs+1]
4905        for i in range(3):
4906            name = pfx+pstr[i]
4907            parmDict[name] = atm[cx+i]
4908        atNo += 1
4909    parmDict['nfixAt'] = len(fixAtoms)       
4910    MCSA = generalData['MCSA controls']
4911    reflName = MCSA['Data source']
4912    MCSAObjs = data['MCSA']['Models']               #list of MCSA models
4913    upper = []
4914    lower = []
4915    MDvec = np.zeros(3)
4916    for i,item in enumerate(MCSAObjs):
4917        mfx = str(i)+':'
4918        parmDict[mfx+'Type'] = item['Type']
4919        if item['Type'] == 'MD':
4920            getMDparms(item,mfx,parmDict,varyList)
4921            MDvec = np.array(item['axis'])
4922        elif item['Type'] == 'Atom':
4923            getAtomparms(item,mfx,aTypes,SGData,parmDict,varyList)
4924            parmDict[mfx+'atNo'] = atNo
4925            atNo += 1
4926        elif item['Type'] in ['Residue','Vector']:
4927            atNo = getRBparms(item,mfx,aTypes,RBdata,SGData,atNo,parmDict,varyList)
4928    parmDict['atNo'] = atNo                 #total no. of atoms
4929    parmDict['nObj'] = len(MCSAObjs)
4930    aTypes = list(aTypes)
4931    Tdata = GetAtomT(RBdata,parmDict)
4932    Xdata = GetAtomX(RBdata,parmDict)
4933    Mdata = GetAtomM(Xdata,SGData)
4934    allT,allM = getAllTX(Tdata,Mdata,Xdata,SGM,SGT)[:2]
4935    FFtables = G2el.GetFFtable(aTypes)
4936    refs = []
4937    allFF = []
4938    cosTable = []
4939    sumFosq = 0
4940    if 'PWDR' in reflName:
4941        for ref in reflData:
4942            h,k,l,m,d,pos,sig,gam,f = ref[:9]
4943            if d >= MCSA['dmin']:
4944                sig = np.sqrt(sig)      #var -> sig in centideg
4945                sig = .01*G2pwd.getgamFW(gam,sig)        #sig,gam -> FWHM in deg
4946                SQ = 0.25/d**2
4947                allFF.append(allM*[G2el.getFFvalues(FFtables,SQ,True)[i] for i in allT]/np.max(allM))
4948                refs.append([h,k,l,m,f*m,pos,sig])
4949                sumFosq += f*m
4950                Heqv = np.inner(np.array([h,k,l]),SGMT)
4951                cosTable.append(G2lat.CosAngle(Heqv,MDvec,Gmat))
4952        nRef = len(refs)
4953        cosTable = np.array(cosTable)**2
4954        rcov = np.zeros((nRef,nRef))
4955        for iref,refI in enumerate(refs):
4956            rcov[iref][iref] = 1./(sq4pi*refI[6])
4957            for jref,refJ in enumerate(refs[:iref]):
4958                t1 = refI[6]**2+refJ[6]**2
4959                t2 = (refJ[5]-refI[5])**2/(2.*t1)
4960                if t2 > 10.:
4961                    rcov[iref][jref] = 0.
4962                else:
4963                    rcov[iref][jref] = 1./(sq2pi*np.sqrt(t1)*np.exp(t2))
4964        rcov += (rcov.T-np.diagflat(np.diagonal(rcov)))
4965        Rdiag = np.sqrt(np.diag(rcov))
4966        Rnorm = np.outer(Rdiag,Rdiag)
4967        rcov /= Rnorm
4968    elif 'Pawley' in reflName:  #need a bail out if Pawley cov matrix doesn't exist.
4969        vNames = []
4970        pfx = str(data['pId'])+'::PWLref:'
4971        for iref,refI in enumerate(reflData):           #Pawley reflection set
4972            h,k,l,m,d,v,f,s = refI
4973            if d >= MCSA['dmin'] and v:       #skip unrefined ones
4974                vNames.append(pfx+str(iref))
4975                SQ = 0.25/d**2
4976                allFF.append(allM*[G2el.getFFvalues(FFtables,SQ,True)[i] for i in allT]/np.max(allM))
4977                refs.append([h,k,l,m,f*m,iref,0.])
4978                sumFosq += f*m
4979                Heqv = np.inner(np.array([h,k,l]),SGMT)
4980                cosTable.append(G2lat.CosAngle(Heqv,MDvec,Gmat))
4981        cosTable = np.array(cosTable)**2
4982        nRef = len(refs)
4983#        if generalData['doPawley'] and (covData['freshCOV'] or  MCSA['newDmin']):
4984        if covData['freshCOV'] or  MCSA['newDmin']:
4985            vList = covData['varyList']
4986            covMatrix = covData['covMatrix']
4987            rcov = getVCov(vNames,vList,covMatrix)
4988            rcov += (rcov.T-np.diagflat(np.diagonal(rcov)))
4989            Rdiag = np.sqrt(np.diag(rcov))
4990            Rdiag = np.where(Rdiag,Rdiag,1.0)
4991            Rnorm = np.outer(Rdiag,Rdiag)
4992            rcov /= Rnorm
4993            MCSA['rcov'] = rcov
4994            covData['freshCOV'] = False
4995            MCSA['newDmin'] = False
4996        else:
4997            rcov = MCSA['rcov']
4998    elif 'HKLF' in reflName:
4999        for ref in reflData:
5000            [h,k,l,m,d],f = ref[:5],ref[6]
5001            if d >= MCSA['dmin']:
5002                SQ = 0.25/d**2
5003                allFF.append(allM*[G2el.getFFvalues(FFtables,SQ,True)[i] for i in allT]/np.max(allM))
5004                refs.append([h,k,l,m,f*m,0.,0.])
5005                sumFosq += f*m
5006        nRef = len(refs)
5007        rcov = np.identity(len(refs))
5008    allFF = np.array(allFF).T
5009    refs = np.array(refs).T
5010    if start:
5011        G2fil.G2Print (' Minimum d-spacing used: %.2f No. reflections used: %d'%(MCSA['dmin'],nRef))
5012        G2fil.G2Print (' Number of parameters varied: %d'%(len(varyList)))
5013        start = False
5014    parmDict['sumFosq'] = sumFosq
5015    x0 = [parmDict[val] for val in varyList]
5016    ifInv = SGData['SGInv']
5017    bounds = np.array(list(zip(lower,upper)))
5018    if MCSA['Algorithm'] == 'Basin Hopping':
5019#        import basinhopping as bs
5020        take_step = RandomDisplacementBounds(np.array(lower), np.array(upper))
5021        results = so.basinhopping(mcsaCalc,x0,take_step=take_step,disp=True,T=MCSA['Annealing'][0],
5022                interval=MCSA['Annealing'][2]/10,niter=MCSA['Annealing'][2],minimizer_kwargs={'method':'L-BFGS-B','bounds':bounds,
5023                'args':(refs,rcov,cosTable,ifInv,allFF,RBdata,varyList,parmDict)},callback=MCSAcallback)
5024    else:
5025        T0 = MCSA['Annealing'][0]
5026        if not T0:
5027            T0 = None
5028        results = anneal(mcsaCalc,x0,args=(refs,rcov,cosTable,ifInv,allFF,RBdata,varyList,parmDict),
5029            schedule=MCSA['Algorithm'], dwell=MCSA['Annealing'][2],maxiter=10000,
5030            T0=T0, Tf=MCSA['Annealing'][1],
5031            quench=MCSA['fast parms'][0], c=MCSA['fast parms'][1], 
5032            lower=lower, upper=upper, slope=MCSA['log slope'],ranStart=MCSA.get('ranStart',False),
5033            ranRange=MCSA.get('ranRange',10.)/100.,autoRan=MCSA.get('autoRan',False),dlg=pgbar)
5034        G2fil.G2Print (' Acceptance rate: %.2f%% MCSA residual: %.2f%%'%(100.*results[5]/results[3],100.*results[1]))
5035        results = so.minimize(mcsaCalc,results[0],method='L-BFGS-B',args=(refs,rcov,cosTable,ifInv,allFF,RBdata,varyList,parmDict),
5036            bounds=bounds,)
5037    mcsaCalc(results['x'],refs,rcov,cosTable,ifInv,allFF,RBdata,varyList,parmDict)
5038    Result = [False,False,results['fun'],0.0,]+list(results['x'])
5039    Result.append(varyList)
5040    return Result,tsum,nsum,rcov
5041
5042       
5043################################################################################
5044##### Quaternion stuff
5045################################################################################
5046
5047def prodQQ(QA,QB):
5048    ''' Grassman quaternion product
5049        QA,QB quaternions; q=r+ai+bj+ck
5050    '''
5051    D = np.zeros(4)
5052    D[0] = QA[0]*QB[0]-QA[1]*QB[1]-QA[2]*QB[2]-QA[3]*QB[3]
5053    D[1] = QA[0]*QB[1]+QA[1]*QB[0]+QA[2]*QB[3]-QA[3]*QB[2]
5054    D[2] = QA[0]*QB[2]-QA[1]*QB[3]+QA[2]*QB[0]+QA[3]*QB[1]
5055    D[3] = QA[0]*QB[3]+QA[1]*QB[2]-QA[2]*QB[1]+QA[3]*QB[0]
5056   
5057#    D[0] = QA[0]*QB[0]-np.dot(QA[1:],QB[1:])
5058#    D[1:] = QA[0]*QB[1:]+QB[0]*QA[1:]+np.cross(QA[1:],QB[1:])
5059   
5060    return D
5061   
5062def normQ(QA):
5063    ''' get length of quaternion & normalize it
5064        q=r+ai+bj+ck
5065    '''
5066    n = np.sqrt(np.sum(np.array(QA)**2))
5067    return QA/n
5068   
5069def invQ(Q):
5070    '''
5071        get inverse of quaternion
5072        q=r+ai+bj+ck; q* = r-ai-bj-ck
5073    '''
5074    return Q*np.array([1,-1,-1,-1])
5075   
5076def prodQVQ(Q,V):
5077    '''
5078    compute the quaternion vector rotation qvq-1 = v'
5079    q=r+ai+bj+ck
5080    '''
5081    T2 = Q[0]*Q[1]
5082    T3 = Q[0]*Q[2]
5083    T4 = Q[0]*Q[3]
5084    T5 = -Q[1]*Q[1]
5085    T6 = Q[1]*Q[2]
5086    T7 = Q[1]*Q[3]
5087    T8 = -Q[2]*Q[2]
5088    T9 = Q[2]*Q[3]
5089    T10 = -Q[3]*Q[3]
5090    M = np.array([[T8+T10,T6-T4,T3+T7],[T4+T6,T5+T10,T9-T2],[T7-T3,T2+T9,T5+T8]])
5091    VP = 2.*np.inner(V,M)
5092    return VP+V
5093   
5094def Q2Mat(Q):
5095    ''' make rotation matrix from quaternion
5096        q=r+ai+bj+ck
5097    '''
5098    QN = normQ(Q)
5099    aa = QN[0]**2
5100    ab = QN[0]*QN[1]
5101    ac = QN[0]*QN[2]
5102    ad = QN[0]*QN[3]
5103    bb = QN[1]**2
5104    bc = QN[1]*QN[2]
5105    bd = QN[1]*QN[3]
5106    cc = QN[2]**2
5107    cd = QN[2]*QN[3]
5108    dd = QN[3]**2
5109    M = [[aa+bb-cc-dd, 2.*(bc-ad),  2.*(ac+bd)],
5110        [2*(ad+bc),   aa-bb+cc-dd,  2.*(cd-ab)],
5111        [2*(bd-ac),    2.*(ab+cd), aa-bb-cc+dd]]
5112    return np.array(M)
5113   
5114def AV2Q(A,V):
5115    ''' convert angle (radians) & vector to quaternion
5116        q=r+ai+bj+ck
5117    '''
5118    Q = np.zeros(4)
5119    d = nl.norm(np.array(V))
5120    if d:
5121        V = V/d
5122        if not A:       #==0.
5123            A = 2.*np.pi
5124        p = A/2.
5125        Q[0] = np.cos(p)
5126        Q[1:4] = V*np.sin(p)
5127    else:
5128        Q[3] = 1.
5129    return Q
5130   
5131def AVdeg2Q(A,V):
5132    ''' convert angle (degrees) & vector to quaternion
5133        q=r+ai+bj+ck
5134    '''
5135    Q = np.zeros(4)
5136    d = nl.norm(np.array(V))
5137    if not A:       #== 0.!
5138        A = 360.
5139    if d:
5140        V = V/d
5141        p = A/2.
5142        Q[0] = cosd(p)
5143        Q[1:4] = V*sind(p)
5144    else:
5145        Q[3] = 1.
5146    return Q
5147   
5148def Q2AVdeg(Q):
5149    ''' convert quaternion to angle (degrees 0-360) & normalized vector
5150        q=r+ai+bj+ck
5151    '''
5152    A = 2.*acosd(Q[0])
5153    V = np.array(Q[1:])
5154    V = V/sind(A/2.)
5155    return A,V
5156   
5157def Q2AV(Q):
5158    ''' convert quaternion to angle (radians 0-2pi) & normalized vector
5159        q=r+ai+bj+ck
5160    '''
5161    A = 2.*np.arccos(Q[0])
5162    V = np.array(Q[1:])
5163    V = V/np.sin(A/2.)
5164    return A,V
5165   
5166def randomQ(r0,r1,r2,r3):
5167    ''' create random quaternion from 4 random numbers in range (-1,1)
5168    '''
5169    sum = 0
5170    Q = np.array(4)
5171    Q[0] = r0
5172    sum += Q[0]**2
5173    Q[1] = np.sqrt(1.-sum)*r1
5174    sum += Q[1]**2
5175    Q[2] = np.sqrt(1.-sum)*r2
5176    sum += Q[2]**2
5177    Q[3] = np.sqrt(1.-sum)*np.where(r3<0.,-1.,1.)
5178    return Q
5179   
5180def randomAVdeg(r0,r1,r2,r3):
5181    ''' create random angle (deg),vector from 4 random number in range (-1,1)
5182    '''
5183    return Q2AVdeg(randomQ(r0,r1,r2,r3))
5184   
5185def makeQuat(A,B,C):
5186    ''' Make quaternion from rotation of A vector to B vector about C axis
5187
5188        :param np.array A,B,C: Cartesian 3-vectors
5189        :returns: quaternion & rotation angle in radians q=r+ai+bj+ck
5190    '''
5191
5192    V1 = np.cross(A,C)
5193    V2 = np.cross(B,C)
5194    if nl.norm(V1)*nl.norm(V2):
5195        V1 = V1/nl.norm(V1)
5196        V2 = V2/nl.norm(V2)
5197        V3 = np.cross(V1,V2)
5198    else:
5199        V3 = np.zeros(3)
5200    Q = np.array([0.,0.,0.,1.])
5201    D = 0.
5202    if nl.norm(V3):
5203        V3 = V3/nl.norm(V3)
5204        D1 = min(1.0,max(-1.0,np.vdot(V1,V2)))
5205        D = np.arccos(D1)/2.0
5206        V1 = C-V3
5207        V2 = C+V3
5208        DM = nl.norm(V1)
5209        DP = nl.norm(V2)
5210        S = np.sin(D)
5211        Q[0] = np.cos(D)
5212        Q[1:] = V3*S
5213        D *= 2.
5214        if DM > DP:
5215            D *= -1.
5216    return Q,D
5217   
5218def annealtests():
5219    from numpy import cos
5220    # minimum expected at ~-0.195
5221    func = lambda x: cos(14.5*x-0.3) + (x+0.2)*x
5222    print (anneal(func,1.0,full_output=1,upper=3.0,lower=-3.0,feps=1e-4,maxiter=2000,schedule='cauchy'))
5223    print (anneal(func,1.0,full_output=1,upper=3.0,lower=-3.0,feps=1e-4,maxiter=2000,schedule='fast'))
5224    print (anneal(func,1.0,full_output=1,upper=3.0,lower=-3.0,feps=1e-4,maxiter=2000,schedule='boltzmann'))
5225
5226    # minimum expected at ~[-0.195, -0.1]
5227    func = lambda x: cos(14.5*x[0]-0.3) + (x[1]+0.2)*x[1] + (x[0]+0.2)*x[0]
5228    print (anneal(func,[1.0, 1.0],full_output=1,upper=[3.0, 3.0],lower=[-3.0, -3.0],feps=1e-4,maxiter=2000,schedule='cauchy'))
5229    print (anneal(func,[1.0, 1.0],full_output=1,upper=[3.0, 3.0],lower=[-3.0, -3.0],feps=1e-4,maxiter=2000,schedule='fast'))
5230    print (anneal(func,[1.0, 1.0],full_output=1,upper=[3.0, 3.0],lower=[-3.0, -3.0],feps=1e-4,maxiter=2000,schedule='boltzmann'))
5231
5232if __name__ == '__main__':
5233    annealtests()
Note: See TracBrowser for help on using the repository browser.