source: trunk/imports/G2phase_CIF.py

Last change on this file was 5577, checked in by toby, 5 months ago

finish docs reformatting with changes to G2tools and GSASIIscripts; ReadTheDocs? html now looks good

  • Property svn:eol-style set to native
  • Property svn:keywords set to Date Author Revision URL Id
File size: 62.4 KB
Line 
1# -*- coding: utf-8 -*-
2########### SVN repository information ###################
3# $Date: 2023-05-11 23:08:12 +0000 (Thu, 11 May 2023) $
4# $Author: toby $
5# $Revision: 5577 $
6# $URL: trunk/imports/G2phase_CIF.py $
7# $Id: G2phase_CIF.py 5577 2023-05-11 23:08:12Z toby $
8########### SVN repository information ###################
9'''
10'''
11# Routines to import Phase information from CIF files
12from __future__ import division, print_function
13import sys
14import random as ran
15import numpy as np
16import re
17import copy
18import os.path
19import GSASIIobj as G2obj
20import GSASIIspc as G2spc
21import GSASIIElem as G2elem
22import GSASIIlattice as G2lat
23import GSASIIpath
24import GSASIIfiles as G2fil
25try:
26    import GSASIIctrlGUI as G2G
27except ImportError:
28    pass
29GSASIIpath.SetVersionNumber("$Revision: 5577 $")
30import CifFile as cif # PyCifRW from James Hester
31debug = GSASIIpath.GetConfigValue('debug')
32#debug = False
33
34class CIFPhaseReader(G2obj.ImportPhase):
35    'Implements a phase importer from a possibly multi-block CIF file'
36    def __init__(self):
37        super(self.__class__,self).__init__( # fancy way to say ImportPhase.__init__
38            extensionlist=('.CIF','.cif','.mcif'),
39            strictExtension=False,
40            formatName = 'CIF',
41            longFormatName = 'Crystallographic Information File import'
42            )
43       
44    def ContentsValidator(self, filename):
45        fp = open(filename,'r')
46        ok = self.CIFValidator(fp)
47        fp.close()
48        return ok
49
50    def Reader(self,filename, ParentFrame=None, usedRanIdList=[], **unused):
51        isodistort_warnings = '' # errors that would prevent an isodistort analysis
52        self.Phase = G2obj.SetNewPhase(Name='new phase') # create a new empty phase dict
53        # make sure the ranId is really unique!
54        while self.Phase['ranId'] in usedRanIdList:
55            self.Phase['ranId'] = ran.randint(0,sys.maxsize)
56        self.MPhase = G2obj.SetNewPhase(Name='new phase') # create a new empty phase dict
57        # make sure the ranId is really unique!
58        while self.MPhase['ranId'] in usedRanIdList:
59            self.MPhase['ranId'] = ran.randint(0,sys.maxsize)
60        returnstat = False
61        cellitems = (
62            '_cell_length_a','_cell_length_b','_cell_length_c',
63            '_cell_angle_alpha','_cell_angle_beta','_cell_angle_gamma',)
64#        cellwaveitems = (
65#            '_cell_wave_vector_seq_id',
66#            '_cell_wave_vector_x','_cell_wave_vector_y','_cell_wave_vector_z')
67        reqitems = (
68             '_atom_site_fract_x',
69             '_atom_site_fract_y',
70             '_atom_site_fract_z',
71            )
72        phasenamefields = (
73            '_chemical_name_common',
74            '_pd_phase_name',
75            '_chemical_formula_sum'
76            )
77        try:
78            cf = G2obj.ReadCIF(filename)
79        except cif.StarError as msg:
80            msg  = 'Unreadable cif file\n'+str(msg)
81            self.errors = msg
82            self.warnings += msg
83            return False
84        # scan blocks for structural info
85        self.errors = 'Error during scan of blocks for datasets'
86        str_blklist = []
87        for blk in cf.keys():
88            for r in reqitems+cellitems:
89                if r not in cf[blk].keys():
90                    break
91            else:
92                str_blklist.append(blk)
93        if not str_blklist:
94            selblk = None # no block to choose
95        elif len(str_blklist) == 1: # only one choice
96            selblk = 0
97        else:                       # choose from options
98            choice = []
99            for blknm in str_blklist:
100                choice.append('')
101                # accumumlate some info about this phase
102                choice[-1] += blknm + ': '
103                for i in phasenamefields: # get a name for the phase
104                    name = cf[blknm].get(i,'phase name').strip()
105                    if name is None or name == '?' or name == '.':
106                        continue
107                    else:
108                        choice[-1] += name.strip() + ', '
109                        break
110                na = len(cf[blknm].get("_atom_site_fract_x"))
111                if na == 1:
112                    choice[-1] += '1 atom'
113                else:
114                    choice[-1] += ('%d' % na) + ' atoms'
115                choice[-1] += ', cell: '
116                fmt = "%.2f,"
117                for i,key in enumerate(cellitems):
118                    if i == 3: fmt = "%.f,"
119                    if i == 5: fmt = "%.f"
120                    choice[-1] += fmt % cif.get_number_with_esd(
121                        cf[blknm].get(key))[0]
122                sg = cf[blknm].get("_symmetry_space_group_name_H-M",'')
123                if not sg: sg = cf[blknm].get("_space_group_name_H-M_alt",'')
124                if not sg: sg = cf[blknm].get("_space_group_ssg_name",'')
125                if not sg: sg = cf[blknm].get("_space_group.magn_ssg_name_BNS",'')
126                if not sg: sg = cf[blknm].get("_space_group.magn_ssg_name",'')
127                #how about checking for super/magnetic ones as well? - reject 'X'?
128                sg = sg.replace('_','')
129                if sg: choice[-1] += ', (' + sg.strip() + ')'
130            selblk = G2G.PhaseSelector(choice,ParentFrame=ParentFrame,
131                title= 'Select a phase from one the CIF data_ blocks below',size=(600,100))
132        self.errors = 'Error during reading of selected block'
133#process selected phase
134        if selblk is None:
135            returnstat = False # no block selected or available
136        else:   #do space group symbol & phase type first
137            blknm = str_blklist[selblk]
138            blk = cf[str_blklist[selblk]]
139            E = True
140            Super = False
141            magnetic = False
142            moddim = int(blk.get("_cell_modulation_dimension",'0'))
143            if moddim:      #incommensurate
144                if moddim > 1:
145                    msg = 'more than 3+1 super symmetry is not allowed in GSAS-II'
146                    self.errors = msg
147                    self.warnings += '\n'+msg
148                    return False
149                if blk.get('_cell_subsystems_number'):
150                    msg = 'Composite super structures not allowed in GSAS-II'
151                    self.errors = msg
152                    self.warnings += '\n'+msg
153                    return False
154                sspgrp = blk.get("_space_group_ssg_name",'')
155                if not sspgrp:          #might be incommensurate magnetic
156                    MSSpGrp = blk.get("_space_group.magn_ssg_name_BNS",'')
157                    if not MSSpGrp:
158                        MSSpGrp = blk.get("_space_group.magn_ssg_name",'') 
159                    if not MSSpGrp:
160                        msg = 'No incommensurate space group name was found in the CIF.'
161                        self.errors = msg
162                        self.warnings += '\n'+msg
163                        return False                                                           
164                    if 'X' in MSSpGrp:
165                        msg = 'Ad hoc incommensurate magnetic space group '+MSSpGrp+' is not allowed in GSAS-II'
166                        self.warnings += '\n'+msg
167                        self.errors = msg
168                        return False
169                    magnetic = True
170                if 'X' in sspgrp:
171                    msg = 'Ad hoc incommensurate space group '+sspgrp+' is not allowed in GSAS-II'
172                    self.warnings += '\n'+msg
173                    self.errors = msg
174                    return False
175                Super = True
176                if magnetic:
177                    sspgrp = MSSpGrp.split('(')
178                    sspgrp[1] = "("+sspgrp[1]
179                    SpGrp = G2spc.StandardizeSpcName(sspgrp[0])
180                    if "1'" in SpGrp: sspgrp[1] = sspgrp[1][:-1]  #take off extra 's'; gets put back later
181                    MSpGrp = sspgrp[0]
182                    self.MPhase['General']['Type'] = 'magnetic'
183                    self.MPhase['General']['AtomPtrs'] = [3,1,10,12]
184                else:
185                    sspgrp = sspgrp.split('(')
186                    sspgrp[1] = "("+sspgrp[1]
187                    SpGrp = sspgrp[0]
188                    if "1'" in SpGrp:       #nonmagnetics can't be gray
189                        SpGrp = SpGrp.replace("1'",'')
190                        sspgrp[1] = sspgrp[1][:-1]  #take off extra 's'
191#                    SpGrp = G2spc.StandardizeSpcName(SpGrp)
192                    self.Phase['General']['Type'] = 'nuclear'
193                if not SpGrp:
194                    print (sspgrp)
195                    self.warnings += 'Space group name '+sspgrp[0]+sspgrp[1]+' not recognized by GSAS-II'
196                    return False
197                SuperSg = sspgrp[1].replace('\\','').replace(',','')
198                SuperVec = [[0,0,.1],False,4]
199            else:   #not incommensurate
200                SpGrp = blk.get("_symmetry_space_group_name_H-M",'')
201                if not SpGrp:
202                    SpGrp = blk.get("_space_group_name_H-M_alt",'')
203                if not SpGrp:   #try magnetic           
204                    MSpGrp = blk.get("_space_group.magn_name_BNS",'')
205                    if not MSpGrp:
206                        MSpGrp = blk.get("_space_group_magn.name_BNS",'')
207                        if not MSpGrp:
208                            msg = 'No recognizable space group name was found in the CIF.'
209                            self.errors = msg
210                            self.warnings += '\n'+msg
211                            return False
212                    SpGrp = blk.get('_parent_space_group.name_H-M_alt')
213                    if not SpGrp:
214                        SpGrp = blk.get('_parent_space_group.name_H-M')
215#                    SpGrp = MSpGrp.replace("'",'')
216                    SpGrp = SpGrp[:2]+SpGrp[2:].replace('_','')   #get rid of screw '_'
217                    if '_' in SpGrp[1]: SpGrp = SpGrp.split('_')[0]+SpGrp[3:]
218                    SpGrp = G2spc.StandardizeSpcName(SpGrp)
219                    magnetic = True
220                    self.MPhase['General']['Type'] = 'magnetic'
221                    self.MPhase['General']['AtomPtrs'] = [3,1,10,12]
222                    if not SpGrp:
223                        print (MSpGrp)
224                        self.warnings += 'No space group name was found in the CIF.'
225                        return False
226                else:
227                    SpGrp = SpGrp.replace('_','').split('(')[0]
228                    SpGrp = G2spc.fullHM2shortHM(SpGrp)
229                    self.Phase['General']['Type'] = 'nuclear'
230#process space group symbol
231            E,SGData = G2spc.SpcGroup(SpGrp)
232            if E and SpGrp:
233                SpGrpNorm = G2spc.StandardizeSpcName(SpGrp)
234                if SpGrpNorm:
235                    E,SGData = G2spc.SpcGroup(SpGrpNorm)
236            # if E:   #try lookup from number  - found full symbol?               
237            #     SpGrpNorm = G2spc.spgbyNum[int(blk.get('_symmetry_Int_Tables_number'))]
238            #     if SpGrpNorm:
239            #         E,SGData = G2spc.SpcGroup(SpGrpNorm)
240            # nope, try the space group "out of the Box"
241            if E:
242                self.warnings += 'ERROR in space group symbol '+SpGrp
243                self.warnings += '\nThe space group has been set to "P 1". '
244                self.warnings += "Change this in phase's General tab."
245                self.warnings += '\nAre there spaces separating axial fields?\n\nError msg: '
246                self.warnings += G2spc.SGErrors(E)
247                SGData = G2obj.P1SGData # P 1
248            self.Phase['General']['SGData'] = SGData
249            # save symmetry operators, if specified (use to check for origin 1 in GSASIIdataGUI OnImportPhase)
250            ops = blk.get("_symmetry_equiv_pos_as_xyz")   # try older name 1st
251            if ops:
252                self.SymOps['xyz'] = ops
253            elif blk.get("_space_group_symop_operation_xyz"):
254                self.SymOps['xyz'] = blk.get("_space_group_symop_operation_xyz")
255            else:
256                if 'xyz' in self.SymOps: del self.SymOps['xyz']
257            if Super:
258                E,SSGData = G2spc.SSpcGroup(SGData,SuperSg)
259                if E:
260                    self.warnings += 'Invalid super symmetry symbol '+SpGrp+SuperSg
261                    self.warnings += '\n'+E
262                    SuperSg = SuperSg[:SuperSg.index(')')+1]
263                    self.warnings += '\nNew super symmetry symbol '+SpGrp+SuperSg
264                    E,SSGData = G2spc.SSpcGroup(SGData,SuperSg)
265                self.Phase['General']['SSGData'] = SSGData
266                if magnetic:
267                    self.MPhase['General']['SGData'] = SGData
268                    self.MPhase['General']['SGData']['MagSpGrp'] = MSSpGrp.replace(',','').replace('\\','')
269                    self.MPhase['General']['SSGData'] = SSGData
270
271            if magnetic:    #replace std operaors with those from cif file - probably not the same!
272                SGData['SGFixed'] = True
273                SGData['SGOps'] = []
274                SGData['SGCen'] = []
275                if Super:
276                    SSGData['SSGOps'] = []
277                    SSGData['SSGCen'] = []
278                    try:
279                        sgoploop = blk.GetLoop('_space_group_symop_magn_ssg_operation.id')
280                        sgcenloop = blk.GetLoop('_space_group_symop_magn_ssg_centering.id')
281                        opid = sgoploop.GetItemPosition('_space_group_symop_magn_ssg_operation.algebraic')[1]
282                        centid = sgcenloop.GetItemPosition('_space_group_symop_magn_ssg_centering.algebraic')[1]                   
283                    except KeyError:        #old mag cif names
284                        sgoploop = blk.GetLoop('_space_group_symop.magn_ssg_id')
285                        sgcenloop = blk.GetLoop('_space_group_symop.magn_ssg_centering_id')
286                        opid = sgoploop.GetItemPosition('_space_group_symop.magn_ssg_operation_algebraic')[1]
287                        centid = sgcenloop.GetItemPosition('_space_group_symop.magn_ssg_centering_algebraic')[1]
288                    spnflp = []
289                    for op in sgoploop:
290                        M,T,S = G2spc.MagSSText2MTS(op[opid])
291                        SSGData['SSGOps'].append([np.array(M,dtype=float),T])
292                        SGData['SGOps'].append([np.array(M,dtype=float)[:3,:3],T[:3]])
293                        spnflp.append(S)
294                    censpn = []
295                    for cent in sgcenloop:
296                        M,C,S = G2spc.MagSSText2MTS(cent[centid])
297                        SSGData['SSGCen'].append(C)
298                        SGData['SGCen'].append(C[:3])
299                        censpn += list(np.array(spnflp)*S)
300                    self.MPhase['General']['SSGData'] = SSGData
301                else:
302                    try:
303                        sgoploop = blk.GetLoop('_space_group_symop_magn_operation.id')
304                        opid = sgoploop.GetItemPosition('_space_group_symop_magn_operation.xyz')[1]
305                        try:
306                            sgcenloop = blk.GetLoop('_space_group_symop_magn_centering.id')
307                            centid = sgcenloop.GetItemPosition('_space_group_symop_magn_centering.xyz')[1]
308                        except KeyError:
309                            sgcenloop = None
310                    except KeyError:                                         
311                        try:
312                            sgoploop = blk.GetLoop('_space_group_symop_magn.id')
313                            sgcenloop = blk.GetLoop('_space_group_symop_magn_centering.id')
314                            opid = sgoploop.GetItemPosition('_space_group_symop_magn_operation.xyz')[1]
315                            centid = sgcenloop.GetItemPosition('_space_group_symop_magn_centering.xyz')[1]                   
316                        except KeyError:        #old mag cif names
317                            sgoploop = blk.GetLoop('_space_group_symop.magn_id')
318                            sgcenloop = blk.GetLoop('_space_group_symop.magn_centering_id')
319                            opid = sgoploop.GetItemPosition('_space_group_symop.magn_operation_xyz')[1]
320                            centid = sgcenloop.GetItemPosition('_space_group_symop.magn_centering_xyz')[1]
321                    spnflp = []
322                    for op in sgoploop:
323                        try:
324                            M,T,S = G2spc.MagText2MTS(op[opid])
325                            SGData['SGOps'].append([np.array(M,dtype=float),T])
326                            spnflp.append(S)
327                        except KeyError:
328                            self.warnings += 'Space group operator '+op[opid]+' is not recognized by GSAS-II'
329                            return False
330                    censpn = []
331                    if sgcenloop:
332                        for cent in sgcenloop:
333                            M,C,S = G2spc.MagText2MTS(cent[centid])
334                            SGData['SGCen'].append(C)
335                            censpn += list(np.array(spnflp)*S)
336                    else:
337                            M,C,S = G2spc.MagText2MTS('x,y,z,+1')
338                            SGData['SGCen'].append(C)
339                            censpn += list(np.array(spnflp)*S)                       
340                self.MPhase['General']['SGData'] = SGData
341                self.MPhase['General']['SGData']['SpnFlp'] = censpn
342                G2spc.GenMagOps(SGData)         #set magMom
343                self.MPhase['General']['SGData']['MagSpGrp'] = MSpGrp
344                if "1'" in MSpGrp:
345                    SGData['SGGray'] = True
346                MagPtGp = blk.get('_space_group.magn_point_group')
347                if not MagPtGp:
348                    MagPtGp = blk.get('_space_group.magn_point_group_name')
349                if not MagPtGp:
350                    MagPtGp = blk.get('_space_group_magn.point_group_name')
351                self.MPhase['General']['SGData']['MagPtGp'] = MagPtGp
352
353            # cell parameters
354            cell = []
355            for lbl in cellitems:
356                cell.append(cif.get_number_with_esd(blk[lbl])[0])
357            Volume = G2lat.calc_V(G2lat.cell2A(cell))
358            self.Phase['General']['Cell'] = [False,]+cell+[Volume,]
359            if magnetic:
360                self.MPhase['General']['Cell'] = [False,]+cell+[Volume,]               
361            if Super:
362                waveloop = blk.GetLoop('_cell_wave_vector_seq_id')
363                waveDict = dict(waveloop.items())
364                SuperVec = [[cif.get_number_with_esd(waveDict['_cell_wave_vector_x'][0].replace('?','0'))[0],
365                    cif.get_number_with_esd(waveDict['_cell_wave_vector_y'][0].replace('?','0'))[0],
366                    cif.get_number_with_esd(waveDict['_cell_wave_vector_z'][0].replace('?','0'))[0]],False,4]
367
368            # read in atoms
369            self.errors = 'Error during reading of atoms'
370            atomlbllist = [] # table to look up atom IDs
371            atomloop = blk.GetLoop('_atom_site_label')
372            atomkeys = [i.lower() for i in atomloop.keys()]
373            if not blk.get('_atom_site_type_symbol'):
374                isodistort_warnings += '\natom types are missing. \n Check & revise atom types as needed'
375            if magnetic:
376                try:
377                    magmoment = '_atom_site_moment.label'
378                    magatomloop = blk.GetLoop(magmoment)
379                    magatomkeys = [i.lower() for i in magatomloop.keys()]
380                    magatomlabels = blk.get(magmoment)
381                    G2MagDict = {'_atom_site_moment.label': 0,
382                                 '_atom_site_moment.crystalaxis_x':7,
383                                 '_atom_site_moment.crystalaxis_y':8,
384                                 '_atom_site_moment.crystalaxis_z':9}
385                except KeyError:
386                    magmoment = '_atom_site_moment_label'
387                    magatomloop = blk.GetLoop(magmoment)
388                    magatomkeys = [i.lower() for i in magatomloop.keys()]
389                    magatomlabels = blk.get(magmoment)
390                    G2MagDict = {'_atom_site_moment_label': 0,
391                                 '_atom_site_moment_crystalaxis_x':7,
392                                 '_atom_site_moment_crystalaxis_y':8,
393                                 '_atom_site_moment_crystalaxis_z':9}
394                   
395            if blk.get('_atom_site_aniso_label'):
396                anisoloop = blk.GetLoop('_atom_site_aniso_label')
397                anisokeys = [i.lower() for i in anisoloop.keys()]
398                anisolabels = blk.get('_atom_site_aniso_label')
399            else:
400                anisoloop = None
401                anisokeys = []
402                anisolabels = []
403            if Super:
404#                occFloop = None
405                occCloop = None
406#                occFdict = {}
407                occCdict = {}
408#                displSloop = None
409                displFloop = None
410                MagFloop = None
411#                displSdict = {}
412                displFdict = {}
413                MagFdict = {}
414                UijFloop = None
415                UijFdict = {}
416                #occupancy modulation
417#                if blk.get('_atom_site_occ_Fourier_atom_site_label'):
418#                    occFloop = blk.GetLoop('_atom_site_occ_Fourier_atom_site_label')
419#                    occFdict = dict(occFloop.items())
420                if blk.get('_atom_site_occ_special_func_atom_site_label'):  #Crenel (i.e. Block Wave) occ
421                    occCloop = blk.GetLoop('_atom_site_occ_special_func_atom_site_label')
422                    occCdict = dict(occCloop.items())
423                #position modulation
424                if blk.get('_atom_site_displace_Fourier_atom_site_label'):
425                    displFloop = blk.GetLoop('_atom_site_displace_Fourier_atom_site_label')
426                    displFdict = dict(displFloop.items())                           
427#                if blk.get('_atom_site_displace_special_func_atom_site_label'): #sawtooth
428#                    displSloop = blk.GetLoop('_atom_site_displace_special_func_atom_site_label')
429#                    displSdict = dict(displSloop.items())
430                #U modulation
431                if blk.get('_atom_site_U_Fourier_atom_site_label'):
432                    UijFloop = blk.GetLoop('_atom_site_U_Fourier_atom_site_label')
433                    UijFdict = dict(UijFloop.items())
434                #Mag moment modulation
435                if blk.get('_atom_site_moment_Fourier_atom_site_label'):
436                    MagFloop = blk.GetLoop('_atom_site_moment_Fourier_atom_site_label')
437                    MagFdict = dict(MagFloop.items())
438                    Mnames =  ['_atom_site_moment_fourier_atom_site_label',
439                               '_atom_site_moment_fourier_axis','_atom_site_moment_fourier_wave_vector_seq_id',
440                               '_atom_site_moment_fourier_param_sin','_atom_site_moment_fourier_param_cos']
441                elif blk.get('_atom_site_moment_Fourier.atom_site_label'):
442                    MagFloop = blk.GetLoop('_atom_site_moment_Fourier.atom_site_label')
443                    MagFdict = dict(MagFloop.items())                           
444                    Mnames =  ['_atom_site_moment_fourier.atom_site_label',
445                               '_atom_site_moment_fourier.axis','_atom_site_moment_fourier.wave_vector_seq_id',
446                               '_atom_site_moment_fourier_param.sin','_atom_site_moment_fourier_param.cos']
447            self.Phase['Atoms'] = []
448            if magnetic:
449                self.MPhase['Atoms'] = []
450            G2AtomDict = {  '_atom_site_type_symbol' : 1,
451                            '_atom_site_label' : 0,
452                            '_atom_site_fract_x' : 3,
453                            '_atom_site_fract_y' : 4,
454                            '_atom_site_fract_z' : 5,
455                            '_atom_site_occupancy' : 6,
456                            '_atom_site_aniso_u_11' : 11,
457                            '_atom_site_aniso_u_22' : 12,
458                            '_atom_site_aniso_u_33' : 13,
459                            '_atom_site_aniso_u_12' : 14,
460                            '_atom_site_aniso_u_13' : 15,
461                            '_atom_site_aniso_u_23' : 16, }
462
463            ranIdlookup = {}
464            for aitem in atomloop:
465                atomlist = ['','','',0.,0.,0.,1.0,'',0.,'I',0.01, 0.,0.,0.,0.,0.,0.,]
466                for val,key in zip(aitem,atomkeys):
467                    col = G2AtomDict.get(key,-1)
468                    if col >= 3:
469                        atomlist[col] = cif.get_number_with_esd(val)[0]
470                        if col >= 11: atomlist[9] = 'A' # if any Aniso term is defined, set flag
471                    elif col is not None and col != -1:
472                        atomlist[col] = val
473                    elif key in ('_atom_site_thermal_displace_type',
474                               '_atom_site_adp_type'):   #Iso or Aniso?
475                        if val.lower() == 'uani':
476                            atomlist[9] = 'A'
477                    elif key == '_atom_site_u_iso_or_equiv':
478                        uisoval = cif.get_number_with_esd(val)[0]
479                        if uisoval is not None: 
480                            atomlist[10] = uisoval
481                    elif key == '_atom_site_b_iso_or_equiv':
482                        uisoval = cif.get_number_with_esd(val)[0]
483                        if uisoval is not None: 
484                            atomlist[10] = uisoval/(8*np.pi**2)
485                if not atomlist[1] and atomlist[0]:
486                    typ = atomlist[0].rstrip('0123456789-+')
487                    if G2elem.CheckElement(typ):
488                        atomlist[1] = typ
489                    if not atomlist[1]:
490                        atomlist[1] = 'Xe'
491                        self.warnings += ' Atom type '+typ+' not recognized; Xe assumed\n'
492                if atomlist[0] in anisolabels: # does this atom have aniso values in separate loop?
493                    atomlist[9] = 'A'
494                    for val,key in zip(anisoloop.GetKeyedPacket('_atom_site_aniso_label',atomlist[0]),anisokeys):
495                        col = G2AtomDict.get(key)
496                        if col:
497                            atomlist[col] = cif.get_number_with_esd(val)[0]
498                if None in atomlist[11:17]:
499                    atomlist[9] = 'I'
500                    atomlist[11:17] =  [0.,0.,0.,0.,0.,0.]
501                atomlist[7],atomlist[8] = G2spc.SytSym(atomlist[3:6],SGData)[:2]
502                atomlist[1] = G2elem.FixValence(atomlist[1])
503                atomlist.append(ran.randint(0,sys.maxsize)) # add a random Id
504                self.Phase['Atoms'].append(atomlist)
505                ranIdlookup[atomlist[0]] = atomlist[-1]
506                if atomlist[0] in atomlbllist:
507                    self.warnings += ' ERROR: repeated atom label: '+atomlist[0]
508                else:
509                    atomlbllist.append(atomlist[0])
510
511                if magnetic and atomlist[0] in magatomlabels:
512                    matomlist = atomlist[:7]+[0.,0.,0.,]+atomlist[7:]
513                    for mval,mkey in zip(magatomloop.GetKeyedPacket(magmoment,atomlist[0]),magatomkeys):
514                        mcol = G2MagDict.get(mkey,-1)
515                        if mcol > 0:
516                            matomlist[mcol] = cif.get_number_with_esd(mval)[0]
517                    self.MPhase['Atoms'].append(matomlist)
518                if Super:
519                    Sfrac = np.zeros((4,2))
520                    Sadp = np.zeros((4,12))
521                    Spos = np.zeros((4,6))
522                    Smag = np.zeros((4,6))
523                    nim = -1
524                    waveType = 'Fourier'
525                    if  occCdict:
526                        for i,item in enumerate(occCdict['_atom_site_occ_special_func_atom_site_label']):
527                            if item == atomlist[0]:
528                                waveType = 'Crenel'
529                                val = occCdict['_atom_site_occ_special_func_crenel_c'][i]
530                                Sfrac[0][0] = cif.get_number_with_esd(val)[0]
531                                val = occCdict['_atom_site_occ_special_func_crenel_w'][i]
532                                Sfrac[0][1] = cif.get_number_with_esd(val)[0]
533                                nim = 1
534                   
535                    if nim >= 0:
536                        Sfrac = [waveType,]+[[sfrac,False] for sfrac in Sfrac[:nim]]
537                    else:
538                        Sfrac = []
539                    nim = -1
540                    if displFdict:
541                        for i,item in enumerate(displFdict['_atom_site_displace_fourier_atom_site_label']):
542                            if item == atomlist[0]:
543                                waveType = 'Fourier'                               
544                                ix = ['x','y','z'].index(displFdict['_atom_site_displace_fourier_axis'][i])
545                                im = int(displFdict['_atom_site_displace_fourier_wave_vector_seq_id'][i])
546                                if im != nim:
547                                    nim = im
548                                val = displFdict['_atom_site_displace_fourier_param_sin'][i]
549                                Spos[im-1][ix] = cif.get_number_with_esd(val)[0]
550                                val = displFdict['_atom_site_displace_fourier_param_cos'][i]
551                                Spos[im-1][ix+3] = cif.get_number_with_esd(val)[0]
552                    if nim >= 0:
553                        Spos = [waveType,]+[[spos,False] for spos in Spos[:nim]]
554                    else:
555                        Spos = []
556                    nim = -1
557                    if UijFdict:
558                        nim = -1
559                        for i,item in enumerate(UijFdict['_atom_site_u_fourier_atom_site_label']):
560                            if item == atomlist[0]:
561                                ix = ['U11','U22','U33','U12','U13','U23'].index(UijFdict['_atom_site_u_fourier_tens_elem'][i])
562                                im = int(UijFdict['_atom_site_u_fourier_wave_vector_seq_id'][i])
563                                if im != nim:
564                                    nim = im
565                                val = UijFdict['_atom_site_u_fourier_param_sin'][i]
566                                Sadp[im-1][ix] = cif.get_number_with_esd(val)[0]
567                                val = UijFdict['_atom_site_u_fourier_param_cos'][i]
568                                Sadp[im-1][ix+6] = cif.get_number_with_esd(val)[0]
569                    if nim >= 0:
570                        Sadp = ['Fourier',]+[[sadp,False] for sadp in Sadp[:nim]]
571                    else:
572                        Sadp = []
573                    nim = -1
574                    if MagFdict:
575                        nim = -1
576                        for i,item in enumerate(MagFdict[Mnames[0]]):
577                            if item == atomlist[0]:
578                                ix = ['x','y','z'].index(MagFdict[Mnames[1]][i])
579                                im = int(MagFdict[Mnames[2]][i])
580                                if im != nim:
581                                    nim = im
582                                val = MagFdict[Mnames[3]][i]
583                                Smag[im-1][ix] = cif.get_number_with_esd(val)[0]
584                                val = MagFdict[Mnames[4]][i]
585                                Smag[im-1][ix+3] = cif.get_number_with_esd(val)[0]
586                    if nim >= 0:
587                        Smag = ['Fourier',]+[[smag,False] for smag in Smag[:nim]]
588                    else:
589                        Smag = []
590                    SSdict = {'SS1':{'Sfrac':Sfrac,'Spos':Spos,'Sadp':Sadp,'Smag':Smag}}
591                    if magnetic and atomlist[0] in magatomlabels:
592                        matomlist.append(SSdict)
593                    atomlist.append(SSdict)
594            if len(atomlbllist) != len(self.Phase['Atoms']):
595                isodistort_warnings += '\nRepeated atom labels prevents ISODISTORT decode'
596            for lbl in phasenamefields: # get a name for the phase
597                try:
598                    name = blk.get(lbl)
599                except:
600                    continue
601                if name is None:
602                    continue
603                name = name.strip()
604                if name == '?' or name == '.':
605                    continue
606                else:
607                    break
608            else: # no name found, use block name for lack of a better choice; for isodistort use filename
609                name = blknm
610            if 'isodistort' in name:
611                self.Phase['General']['Name'] = os.path.split(filename)[1].split('.')[0]
612            else:
613                self.Phase['General']['Name'] = name.strip()
614            self.Phase['General']['Super'] = Super
615            self.Phase = copy.deepcopy(self.Phase)      #clean copy
616            if magnetic:
617                self.MPhase = copy.deepcopy(self.MPhase)    #clean copy
618                self.MPhase['General']['Type'] = 'magnetic'               
619                self.MPhase['General']['Name'] = name.strip()+' mag'
620                self.MPhase['General']['Super'] = Super
621                if Super:
622                    self.MPhase['General']['Modulated'] = True
623                    self.MPhase['General']['SuperVec'] = SuperVec
624                    self.MPhase['General']['SuperSg'] = SuperSg
625                    if self.MPhase['General']['SGData']['SGGray']:
626                        self.MPhase['General']['SuperSg'] += 's'
627                if 'mcif' not in filename:
628                    self.Phase = copy.deepcopy(self.MPhase)
629                    del self.MPhase
630            else:
631                self.MPhase = None
632            if Super:
633                if self.Phase['General']['SGData']['SGGray']:
634                    SGData = self.Phase['General']['SGData']
635                    SGData['SGGray'] = False
636                    ncen = len(SGData['SGCen'])
637                    SGData['SGCen'] = SGData['SGCen'][:ncen//2]
638                    self.Phase['General']['SGData'].update(SGData)
639                self.Phase['General']['Modulated'] = True
640                self.Phase['General']['SuperVec'] = SuperVec
641                self.Phase['General']['SuperSg'] = SuperSg
642                if not self.Phase['General']['SGData']['SGFixed']:
643                    self.Phase['General']['SSGData'] = G2spc.SSpcGroup(SGData,SuperSg)[1]
644            if self.ISODISTORT_test(blk):
645                if isodistort_warnings:
646                    self.warnings += isodistort_warnings
647                else:
648                    self.errors = "Error while processing ISODISTORT constraints"
649                    self.ISODISTORT_proc(blk,atomlbllist,ranIdlookup,filename)
650                    self.errors = ""
651            returnstat = True
652        return returnstat
653       
654    def ISODISTORT_test(self,blk):
655        '''Test if there is any ISODISTORT information in CIF
656
657        At present only _iso_displacivemode... and _iso_occupancymode... are
658        tested.
659        '''
660        for i in ('_iso_displacivemode_label',
661                  '_iso_occupancymode_label'):
662            if blk.get(i): return True
663        return False
664       
665    def ISODISTORT_proc(self,blk,atomlbllist,ranIdlookup,filename):
666        '''Process ISODISTORT items to create constraints etc.
667        Constraints are generated from information extracted from
668        loops beginning with _iso_ and are placed into
669        self.Constraints, which contains a list of
670        :ref:`constraints tree items <Constraint_definitions_table>`
671        and one dict.
672        The dict contains help text for each generated ISODISTORT variable
673
674        At present only _iso_displacivemode... and _iso_occupancymode... are
675        processed. Not yet processed: _iso_magneticmode...,
676        _iso_rotationalmode... & _iso_strainmode...
677        '''
678        varLookup = {'dx':'dAx','dy':'dAy','dz':'dAz','do':'Afrac'}
679        'Maps ISODISTORT parm names to GSAS-II names'
680        # used for all types of modes
681        self.Constraints = []
682        explaination = {}
683        G2obj.AddPhase2Index(self,filename)   # put phase info into Var index
684        #----------------------------------------------------------------------
685        # read in the ISODISTORT displacement modes
686        #----------------------------------------------------------------------
687        if blk.get('_iso_displacivemode_label'):
688            modelist = []
689            shortmodelist = []
690            modedispl = []
691            idlist = []
692            for id,lbl,val in zip(
693                blk.get('_iso_displacivemode_ID'),
694                blk.get('_iso_displacivemode_label'),
695                blk.get('_iso_displacivemode_value')):
696                idlist.append(int(id))
697                modelist.append(lbl)
698                modedispl.append(float(val))
699                ISODISTORT_shortLbl(lbl,shortmodelist) # shorten & make unique
700            # just in case the items are not ordered increasing by id, sort them here
701            modelist = [i for i,j in sorted(zip(modelist,idlist),key=lambda k:k[1])]
702            shortmodelist = [i for i,j in sorted(zip(shortmodelist,idlist),key=lambda k:k[1])]
703            # read in the coordinate offset variables names and map them to G2 names/objects
704            coordVarLbl = []
705            G2varObj = []
706            G2coordOffset = []
707            idlist = []
708            error = False
709            ParentCoordinates = {}
710            for lbl,exp in zip(
711                blk.get('_iso_coordinate_label'),
712                blk.get('_iso_coordinate_formula') ):
713                if '_' in lbl:
714                    albl = lbl[:lbl.rfind('_')]
715                    vlbl = lbl[lbl.rfind('_')+1:]
716                else:
717                    self.warnings += ' ERROR: _iso_coordinate_label not parsed: '+lbl
718                    error = True
719                    continue
720                if vlbl not in 'xyz' or len(vlbl) != 1:
721                    self.warnings += ' ERROR: _iso_coordinate_label coordinate not parsed: '+lbl
722                    error = True
723                    continue
724                i = 'xyz'.index(vlbl)
725                if not ParentCoordinates.get(albl):
726                    ParentCoordinates[albl] = [None,None,None]
727                if '+' in exp:
728                    val = exp.split('+')[0].strip()
729                    val = G2fil.FormulaEval(val)
730                elif '-' in exp:
731                    val = exp.split('-')[0].strip()
732                    val = G2fil.FormulaEval(val)
733                else:
734                    val = G2fil.FormulaEval(exp)
735                if val is None:
736                    self.warnings += ' ERROR: _iso_coordinate_formula coordinate not interpreted: '+lbl
737                    error = True
738                    continue
739                else:
740                    ParentCoordinates[albl][i] = val
741            if error:
742                print (self.warnings)
743                raise Exception("Error decoding variable labels")
744           
745            error = False
746            coordOffset = {xyz:i for i,xyz in enumerate(('dx','dy','dz'))}
747            for id,lbl,val in zip(
748                blk.get('_iso_deltacoordinate_ID'),
749                blk.get('_iso_deltacoordinate_label'),
750                blk.get('_iso_deltacoordinate_value') ):
751                idlist.append(int(id))
752                coordVarLbl.append(lbl)
753                if '_' in lbl:
754                    albl = lbl[:lbl.rfind('_')]
755                    vlbl = lbl[lbl.rfind('_')+1:]
756                else:
757                    self.warnings += ' ERROR: _iso_deltacoordinate_label not parsed: '+lbl
758                    error = True
759                    continue
760                if albl not in atomlbllist:
761                    self.warnings += ' ERROR: _iso_deltacoordinate_label atom not found: '+lbl
762                    error = True
763                    continue
764                # else:
765                #     anum = atomlbllist.index(albl)
766                var = varLookup.get(vlbl)
767                if not var:
768                    self.warnings += ' ERROR: _iso_deltacoordinate_label variable not found: '+lbl
769                    error = True
770                    continue
771                G2coordOffset.append(ParentCoordinates[albl][coordOffset[vlbl]] - float(val))
772                G2varObj.append(G2obj.G2VarObj(
773                    (self.Phase['ranId'],None,var,ranIdlookup[albl])
774                    ))
775            if error:
776                raise Exception("Error decoding variable labels")
777            # just in case the items are not ordered increasing by id, sort them here
778            coordVarLbl = [i for i,j in sorted(zip(coordVarLbl,idlist),key=lambda k:k[1])]
779            G2varObj = [i for i,j in sorted(zip(G2varObj,idlist),key=lambda k:k[1])]
780
781            if len(G2varObj) != len(modelist):
782                print ("non-square input")
783                raise Exception("Rank of _iso_displacivemode != _iso_deltacoordinate")
784
785            # normalization constants
786            normlist = []
787            idlist = []
788            for id,exp in zip(
789                blk.get('_iso_displacivemodenorm_ID'),
790                blk.get('_iso_displacivemodenorm_value'),
791                ):
792                idlist.append(int(id))
793                normlist.append(float(exp))
794            normlist = [i for i,j in sorted(zip(normlist,idlist),key=lambda k:k[1])]
795            # get mapping of modes to atomic coordinate displacements
796            displacivemodematrix = np.zeros((len(G2varObj),len(G2varObj)))
797            for row,col,val in zip(
798                blk.get('_iso_displacivemodematrix_row'),
799                blk.get('_iso_displacivemodematrix_col'),
800                blk.get('_iso_displacivemodematrix_value'),):
801                displacivemodematrix[int(row)-1,int(col)-1] = float(val)
802            # Invert to get mapping of atom displacements to modes
803            Var2ModeMatrix = np.linalg.inv(displacivemodematrix)
804            # create the constraints
805            modeVarList = []
806            for i,(row,norm) in enumerate(zip(Var2ModeMatrix,normlist)):
807                constraint = []
808                for j,(lbl,k) in enumerate(zip(coordVarLbl,row)):
809                    if k == 0: continue
810                    constraint.append([k/norm,G2varObj[j]])
811                modeVar = G2obj.G2VarObj(
812                    (self.Phase['ranId'],None,shortmodelist[i],None))
813                modeVarList.append(modeVar)               
814                constraint += [modeVar,False,'f']
815                self.Constraints.append(constraint)
816            #----------------------------------------------------------------------
817            # save the ISODISTORT info for "mode analysis"
818            if 'ISODISTORT' not in self.Phase: self.Phase['ISODISTORT'] = {}
819            self.Phase['ISODISTORT'].update({
820                # coordinate items
821                'IsoVarList' : coordVarLbl,
822                'G2VarList' : G2varObj,
823                'G2coordOffset' : G2coordOffset,
824                'G2parentCoords' : [ParentCoordinates[item] for item in ParentCoordinates], #Assumes python 3.7 dict ordering!
825                # mode items
826                'IsoModeList' : modelist,
827                'G2ModeList' : modeVarList,
828                'NormList' : normlist,
829                'modeDispl' : modedispl,
830                'ISOmodeDispl' : copy.deepcopy(modedispl),
831                # transform matrices
832                'Var2ModeMatrix' : Var2ModeMatrix,
833                'Mode2VarMatrix' : displacivemodematrix,
834                })
835            # make explaination dictionary
836            for mode,shortmode in zip(modelist,shortmodelist):
837                modeVar = G2obj.G2VarObj(
838                    (self.Phase['ranId'],None,shortmode,None))
839                explaination[modeVar] = ("Full ISODISTORT name for " +
840                                            shortmode + " is " + str(mode))
841        #----------------------------------------------------------------------
842        # now read in the ISODISTORT occupancy modes
843        #----------------------------------------------------------------------
844        if blk.get('_iso_occupancymode_label'):
845            modelist = []
846            shortmodelist = []
847            idlist = []
848            for id,lbl in zip(
849                blk.get('_iso_occupancymode_ID'),
850                blk.get('_iso_occupancymode_label')):
851                idlist.append(int(id))
852                modelist.append(lbl)
853                ISODISTORT_shortLbl(lbl,shortmodelist) # shorten & make unique
854            # just in case the items are not ordered increasing by id, sort them here
855            modelist = [i for i,j in sorted(zip(modelist,idlist),key=lambda k:k[1])]
856            shortmodelist = [i for i,j in sorted(zip(shortmodelist,idlist),key=lambda k:k[1])]
857            # read in the coordinate offset variables names and map them to G2 names/objects
858            occVarLbl = []
859            G2varObj = []
860            idlist = []
861            error = False
862            for id,lbl in zip(
863                blk.get('_iso_deltaoccupancy_ID'),
864                blk.get('_iso_deltaoccupancy_label') ):
865                idlist.append(int(id))
866                occVarLbl.append(lbl)
867                if '_' in lbl:
868                    albl = lbl[:lbl.rfind('_')]
869                    vlbl = lbl[lbl.rfind('_')+1:]
870                else:
871                    self.warnings += ' ERROR: _iso_deltaoccupancy_label not parsed: '+lbl
872                    error = True
873                    continue
874                if albl not in atomlbllist:
875                    self.warnings += ' ERROR: _iso_deltaoccupancy_label atom not found: '+lbl
876                    error = True
877                    continue
878                # else:
879                #     anum = atomlbllist.index(albl)
880                var = varLookup.get(vlbl)
881                if not var:
882                    self.warnings += ' ERROR: _iso_deltaoccupancy_label variable not found: '+lbl
883                    error = True
884                    continue
885                G2varObj.append(G2obj.G2VarObj(
886                    (self.Phase['ranId'],None,var,ranIdlookup[albl])
887                    ))
888            if error:
889                raise Exception("Error decoding variable labels")
890            # just in case the items are not ordered increasing by id, sort them here
891            occVarLbl = [i for i,j in sorted(zip(occVarLbl,idlist),key=lambda k:k[1])]
892            G2varObj = [i for i,j in sorted(zip(G2varObj,idlist),key=lambda k:k[1])]
893
894            if len(G2varObj) != len(modelist):
895                print ("non-square input")
896                raise Exception("Rank of _iso_occupancymode != _iso_deltaoccupancy")
897
898            error = False
899            ParentOcc = {}
900            for lbl,exp in zip(
901                blk.get('_iso_occupancy_label'),
902                blk.get('_iso_occupancy_formula') ):
903                if '_' in lbl:
904                    albl = lbl[:lbl.rfind('_')]
905                    vlbl = lbl[lbl.rfind('_')+1:]
906                else:
907                    self.warnings += ' ERROR: _iso_occupancy_label not parsed: '+lbl
908                    error = True
909                    continue
910                if vlbl != 'occ':
911                    self.warnings += ' ERROR: _iso_occupancy_label coordinate not parsed: '+lbl
912                    error = True
913                    continue
914                if '+' in exp:
915                    val = exp.split('+')[0].strip()
916                    val = G2fil.FormulaEval(val)
917                    if val is None:
918                        self.warnings += ' ERROR: _iso_occupancy_formula coordinate not interpreted: '+lbl
919                        error = True
920                        continue
921                    ParentOcc[albl] = val
922            if error:
923                raise Exception("Error decoding occupancy labels")
924            # get mapping of modes to atomic coordinate displacements
925            occupancymodematrix = np.zeros((len(G2varObj),len(G2varObj)))
926            for row,col,val in zip(
927                blk.get('_iso_occupancymodematrix_row'),
928                blk.get('_iso_occupancymodematrix_col'),
929                blk.get('_iso_occupancymodematrix_value'),):
930                occupancymodematrix[int(row)-1,int(col)-1] = float(val)
931            # Invert to get mapping of atom displacements to modes
932            occupancymodeInvmatrix = np.linalg.inv(occupancymodematrix)
933            # create the constraints
934            modeVarList = []
935            for i,row in enumerate(occupancymodeInvmatrix):
936                constraint = []
937                for j,(lbl,k) in enumerate(zip(occVarLbl,row)):
938                    if k == 0: continue
939                    constraint.append([k,G2varObj[j]])
940                modeVar = G2obj.G2VarObj(
941                    (self.Phase['ranId'],None,shortmodelist[i],None))
942                modeVarList.append(modeVar)               
943                constraint += [modeVar,False,'f']
944                self.Constraints.append(constraint)
945            # normilization constants
946            normlist = []
947            idlist = []
948            for id,exp in zip(
949                blk.get('_iso_occupancymodenorm_ID'),
950                blk.get('_iso_occupancymodenorm_value'),
951                ):
952                idlist.append(int(id))
953                normlist.append(float(exp))
954            normlist = [i for i,j in sorted(zip(normlist,idlist),key=lambda k:k[1])]
955            #----------------------------------------------------------------------
956            # save the ISODISTORT info for "mode analysis"
957            if 'ISODISTORT' not in self.Phase: self.Phase['ISODISTORT'] = {}
958            self.Phase['ISODISTORT'].update({
959                # coordinate items
960                'OccVarList' : occVarLbl,
961                'G2OccVarList' : G2varObj,
962                'BaseOcc' : ParentOcc,
963                # mode items
964                'OccModeList' : modelist,
965                'G2OccModeList' : modeVarList,
966                'OccNormList' : normlist,
967                # transform matrices
968                'Var2OccMatrix' : occupancymodeInvmatrix,
969                'Occ2VarMatrix' : occupancymodematrix,
970                })
971            # make explaination dictionary
972            for mode,shortmode in zip(modelist,shortmodelist):
973                modeVar = G2obj.G2VarObj(
974                    (self.Phase['ranId'],None,shortmode,None))
975                explaination[modeVar] = ("Full ISODISTORT name for " +
976                                            shortmode + " is " + str(mode))
977        if explaination: self.Constraints.append(explaination)
978        #----------------------------------------------------------------------
979        # done with read
980        #----------------------------------------------------------------------
981
982        def fmtEqn(i,head,l,var,k):
983            'format a section of a row of variables and multipliers' 
984            if np.isclose(k,0): return head,l
985            if len(head) + len(l) > 65:
986                print(head+l)
987                head = 20*' '
988                l = ''
989            if k < 0 and i > 0:
990                l += ' - '
991                k = -k
992            elif i > 0: 
993                l += ' + '
994            if k == 1:
995                l += '%s ' % str(var)
996            else:
997                l += '%.3f * %s' % (k,str(var))
998            return head,l
999
1000        # debug: show displacive mode var to mode relations
1001        if debug and 'IsoVarList' in self.Phase['ISODISTORT']:
1002            print('\n' + 70*'=')
1003            print('ISO modes from Iso coordinate vars (using Var2ModeMatrix, IsoVarList, G2VarList & G2ModeList)' )
1004            for i,row in enumerate(self.Phase['ISODISTORT']['Var2ModeMatrix']):
1005                norm = self.Phase['ISODISTORT']['NormList'][i]
1006                head = '  ' + str(self.Phase['ISODISTORT']['G2ModeList'][i]) + ' = ('
1007                line = ''
1008                for j,(lbl,k) in enumerate(zip(coordVarLbl,row)):
1009                    var = self.Phase['ISODISTORT']['IsoVarList'][j]
1010                    head,line = fmtEqn(j,head,line,var,k)
1011                print(head+line+') / {:.3g}'.format(norm))
1012                head = '              = '
1013                line = ''
1014                for j,(lbl,k) in enumerate(zip(coordVarLbl,row)):
1015                    var = self.Phase['ISODISTORT']['IsoVarList'][j]
1016                    head,line = fmtEqn(j,head,line,var,k/norm)
1017                print(head+line)
1018            print('\nConstraints')
1019            for c in self.Constraints:
1020                if type(c) is dict: continue
1021                if c[-1] != 'f': continue
1022                line = ''
1023                head = '  ' + str(c[-3]) + ' = '
1024                for j,(k,var) in enumerate(c[:-3]):
1025                    head,line = fmtEqn(j,head,line,var,k)
1026                print(head+line)
1027
1028            # Get the ISODISTORT offset values
1029            coordVarDelta = {}
1030            for lbl,val in zip(
1031                blk.get('_iso_deltacoordinate_label'),
1032                blk.get('_iso_deltacoordinate_value'),):
1033                coordVarDelta[lbl] = float(val)
1034            modeVarDelta = {}
1035            for lbl,val in zip(
1036                blk.get('_iso_displacivemode_label'),
1037                blk.get('_iso_displacivemode_value'),):
1038                modeVarDelta[lbl] = cif.get_number_with_esd(val)[0]
1039
1040            print('\n' + 70*'=')
1041            print('Confirming inverse mode relations computed from displacement values',
1042                      '\nusing Var2ModeMatrix, NormList, IsoVarList')
1043            # compute the mode values from the reported coordinate deltas
1044            for i,(row,n) in enumerate(zip(self.Phase['ISODISTORT']['Var2ModeMatrix'],
1045                                           self.Phase['ISODISTORT']['NormList'])):
1046                line = ''
1047                print(str(self.Phase['ISODISTORT']['IsoModeList'][i])+' = ')
1048                head = '  = ('
1049                for j,(lbl,k) in enumerate(zip(coordVarLbl,row)):
1050                    head,line = fmtEqn(j,head,line,lbl,k)
1051                print(head+line+') / '+('%.3f'%n))
1052                line = ''
1053                head = '  = ('
1054                vsum = 0.
1055                for j,(lbl,k) in enumerate(zip(coordVarLbl,row)):
1056                    val = "{:3g}".format(coordVarDelta[lbl])
1057                    head,line = fmtEqn(j,head,line,val,k)
1058                    vsum += coordVarDelta[lbl] * k
1059                print(head+line+') / '+('%.3f'%n))
1060                fileval = modeVarDelta[self.Phase['ISODISTORT']['IsoModeList'][i]]
1061                print("{} = {:4g} (value read from CIF = {:4g})\n".format(
1062                    self.Phase['ISODISTORT']['IsoModeList'][i], vsum, fileval))
1063
1064            print( 70*'=')
1065            print('Direct displacement relations computed from ISO modes in CIF',
1066                      '\nusing Mode2VarMatrix, NormList, IsoModeList, IsoVarList',)
1067            # compute the coordinate displacements from the reported mode values
1068            for lbl,row in zip(self.Phase['ISODISTORT']['IsoVarList'],
1069                               self.Phase['ISODISTORT']['Mode2VarMatrix']):
1070                l = ''
1071                s = 0.0
1072                head = lbl+' ='
1073                for j,(k,n) in enumerate(zip(row,self.Phase['ISODISTORT']['NormList'])):
1074                    if k == 0: continue
1075                    if len(l) > 65:
1076                        print(head,l)
1077                        head = 20*' '
1078                        l = ''
1079                    l1 = ''
1080                    k1 = k
1081                    if j > 0 and k < 0:
1082                        k1 = -k
1083                        l1 = ' - '
1084                    elif j > 0:
1085                        l1 += ' + '
1086                    l += '{:} {:3g} * {:4g} * {:}'.format(
1087                        l1, k1, n, self.Phase['ISODISTORT']['IsoModeList'][j])
1088                   
1089                    s += n * modeVarDelta[self.Phase['ISODISTORT']['IsoModeList'][j]] * k
1090                print(head,l)
1091                print(lbl,'=',s)
1092                print(lbl,'==>',str(self.Phase['ISODISTORT']['G2VarList'][i]),'\n')
1093            DeltaCoords = {}
1094            for i,lbl,row in zip(range(len(coordVarLbl)),coordVarLbl,displacivemodematrix):
1095                s = 0.0
1096                for j,(k,n) in enumerate(zip(row,self.Phase['ISODISTORT']['NormList'])):
1097                    s += k * n * modeVarDelta[self.Phase['ISODISTORT']['IsoModeList'][j]]
1098                at,d = lbl.rsplit('_',1)
1099                if at not in DeltaCoords:
1100                    DeltaCoords[at] = [0,0,0]
1101                if d == 'dx':
1102                    DeltaCoords[at][0] = s
1103                elif d == 'dy':
1104                    DeltaCoords[at][1] = s
1105                elif d == 'dz':
1106                    DeltaCoords[at][2] = s
1107                #else:
1108                #    print('unexpected',d)
1109
1110            print( 70*'=')
1111            print('Coordinate checks')
1112            print("\nxyz's Computed from ISO mode values, as above")
1113            for at in sorted(DeltaCoords):
1114                s = at
1115                for i in range(3):
1116                    s += '  '
1117                    s += str(ParentCoordinates[at][i]+DeltaCoords[at][i])
1118                print(s)
1119
1120            # determine the coordinate delta values from deviations from the parent structure
1121            print("\nxyz Values read directly from CIF")
1122            for atmline in self.Phase['Atoms']:
1123                lbl = atmline[0]
1124                x,y,z = atmline[3:6]
1125                print( lbl,x,y,z)
1126
1127            print('\n' + 70*'=')
1128            print("G2 short name ==> ISODISTORT full name",
1129                      " (from IsoModeList and G2ModeList)")
1130            for mode,G2mode in zip(self.Phase['ISODISTORT']['IsoModeList'],
1131                                   self.Phase['ISODISTORT']['G2ModeList']):
1132                print('{} ==> {}'.format(str(G2mode), mode))
1133            print('\nConstraint help dict info')
1134            for i in self.Constraints:
1135                if type(i) is dict:
1136                    for key in i:
1137                        print('\t',key,':',i[key])
1138            print( 70*'=')
1139
1140        #======================================================================
1141        # debug: show occupancy mode var to mode relations
1142        if debug and 'G2OccVarList' in self.Phase['ISODISTORT']:
1143            # coordinate items
1144            #occVarLbl = self.Phase['ISODISTORT']['OccVarList']
1145            G2varObj = self.Phase['ISODISTORT']['G2OccVarList']
1146            #ParentOcc = self.Phase['ISODISTORT']['BaseOcc']
1147            # mode items
1148            modelist = self.Phase['ISODISTORT']['OccModeList']
1149            modeVarList = self.Phase['ISODISTORT']['G2OccModeList']
1150            normlist = self.Phase['ISODISTORT']['OccNormList']
1151            # transform matrices
1152            #occupancymodeInvmatrix = self.Phase['ISODISTORT']['Var2OccMatrix']
1153            #occupancymodematrix = self.Phase['ISODISTORT']['Occ2VarMatrix']
1154           
1155            print( 70*'=')
1156            print('\nVar2OccMatrix' ,'OccVarList' )
1157            for i,row in enumerate(occupancymodeInvmatrix):
1158                l = ''
1159                for j,(lbl,k) in enumerate(zip(occVarLbl,row)):
1160                    if k == 0: continue
1161                    if l: l += ' + '
1162                    #l += lbl+' * '+str(k)
1163                    l += str(G2varObj[j])+' * '+str(k)
1164                print( str(i) + ': '+str(modeVarList[i])+' = '+l)
1165
1166            # Get the ISODISTORT offset values
1167            occVarDelta = {}
1168            for lbl,val in zip(
1169                blk.get('_iso_deltaoccupancy_label'),
1170                blk.get('_iso_deltaoccupancy_value'),):
1171                occVarDelta[lbl] = float(val)
1172            modeVarDelta = {}
1173            for lbl,val in zip(
1174                blk.get('_iso_occupancymode_label'),
1175                blk.get('_iso_occupancymode_value'),):
1176                modeVarDelta[lbl] = cif.get_number_with_esd(val)[0]
1177
1178            print( 70*'=')
1179            print('\nInverse relations using Var2OccModeMatrix, OccNormList, OccVarList')
1180            # compute the mode values from the reported coordinate deltas
1181            for i,(row,n) in enumerate(zip(occupancymodeInvmatrix,normlist)):
1182                l = ''
1183                for lbl,k in zip(occVarLbl,row):
1184                    if k == 0: continue
1185                    if l: l += ' + '
1186                    l += lbl+' * '+str(k)
1187                print('a'+str(i)+' = '+str(modeVarList[i])+' = ('+l+')/'+str(n))
1188            print('\nCalculation checks\n')
1189            for i,(row,n) in enumerate(zip(occupancymodeInvmatrix,normlist)):
1190                #l = ''
1191                sl = ''
1192                s = 0.
1193                for lbl,k in zip(occVarLbl,row):
1194                    if k == 0: continue
1195                    #if l: l += ' + '
1196                    #l += lbl+' * '+str(k)
1197                    if sl: sl += ' + '
1198                    sl += str(occVarDelta[lbl])+' * '+str(k)
1199                    s += occVarDelta[lbl] * k
1200                print(str(modeVarList[i]),'=','('+sl+') / ',n,'=',s/n)
1201                print(' ?= ',modeVarDelta[modelist[i]])
1202                print()
1203
1204            print( 70*'=')
1205            print('\nDirect relations using Occ2VarMatrix, OccNormList, OccVarList')
1206            # compute the coordinate displacements from the reported mode values
1207            Occ = {}
1208            for i,lbl,row in zip(range(len(occVarLbl)),occVarLbl,occupancymodematrix):
1209                l = ''
1210                s = 0.0
1211                for j,(k,n) in enumerate(zip(row,normlist)):
1212                    if k == 0: continue
1213                    if l: l += ' + '
1214                    l += str(n)+' * '+str(modeVarList[j])+' * '+str(k)
1215                    s += n * modeVarDelta[modelist[j]] * k
1216                print( lbl,'=',str(G2varObj[i]),'=',l,'=',s,'\n')
1217                j = lbl.split('_')[0] 
1218                Occ[j] = ParentOcc[j]+s
1219               
1220            # determine the coordinate delta values from deviations from the parent structure
1221            print('\nOccupancy from CIF vs computed')
1222            for atmline in self.Phase['Atoms']:
1223                lbl = atmline[0]
1224                if lbl in Occ: print( lbl,atmline[6],Occ[lbl])
1225
1226            print( 70*'=')
1227            print('\nGenerated constraints')
1228            for i in self.Constraints:
1229                if type(i) is dict:
1230                    print('\nconstraint help dict')
1231                    for key in i:
1232                        print('\t',key,':',i[key])
1233                elif i[-1] == 'f':
1234                    print('\n\t',i[-3],' =')
1235                    for m,j in i[:-3]:
1236                        print('\t\t+',m,' * ',j,'   ',repr(j))
1237                else:
1238                    print('  unexpected: ',repr(i))
1239            print("\nG2name ==> ISODISTORT full name",
1240                      ".Phase['ISODISTORT']['OccModeList']",
1241                      ".Phase['ISODISTORT']['G2OccModeList']")
1242            for mode,G2mode in zip(modelist,modeVarList):
1243                print("  ?::"+str(G2mode),' ==>', mode)
1244               
1245def ISODISTORT_shortLbl(lbl,shortmodelist):
1246    '''Shorten model labels and remove special characters
1247    '''
1248    regexp = re.match(r'.*?\[.*?\](.*?)\(.*?\)\[.*?\](.*)',lbl)
1249    # assume lbl is of form SSSSS[x,y,z]AAAA(a,b,...)[...]BBBBB
1250    # where SSSSS is the parent spacegroup, [x,y,z] is a location, etc.
1251    if regexp:
1252        # this extracts the AAAAA and BBBBB parts of the string
1253        lbl = regexp.expand(r'\1\2') # parse succeeded, make a short version
1254    else:
1255        # try form SSSSS[x,y,z]AAAA(a,b,...)BBBBB
1256        regexp = re.match(r'.*?\[.*?\](.*?)\(.*?\)(.*)',lbl)
1257        if regexp:
1258            lbl = regexp.expand(r'\1\2') # parse succeeded, make a short version
1259    lbl = lbl.replace('order','o')
1260    lbl = lbl.replace('+','_')
1261    lbl = lbl.replace('-','_')
1262    G2obj.MakeUniqueLabel(lbl,shortmodelist) # make unique and add to list
1263               
Note: See TracBrowser for help on using the repository browser.