source: trunk/GSASIIconstrGUI.py @ 4400

Last change on this file since 4400 was 4400, checked in by vondreele, 20 months ago

add rigid body save to make new pdb file with properly named atoms - needed for fullrmc
implement use of pdbparser (part of fullrmc) to prepare amorphous big box models from isolated molecules.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Date Author Revision URL Id
File size: 115.2 KB
Line 
1# -*- coding: utf-8 -*-
2#GSASIIconstrGUI - constraint GUI routines
3########### SVN repository information ###################
4# $Date: 2020-04-13 22:06:27 +0000 (Mon, 13 Apr 2020) $
5# $Author: vondreele $
6# $Revision: 4400 $
7# $URL: trunk/GSASIIconstrGUI.py $
8# $Id: GSASIIconstrGUI.py 4400 2020-04-13 22:06:27Z vondreele $
9########### SVN repository information ###################
10'''
11*GSASIIconstrGUI: Constraint GUI routines*
12------------------------------------------
13
14Used to define constraints and rigid bodies.
15
16'''
17from __future__ import division, print_function
18import sys
19import copy
20import os.path
21import wx
22import wx.grid as wg
23import wx.lib.scrolledpanel as wxscroll
24import random as ran
25import numpy as np
26import numpy.ma as ma
27import numpy.linalg as nl
28import GSASIIpath
29GSASIIpath.SetVersionNumber("$Revision: 4400 $")
30import GSASIIElem as G2elem
31import GSASIIElemGUI as G2elemGUI
32import GSASIIstrIO as G2stIO
33import GSASIImapvars as G2mv
34import GSASIImath as G2mth
35import GSASIIlattice as G2lat
36import GSASIIdataGUI as G2gd
37import GSASIIctrlGUI as G2G
38import GSASIIplot as G2plt
39import GSASIIobj as G2obj
40import GSASIIspc as G2spc
41import GSASIIpy3 as G2py3
42VERY_LIGHT_GREY = wx.Colour(235,235,235)
43WACV = wx.ALIGN_CENTER_VERTICAL
44
45class ConstraintDialog(wx.Dialog):
46    '''Window to edit Constraint values
47    '''
48    def __init__(self,parent,title,text,data,separator='*',varname="",varyflag=False):
49        wx.Dialog.__init__(self,parent,-1,'Edit '+title, 
50            pos=wx.DefaultPosition,style=wx.DEFAULT_DIALOG_STYLE)
51        self.data = data[:]
52        self.newvar = [varname,varyflag]
53        panel = wx.Panel(self)
54        mainSizer = wx.BoxSizer(wx.VERTICAL)
55        topLabl = wx.StaticText(panel,-1,text)
56        mainSizer.Add((10,10),1)
57        mainSizer.Add(topLabl,0,wx.ALIGN_CENTER_VERTICAL|wx.LEFT,10)
58        mainSizer.Add((10,10),1)
59        dataGridSizer = wx.FlexGridSizer(cols=3,hgap=2,vgap=2)
60        self.OkBtn = wx.Button(panel,wx.ID_OK)
61        for id in range(len(self.data)):
62            lbl1 = lbl = str(self.data[id][1])
63            if lbl[-1] != '=': lbl1 = lbl + ' ' + separator + ' '
64            name = wx.StaticText(panel,wx.ID_ANY,lbl1,
65                                 style=wx.ALIGN_RIGHT)
66            scale = G2G.ValidatedTxtCtrl(panel,self.data[id],0,
67                                          typeHint=float,
68                                          OKcontrol=self.DisableOK)
69            dataGridSizer.Add(name,0,wx.LEFT|wx.RIGHT|wx.ALIGN_CENTER_VERTICAL,5)
70            dataGridSizer.Add(scale,0,wx.RIGHT,3)
71            if ':' in lbl:
72                dataGridSizer.Add(
73                    wx.StaticText(panel,-1,G2obj.fmtVarDescr(lbl)),
74                    0,wx.RIGHT|wx.ALIGN_CENTER_VERTICAL,3)
75            else:
76                dataGridSizer.Add((-1,-1))
77        if title == 'New Variable':
78            name = wx.StaticText(panel,wx.ID_ANY,"New variable's\nname (optional)",
79                                 style=wx.ALIGN_CENTER)
80            scale = G2G.ValidatedTxtCtrl(panel,self.newvar,0,
81                                          typeHint=str,notBlank=False)
82            dataGridSizer.Add(name,0,wx.LEFT|wx.RIGHT|wx.ALIGN_CENTER_VERTICAL,5)
83            dataGridSizer.Add(scale,0,wx.RIGHT|wx.ALIGN_CENTER_VERTICAL,3)
84            self.refine = wx.CheckBox(panel,label='Refine?')
85            self.refine.SetValue(self.newvar[1]==True)
86            self.refine.Bind(wx.EVT_CHECKBOX, self.OnCheckBox)
87            dataGridSizer.Add(self.refine,0,wx.RIGHT|wx.ALIGN_CENTER_VERTICAL,3)
88           
89        mainSizer.Add(dataGridSizer,0,wx.EXPAND)
90        self.OkBtn.Bind(wx.EVT_BUTTON, self.OnOk)
91        self.OkBtn.SetDefault()
92        cancelBtn = wx.Button(panel,wx.ID_CANCEL)
93        cancelBtn.Bind(wx.EVT_BUTTON, self.OnCancel)
94        btnSizer = wx.BoxSizer(wx.HORIZONTAL)
95        btnSizer.Add((20,20),1)
96        btnSizer.Add(self.OkBtn)
97        btnSizer.Add((20,20),1)
98        btnSizer.Add(cancelBtn)
99        btnSizer.Add((20,20),1)
100
101        mainSizer.Add(btnSizer,0,wx.EXPAND|wx.BOTTOM|wx.TOP, 10)
102        panel.SetSizer(mainSizer)
103        panel.Fit()
104        self.Fit()
105        self.CenterOnParent()
106       
107    def DisableOK(self,setting):
108        if setting:
109            self.OkBtn.Enable()
110        else:
111            self.OkBtn.Disable()
112
113    def OnCheckBox(self,event):
114        self.newvar[1] = self.refine.GetValue()
115
116    def OnOk(self,event):
117        parent = self.GetParent()
118        parent.Raise()
119        self.EndModal(wx.ID_OK)             
120
121    def OnCancel(self,event):
122        parent = self.GetParent()
123        parent.Raise()
124        self.EndModal(wx.ID_CANCEL)             
125
126    def GetData(self):
127        return self.data
128       
129################################################################################
130#####  Constraints
131################################################################################           
132def UpdateConstraints(G2frame,data):
133    '''Called when Constraints tree item is selected.
134    Displays the constraints in the data window
135    '''
136       
137    def FindEquivVarb(name,nameList):
138        'Creates a list of variables appropriate to constrain with name'
139        outList = []
140        #phlist = []
141        items = name.split(':')
142        namelist = [items[2],]
143        if 'dA' in name:
144            namelist = ['dAx','dAy','dAz']
145        elif 'AU' in name:
146            namelist = ['AUiso','AU11','AU22','AU33','AU12','AU13','AU23']
147        elif 'AM' in name:
148            namelist = ['AMx','AMy','AMz']
149        elif items[-1] in ['A0','A1','A2','A3','A4','A5']:
150            namelist = ['A0','A1','A2','A3','A4','A5']
151        elif items[-1] in ['D11','D22','D33','D12','D13','D23']:
152            namelist = ['D11','D22','D33','D12','D13','D23']
153        elif 'Tm' in name:
154            namelist = ['Tmin','Tmax']
155        elif 'MX' in name or 'MY' in name or 'MZ' in name:
156            namelist = ['MXcos','MYcos','MZcos','MXsin','MYsin','MZsin']
157        elif 'mV' in name:
158            namelist = ['mV0','mV1','mV2']
159        elif 'RB' in name:
160            rbfx = 'RB'+items[2][2]
161            if 'T' in name and 'Tr' not in name:
162                namelist = [rbfx+'T11',rbfx+'T22',rbfx+'T33',rbfx+'T12',rbfx+'T13',rbfx+'T23']
163            if 'L' in name:
164                namelist = [rbfx+'L11',rbfx+'L22',rbfx+'L33',rbfx+'L12',rbfx+'L13',rbfx+'L23']
165            if 'S' in name:
166                namelist = [rbfx+'S12',rbfx+'S13',rbfx+'S21',rbfx+'S23',rbfx+'S31',rbfx+'S32',rbfx+'SAA',rbfx+'SBB']
167            if 'U' in name:
168                namelist = [rbfx+'U',]
169
170        for item in nameList:
171            keys = item.split(':')
172            #if keys[0] not in phlist:
173            #    phlist.append(keys[0])
174            if items[1] == '*' and keys[2] in namelist: # wildcard -- select only sequential options
175                keys[1] = '*'
176                mitem = ':'.join(keys)
177                if mitem == name: continue
178                if mitem not in outList: outList.append(mitem)
179            elif keys[2] in namelist and item != name:
180                outList.append(item)
181        return outList
182       
183    def SelectVarbs(page,FrstVarb,varList,legend,constType):
184        '''Select variables used in constraints after one variable has
185        been selected. This routine determines the appropriate variables to be
186        used based on the one that has been selected and asks for more to be added.
187
188        It then creates the constraint and adds it to the constraints list.
189       
190        Called from OnAddEquivalence, OnAddFunction & OnAddConstraint (all but
191        OnAddHold)
192
193        :param list page: defines calling page -- type of variables to be used
194        :parm GSASIIobj.G2VarObj FrstVarb: reference to first selected variable
195        :param list varList: list of other appropriate variables to select from
196        :param str legend: header for selection dialog
197        :param str constType: type of constraint to be generated
198        :returns: a constraint, as defined in
199          :ref:`GSASIIobj <Constraint_definitions_table>`
200        '''
201        choices = [[i]+list(G2obj.VarDescr(i)) for i in varList]
202        meaning = G2obj.getDescr(FrstVarb.name)
203        if not meaning:
204            meaning = "(no definition found!)"
205        l = str(FrstVarb).split(':')
206        # make lists of phases & histograms to iterate over
207        phaselist = [l[0]]
208        if l[0]:
209            phaselbl = ['phase #'+l[0]]
210            if len(Phases) > 1:
211                phaselist += ['all'] 
212                phaselbl += ['all phases']
213        else:
214            phaselbl = ['']
215        histlist = [l[1]]
216        if l[1] == '*':
217            pass
218        elif l[1]:
219            histlbl = ['histogram #'+l[1]]
220            if len(Histograms) > 1:
221                histlist += ['all']
222                histlbl += ['all histograms']
223                typ = Histograms[G2obj.LookupHistName(l[1])[0]]['Instrument Parameters'][0]['Type'][1]
224                i = 0
225                for hist in Histograms:
226                    if Histograms[hist]['Instrument Parameters'][0]['Type'][1] == typ: i += 1
227                if i > 1:
228                    histlist += ['all='+typ]
229                    histlbl += ['all '+typ+' histograms']
230        else:
231            histlbl = ['']
232        # make a list of equivalent parameter names
233        nameList = [FrstVarb.name]
234        for var in varList:
235            nam = var.split(":")[2]
236            if nam not in nameList: nameList += [nam]
237        # add "wild-card" names to the list of variables
238        if l[1] == '*':
239            pass
240        elif page[1] == 'phs':
241            if 'RB' in FrstVarb.name:
242                pass
243            elif FrstVarb.atom is None:
244                for nam in nameList:
245                    for ph,plbl in zip(phaselist,phaselbl):
246                        if plbl: plbl = 'For ' + plbl
247                        var = ph+"::"+nam
248                        if var == str(FrstVarb) or var in varList: continue
249                        varList += [var]
250                        choices.append([var,plbl,meaning])
251            else:
252                for nam in nameList:
253                    for ph,plbl in zip(phaselist,phaselbl):
254                        if plbl: plbl = ' in ' + plbl
255                        for atype in ['']+TypeList:
256                            if atype:
257                                albl = "For "+atype+" atoms"
258                                akey = "all="+atype                       
259                            else:
260                                albl = "For all atoms"
261                                akey = "all"
262                            var = ph+"::"+nam+":"+akey
263                            if var == str(FrstVarb) or var in varList: continue
264                            varList += [var]
265                            choices.append([var,albl+plbl,meaning])
266        elif page[1] == 'hap':
267            if FrstVarb.name == "Scale":
268                meaning = "Phase fraction"
269            for nam in nameList:
270                for ph,plbl in zip(phaselist,phaselbl):
271                    if plbl: plbl = 'For ' + plbl
272                    for hst,hlbl in zip(histlist,histlbl):
273                        if hlbl:
274                            if plbl:
275                                hlbl = ' in ' + hlbl
276                            else:
277                                hlbl = 'For ' + hlbl                               
278                            var = ph+":"+hst+":"+nam
279                            if var == str(FrstVarb) or var in varList: continue
280                            varList += [var]
281                            choices.append([var,plbl+hlbl,meaning])
282        elif page[1] == 'hst':
283            if FrstVarb.name == "Scale":
284                meaning = "Scale factor"
285            for nam in nameList:
286                for hst,hlbl in zip(histlist,histlbl):
287                    if hlbl:
288                        hlbl = 'For ' + hlbl                               
289                        var = ":"+hst+":"+nam
290                        if var == str(FrstVarb) or var in varList: continue
291                        varList += [var]
292                        choices.append([var,hlbl,meaning])
293        elif page[1] == 'glb' or page[1] == 'sym':
294            pass
295        else:
296            raise Exception('Unknown constraint page '+ page[1])                   
297        if len(choices):
298            l1 = l2 = 1
299            for i1,i2,i3 in choices:
300                l1 = max(l1,len(i1))
301                l2 = max(l2,len(i2))
302            fmt = "{:"+str(l1)+"s} {:"+str(l2)+"s} {:s}"
303            atchoices = [fmt.format(*i1) for i1 in choices] # reformat list as str with columns
304            dlg = G2G.G2MultiChoiceDialog(
305                G2frame,legend,
306                'Constrain '+str(FrstVarb)+' with...',atchoices,
307                toggle=False,size=(625,400),monoFont=True)
308            dlg.CenterOnParent()
309            res = dlg.ShowModal()
310            Selections = dlg.GetSelections()[:]
311            dlg.Destroy()
312            if res != wx.ID_OK: return []
313            if len(Selections) == 0:
314                dlg = wx.MessageDialog(
315                    G2frame,
316                    'No variables were selected to include with '+str(FrstVarb),
317                    'No variables')
318                dlg.CenterOnParent()
319                dlg.ShowModal()
320                dlg.Destroy()
321                return []
322        else:
323            dlg = wx.MessageDialog(
324                G2frame,
325                'There are no appropriate variables to include with '+str(FrstVarb),
326                'No variables')
327            dlg.CenterOnParent()
328            dlg.ShowModal()
329            dlg.Destroy()
330            return []
331        # now process the variables provided by the user
332        varbs = [str(FrstVarb),] # list of selected variables
333        for sel in Selections:
334            var = varList[sel]
335            # phase(s) included
336            l = var.split(':')
337            if l[0] == "all":
338                phlist = [str(Phases[phase]['pId']) for phase in Phases]
339            else:
340                phlist = [l[0]]
341            # histogram(s) included
342            if l[1] == "all":
343                hstlist = [str(Histograms[hist]['hId']) for hist in Histograms]
344            elif '=' in l[1]:
345                htyp = l[1].split('=')[1]
346                hstlist = [str(Histograms[hist]['hId']) for hist in Histograms if 
347                           Histograms[hist]['Instrument Parameters'][0]['Type'][1] == htyp]
348            else:
349                hstlist = [l[1]]
350            if len(l) == 3:
351                for ph in phlist:
352                    for hst in hstlist:
353                        var = ph + ":" + hst + ":" + l[2]
354                        if var in varbs: continue
355                        varbs.append(var)
356            else: # constraints with atoms or rigid bodies
357                if len(l) == 5: # rigid body parameter
358                    var = ':'.join(l)
359                    if var in varbs: continue
360                    varbs.append(var)
361                elif l[3] == "all":
362                    for ph in phlist:
363                        key = G2obj.LookupPhaseName(ph)[0]
364                        for hst in hstlist: # should be blank
365                            for iatm,at in enumerate(Phases[key]['Atoms']):
366                                var = ph + ":" + hst + ":" + l[2] + ":" + str(iatm)
367                                if var in varbs: continue
368                                varbs.append(var)
369                elif '=' in l[3]:
370                    for ph in phlist:
371                        key = G2obj.LookupPhaseName(ph)[0]
372                        cx,ct,cs,cia = Phases[key]['General']['AtomPtrs']
373                        for hst in hstlist: # should be blank
374                            atyp = l[3].split('=')[1]
375                            for iatm,at in enumerate(Phases[key]['Atoms']):
376                                if at[ct] != atyp: continue
377                                var = ph + ":" + hst + ":" + l[2] + ":" + str(iatm)
378                                if var in varbs: continue
379                                varbs.append(var)
380                else:
381                    for ph in phlist:
382                        key = G2obj.LookupPhaseName(ph)[0]
383                        for hst in hstlist: # should be blank
384                            var = ph + ":" + hst + ":" + l[2] + ":" + l[3]
385                            if var in varbs: continue
386                            varbs.append(var)
387        if len(varbs) >= 1 or 'constraint' in constType:
388            constr = [[1.0,FrstVarb]]
389            for item in varbs[1:]:
390                constr += [[1.0,G2obj.G2VarObj(item)]]
391            if 'equivalence' in constType:
392                return [constr+[None,None,'e']]
393            elif 'function' in constType:
394                return [constr+[None,False,'f']]
395            elif 'constraint' in constType:
396                return [constr+[1.0,None,'c']]
397            else:
398                raise Exception('Unknown constraint type: '+str(constType))
399        else:
400            dlg = wx.MessageDialog(
401                G2frame,
402                'There are no selected variables to include with '+str(FrstVarb),
403                'No variables')
404            dlg.CenterOnParent()
405            dlg.ShowModal()
406            dlg.Destroy()
407        return []
408   
409    def ConstraintsLoad(data,newcons=[]):
410        '''Load all constraints. Constraints based on symmetry (etc.)
411        are generated by running :func:`GSASIIstrIO.GetPhaseData`.
412        '''
413        G2mv.InitVars()
414        #Find all constraints
415        constraintSet = []
416        for key in data:
417            if key.startswith('_'): continue
418            constraintSet += data[key]
419        if newcons:
420            constraintSet = constraintSet + newcons
421        constDictList,fixedList,ignored = G2stIO.ProcessConstraints(constraintSet)
422        # generate symmetry constraints to check for conflicts
423        rigidbodyDict = G2frame.GPXtree.GetItemPyData(   
424            G2gd.GetGPXtreeItemId(G2frame,G2frame.root,'Rigid bodies'))
425        rbIds = rigidbodyDict.get('RBIds',{'Vector':[],'Residue':[]})
426        rbVary,rbDict = G2stIO.GetRigidBodyModels(rigidbodyDict,Print=False)
427        Natoms,atomIndx,phaseVary,phaseDict,pawleyLookup,FFtables,BLtables,MFtables,maxSSwave = G2stIO.GetPhaseData(
428            Phases,RestraintDict=None,rbIds=rbIds,Print=False) # generates atom symmetry constraints
429        return constDictList,phaseDict,fixedList
430           
431    def ConstraintsCheck(data,newcons=[]):
432        '''Load constraints & check them for errors. Since error checking
433        can cause changes in constraints in case of repairable conflicts
434        between equivalences, reload the constraints again after the check.
435        This could probably be done more effectively, but only reloading when
436        needed, but the reload should be quick.
437        '''
438        constDictList,phaseDict,fixedList = ConstraintsLoad(data,newcons)
439        msg = G2mv.EvaluateMultipliers(constDictList,phaseDict)
440        if msg:
441            return 'Unable to interpret multiplier(s): '+msg,''
442        res = G2mv.CheckConstraints('',constDictList,fixedList)
443        # reload constraints in case any were merged in MoveConfEquiv
444        ConstraintsLoad(data,newcons)
445        return res
446
447    def CheckAddedConstraint(newcons):
448        '''Check a new constraint that has just been input.
449        If there is an error display a message and discard the last entry
450
451        Since the varylist is not available, no warning messages
452        should be generated here
453
454        :returns: True if constraint should be added
455        '''
456       
457        errmsg,warnmsg = ConstraintsCheck(data,newcons)
458        if errmsg:
459            G2frame.ErrorDialog('Constraint Error',
460                'Error with newly added constraint:\n'+errmsg+
461                '\nIgnoring newly added constraint',parent=G2frame)
462            # reset error status
463            errmsg,warnmsg = ConstraintsCheck(data)
464            if errmsg:
465                print (errmsg)
466                print (G2mv.VarRemapShow([],True))
467            return False
468        elif warnmsg:
469            print ('Unexpected contraint warning:\n'+warnmsg)
470        return True
471
472    def CheckChangedConstraint():
473        '''Check all constraints after an edit has been made.
474        If there is an error display a message and reject the change.
475
476        Since the varylist is not available, no warning messages
477        should be generated.
478       
479        :returns: True if the edit should be retained
480        '''
481        errmsg,warnmsg = ConstraintsCheck(data)
482        if errmsg:
483            G2frame.ErrorDialog('Constraint Error',
484                'Error after editing constraint:\n'+errmsg+
485                '\nDiscarding last constraint edit',parent=G2frame)
486            # reset error status
487            errmsg,warnmsg = ConstraintsCheck(data)
488            if errmsg:
489                print (errmsg)
490                print (G2mv.VarRemapShow([],True))
491            return False
492        elif warnmsg:
493            print ('Unexpected contraint warning:\n'+warnmsg)
494        return True
495             
496    def PageSelection(page):
497        'Decode page reference'
498        if page[1] == "phs":
499            vartype = "phase"
500            varList = G2obj.removeNonRefined(phaseList)  # remove any non-refinable prms from list
501            constrDictEnt = 'Phase'
502        elif page[1] == "hap":
503            vartype = "Histogram*Phase"
504            varList = G2obj.removeNonRefined(hapList)  # remove any non-refinable prms from list
505            constrDictEnt = 'HAP'
506        elif page[1] == "hst":
507            vartype = "Histogram"
508            varList = G2obj.removeNonRefined(histList)  # remove any non-refinable prms from list
509            constrDictEnt = 'Hist'
510        elif page[1] == "glb":
511            vartype = "Global"
512            varList = G2obj.removeNonRefined(globalList)   # remove any non-refinable prms from list
513
514            constrDictEnt = 'Global'
515        elif page[1] == "sym":
516            return None,None,None
517        else:
518            raise Exception('Should not happen!')
519        return vartype,varList,constrDictEnt
520
521    def OnAddHold(event):
522        '''Create a new Hold constraint
523
524        Hold constraints allows the user to select one variable (the list of available
525        variables depends on which tab is currently active).
526        '''
527        page = G2frame.Page
528        vartype,varList,constrDictEnt = PageSelection(page)
529        if vartype is None: return
530        varList = G2obj.SortVariables(varList)
531        title1 = "Hold "+vartype+" variable"
532        if not varList:
533            G2frame.ErrorDialog('No variables','There are no variables of type '+vartype,
534                parent=G2frame)
535            return
536        l2 = l1 = 1
537        for i in varList:
538            l1 = max(l1,len(i))
539            loc,desc = G2obj.VarDescr(i)
540            l2 = max(l2,len(loc))
541        fmt = "{:"+str(l1)+"s} {:"+str(l2)+"s} {:s}"
542        varListlbl = [fmt.format(i,*G2obj.VarDescr(i)) for i in varList]
543        #varListlbl = ["("+i+") "+G2obj.fmtVarDescr(i) for i in varList]
544        legend = "Select variables to hold (Will not be varied, even if vary flag is set)"
545        dlg = G2G.G2MultiChoiceDialog(G2frame,
546            legend,title1,varListlbl,toggle=False,size=(625,400),monoFont=True)
547        dlg.CenterOnParent()
548        if dlg.ShowModal() == wx.ID_OK:
549            for sel in dlg.GetSelections():
550                Varb = varList[sel]
551                VarObj = G2obj.G2VarObj(Varb)
552                newcons = [[[0.0,VarObj],None,None,'h']]
553                if CheckAddedConstraint(newcons):
554                    data[constrDictEnt] += newcons
555        dlg.Destroy()
556        wx.CallAfter(OnPageChanged,None)
557       
558    def OnAddEquivalence(event):
559        '''add an Equivalence constraint'''
560        page = G2frame.Page
561        vartype,varList,constrDictEnt = PageSelection(page)
562        if vartype is None: return
563        title1 = "Create equivalence constraint between "+vartype+" variables"
564        title2 = "Select additional "+vartype+" variable(s) to be equivalent with "
565        if not varList:
566            G2frame.ErrorDialog('No variables','There are no variables of type '+vartype,
567                parent=G2frame)
568            return
569#        legend = "Select variables to make equivalent (only one of the variables will be varied when all are set to be varied)"
570        GetAddVars(page,title1,title2,varList,constrDictEnt,'equivalence')
571       
572    def OnAddAtomEquiv(event):
573        ''' Add equivalences between all parameters on atoms '''
574        page = G2frame.Page
575        vartype,varList,constrDictEnt = PageSelection(page)
576        if vartype is None: return
577        title1 = "Setup equivalent atom variables"
578        title2 = "Select additional atoms(s) to be equivalent with "
579        if not varList:
580            G2frame.ErrorDialog('No variables','There are no variables of type '+vartype,
581                parent=G2frame)
582            return
583#        legend = "Select atoms to make equivalent (only one of the atom variables will be varied when all are set to be varied)"
584        GetAddAtomVars(page,title1,title2,varList,constrDictEnt,'equivalence')
585       
586    def OnAddRiding(event):
587        ''' Add riding equivalences between all parameters on atoms '''
588        page = G2frame.Page
589        vartype,varList,constrDictEnt = PageSelection(page)
590        if vartype is None: return
591        title1 = "Setup riding atoms "
592        title2 = "Select additional atoms(s) to ride on "
593        if not varList:
594            G2frame.ErrorDialog('No variables','There are no variables of type '+vartype,
595                parent=G2frame)
596            return
597#        legend = "Select atoms to ride (only one of the atom variables will be varied when all are set to be varied)"
598        GetAddAtomVars(page,title1,title2,varList,constrDictEnt,'riding')
599   
600    def OnAddFunction(event):
601        '''add a Function (new variable) constraint'''
602        page = G2frame.Page
603        vartype,varList,constrDictEnt = PageSelection(page)
604        if vartype is None: return
605        title1 = "Setup new variable based on "+vartype+" variables"
606        title2 = "Include additional "+vartype+" variable(s) to be included with "
607        if not varList:
608            G2frame.ErrorDialog('No variables','There are no variables of type '+vartype,
609                parent=G2frame)
610            return
611#        legend = "Select variables to include in a new variable (the new variable will be varied when all included variables are varied)"
612        GetAddVars(page,title1,title2,varList,constrDictEnt,'function')
613                       
614    def OnAddConstraint(event):
615        '''add a constraint equation to the constraints list'''
616        page = G2frame.Page
617        vartype,varList,constrDictEnt = PageSelection(page)
618        if vartype is None: return
619        title1 = "Creating constraint on "+vartype+" variables"
620        title2 = "Select additional "+vartype+" variable(s) to include in constraint with "
621        if not varList:
622            G2frame.ErrorDialog('No variables','There are no variables of type '+vartype,
623                parent=G2frame)
624            return
625#        legend = "Select variables to include in a constraint equation (the values will be constrainted to equal a specified constant)"
626        GetAddVars(page,title1,title2,varList,constrDictEnt,'constraint')
627
628    def GetAddVars(page,title1,title2,varList,constrDictEnt,constType):
629        '''Get the variables to be added for OnAddEquivalence, OnAddFunction,
630        and OnAddConstraint. Then create and check the constraint.
631        '''
632        #varListlbl = ["("+i+") "+G2obj.fmtVarDescr(i) for i in varList]
633        if constType == 'equivalence':
634            omitVars = G2mv.GetDependentVars()
635        else:
636            omitVars = []
637        varList = G2obj.SortVariables([i for i in varList if i not in omitVars])
638        l2 = l1 = 1
639        for i in varList:
640            l1 = max(l1,len(i))
641            loc,desc = G2obj.VarDescr(i)
642            l2 = max(l2,len(loc))
643        fmt = "{:"+str(l1)+"s} {:"+str(l2)+"s} {:s}"
644        varListlbl = [fmt.format(i,*G2obj.VarDescr(i)) for i in varList]
645        dlg = G2G.G2SingleChoiceDialog(G2frame,'Select 1st variable:',
646            title1,varListlbl,monoFont=True,size=(625,400))
647        dlg.CenterOnParent()
648        if dlg.ShowModal() == wx.ID_OK:
649            if constType == 'equivalence':
650                omitVars = G2mv.GetDependentVars() + G2mv.GetIndependentVars()
651            sel = dlg.GetSelection()
652            FrstVarb = varList[sel]
653            VarObj = G2obj.G2VarObj(FrstVarb)
654            moreVarb = G2obj.SortVariables(FindEquivVarb(FrstVarb,[i for i in varList if i not in omitVars]))
655            newcons = SelectVarbs(page,VarObj,moreVarb,title2+FrstVarb,constType)
656            if len(newcons) > 0:
657                if CheckAddedConstraint(newcons):
658                    data[constrDictEnt] += newcons
659        dlg.Destroy()
660        wx.CallAfter(OnPageChanged,None)
661                       
662    def FindNeighbors(phase,FrstName,AtNames):
663        General = phase['General']
664        cx,ct,cs,cia = General['AtomPtrs']
665        Atoms = phase['Atoms']
666        atNames = [atom[ct-1] for atom in Atoms]
667        Cell = General['Cell'][1:7]
668        Amat,Bmat = G2lat.cell2AB(Cell)
669        atTypes = General['AtomTypes']
670        Radii = np.array(General['BondRadii'])
671        AtInfo = dict(zip(atTypes,Radii)) #or General['BondRadii']
672        Orig = atNames.index(FrstName.split()[1])
673        OType = Atoms[Orig][ct]
674        XYZ = G2mth.getAtomXYZ(Atoms,cx)       
675        Neigh = []
676        Dx = np.inner(Amat,XYZ-XYZ[Orig]).T
677        dist = np.sqrt(np.sum(Dx**2,axis=1))
678        sumR = AtInfo[OType]+0.5    #H-atoms only!
679        IndB = ma.nonzero(ma.masked_greater(dist-0.85*sumR,0.))
680        for j in IndB[0]:
681            if j != Orig:
682                Neigh.append(AtNames[j])
683        return Neigh
684       
685    def GetAddAtomVars(page,title1,title2,varList,constrDictEnt,constType):
686        '''Get the atom variables to be added for OnAddAtomEquiv. Then create and
687        check the constraints. Riding for H atoms only.
688        '''
689        Atoms = {G2obj.VarDescr(i)[0]:[] for i in varList if 'Atom' in G2obj.VarDescr(i)[0]}
690        for item in varList:
691            atName = G2obj.VarDescr(item)[0]
692            if atName in Atoms:
693                Atoms[atName].append(item)
694        AtNames = list(Atoms.keys())
695        AtNames.sort()
696        dlg = G2G.G2SingleChoiceDialog(G2frame,'Select 1st atom:',
697            title1,AtNames,monoFont=True,size=(625,400))
698        dlg.CenterOnParent()
699        FrstAtom = ''
700        if dlg.ShowModal() == wx.ID_OK:
701            sel = dlg.GetSelection()
702            FrstAtom = AtNames[sel]
703            if 'riding' in constType:
704                phaseName = (FrstAtom.split(' in ')[1]).strip()
705                phase = Phases[phaseName]
706                AtNames = FindNeighbors(phase,FrstAtom,AtNames)
707            else:
708                AtNames.remove(FrstAtom)
709        dlg.Destroy()
710        if FrstAtom == '':
711            print ('no atom selected')
712            return
713        dlg = G2G.G2MultiChoiceDialog(
714            G2frame,title2+FrstAtom,
715            'Constrain '+str(FrstAtom)+' with...',AtNames,
716            toggle=False,size=(625,400),monoFont=True)
717        if dlg.ShowModal() == wx.ID_OK:
718            Selections = dlg.GetSelections()[:]
719        else:
720            print ('no target atom selected')
721            dlg.Destroy()
722            return
723        dlg.Destroy()
724        for name in Atoms[FrstAtom]:
725            newcons = []
726            constr = []
727            if 'riding' in constType:
728                if 'AUiso' in name:
729                    constr = [[1.0,G2obj.G2VarObj(name)]]
730                elif 'AU11' in name:
731                    pass
732                elif 'AU' not in name:
733                    constr = [[1.0,G2obj.G2VarObj(name)]]
734            else:
735                constr = [[1.0,G2obj.G2VarObj(name)]]
736            pref = ':'+name.rsplit(':',1)[0].split(':',1)[1]    #get stuff between phase id & atom id
737            for sel in Selections:
738                name2 = Atoms[AtNames[sel]][0]
739                pid = name2.split(':',1)[0]                     #get phase id for 2nd atom
740                id = name2.rsplit(':',1)[-1]                    #get atom no. for 2nd atom
741                if 'riding' in constType:
742                    pref = pid+pref
743                    if 'AUiso' in pref:
744                        parts = pref.split('AUiso')
745                        constr += [[1.2,G2obj.G2VarObj('%s:%s'%(parts[0]+'AUiso',id))]]
746                    elif 'AU' not in pref:
747                        constr += [[1.0,G2obj.G2VarObj('%s:%s'%(pref,id))]]
748                else:
749                    constr += [[1.0,G2obj.G2VarObj('%s:%s'%(pid+pref,id))]]
750            if not constr:
751                continue
752            if 'frac' in pref and 'riding' not in constType:
753                newcons = [constr+[1.0,None,'c']]
754            else:
755                newcons = [constr+[None,None,'e']]
756            if len(newcons) > 0:
757                if CheckAddedConstraint(newcons):
758                    data[constrDictEnt] += newcons
759        wx.CallAfter(OnPageChanged,None)
760                       
761    def MakeConstraintsSizer(name,pageDisplay):
762        '''Creates a sizer displaying all of the constraints entered of
763        the specified type.
764
765        :param str name: the type of constraints to be displayed ('HAP',
766          'Hist', 'Phase', 'Global', 'Sym-Generated')
767        :param wx.Panel pageDisplay: parent panel for sizer
768        :returns: wx.Sizer created by method
769        '''
770        if name == 'Sym-Generated':         #show symmetry generated constraints
771            Sizer1 =  wx.BoxSizer(wx.VERTICAL)
772            Sizer1.Add(wx.StaticText(pageDisplay,wx.ID_ANY,
773                                    'Equivalences generated based on cell/space group input'))
774            Sizer1.Add((-1,5))
775            Sizer = wx.FlexGridSizer(0,2,0,0)
776            Sizer1.Add(Sizer)
777            for sym in G2mv.GetSymEquiv():
778                Sizer.Add(wx.StaticText(pageDisplay,wx.ID_ANY,'EQUIV'),
779                           0,wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_CENTER|wx.RIGHT|wx.LEFT,3)
780                Sizer.Add(wx.StaticText(pageDisplay,wx.ID_ANY,sym))
781                Sizer.Add((-1,-1))
782                Sizer.Add((-1,2))
783            return Sizer1
784        constSizer = wx.FlexGridSizer(0,6,0,0)
785        maxlen = 50 # characters before wrapping a constraint
786        for Id,item in enumerate(data[name]):
787            refineflag = False
788            helptext = ""
789            eqString = ['',]
790            problemItem = False
791            for term in item[:-3]:
792                if str(term[1]) in G2mv.problemVars:
793                    problemItem = True
794            if item[-1] == 'h': # Hold on variable
795                constSizer.Add((-1,-1),0)              # blank space for edit button
796                typeString = 'FIXED'
797                var = str(item[0][1])
798                varMean = G2obj.fmtVarDescr(var)
799                eqString[-1] =  var +'   '
800                helptext = "Prevents variable:\n"+ var + " ("+ varMean + ")\nfrom being changed"
801            elif item[-1] == 'f' or item[-1] == 'e' or item[-1] == 'c': # not true on original-style (2011?) constraints
802                constEdit = wx.Button(pageDisplay,wx.ID_ANY,'Edit',style=wx.BU_EXACTFIT)
803                constEdit.Bind(wx.EVT_BUTTON,OnConstEdit)
804                constSizer.Add(constEdit,0,wx.LEFT|wx.RIGHT|wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_CENTER,1)            # edit button
805                Indx[constEdit.GetId()] = [Id,name]
806                if item[-1] == 'f':
807                    helptext = "A new variable"
808                    if item[-3]:
809                        helptext += " named "+str(item[-3])
810                    helptext += " is created from a linear combination of the following variables:\n"
811                    for term in item[:-3]:
812                        var = str(term[1])
813                        if len(eqString[-1]) > maxlen:
814                            eqString.append(' ')
815                        m = term[0]
816                        if eqString[-1] != '':
817                            if m >= 0:
818                                eqString[-1] += ' + '
819                            else:
820                                eqString[-1] += ' - '
821                                m = abs(m)
822                        if m == 1:
823                            eqString[-1] += '{:} '.format(var)
824                        else:
825                            eqString[-1] += '{:g}*{:} '.format(m,var)
826                        varMean = G2obj.fmtVarDescr(var)
827                        helptext += "\n" + var + " ("+ varMean + ")"
828                    # Add ISODISTORT help items
829                    if '_Explain' in data:
830                        # this ignores the phase number. TODO: refactor that in
831                        hlptxt = None
832                        try:
833                            hlptxt = data['_Explain'].get(item[-3])
834                        except TypeError:
835                            # note that phase RanId is item[-3].phase
836                            hlptxt = data['_Explain'].get(str(item[-3].phase)+item[-3].name)
837                        if hlptxt:
838                            helptext += '\n\n'
839                            helptext += hlptxt
840                    # typeString = 'NEWVAR'
841                    # if item[-3]:
842                    #     eqString[-1] += ' = '+item[-3]
843                    # else:
844                    #     eqString[-1] += ' = New Variable'
845                    if item[-3]:
846                        typeString = str(item[-3]) + ' = '
847                    else:
848                        typeString = 'New Variable = '
849                    #print 'refine',item[-2]
850                    refineflag = True
851                elif item[-1] == 'c':
852                    helptext = "The following variables constrained to equal a constant:"
853                    for term in item[:-3]:
854                        var = str(term[1])
855                        if len(eqString[-1]) > maxlen:
856                            eqString.append(' ')
857                        m = term[0]
858                        if eqString[-1] != '':
859                            if term[0] > 0:
860                                eqString[-1] += ' + '
861                            else:
862                                eqString[-1] += ' - '
863                                m = -term[0]
864                        if m == 1:
865                            eqString[-1] += '{:} '.format(var)
866                        else:
867                            eqString[-1] += '{:g}*{:} '.format(m,var)
868                        varMean = G2obj.fmtVarDescr(var)
869                        helptext += "\n" + var + " ("+ varMean + ")"
870                    typeString = 'CONST'
871                    eqString[-1] += ' = '+str(item[-3])
872                elif item[-1] == 'e':
873                    helptext = "The following variables are set to be equivalent, noting multipliers:"
874                    normval = item[:-3][1][0]
875                    for i,term in enumerate(item[:-3]):
876                        var = str(term[1])
877                        if term[0] == 0: term[0] = 1.0
878                        if len(eqString[-1]) > maxlen:
879                            eqString.append(' ')
880                        if i == 0: # move independent variable to end
881                            indepterm = term
882                            continue
883                        elif eqString[-1] != '':
884                            eqString[-1] += ' = '
885                        if normval/term[0] == 1:
886                            eqString[-1] += '{:}'.format(var)
887                        else:
888                            eqString[-1] += '{:g}*{:} '.format(normval/term[0],var)
889                        varMean = G2obj.fmtVarDescr(var)
890                        helptext += "\n" + var + " ("+ varMean + ")"
891                    if normval/indepterm[0] == 1:
892                        eqString[-1] += ' = {:} '.format(indepterm[1])
893                    else:
894                        eqString[-1] += ' = {:g}*{:} '.format(normval/indepterm[0],str(indepterm[1]))
895                    typeString = 'EQUIV'
896                else:
897                    print ('Unexpected constraint'+item)
898               
899            else:
900                print ('Removing old-style constraints')
901                data[name] = []
902                return constSizer
903            constDel = wx.Button(pageDisplay,wx.ID_ANY,'Delete',style=wx.BU_EXACTFIT)
904            constDel.Bind(wx.EVT_BUTTON,OnConstDel)
905            Indx[constDel.GetId()] = [Id,name]
906            constSizer.Add(constDel,0,wx.LEFT|wx.RIGHT|wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_CENTER,1)             # delete button
907            if helptext:
908                ch = G2G.HelpButton(pageDisplay,helptext)
909                constSizer.Add(ch,0,wx.LEFT|wx.RIGHT|wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_CENTER,1)
910            else:
911                constSizer.Add((-1,-1))
912            if refineflag:
913                ch = G2G.G2CheckBox(pageDisplay,'',item,-2)
914                constSizer.Add(ch,0,wx.LEFT|wx.RIGHT|wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_CENTER,1)
915            else:
916                constSizer.Add((-1,-1))               
917            constSizer.Add(wx.StaticText(pageDisplay,wx.ID_ANY,typeString),
918                           0,wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_CENTER|wx.RIGHT|wx.LEFT,3)
919            if problemItem: eqString[-1] += ' -- Conflict: see console'
920            if len(eqString) > 1:
921                Eq = wx.BoxSizer(wx.VERTICAL)
922                for s in eqString:
923                    line = wx.StaticText(pageDisplay,wx.ID_ANY,s)
924                    if problemItem: line.SetBackgroundColour(wx.YELLOW)
925                    Eq.Add(line,0,wx.ALIGN_CENTER_VERTICAL)
926                Eq.Add((-1,4))
927            else:
928                Eq = wx.StaticText(pageDisplay,wx.ID_ANY,eqString[0])
929                if problemItem: Eq.SetBackgroundColour(wx.YELLOW)
930            constSizer.Add(Eq,1,wx.ALIGN_CENTER_VERTICAL)
931        return constSizer
932               
933    def OnConstDel(event):
934        'Delete a constraint'
935        Obj = event.GetEventObject()
936        Id,name = Indx[Obj.GetId()]
937        del data[name][Id]
938        ConstraintsLoad(data)
939        wx.CallAfter(OnPageChanged,None)
940
941    def OnConstEdit(event):
942        '''Called to edit an individual contraint in response to a
943        click on its Edit button
944        '''
945        Obj = event.GetEventObject()
946        Id,name = Indx[Obj.GetId()]
947        if data[name][Id][-1] == 'f':
948            items = data[name][Id][:-3]
949            constType = 'New Variable'
950            if data[name][Id][-3]:
951                varname = str(data[name][Id][-3])
952            else:
953                varname = ""
954            lbl = 'Enter value for each term in constraint; sum = new variable'
955            dlg = ConstraintDialog(G2frame,constType,lbl,items,
956                                   varname=varname,varyflag=data[name][Id][-2])
957        elif data[name][Id][-1] == 'c':
958            items = data[name][Id][:-3]+[
959                [str(data[name][Id][-3]),'fixed value =']]
960            constType = 'Constraint'
961            lbl = 'Edit value for each term in constant constraint sum'
962            dlg = ConstraintDialog(G2frame,constType,lbl,items)
963        elif data[name][Id][-1] == 'e':
964            items = data[name][Id][:-3]
965            constType = 'Equivalence'
966            lbl = 'The following terms are set to be equal:'
967            dlg = ConstraintDialog(G2frame,constType,lbl,items,'/')
968        else:
969            return
970        try:
971            prev = copy.deepcopy(data[name][Id])
972            if dlg.ShowModal() == wx.ID_OK:
973                result = dlg.GetData()
974                for i in range(len(data[name][Id][:-3])):
975                    if type(data[name][Id][i]) is tuple: # fix non-mutable construct
976                        data[name][Id][i] = list(data[name][Id][i])
977                    data[name][Id][i][0] = result[i][0]
978                if data[name][Id][-1] == 'c':
979                    data[name][Id][-3] = str(result[-1][0])
980                elif data[name][Id][-1] == 'f':
981                    data[name][Id][-2] = dlg.newvar[1]
982                    if type(data[name][Id][-3]) is str:
983                        # process the variable name to put in global form (::var)
984                        varname = str(dlg.newvar[0]).strip().replace(' ','_')
985                        if varname.startswith('::'):
986                            varname = varname[2:]
987                        varname = varname.replace(':',';')
988                        if varname:
989                            data[name][Id][-3] = varname
990                        else:
991                            data[name][Id][-3] = ''
992                if not CheckChangedConstraint():
993                    data[name][Id] = prev
994            else:
995                data[name][Id] = prev
996        except:
997            import traceback
998            print (traceback.format_exc())
999        finally:
1000            dlg.Destroy()
1001        wx.CallAfter(OnPageChanged,None)
1002   
1003    def UpdateConstraintPanel(panel,typ):
1004        '''Update the contents of the selected Constraint
1005        notebook tab. Called in :func:`OnPageChanged`
1006        '''
1007        if panel.GetSizer(): panel.GetSizer().Clear(True)
1008        Siz = wx.BoxSizer(wx.VERTICAL)
1009        Siz.Add((5,5),0)
1010        Siz.Add(MakeConstraintsSizer(typ,panel),1,wx.EXPAND)
1011        panel.SetSizer(Siz,True)
1012        Size = Siz.GetMinSize()
1013        Size[0] += 40
1014        Size[1] = max(Size[1],450) + 20
1015        panel.SetSize(Size)
1016        panel.SetScrollbars(10,10,Size[0]/10-4,Size[1]/10-1)
1017        panel.Show()
1018
1019    def OnPageChanged(event):
1020        '''Called when a tab is pressed or when a "select tab" menu button is
1021        used (see RaisePage), or to refresh the current tab contents (event=None)
1022        '''
1023        if event:       #page change event!
1024            page = event.GetSelection()
1025        else: # called directly, get current page
1026            page = G2frame.constr.GetSelection()
1027        G2frame.constr.ChangeSelection(page)
1028        text = G2frame.constr.GetPageText(page)
1029        G2frame.dataWindow.ConstraintEdit.Enable(G2G.wxID_EQUIVALANCEATOMS,False)
1030#        G2frame.dataWindow.ConstraintEdit.Enable(G2G.wxID_ADDRIDING,False)
1031        if text == 'Histogram/Phase':
1032            enableEditCons = [False]+4*[True]
1033            G2frame.Page = [page,'hap']
1034            UpdateConstraintPanel(HAPConstr,'HAP')
1035        elif text == 'Histogram':
1036            enableEditCons = [False]+4*[True]
1037            G2frame.Page = [page,'hst']
1038            UpdateConstraintPanel(HistConstr,'Hist')
1039        elif text == 'Phase':
1040            enableEditCons = 5*[True]
1041            G2frame.Page = [page,'phs']
1042            G2frame.dataWindow.ConstraintEdit.Enable(G2G.wxID_EQUIVALANCEATOMS,True)
1043#            G2frame.dataWindow.ConstraintEdit.Enable(G2G.wxID_ADDRIDING,True)
1044            if 'DELETED' in str(PhaseConstr):   #seems to be no other way to do this (wx bug)
1045                if GSASIIpath.GetConfigValue('debug'):
1046                    print ('DBG_wx error: PhaseConstr not cleanly deleted after Refine')
1047                return
1048            UpdateConstraintPanel(PhaseConstr,'Phase')
1049        elif text == 'Global':
1050            enableEditCons = [False]+4*[True]
1051            G2frame.Page = [page,'glb']
1052            UpdateConstraintPanel(GlobalConstr,'Global')
1053        else:
1054            enableEditCons = 5*[False]
1055            G2frame.Page = [page,'sym']
1056            UpdateConstraintPanel(SymConstr,'Sym-Generated')
1057        # remove menu items when not allowed
1058        for obj,flag in zip(G2frame.dataWindow.ConstraintEdit.GetMenuItems(),enableEditCons): 
1059            obj.Enable(flag)
1060        G2frame.dataWindow.SetDataSize()
1061
1062    def RaisePage(event):
1063        'Respond to a "select tab" menu button'
1064        try:
1065            i = (G2G.wxID_CONSPHASE,
1066                 G2G.wxID_CONSHAP,
1067                 G2G.wxID_CONSHIST,
1068                 G2G.wxID_CONSGLOBAL,
1069                 G2G.wxID_CONSSYM,
1070                ).index(event.GetId())
1071            G2frame.constr.SetSelection(i)
1072            wx.CallAfter(OnPageChanged,None)
1073        except ValueError:
1074            print('Unexpected event in RaisePage')
1075
1076    def SetStatusLine(text):
1077        G2frame.GetStatusBar().SetStatusText(text,1)
1078
1079    # UpdateConstraints execution starts here
1080    if not data:
1081        data.update({'Hist':[],'HAP':[],'Phase':[],'Global':[]})       #empty dict - fill it
1082    if 'Global' not in data:                                            #patch
1083        data['Global'] = []
1084    # DEBUG code ########################################
1085    #import GSASIIconstrGUI
1086    #reload(GSASIIconstrGUI)
1087    #reload(G2obj)
1088    #reload(G2stIO)
1089    #import GSASIIstrMain
1090    #reload(GSASIIstrMain)   
1091    #reload(G2mv)
1092    #reload(G2gd)
1093    ###################################################
1094    Histograms,Phases = G2frame.GetUsedHistogramsAndPhasesfromTree()
1095    if not len(Phases) or not len(Histograms):
1096        dlg = wx.MessageDialog(G2frame,'You need both phases and histograms to see Constraints',
1097            'No phases or histograms')
1098        dlg.CenterOnParent()
1099        dlg.ShowModal()
1100        dlg.Destroy()
1101        return
1102    G2obj.IndexAllIds(Histograms,Phases)
1103    for p in Phases:
1104        if 'ISODISTORT' in Phases[p]:
1105            G2frame.dataWindow.ConstraintEdit.Enable(G2G.wxID_SHOWISO,True)
1106            break
1107    ##################################################################################
1108    # patch: convert old-style (str) variables in constraints to G2VarObj objects
1109    for key,value in data.items():
1110        if key.startswith('_'): continue
1111        j = 0
1112        for cons in value:
1113            #print cons             # DEBUG
1114            for i in range(len(cons[:-3])):
1115                if type(cons[i][1]) is str:
1116                    cons[i][1] = G2obj.G2VarObj(cons[i][1])
1117                    j += 1
1118        if j:
1119            print (str(key) + ': '+str(j)+' variable(s) as strings converted to objects')
1120    ##################################################################################
1121    rigidbodyDict = G2frame.GPXtree.GetItemPyData(
1122        G2gd.GetGPXtreeItemId(G2frame,G2frame.root,'Rigid bodies'))
1123    rbIds = rigidbodyDict.get('RBIds',{'Vector':[],'Residue':[]})
1124    rbVary,rbDict = G2stIO.GetRigidBodyModels(rigidbodyDict,Print=False)
1125    badPhaseParms = ['Ax','Ay','Az','Amul','AI/A','Atype','SHorder','AwaveType','FwaveType','PwaveType','MwaveType','Vol','isMag',]
1126    globalList = list(rbDict.keys())
1127    globalList.sort()
1128    try:
1129        AtomDict = dict([Phases[phase]['pId'],Phases[phase]['Atoms']] for phase in Phases)
1130    except KeyError:
1131        G2frame.ErrorDialog('Constraint Error','Constraints cannot be set until a cycle of least squares'+
1132                            ' has been run.\nWe suggest you refine a scale factor.')
1133        return
1134
1135    # create a list of the phase variables
1136    Natoms,atomIndx,phaseVary,phaseDict,pawleyLookup,FFtable,BLtable,MFtable,maxSSwave = G2stIO.GetPhaseData(Phases,rbIds=rbIds,Print=False)
1137    phaseList = []
1138    for item in phaseDict:
1139        if item.split(':')[2] not in badPhaseParms:
1140            phaseList.append(item)
1141    phaseList.sort()
1142    phaseAtNames = {}
1143    phaseAtTypes = {}
1144    TypeList = []
1145    for item in phaseList:
1146        Split = item.split(':')
1147        if Split[2][:2] in ['AU','Af','dA','AM']:
1148            Id = int(Split[0])
1149            phaseAtNames[item] = AtomDict[Id][int(Split[3])][0]
1150            phaseAtTypes[item] = AtomDict[Id][int(Split[3])][1]
1151            if phaseAtTypes[item] not in TypeList:
1152                TypeList.append(phaseAtTypes[item])
1153        else:
1154            phaseAtNames[item] = ''
1155            phaseAtTypes[item] = ''
1156             
1157    # create a list of the hist*phase variables
1158    seqList = G2frame.testSeqRefineMode()
1159    if seqList: # for sequential refinement, only process 1st histgram in list
1160        histDict = {seqList[0]:Histograms[seqList[0]]}
1161    else:
1162        histDict = Histograms
1163    hapVary,hapDict,controlDict = G2stIO.GetHistogramPhaseData(Phases,histDict,Print=False,resetRefList=False)
1164    hapList = sorted([i for i in hapDict.keys() if i.split(':')[2] not in ('Type',)])
1165    if seqList: # convert histogram # to wildcard
1166        wildList = [] # list of variables with "*" for histogram number
1167        for i in hapList:
1168            s = i.split(':')
1169            if s[1] == "": continue
1170            s[1] = '*'
1171            sj = ':'.join(s)
1172            if sj not in wildList: wildList.append(sj)
1173        hapList = wildList
1174    histVary,histDict,controlDict = G2stIO.GetHistogramData(histDict,Print=False)
1175    histList = list(histDict.keys())
1176    histList.sort()
1177    if seqList: # convert histogram # to wildcard
1178        wildList = [] # list of variables with "*" for histogram number
1179        for i in histList:
1180            s = i.split(':')
1181            if s[1] == "": continue
1182            s[1] = '*'
1183            sj = ':'.join(s)
1184            if sj not in wildList: wildList.append(sj)
1185        histList = wildList       
1186    Indx = {}
1187    G2frame.Page = [0,'phs']
1188   
1189    G2gd.SetDataMenuBar(G2frame,G2frame.dataWindow.ConstraintMenu)
1190    SetStatusLine('')
1191   
1192    G2frame.Bind(wx.EVT_MENU, OnAddConstraint, id=G2G.wxID_CONSTRAINTADD)
1193    G2frame.Bind(wx.EVT_MENU, OnAddFunction, id=G2G.wxID_FUNCTADD)
1194    G2frame.Bind(wx.EVT_MENU, OnAddEquivalence, id=G2G.wxID_EQUIVADD)
1195    G2frame.Bind(wx.EVT_MENU, OnAddHold, id=G2G.wxID_HOLDADD)
1196    G2frame.Bind(wx.EVT_MENU, OnAddAtomEquiv, id=G2G.wxID_EQUIVALANCEATOMS)
1197#    G2frame.Bind(wx.EVT_MENU, OnAddRiding, id=G2G.wxID_ADDRIDING)
1198    def OnShowISODISTORT(event):
1199        ShowIsoDistortCalc(G2frame)
1200    G2frame.Bind(wx.EVT_MENU, OnShowISODISTORT, id=G2G.wxID_SHOWISO)
1201    # tab commands
1202    for id in (G2G.wxID_CONSPHASE,
1203               G2G.wxID_CONSHAP,
1204               G2G.wxID_CONSHIST,
1205               G2G.wxID_CONSGLOBAL,
1206               G2G.wxID_CONSSYM,
1207               ):
1208        G2frame.Bind(wx.EVT_MENU, RaisePage,id=id)
1209
1210    #G2frame.constr = G2G.GSNoteBook(parent=G2frame.dataWindow,size=G2frame.dataWindow.GetClientSize())
1211    G2frame.constr = G2G.GSNoteBook(parent=G2frame.dataWindow)
1212    G2frame.dataWindow.GetSizer().Add(G2frame.constr,1,wx.ALL|wx.EXPAND)
1213    # note that order of pages is hard-coded in RaisePage
1214    PhaseConstr = wx.ScrolledWindow(G2frame.constr)
1215    G2frame.constr.AddPage(PhaseConstr,'Phase')
1216    HAPConstr = wx.ScrolledWindow(G2frame.constr)
1217    G2frame.constr.AddPage(HAPConstr,'Histogram/Phase')
1218    HistConstr = wx.ScrolledWindow(G2frame.constr)
1219    G2frame.constr.AddPage(HistConstr,'Histogram')
1220    GlobalConstr = wx.ScrolledWindow(G2frame.constr)
1221    G2frame.constr.AddPage(GlobalConstr,'Global')
1222    SymConstr = wx.ScrolledWindow(G2frame.constr)
1223    G2frame.constr.AddPage(SymConstr,'Sym-Generated')
1224    wx.CallAfter(OnPageChanged,None)
1225    G2frame.constr.Bind(wx.aui.EVT_AUINOTEBOOK_PAGE_CHANGED, OnPageChanged)
1226    # validate all the constrants -- should not see any errors here normally
1227    errmsg,warnmsg = ConstraintsCheck(data)
1228    if errmsg:
1229        G2frame.ErrorDialog('Constraint Error',
1230                            'Error in constraints:\n'+errmsg+'\nCheck console output for more information',
1231                            parent=G2frame)
1232        print (errmsg)
1233        print (G2mv.VarRemapShow([],True))
1234    elif warnmsg:
1235        print ('Unexpected contraint warning:\n'+warnmsg)
1236       
1237################################################################################
1238# check scale & phase fractions, create constraint if needed
1239################################################################################
1240def CheckAllScalePhaseFractions(G2frame):
1241    '''Check if scale factor and all phase fractions are refined without a constraint
1242    for all used histograms, if so, offer the user a chance to create a constraint
1243    on the sum of phase fractions
1244    '''
1245    histograms, phases = G2frame.GetUsedHistogramsAndPhasesfromTree()
1246    for i,hist in enumerate(histograms):
1247        CheckScalePhaseFractions(G2frame,hist,histograms,phases)
1248       
1249def CheckScalePhaseFractions(G2frame,hist,histograms,phases):
1250    '''Check if scale factor and all phase fractions are refined without a constraint
1251    for histogram hist, if so, offer the user a chance to create a constraint
1252    on the sum of phase fractions
1253    '''
1254    if G2frame.testSeqRefineMode():
1255        histStr = '*'
1256    else:
1257        histStr = str(histograms[hist]['hId'])
1258    # Is this powder?
1259    if not hist.startswith('PWDR '): return
1260    # do this only if the scale factor is varied
1261    if not histograms[hist]['Sample Parameters']['Scale'][1]: return
1262    # are all phase fractions varied in all used histograms?
1263    phaseCount = 0
1264    for p in phases:
1265        if hist not in phases[p]['Histograms']: continue
1266        if phases[p]['Histograms'][hist]['Use'] and not phases[p]['Histograms'][hist]['Scale'][1]:
1267            return
1268        else:
1269            phaseCount += 1
1270   
1271    # all phase fractions and scale factor varied, now scan through constraints
1272    sub = G2gd.GetGPXtreeItemId(G2frame,G2frame.root,'Constraints') 
1273    Constraints = G2frame.GPXtree.GetItemPyData(sub)
1274    for c in Constraints.get('HAP',[]):
1275        if c[-1] != 'c': continue
1276        if not c[-3]: continue
1277        if len(c[:-3]) != phaseCount: continue
1278        # got a constraint equation with right number of terms, is it on phase fractions for
1279        # the correct histogram?
1280        if all([(i[1].name == 'Scale' and i[1].varname().split(':')[1] == histStr) for i in c[:-3]]):
1281            # got a constraint, this is OK
1282            return
1283    dlg = wx.MessageDialog(G2frame,'You are refining the scale factor and all phase fractions for histogram #'+
1284        histStr+'. This will produce an unstable refinement. '+
1285        'Do you want to constrain the sum of phase fractions?','Create constraint?',wx.OK|wx.CANCEL)
1286    if dlg.ShowModal() != wx.ID_OK:
1287        dlg.Destroy()
1288        return
1289    dlg.Destroy()
1290
1291    constr = []
1292    for p in phases:
1293        if hist not in phases[p]['Histograms']: continue
1294        if not phases[p]['Histograms'][hist]['Use']: continue
1295        constr += [[1.0,G2obj.G2VarObj(':'.join((str(phases[p]['pId']),histStr,'Scale')))]]
1296    constr += [1.0,None,'c']
1297    Constraints['HAP'] += [constr]
1298       
1299################################################################################
1300#### Make nuclear/magnetic phase transition constraints - called by OnTransform in G2phsGUI
1301################################################################################       
1302       
1303def TransConstraints(G2frame,oldPhase,newPhase,Trans,Vec,atCodes):
1304    '''Add constraints for new magnetic phase created via transformation of old
1305    nuclear one
1306    NB: A = [G11,G22,G33,2*G12,2*G13,2*G23]
1307    '''
1308   
1309    def SetUniqAj(pId,iA,SGData):
1310        SGLaue = SGData['SGLaue']
1311        SGUniq = SGData['SGUniq']
1312        if SGLaue in ['m3','m3m']:
1313            if iA in [0,1,2]:
1314                parm = '%d::%s'%(pId,'A0')
1315            else:
1316                parm = None
1317        elif SGLaue in ['4/m','4/mmm']:
1318            if iA in [0,1]:
1319                parm = '%d::%s'%(pId,'A0')
1320            elif iA == 2:
1321                parm = '%d::%s'%(pId,'A2')
1322            else:
1323                parm = None
1324        elif SGLaue in ['6/m','6/mmm','3m1', '31m', '3']:
1325            if iA in [0,1,3]:
1326                parm = '%d::%s'%(pId,'A0')
1327            elif iA == 2:
1328                parm = '%d::%s'%(pId,'A2')
1329            else:
1330                parm = None
1331        elif SGLaue in ['3R', '3mR']:
1332            if ia in [0,1,2]:
1333                parm = '%d::%s'%(pId,'A0')
1334            else:
1335                parm = '%d::%s'%(pId,'A3')
1336        elif SGLaue in ['mmm',]:
1337            if iA in [0,1,2]:
1338                parm = '%d::A%s'%(pId,iA)
1339            else:
1340                parm = None
1341        elif SGLaue == '2/m':
1342            if iA in [0,1,2]:
1343                parm = '%d::A%s'%(pId,iA)
1344            elif iA == 3 and SGUniq == 'c':
1345                parm = '%d::A%s'%(pId,iA)
1346            elif iA == 4 and SGUniq == 'b':
1347                parm = '%d::A%s'%(pId,iA)
1348            elif iA == 5 and SGUniq == 'a':
1349                parm = '%d::A%s'%(pId,iA)
1350            else:
1351                parm = None           
1352        else:
1353            parm = '%d::A%s'%(pId,iA)
1354        return parm
1355   
1356    Histograms,Phases = G2frame.GetUsedHistogramsAndPhasesfromTree()
1357    UseList = newPhase['Histograms']
1358    detTrans = np.abs(nl.det(Trans))
1359    invTrans = nl.inv(Trans)
1360    opId = oldPhase['pId']
1361    npId = newPhase['pId']
1362    cx,ct,cs,cia = newPhase['General']['AtomPtrs']
1363    nAtoms = newPhase['Atoms']
1364    oSGData = oldPhase['General']['SGData']
1365    nSGData = newPhase['General']['SGData']
1366    #oAcof = G2lat.cell2A(oldPhase['General']['Cell'][1:7])
1367    #nAcof = G2lat.cell2A(newPhase['General']['Cell'][1:7])
1368    item = G2gd.GetGPXtreeItemId(G2frame,G2frame.root,'Constraints')
1369    if not item:
1370        print('Error: no constraints in Data Tree')
1371        return
1372    constraints = G2frame.GPXtree.GetItemPyData(item)
1373    xnames = ['dAx','dAy','dAz']
1374    # constraints on matching atom params between phases
1375    for ia,code in enumerate(atCodes):
1376        atom = nAtoms[ia]
1377        if not ia and atom[cia] == 'A':
1378            wx.MessageDialog(G2frame,
1379                'Anisotropic thermal motion constraints are not developed at the present time',
1380                'Anisotropic thermal constraint?',style=wx.ICON_INFORMATION).ShowModal()
1381        siteSym = G2spc.SytSym(atom[cx:cx+3],nSGData)[0]
1382        CSX = G2spc.GetCSxinel(siteSym)
1383#        CSU = G2spc.GetCSuinel(siteSym)
1384        item = code.split('+')[0]
1385        iat,opr = item.split(':')
1386        Nop = abs(int(opr))%100-1
1387        if '-' in opr:
1388            Nop *= -1
1389        Opr = oldPhase['General']['SGData']['SGOps'][abs(Nop)][0]
1390        if Nop < 0:         #inversion
1391            Opr *= -1
1392        XOpr = np.inner(Opr,Trans)
1393        for i,ix in enumerate(list(CSX[0])):
1394            if not ix:
1395                continue
1396            name = xnames[i]
1397            IndpCon = [1.0,G2obj.G2VarObj('%d::%s:%d'%(npId,name,ia))]
1398            DepCons = []
1399            for iop,opval in enumerate(XOpr[i]):
1400                if opval:
1401                    DepCons.append([opval,G2obj.G2VarObj('%d::%s:%s'%(opId,xnames[iop],iat))])
1402            if len(DepCons) == 1:
1403                constraints['Phase'].append([DepCons[0],IndpCon,None,None,'e'])
1404            elif len(DepCons) > 1:
1405                for Dep in DepCons:
1406                    Dep[0] *= -1
1407                constraints['Phase'].append([IndpCon]+DepCons+[0.0,None,'c'])
1408        for name in ['Afrac','AUiso']:
1409            IndpCon = [1.0,G2obj.G2VarObj('%d::%s:%d'%(npId,name,ia))]
1410            DepCons = [1.0,G2obj.G2VarObj('%d::%s:%s'%(opId,name,iat))]
1411            constraints['Phase'].append([DepCons,IndpCon,None,None,'e'])
1412           
1413        # unfinished Anisotropic constraint generation
1414#        Uids = [[0,0,'AU11'],[1,1,'AU22'],[2,2,'AU33'],[0,1,'AU12'],[0,2,'AU13'],[1,2,'AU23']]
1415#        DepConsDict = dict(zip(Us,[[],[],[],[],[],[]]))
1416#        for iu,Uid in enumerate(Uids):
1417#            UMT = np.zeros((3,3))
1418#            UMT[Uid[0],Uid[1]] = 1
1419#            nUMT = G2lat.prodMGMT(UMT,invTrans)
1420#            nUT = G2lat.UijtoU6(nUMT)
1421#            for iu,nU in enumerate(nUT):
1422#                if abs(nU) > 1.e-8:
1423#                    parm = '%d::%s;%s'%(opId,Us[iu],iat)
1424#                    DepConsDict[Uid[2]].append([abs(nU%1.),G2obj.G2VarObj(parm)])
1425#        nUcof = atom[iu:iu+6]
1426#        conStrings = []
1427#        for iU,Usi in enumerate(Us):
1428#            parm = '%d::%s;%d'%(npId,Usi,ia)
1429#            parmDict[parm] = nUcof[iU]
1430#            varyList.append(parm)
1431#            IndpCon = [1.0,G2obj.G2VarObj(parm)]
1432#            conStr = str([IndpCon,DepConsDict[Usi]])
1433#            if conStr in conStrings:
1434#                continue
1435#            conStrings.append(conStr)
1436#            if len(DepConsDict[Usi]) == 1:
1437#                if DepConsDict[Usi][0]:
1438#                    constraints['Phase'].append([IndpCon,DepConsDict[Usi][0],None,None,'e'])
1439#            elif len(DepConsDict[Usi]) > 1:       
1440#                for Dep in DepConsDict[Usi]:
1441#                    Dep[0] *= -1
1442#                constraints['Phase'].append([IndpCon]+DepConsDict[Usi]+[0.0,None,'c'])
1443           
1444        #how do I do Uij's for most Trans?
1445
1446    # constraints on lattice parameters between phases
1447#    T = nl.inv(Trans).T
1448    T = Trans.T
1449    conMat = [
1450        [T[0,0]**2,T[0,1]**2,T[0,2]**2,T[0,0]*T[0,1],T[0,0]*T[0,2],T[0,1]*T[0,2]],
1451        [T[1,0]**2,T[1,1]**2,T[1,2]**2,T[1,0]*T[1,1],T[1,0]*T[1,2],T[1,1]*T[1,2]],
1452        [T[2,0]**2,T[2,1]**2,T[2,2]**2,T[2,0]*T[2,1],T[2,0]*T[2,2],T[2,1]*T[2,2]],
1453        [2.*T[0,0]*T[1,0],2.*T[0,1]*T[1,1],2.*T[0,2]*T[1,2],T[0,0]*T[1,1]+T[0,1]*T[1,0],T[0,0]*T[1,2]+T[0,2]*T[1,0],T[0,1]*T[1,2]+T[0,2]*T[1,1]],
1454        [2.*T[0,0]*T[2,0],2.*T[0,1]*T[2,1],2.*T[0,2]*T[2,2],T[0,0]*T[2,1]+T[0,1]*T[2,0],T[0,0]*T[2,2]+T[0,2]*T[2,0],T[0,1]*T[2,2]+T[0,2]*T[2,1]],
1455        [2.*T[1,0]*T[2,0],2.*T[1,1]*T[2,1],2.*T[1,2]*T[2,2],T[1,0]*T[2,1]+T[1,1]*T[2,0],T[1,0]*T[2,2]+T[1,2]*T[2,0],T[1,1]*T[2,2]+T[1,2]*T[2,1]]
1456        ]
1457    # Gnew = conMat * A:
1458#         T00**2*a0  T01**2*a1 T02**2*a2 T00*T01*a3    T00*T02*a4    T01*T02*a5
1459#         T10**2*a0  T11**2*a1 T12**2*a2 T10*T11*a3    T10*T12*a4    T11*T12*a5
1460#         T20**2*a0  T21**2*a1 T22**2*a2 T20*T21*a3    T20*T22*a4    T21*T22*a5
1461#         2*T00*T10*a0      2*T01*T11*a1     2*T02*T12*a2     (T00*T11 + T01*T10)*a3      (T00*T12 + T02*T10)*a4      (T01*T12 + T02*T11)*a5
1462#         2*T00*T20*a0      2*T01*T21*a1     2*T02*T22*a2     (T00*T21 + T01*T20)*a3      (T00*T22 + T02*T20)*a4      (T01*T22 + T02*T21)*a5
1463#         2*T10*T20*a0      2*T11*T21*a1     2*T12*T22*a2     (T10*T21 + T11*T20)*a3      (T10*T22 + T12*T20)*a4      (T11*T22 + T12*T21)*a5
1464    # Generated as symbolic code using:
1465    # import sym
1466    # A0, A1, A2, A3, A4, A5 = sym.symbols('A0, A1, A2, A3, A4, A5')
1467    # G = sym.Matrix([ [A0,    A3/2.,  A4/2.], [A3/2.,  A1,    A5/2.], [A4/2., A5/2.,    A2]])
1468    # transformation matrix
1469    # T00, T10, T20, T01, T11, T21, T02, T12, T22 = sym.symbols('T00, T10, T20, T01, T11, T21, T02, T12, T22')
1470    # Tr = sym.Matrix([ [T00, T10, T20], [T01, T11, T21], [T02, T12, T22],])
1471    # Gnew = (Tr.T*G)*Tr
1472   
1473    #print('old A',G2lat.cell2A(oldPhase['General']['Cell'][1:7]))
1474    #print('new A',G2lat.cell2A(newPhase['General']['Cell'][1:7]))
1475   
1476#this is still incorrect for hex/trig/ortho/tetragonal --> monoclinic
1477   
1478#    for iAnew,Asi in enumerate(['A0','A1','A2','A3','A4','A5']): # loop through A[i] for new cell
1479#        Nparm = str(npId) + '::' + Asi
1480#        if Nparm != SetUniqAj(npId,iAnew,nSGData):
1481#            continue # skip: Ai constrained from Aj or must be zero
1482#        multDict = {}
1483#        for iAorg in range(6):
1484#            cA = conMat[iAnew][iAorg] # coeff for A[i] in constraint matrix
1485#            if abs(cA) < 1.e-8: continue
1486#            parm = SetUniqAj(opId,iAorg,oSGData) # translate to unique A[i] in original cell
1487#            if not parm: continue # must be zero
1488#            # sum coeff
1489#            if parm in multDict:
1490#                multDict[parm] += cA
1491#            else:
1492#                multDict[parm] = cA
1493#        # any non-zero multipliers?
1494#        maxMult = 0
1495#        for i in multDict:
1496#            maxMult = max(maxMult,abs(multDict[i]))
1497#        if maxMult <= 0:  # Nparm computes as zero; Fix this parameter
1498#            constraints['Phase'] += [[
1499#                [0.0,G2obj.G2VarObj(Nparm)],
1500#                None,None,'h']]
1501#        elif len(multDict) == 1:        # create equivalence
1502#            key = list(multDict.keys())[0]
1503#            constraints['Phase'] += [[
1504#                [1.0,G2obj.G2VarObj(key)],
1505#                [multDict[key],G2obj.G2VarObj(Nparm)],
1506#                None,None,'e']]
1507#        else:                           # create constraint
1508#            constr = [[-1.0,G2obj.G2VarObj(Nparm)]]
1509#            for key in multDict:
1510#                constr += [[multDict[key],G2obj.G2VarObj(key)]]
1511#            constr += [0.0,None,'c']
1512#            constraints['Phase'] += [constr]
1513   
1514    # constraints on HAP Scale, etc.
1515    for hId,hist in enumerate(UseList):    #HAP - seems OK
1516        ohapkey = '%d:%d:'%(opId,hId)
1517        nhapkey = '%d:%d:'%(npId,hId)
1518        IndpCon = [1.0,G2obj.G2VarObj(ohapkey+'Scale')]
1519        DepCons = [detTrans,G2obj.G2VarObj(nhapkey+'Scale')]
1520        constraints['HAP'].append([DepCons,IndpCon,None,None,'e'])
1521        for name in ['Size;i','Mustrain;i']:
1522            IndpCon = [1.0,G2obj.G2VarObj(ohapkey+name)]
1523            DepCons = [1.0,G2obj.G2VarObj(nhapkey+name)]
1524            constraints['HAP'].append([IndpCon,DepCons,None,None,'e'])
1525       
1526################################################################################
1527#### Rigid bodies
1528################################################################################
1529
1530def UpdateRigidBodies(G2frame,data):
1531    '''Called when Rigid bodies tree item is selected.
1532    Displays the rigid bodies in the data window
1533    '''
1534    if not data.get('RBIds') or not data:
1535        data.update({'Vector':{'AtInfo':{}},'Residue':{'AtInfo':{}},
1536            'RBIds':{'Vector':[],'Residue':[]}})       #empty/bad dict - fill it
1537           
1538    global resList,rbId
1539    Indx = {}
1540    resList = []
1541    plotDefaults = {'oldxy':[0.,0.],'Quaternion':[0.,0.,0.,1.],'cameraPos':30.,'viewDir':[0,0,1],}
1542
1543    G2frame.rbBook = G2G.GSNoteBook(parent=G2frame.dataWindow)
1544    G2frame.dataWindow.GetSizer().Add(G2frame.rbBook,1,wx.ALL|wx.EXPAND)
1545    VectorRB = wx.ScrolledWindow(G2frame.rbBook)
1546    VectorRBDisplay = wx.Panel(VectorRB)
1547    G2frame.rbBook.AddPage(VectorRB,'Vector rigid bodies')
1548    ResidueRB = wx.ScrolledWindow(G2frame.rbBook)
1549    ResidueRBDisplay = wx.Panel(ResidueRB)
1550    G2frame.rbBook.AddPage(ResidueRB,'Residue rigid bodies')
1551   
1552    def OnPageChanged(event):
1553        global resList
1554        resList = []
1555        if event:       #page change event!
1556            page = event.GetSelection()
1557        else:
1558            page = G2frame.rbBook.GetSelection()
1559        G2frame.rbBook.ChangeSelection(page)
1560        text = G2frame.rbBook.GetPageText(page)
1561        if text == 'Vector rigid bodies':
1562            G2gd.SetDataMenuBar(G2frame,G2frame.dataWindow.VectorBodyMenu)
1563            G2frame.Bind(wx.EVT_MENU, AddVectorRB, id=G2G.wxID_VECTORBODYADD)
1564            G2frame.Page = [page,'vrb']
1565            UpdateVectorRB()
1566        elif text == 'Residue rigid bodies':
1567            G2gd.SetDataMenuBar(G2frame,G2frame.dataWindow.RigidBodyMenu)
1568            G2frame.Bind(wx.EVT_MENU, AddResidueRB, id=G2G.wxID_RIGIDBODYADD)
1569            G2frame.Bind(wx.EVT_MENU, OnImportRigidBody, id=G2G.wxID_RIGIDBODYIMPORT)
1570            G2frame.Bind(wx.EVT_MENU, OnSaveRigidBody, id=G2G.wxID_RIGIDBODYSAVE)
1571            G2frame.Bind(wx.EVT_MENU, OnDefineTorsSeq, id=G2G.wxID_RESIDUETORSSEQ) #enable only if residue RBs exist?
1572            G2frame.Page = [page,'rrb']
1573            UpdateResidueRB()
1574           
1575    def getMacroFile(macName):
1576        defDir = os.path.join(os.path.split(__file__)[0],'GSASIImacros')
1577        dlg = wx.FileDialog(G2frame,message='Choose '+macName+' rigid body macro file',
1578            defaultDir=defDir,defaultFile="",wildcard="GSAS-II macro file (*.mac)|*.mac",
1579            style=wx.FD_OPEN | wx.FD_CHANGE_DIR)
1580        try:
1581            if dlg.ShowModal() == wx.ID_OK:
1582                macfile = dlg.GetPath()
1583                macro = open(macfile,'Ur')
1584                head = macro.readline()
1585                if macName not in head:
1586                    print (head)
1587                    print ('**** ERROR - wrong restraint macro file selected, try again ****')
1588                    macro = []
1589            else: # cancel was pressed
1590                macro = []
1591        finally:
1592            dlg.Destroy()
1593        return macro        #advanced past 1st line
1594       
1595    def getTextFile():
1596        dlg = wx.FileDialog(G2frame,'Choose rigid body text file', G2frame.LastGPXdir, '',
1597            "GSAS-II text file (*.txt)|*.txt|XYZ file (*.xyz)|*.xyz|"
1598            "Sybyl mol2 file (*.mol2)|*.mol2|PDB file (*.pdb;*.ent)|*.pdb;*.ent",
1599            wx.FD_OPEN | wx.FD_CHANGE_DIR)
1600        try:
1601            if dlg.ShowModal() == wx.ID_OK:
1602                txtfile = dlg.GetPath()
1603                ext = os.path.splitext(txtfile)[1]
1604                text = open(txtfile,'Ur')
1605            else: # cancel was pressed
1606                ext = ''
1607                text = []
1608        finally:
1609            dlg.Destroy()
1610        if 'ent' in ext:
1611            ext = '.pdb'
1612        return text,ext.lower()
1613       
1614    def OnImportRigidBody(event):
1615        page = G2frame.rbBook.GetSelection()
1616        if 'Vector' in G2frame.rbBook.GetPageText(page):
1617            pass
1618        elif 'Residue' in G2frame.rbBook.GetPageText(page):
1619            ImportResidueRB()
1620           
1621    def OnSaveRigidBody(event):
1622        page = G2frame.rbBook.GetSelection()
1623        if 'Vector' in G2frame.rbBook.GetPageText(page):
1624            pass
1625        elif 'Residue' in G2frame.rbBook.GetPageText(page):
1626            SaveResidueRB()
1627           
1628    def AddVectorRB(event):
1629        AtInfo = data['Vector']['AtInfo']
1630        dlg = G2G.MultiIntegerDialog(G2frame,'New Rigid Body',['No. atoms','No. translations'],[1,1])
1631        if dlg.ShowModal() == wx.ID_OK:
1632            nAtoms,nTrans = dlg.GetValues()
1633            rbId = ran.randint(0,sys.maxsize)
1634            vecMag = [1.0 for i in range(nTrans)]
1635            vecRef = [False for i in range(nTrans)]
1636            vecVal = [np.zeros((nAtoms,3)) for j in range(nTrans)]
1637            rbTypes = ['C' for i in range(nAtoms)]
1638            Info = G2elem.GetAtomInfo('C')
1639            AtInfo['C'] = [Info['Drad'],Info['Color']]
1640            data['Vector'][rbId] = {'RBname':'UNKRB','VectMag':vecMag,'rbXYZ':np.zeros((nAtoms,3)),
1641                'rbRef':[0,1,2,False],'VectRef':vecRef,'rbTypes':rbTypes,'rbVect':vecVal,'useCount':0}
1642            data['RBIds']['Vector'].append(rbId)
1643        dlg.Destroy()
1644        UpdateVectorRB()
1645       
1646    def AddResidueRB(event):
1647        global rbId
1648        AtInfo = data['Residue']['AtInfo']
1649        macro = getMacroFile('rigid body')
1650        if not macro:
1651            return
1652        macStr = macro.readline()
1653        while macStr:
1654            items = macStr.split()
1655            if 'I' == items[0]:
1656                rbId = ran.randint(0,sys.maxsize)
1657                rbName = items[1]
1658                rbTypes = []
1659                rbXYZ = []
1660                rbSeq = []
1661                atNames = []
1662                nAtms,nSeq,nOrig,mRef,nRef = [int(items[i]) for i in [2,3,4,5,6]]
1663                for iAtm in range(nAtms):
1664                    macStr = macro.readline().split()
1665                    atName = macStr[0]
1666                    atType = macStr[1]
1667                    atNames.append(atName)
1668                    rbXYZ.append([float(macStr[i]) for i in [2,3,4]])
1669                    rbTypes.append(atType)
1670                    if atType not in AtInfo:
1671                        Info = G2elem.GetAtomInfo(atType)
1672                        AtInfo[atType] = [Info['Drad'],Info['Color']]
1673                rbXYZ = np.array(rbXYZ)-np.array(rbXYZ[nOrig-1])
1674                for iSeq in range(nSeq):
1675                    macStr = macro.readline().split()
1676                    mSeq = int(macStr[0])
1677                    for jSeq in range(mSeq):
1678                        macStr = macro.readline().split()
1679                        iBeg = int(macStr[0])-1
1680                        iFin = int(macStr[1])-1
1681                        angle = 0.0
1682                        nMove = int(macStr[2])
1683                        iMove = [int(macStr[i])-1 for i in range(3,nMove+3)]
1684                        rbSeq.append([iBeg,iFin,angle,iMove])
1685                data['Residue'][rbId] = {'RBname':rbName,'rbXYZ':rbXYZ,'rbTypes':rbTypes,
1686                    'atNames':atNames,'rbRef':[nOrig-1,mRef-1,nRef-1,True],'rbSeq':rbSeq,
1687                    'SelSeq':[0,0],'useCount':0}
1688                data['RBIds']['Residue'].append(rbId)
1689                print ('Rigid body '+rbName+' added')
1690            macStr = macro.readline()
1691        macro.close()
1692        UpdateResidueRB()
1693       
1694    def ImportResidueRB():
1695        global rbId
1696        AtInfo = data['Residue']['AtInfo']
1697        text,ext = getTextFile()
1698        if not text:
1699            return
1700        rbId = ran.randint(0,sys.maxsize)
1701        rbTypes = []
1702        rbXYZ = []
1703        atNames = []
1704        txtStr = text.readline()
1705        if 'xyz' in ext:
1706            txtStr = text.readline()
1707            txtStr = text.readline()
1708        elif 'mol2' in ext:
1709            while 'ATOM' not in txtStr:
1710                txtStr = text.readline()
1711            txtStr = text.readline()
1712        elif 'pdb' in ext:
1713            while 'ATOM' not in txtStr[:6] and 'HETATM' not in txtStr[:6]:
1714                txtStr = text.readline()
1715        items = txtStr.split()
1716        nat = 1
1717        while len(items):
1718            if 'txt' in ext:
1719                atName = items[0]
1720                atType = items[1]
1721                rbXYZ.append([float(items[i]) for i in [2,3,4]])
1722            elif 'xyz' in ext:
1723                atType = items[0]
1724                rbXYZ.append([float(items[i]) for i in [1,2,3]])
1725                atName = '%s%d'%(atType,nat)
1726            elif 'mol2' in ext:
1727                atType = items[1]
1728                atName = items[1]+items[0]
1729                rbXYZ.append([float(items[i]) for i in [2,3,4]])
1730            elif 'pdb' in ext:
1731                atType = items[-1]
1732                if not items[2][-1].isnumeric():
1733                    atName = '%s%d'%(items[2],nat)
1734                else:
1735                    atName = '5s'%items[2]
1736                xyz = txtStr[30:55].split()                   
1737                rbXYZ.append([float(x) for x in xyz])
1738            atNames.append(atName)
1739            rbTypes.append(atType)
1740            if atType not in AtInfo:
1741                Info = G2elem.GetAtomInfo(atType)
1742                AtInfo[atType] = [Info['Drad'],Info['Color']]
1743            txtStr = text.readline()
1744            if 'mol2' in ext and 'BOND' in txtStr:
1745                break
1746            if 'pdb' in ext and ('ATOM' not in txtStr[:6] and 'HETATM' not in txtStr[:6]):
1747                break
1748            items = txtStr.split()
1749            nat += 1
1750        if len(atNames) < 3:
1751            G2G.G2MessageBox(G2frame,'Not enough atoms in rigid body; must be 3 or more')
1752        else:
1753            rbXYZ = np.array(rbXYZ)-np.array(rbXYZ[0])
1754            Xxyz = rbXYZ[1]
1755            X = Xxyz/np.sqrt(np.sum(Xxyz**2))
1756            Yxyz = rbXYZ[2]
1757            Y = Yxyz/np.sqrt(np.sum(Yxyz**2))
1758            Mat = G2mth.getRBTransMat(X,Y)
1759            rbXYZ = np.inner(Mat,rbXYZ).T
1760            data['Residue'][rbId] = {'RBname':'UNKRB','rbXYZ':rbXYZ,'rbTypes':rbTypes,
1761                'atNames':atNames,'rbRef':[0,1,2,False],'rbSeq':[],'SelSeq':[0,0],'useCount':0}
1762            data['RBIds']['Residue'].append(rbId)
1763            print ('Rigid body UNKRB added')
1764        text.close()
1765        UpdateResidueRB()
1766       
1767    def SaveResidueRB():
1768        global rbId
1769        pth = G2G.GetExportPath(G2frame)
1770        dlg = wx.FileDialog(G2frame, 'Choose PDB file for Atom XYZ', pth, '', 
1771            'PDB files (*.pdb)|*.pdb',wx.FD_SAVE|wx.FD_OVERWRITE_PROMPT)
1772        try:
1773            if dlg.ShowModal() == wx.ID_OK:
1774                filename = dlg.GetPath()
1775                # make sure extension is .pkslst
1776                filename = os.path.splitext(filename)[0]+'.pdb'
1777                File = open(filename,'w')       
1778                rbData =  data['Residue'][rbId]
1779                for iat,xyz in enumerate(rbData['rbXYZ']):
1780                    File.write('ATOM %6d  %-4s%3s     1    %8.3f%8.3f%8.3f  1.00  0.00          %2s\n'%(
1781                        iat,rbData['atNames'][iat],rbData['RBname'][:3],xyz[0],xyz[1],xyz[2],rbData['rbTypes'][iat]))
1782                File.close()
1783                print ('Atom XYZ saved to: '+filename)
1784        finally:
1785            dlg.Destroy()
1786       
1787       
1788    def FindNeighbors(Orig,XYZ,atTypes,atNames,AtInfo):
1789        Radii = []
1790        for Atype in atTypes:
1791            Radii.append(AtInfo[Atype][0])
1792        Radii = np.array(Radii)
1793        Neigh = []
1794        Dx = XYZ-XYZ[Orig]
1795        dist = np.sqrt(np.sum(Dx**2,axis=1))
1796        sumR = Radii[Orig]+Radii
1797        IndB = ma.nonzero(ma.masked_greater(dist-0.85*sumR,0.))
1798        for j in IndB[0]:
1799            if j != Orig and atTypes[j] != 'H':
1800                Neigh.append(atNames[j])
1801        return Neigh
1802       
1803    def FindAllNeighbors(XYZ,atTypes,atNames,AtInfo):
1804        NeighDict = {}
1805        for iat,xyz in enumerate(atNames):
1806            NeighDict[atNames[iat]] = FindNeighbors(iat,XYZ,atTypes,atNames,AtInfo)
1807        return NeighDict
1808       
1809    def FindRiding(Orig,Pivot,NeighDict):
1810        riding = [Orig,Pivot]
1811        iAdd = 1
1812        new = True
1813        while new:
1814            newAtms = NeighDict[riding[iAdd]]
1815            for At in newAtms:
1816                new = False
1817                if At not in riding:
1818                    riding.append(At)
1819                    new = True
1820            iAdd += 1
1821            if iAdd < len(riding):
1822                new = True
1823        return riding[2:]
1824                       
1825    def OnDefineTorsSeq(event):
1826        global rbId
1827        rbData = data['Residue'][rbId]
1828        if not len(rbData):
1829            return
1830        atNames = rbData['atNames']
1831        AtInfo = data['Residue']['AtInfo']
1832        atTypes = rbData['rbTypes']
1833        XYZ = rbData['rbXYZ']
1834        neighDict = FindAllNeighbors(XYZ,atTypes,atNames,AtInfo)
1835        TargList = []           
1836        dlg = wx.SingleChoiceDialog(G2frame,'Select origin atom for torsion sequence','Origin atom',rbData['atNames'])
1837        if dlg.ShowModal() == wx.ID_OK:
1838            Orig = dlg.GetSelection()
1839            TargList = neighDict[atNames[Orig]]
1840        dlg.Destroy()
1841        if not len(TargList):
1842            return
1843        dlg = wx.SingleChoiceDialog(G2frame,'Select pivot atom for torsion sequence','Pivot atom',TargList)
1844        if dlg.ShowModal() == wx.ID_OK:
1845            Piv = atNames.index(TargList[dlg.GetSelection()])
1846            riding = FindRiding(atNames[Orig],atNames[Piv],neighDict)
1847            Riding = []
1848            for atm in riding:
1849                Riding.append(atNames.index(atm))
1850            rbData['rbSeq'].append([Orig,Piv,0.0,Riding])           
1851        dlg.Destroy()
1852        UpdateResidueRB()
1853
1854    def UpdateVectorRB(Scroll=0):
1855        AtInfo = data['Vector']['AtInfo']
1856        refChoice = {}
1857        if 'DELETED' in str(G2frame.GetStatusBar()):   #seems to be no other way to do this (wx bug)
1858            if GSASIIpath.GetConfigValue('debug'):
1859                print ('DBG_wx error: Rigid Body/Status not cleanly deleted after Refine')
1860            return
1861        SetStatusLine(' You may use e.g. "c60" or "s60" for a vector entry')
1862        def rbNameSizer(rbId,rbData):
1863
1864            def OnRBName(event):
1865                event.Skip()
1866                Obj = event.GetEventObject()
1867                rbData['RBname'] = Obj.GetValue()
1868               
1869            def OnDelRB(event):
1870                Obj = event.GetEventObject()
1871                rbId = Indx[Obj.GetId()]
1872                if rbId in data['Vector']:
1873                    del data['Vector'][rbId]
1874                    data['RBIds']['Vector'].remove(rbId)
1875                    rbData['useCount'] -= 1
1876                wx.CallAfter(UpdateVectorRB)
1877               
1878            def OnPlotRB(event):
1879                Obj = event.GetEventObject()
1880                Obj.SetValue(False)
1881                G2plt.PlotRigidBody(G2frame,'Vector',AtInfo,rbData,plotDefaults)
1882           
1883            nameSizer = wx.BoxSizer(wx.HORIZONTAL)
1884            nameSizer.Add(wx.StaticText(VectorRBDisplay,-1,'Rigid body name: '),
1885                0,wx.ALIGN_CENTER_VERTICAL)
1886            RBname = wx.TextCtrl(VectorRBDisplay,-1,rbData['RBname'])
1887            Indx[RBname.GetId()] = rbId
1888            RBname.Bind(wx.EVT_TEXT_ENTER,OnRBName)
1889            RBname.Bind(wx.EVT_KILL_FOCUS,OnRBName)
1890            nameSizer.Add(RBname,0,wx.ALIGN_CENTER_VERTICAL)
1891            nameSizer.Add((5,0),)
1892            plotRB = wx.CheckBox(VectorRBDisplay,-1,'Plot?')
1893            Indx[plotRB.GetId()] = rbId
1894            plotRB.Bind(wx.EVT_CHECKBOX,OnPlotRB)
1895            nameSizer.Add(plotRB,0,wx.ALIGN_CENTER_VERTICAL)
1896            nameSizer.Add((5,0),)
1897            if not rbData['useCount']:
1898                delRB = wx.CheckBox(VectorRBDisplay,-1,'Delete?')
1899                Indx[delRB.GetId()] = rbId
1900                delRB.Bind(wx.EVT_CHECKBOX,OnDelRB)
1901                nameSizer.Add(delRB,0,wx.ALIGN_CENTER_VERTICAL)
1902            return nameSizer
1903           
1904        def rbRefAtmSizer(rbId,rbData):
1905           
1906            def OnRefSel(event):
1907                Obj = event.GetEventObject()
1908                iref = Indx[Obj.GetId()]
1909                sel = Obj.GetValue()
1910                rbData['rbRef'][iref] = atNames.index(sel)
1911                FillRefChoice(rbId,rbData)
1912           
1913            refAtmSizer = wx.BoxSizer(wx.HORIZONTAL)
1914            atNames = [name+str(i) for i,name in enumerate(rbData['rbTypes'])]
1915            rbRef = rbData.get('rbRef',[0,1,2,False])
1916            rbData['rbRef'] = rbRef
1917            if rbData['useCount']:
1918                refAtmSizer.Add(wx.StaticText(VectorRBDisplay,-1,
1919                    'Orientation reference atoms A-B-C: %s, %s, %s'%(atNames[rbRef[0]], \
1920                     atNames[rbRef[1]],atNames[rbRef[2]])),0)
1921            else:
1922                refAtmSizer.Add(wx.StaticText(VectorRBDisplay,-1,
1923                    'Orientation reference atoms A-B-C: '),0,wx.ALIGN_CENTER_VERTICAL)
1924                for i in range(3):
1925                    choices = [atNames[j] for j in refChoice[rbId][i]]
1926                    refSel = wx.ComboBox(VectorRBDisplay,-1,value='',
1927                        choices=choices,style=wx.CB_READONLY|wx.CB_DROPDOWN)
1928                    refSel.SetValue(atNames[rbRef[i]])
1929                    refSel.Bind(wx.EVT_COMBOBOX, OnRefSel)
1930                    Indx[refSel.GetId()] = i
1931                    refAtmSizer.Add(refSel,0,wx.ALIGN_CENTER_VERTICAL)
1932            return refAtmSizer
1933                       
1934        def rbVectMag(rbId,imag,rbData):
1935           
1936            def OnRBVectorMag(event):
1937                event.Skip()
1938                Obj = event.GetEventObject()
1939                rbId,imag = Indx[Obj.GetId()]
1940                try:
1941                    val = float(Obj.GetValue())
1942                    if val <= 0.:
1943                        raise ValueError
1944                    rbData['VectMag'][imag] = val
1945                except ValueError:
1946                    pass
1947                Obj.SetValue('%8.4f'%(val))
1948                wx.CallAfter(UpdateVectorRB,VectorRB.GetScrollPos(wx.VERTICAL))
1949                G2plt.PlotRigidBody(G2frame,'Vector',AtInfo,data['Vector'][rbId],plotDefaults)
1950               
1951            def OnRBVectorRef(event):
1952                Obj = event.GetEventObject()
1953                rbId,imag = Indx[Obj.GetId()]
1954                rbData['VectRef'][imag] = Obj.GetValue()
1955                       
1956            magSizer = wx.BoxSizer(wx.HORIZONTAL)
1957            magSizer.Add(wx.StaticText(VectorRBDisplay,-1,'Translation magnitude: '),
1958                0,wx.ALIGN_CENTER_VERTICAL)
1959            magValue = wx.TextCtrl(VectorRBDisplay,-1,'%8.4f'%(rbData['VectMag'][imag]))
1960            Indx[magValue.GetId()] = [rbId,imag]
1961            magValue.Bind(wx.EVT_TEXT_ENTER,OnRBVectorMag)
1962            magValue.Bind(wx.EVT_KILL_FOCUS,OnRBVectorMag)
1963            magSizer.Add(magValue,0,wx.ALIGN_CENTER_VERTICAL)
1964            magSizer.Add((5,0),)
1965            magref = wx.CheckBox(VectorRBDisplay,-1,label=' Refine?') 
1966            magref.SetValue(rbData['VectRef'][imag])
1967            magref.Bind(wx.EVT_CHECKBOX,OnRBVectorRef)
1968            Indx[magref.GetId()] = [rbId,imag]
1969            magSizer.Add(magref,0,wx.ALIGN_CENTER_VERTICAL)
1970            return magSizer
1971           
1972        def rbVectors(rbId,imag,mag,XYZ,rbData):
1973
1974            def TypeSelect(event):
1975                AtInfo = data['Vector']['AtInfo']
1976                r,c = event.GetRow(),event.GetCol()
1977                if vecGrid.GetColLabelValue(c) == 'Type':
1978                    PE = G2elemGUI.PickElement(G2frame,oneOnly=True)
1979                    if PE.ShowModal() == wx.ID_OK:
1980                        if PE.Elem != 'None':
1981                            El = PE.Elem.strip().lower().capitalize()
1982                            if El not in AtInfo:
1983                                Info = G2elem.GetAtomInfo(El)
1984                                AtInfo[El] = [Info['Drad'],Info['Color']]
1985                            rbData['rbTypes'][r] = El
1986                            vecGrid.SetCellValue(r,c,El)
1987                    PE.Destroy()
1988                wx.CallAfter(UpdateVectorRB,VectorRB.GetScrollPos(wx.VERTICAL))
1989
1990            def ChangeCell(event):
1991                r,c =  event.GetRow(),event.GetCol()
1992                if r >= 0 and (0 <= c < 3):
1993                    try:
1994                        val = float(vecGrid.GetCellValue(r,c))
1995                        rbData['rbVect'][imag][r][c] = val
1996                    except ValueError:
1997                        pass
1998                G2plt.PlotRigidBody(G2frame,'Vector',AtInfo,data['Vector'][rbId],plotDefaults)
1999                wx.CallAfter(UpdateVectorRB,VectorRB.GetScrollPos(wx.VERTICAL))
2000
2001            vecSizer = wx.BoxSizer()
2002            Types = 3*[wg.GRID_VALUE_FLOAT+':10,5',]+[wg.GRID_VALUE_STRING,]+3*[wg.GRID_VALUE_FLOAT+':10,5',]
2003            colLabels = ['Vector x','Vector y','Vector z','Type','Cart x','Cart y','Cart z']
2004            table = []
2005            rowLabels = []
2006            for ivec,xyz in enumerate(rbData['rbVect'][imag]):
2007                table.append(list(xyz)+[rbData['rbTypes'][ivec],]+list(XYZ[ivec]))
2008                rowLabels.append(str(ivec))
2009            vecTable = G2G.Table(table,rowLabels=rowLabels,colLabels=colLabels,types=Types)
2010            vecGrid = G2G.GSGrid(VectorRBDisplay)
2011            vecGrid.SetTable(vecTable, True)
2012            if 'phoenix' in wx.version():
2013                vecGrid.Bind(wg.EVT_GRID_CELL_CHANGED, ChangeCell)
2014            else:
2015                vecGrid.Bind(wg.EVT_GRID_CELL_CHANGE, ChangeCell)
2016            if not imag:
2017                vecGrid.Bind(wg.EVT_GRID_CELL_LEFT_DCLICK, TypeSelect)
2018            attr = wx.grid.GridCellAttr()
2019            attr.SetEditor(G2G.GridFractionEditor(vecGrid))
2020            for c in range(3):
2021                vecGrid.SetColAttr(c, attr)
2022            for row in range(vecTable.GetNumberRows()):
2023                if imag:
2024                    vecGrid.SetCellStyle(row,3,VERY_LIGHT_GREY,True)                   
2025                for col in [4,5,6]:
2026                    vecGrid.SetCellStyle(row,col,VERY_LIGHT_GREY,True)
2027#            vecGrid.SetScrollRate(0,0)
2028            vecGrid.AutoSizeColumns(False)
2029            vecSizer.Add(vecGrid)
2030            return vecSizer
2031       
2032        def FillRefChoice(rbId,rbData):
2033            choiceIds = [i for i in range(len(rbData['rbTypes']))]
2034           
2035            rbRef = rbData.get('rbRef',[-1,-1,-1,False])
2036            for i in range(3):
2037                choiceIds.remove(rbRef[i])
2038            refChoice[rbId] = [choiceIds[:],choiceIds[:],choiceIds[:]]
2039            for i in range(3):
2040                refChoice[rbId][i].append(rbRef[i])
2041                refChoice[rbId][i].sort()     
2042           
2043        if VectorRB.GetSizer(): VectorRB.GetSizer().Clear(True)
2044        VectorRBSizer = wx.BoxSizer(wx.VERTICAL)
2045        for rbId in data['RBIds']['Vector']:
2046            if rbId != 'AtInfo':
2047                rbData = data['Vector'][rbId]
2048                FillRefChoice(rbId,rbData)
2049                VectorRBSizer.Add(rbNameSizer(rbId,rbData),0)
2050                VectorRBSizer.Add(rbRefAtmSizer(rbId,rbData),0)
2051                XYZ = np.array([[0.,0.,0.] for Ty in rbData['rbTypes']])
2052                for imag,mag in enumerate(rbData['VectMag']):
2053                    XYZ += mag*rbData['rbVect'][imag]
2054                    VectorRBSizer.Add(rbVectMag(rbId,imag,rbData),0)
2055                    VectorRBSizer.Add(rbVectors(rbId,imag,mag,XYZ,rbData),0)
2056                VectorRBSizer.Add((5,5),0)
2057                data['Vector'][rbId]['rbXYZ'] = XYZ       
2058        VectorRBSizer.Layout()   
2059        VectorRBDisplay.SetSizer(VectorRBSizer,True)
2060        Size = VectorRBSizer.GetMinSize()
2061        Size[0] += 40
2062        Size[1] = max(Size[1],450) + 20
2063        VectorRBDisplay.SetSize(Size)
2064        VectorRB.SetScrollbars(10,10,Size[0]/10-4,Size[1]/10-1)
2065        VectorRB.Scroll(0,Scroll)
2066       
2067    def UpdateResidueRB():
2068        '''Draw the contents of the Residue Rigid Body tab for Rigid Bodies tree entry
2069        '''
2070        global rbId
2071        def rbNameSizer(rbId,rbData):
2072
2073            def OnDelRB(event):
2074                Obj = event.GetEventObject()
2075                rbId = Indx[Obj.GetId()]
2076                if rbId in data['Residue']: 
2077                    del data['Residue'][rbId]
2078                    data['RBIds']['Residue'].remove(rbId)
2079                wx.CallAfter(UpdateResidueRB)
2080               
2081            def OnStripH(event):
2082                Obj = event.GetEventObject()
2083                rbId = Indx[Obj.GetId()]
2084                if rbId in data['Residue']:
2085                    newNames = []
2086                    newTypes = []
2087                    newXYZ = []
2088                    for i,atype in enumerate(rbData['rbTypes']):
2089                        if atype != 'H':
2090                            newNames.append(rbData['atNames'][i])
2091                            newTypes.append(rbData['rbTypes'][i])
2092                            newXYZ.append(rbData['rbXYZ'][i])
2093                    rbData['atNames'] = newNames
2094                    rbData['rbTypes'] = newTypes
2095                    rbData['rbXYZ'] = newXYZ
2096                G2plt.PlotRigidBody(G2frame,'Residue',AtInfo,rbData,plotDefaults)
2097                wx.CallAfter(UpdateResidueRB)
2098                   
2099            def OnPlotRB(event):
2100                Obj = event.GetEventObject()
2101                Obj.SetValue(False)
2102                G2plt.PlotRigidBody(G2frame,'Residue',AtInfo,rbData,plotDefaults)
2103               
2104            # start of rbNameSizer
2105            nameSizer = wx.BoxSizer(wx.HORIZONTAL)
2106            nameSizer.Add(wx.StaticText(ResidueRBDisplay,-1,'Residue name: '),
2107                0,wx.ALIGN_CENTER_VERTICAL)
2108            nameSizer.Add(G2G.ValidatedTxtCtrl(ResidueRBDisplay,rbData,'RBname'),0,WACV)
2109            nameSizer.Add((5,0),)
2110            plotRB = wx.CheckBox(ResidueRBDisplay,-1,'Plot?')
2111            Indx[plotRB.GetId()] = rbId
2112            plotRB.Bind(wx.EVT_CHECKBOX,OnPlotRB)
2113            nameSizer.Add(plotRB,0,wx.ALIGN_CENTER_VERTICAL)
2114            nameSizer.Add((5,0),)
2115            if not rbData['useCount']:
2116                delRB = wx.CheckBox(ResidueRBDisplay,-1,'Delete?')
2117                Indx[delRB.GetId()] = rbId
2118                delRB.Bind(wx.EVT_CHECKBOX,OnDelRB)
2119                nameSizer.Add(delRB,0,wx.ALIGN_CENTER_VERTICAL)
2120                if 'H'  in rbData['rbTypes']:
2121                    stripH = wx.CheckBox(ResidueRBDisplay,-1,'Strip H-atoms?')
2122                    Indx[stripH.GetId()] = rbId
2123                    stripH.Bind(wx.EVT_CHECKBOX,OnStripH)
2124                    nameSizer.Add(stripH,0,wx.ALIGN_CENTER_VERTICAL)
2125            return nameSizer
2126           
2127        def rbResidues(rbId,rbData):
2128           
2129            def TypeSelect(event):
2130                AtInfo = data['Residue']['AtInfo']
2131                r,c = event.GetRow(),event.GetCol()
2132                if vecGrid.GetColLabelValue(c) == 'Type':
2133                    PE = G2elemGUI.PickElement(G2frame,oneOnly=True)
2134                    if PE.ShowModal() == wx.ID_OK:
2135                        if PE.Elem != 'None':
2136                            El = PE.Elem.strip().lower().capitalize()
2137                            if El not in AtInfo:
2138                                Info = G2elem.GetAtomInfo(El)
2139                                AtInfo[El] = [Info['Drad']['Color']]
2140                            rbData['rbTypes'][r] = El
2141                            vecGrid.SetCellValue(r,c,El)
2142                    PE.Destroy()
2143
2144            def ChangeCell(event):
2145                r,c =  event.GetRow(),event.GetCol()
2146                if r >= 0 and (0 <= c < 3):
2147                    try:
2148                        val = float(vecGrid.GetCellValue(r,c))
2149                        rbData['rbXYZ'][r][c] = val
2150                    except ValueError:
2151                        pass
2152                       
2153            def RowSelect(event):
2154                r,c =  event.GetRow(),event.GetCol()
2155                if c < 0:                   #only row clicks
2156                    for vecgrid in resList:
2157                        vecgrid.ClearSelection()
2158                    vecGrid.SelectRow(r,True)
2159
2160            def OnRefSel(event):
2161               
2162                Obj = event.GetEventObject()
2163                iref,res,jref = Indx[Obj.GetId()]
2164                sel = Obj.GetValue()
2165                ind = atNames.index(sel)
2166                if rbData['rbTypes'][ind] == 'H':
2167                    G2G.G2MessageBox(G2frame,'You should not select an H-atom for rigid body orientation')
2168                rbData['rbRef'][iref] = ind
2169                FillRefChoice(rbId,rbData)
2170                for i,ref in enumerate(RefObjs[jref]):
2171                    ref.SetItems([atNames[j] for j in refChoice[rbId][i]])
2172                    ref.SetValue(atNames[rbData['rbRef'][i]])                   
2173                rbXYZ = rbData['rbXYZ']
2174                if not iref:     #origin change
2175                    rbXYZ -= rbXYZ[ind]
2176                #TODO - transform all atom XYZ by axis choices
2177                Xxyz = rbXYZ[rbData['rbRef'][1]]
2178                X = Xxyz/np.sqrt(np.sum(Xxyz**2))
2179                Yxyz = rbXYZ[rbData['rbRef'][2]]
2180                Y = Yxyz/np.sqrt(np.sum(Yxyz**2))
2181                Mat = G2mth.getRBTransMat(X,Y)
2182                rbXYZ = np.inner(Mat,rbXYZ).T
2183                rbData['rbXYZ'] = rbXYZ
2184                res.ClearSelection()
2185                resTable = res.GetTable()
2186                for r in range(res.GetNumberRows()):
2187                    row = resTable.GetRowValues(r)
2188                    row[2:4] = rbXYZ[r]
2189                    resTable.SetRowValues(r,row)
2190                res.ForceRefresh()
2191                G2plt.PlotRigidBody(G2frame,'Residue',AtInfo,rbData,plotDefaults)
2192               
2193            Types = 2*[wg.GRID_VALUE_STRING,]+3*[wg.GRID_VALUE_FLOAT+':10,5',]
2194            colLabels = ['Name','Type','Cart x','Cart y','Cart z']
2195            table = []
2196            rowLabels = []
2197            for ivec,xyz in enumerate(rbData['rbXYZ']):
2198                table.append([rbData['atNames'][ivec],]+[rbData['rbTypes'][ivec],]+list(xyz))
2199                rowLabels.append(str(ivec))
2200            vecTable = G2G.Table(table,rowLabels=rowLabels,colLabels=colLabels,types=Types)
2201            vecGrid = G2G.GSGrid(ResidueRBDisplay)
2202            Indx[vecGrid.GetId()] = rbId
2203            resList.append(vecGrid)
2204            vecGrid.SetTable(vecTable, True)
2205            if 'phoenix' in wx.version():
2206                vecGrid.Bind(wg.EVT_GRID_CELL_CHANGED, ChangeCell)
2207            else:
2208                vecGrid.Bind(wg.EVT_GRID_CELL_CHANGE, ChangeCell)
2209            vecGrid.Bind(wg.EVT_GRID_CELL_LEFT_DCLICK, TypeSelect)
2210            vecGrid.Bind(wg.EVT_GRID_LABEL_LEFT_CLICK, RowSelect)
2211            attr = wx.grid.GridCellAttr()
2212            attr.SetEditor(G2G.GridFractionEditor(vecGrid))
2213            for c in range(3):
2214                vecGrid.SetColAttr(c, attr)
2215            for row in range(vecTable.GetNumberRows()):
2216                for col in range(5):
2217                    vecGrid.SetCellStyle(row,col,VERY_LIGHT_GREY,True)
2218            vecGrid.AutoSizeColumns(False)
2219            vecSizer = wx.BoxSizer()
2220            vecSizer.Add(vecGrid)
2221           
2222            refAtmSizer = wx.BoxSizer(wx.HORIZONTAL)
2223            atNames = rbData['atNames']
2224            rbRef = rbData['rbRef']
2225            if rbData['rbRef'][3] or rbData['useCount']:
2226                refAtmSizer.Add(wx.StaticText(ResidueRBDisplay,-1,
2227                    'Orientation reference non-H atoms A-B-C: %s, %s, %s'%(atNames[rbRef[0]], \
2228                     atNames[rbRef[1]],atNames[rbRef[2]])),0)
2229            else:
2230                refAtmSizer.Add(wx.StaticText(ResidueRBDisplay,-1,
2231                    'Orientation reference non-H atoms A-B-C: '),0,wx.ALIGN_CENTER_VERTICAL)
2232                refObj = [0,0,0]
2233                for i in range(3):
2234                    choices = [atNames[j] for j in refChoice[rbId][i]]
2235                    refSel = wx.ComboBox(ResidueRBDisplay,-1,value='',
2236                        choices=choices,style=wx.CB_READONLY|wx.CB_DROPDOWN)
2237                    refSel.SetValue(atNames[rbRef[i]])
2238                    refSel.Bind(wx.EVT_COMBOBOX, OnRefSel)
2239                    Indx[refSel.GetId()] = [i,vecGrid,len(RefObjs)]
2240                    refObj[i] = refSel
2241                    refAtmSizer.Add(refSel,0,wx.ALIGN_CENTER_VERTICAL)
2242                RefObjs.append(refObj)
2243           
2244            mainSizer = wx.BoxSizer(wx.VERTICAL)
2245            mainSizer.Add(refAtmSizer)
2246            mainSizer.Add(vecSizer)
2247            return mainSizer
2248           
2249        def SeqSizer(angSlide,rbId,iSeq,Seq,atNames):
2250           
2251            def ChangeAngle(event):
2252                event.Skip()
2253                Obj = event.GetEventObject()
2254                rbId,Seq = Indx[Obj.GetId()][:2]
2255                val = Seq[2]
2256                try:
2257                    val = float(Obj.GetValue())
2258                    Seq[2] = val
2259                except ValueError:
2260                    pass
2261                Obj.SetValue('%8.2f'%(val))
2262                G2plt.PlotRigidBody(G2frame,'Residue',AtInfo,data['Residue'][rbId],plotDefaults)
2263               
2264            def OnRadBtn(event):
2265                Obj = event.GetEventObject()
2266                Seq,iSeq,angId = Indx[Obj.GetId()]
2267                data['Residue'][rbId]['SelSeq'] = [iSeq,angId]
2268                angSlide.SetValue(int(100*Seq[2]))
2269               
2270            def OnDelBtn(event):
2271                Obj = event.GetEventObject()
2272                rbId,Seq = Indx[Obj.GetId()]
2273                data['Residue'][rbId]['rbSeq'].remove(Seq)       
2274                wx.CallAfter(UpdateResidueRB)
2275           
2276            seqSizer = wx.FlexGridSizer(0,5,2,2)
2277            seqSizer.AddGrowableCol(3,0)
2278            iBeg,iFin,angle,iMove = Seq
2279            ang = wx.TextCtrl(ResidueRBDisplay,-1,'%8.2f'%(angle),size=(50,20))
2280            if not iSeq:
2281                radBt = wx.RadioButton(ResidueRBDisplay,-1,'',style=wx.RB_GROUP)
2282                data['Residue'][rbId]['SelSeq'] = [iSeq,ang.GetId()]
2283            else:
2284                radBt = wx.RadioButton(ResidueRBDisplay,-1,'')
2285            radBt.Bind(wx.EVT_RADIOBUTTON,OnRadBtn)                   
2286            seqSizer.Add(radBt)
2287            delBt = wx.RadioButton(ResidueRBDisplay,-1,'')
2288            delBt.Bind(wx.EVT_RADIOBUTTON,OnDelBtn)
2289            seqSizer.Add(delBt)
2290            bond = wx.TextCtrl(ResidueRBDisplay,-1,'%s %s'%(atNames[iBeg],atNames[iFin]),size=(50,20))
2291            seqSizer.Add(bond,0,wx.ALIGN_CENTER_VERTICAL)
2292            Indx[radBt.GetId()] = [Seq,iSeq,ang.GetId()]
2293            Indx[delBt.GetId()] = [rbId,Seq]
2294            Indx[ang.GetId()] = [rbId,Seq,ang]
2295            ang.Bind(wx.EVT_TEXT_ENTER,ChangeAngle)
2296            ang.Bind(wx.EVT_KILL_FOCUS,ChangeAngle)
2297            seqSizer.Add(ang,0,wx.ALIGN_CENTER_VERTICAL)
2298            atms = ''
2299            for i in iMove:   
2300                atms += ' %s,'%(atNames[i])
2301            moves = wx.TextCtrl(ResidueRBDisplay,-1,atms[:-1],size=(200,20))
2302            seqSizer.Add(moves,1,wx.ALIGN_CENTER_VERTICAL|wx.EXPAND|wx.RIGHT)
2303            return seqSizer
2304           
2305        def SlideSizer():
2306           
2307            def OnSlider(event):
2308                Obj = event.GetEventObject()
2309                rbData = Indx[Obj.GetId()]
2310                iSeq,angId = rbData['SelSeq']
2311                val = float(Obj.GetValue())/100.
2312                rbData['rbSeq'][iSeq][2] = val
2313                Indx[angId][2].SetValue('%8.2f'%(val))
2314                G2plt.PlotRigidBody(G2frame,'Residue',AtInfo,rbData,plotDefaults)
2315           
2316            slideSizer = wx.BoxSizer(wx.HORIZONTAL)
2317            slideSizer.Add(wx.StaticText(ResidueRBDisplay,-1,'Selected torsion angle:'),0)
2318            iSeq,angId = rbData['SelSeq']
2319            angSlide = wx.Slider(ResidueRBDisplay,-1,
2320                int(100*rbData['rbSeq'][iSeq][2]),0,36000,size=(200,20),
2321                style=wx.SL_HORIZONTAL)
2322            angSlide.Bind(wx.EVT_SLIDER, OnSlider)
2323            Indx[angSlide.GetId()] = rbData
2324            slideSizer.Add(angSlide,0)           
2325            return slideSizer,angSlide
2326           
2327        def FillRefChoice(rbId,rbData):
2328            choiceIds = [i for i in range(len(rbData['atNames']))]
2329            for seq in rbData['rbSeq']:
2330                for i in seq[3]:
2331                    try:
2332                        choiceIds.remove(i)
2333                    except ValueError:
2334                        pass
2335            rbRef = rbData['rbRef']
2336            for i in range(3):
2337                try:
2338                    choiceIds.remove(rbRef[i])
2339                except ValueError:
2340                    pass
2341            refChoice[rbId] = [choiceIds[:],choiceIds[:],choiceIds[:]]
2342            for i in range(3):
2343                refChoice[rbId][i].append(rbRef[i])
2344                refChoice[rbId][i].sort()
2345               
2346        def OnSelect(event):
2347            global rbId
2348            rbname = rbchoice[select.GetSelection()]
2349            rbId = RBnames[rbname]
2350            wx.CallLater(100,UpdateResidueRB)
2351           
2352        #----- beginning of UpdateResidueRB -----------------------------------------------
2353        AtInfo = data['Residue']['AtInfo']
2354        refChoice = {}
2355        RefObjs = []
2356
2357        GS = ResidueRBDisplay.GetSizer()
2358        if GS: 
2359            try:        #get around a c++ error in wx 4.0; doing is again seems to be OK
2360                GS.Clear(True)
2361            except:
2362                GS.Clear(True)
2363       
2364        RBnames = {}
2365        for rbid in data['RBIds']['Residue']:
2366            RBnames.update({data['Residue'][rbid]['RBname']:rbid,})
2367        if not RBnames:
2368            return
2369        rbchoice = list(RBnames.keys())
2370        if GS: 
2371            ResidueRBSizer = GS
2372        else:
2373            ResidueRBSizer = wx.BoxSizer(wx.VERTICAL)
2374        if len(RBnames) > 1:
2375            selSizer = wx.BoxSizer(wx.HORIZONTAL)
2376            selSizer.Add(wx.StaticText(ResidueRBDisplay,label=' Select residue to view:'),0)
2377            rbchoice.sort()
2378            select = wx.ComboBox(ResidueRBDisplay,choices=rbchoice)
2379            select.Bind(wx.EVT_COMBOBOX,OnSelect)
2380            selSizer.Add(select,0)
2381            ResidueRBSizer.Add(selSizer,0)
2382        try:
2383            if not rbId:
2384                rbId = RBnames[rbchoice[0]]
2385        except NameError:
2386            rbId = RBnames[rbchoice[0]]
2387        rbData = data['Residue'][rbId]
2388        FillRefChoice(rbId,rbData)
2389        ResidueRBSizer.Add(rbNameSizer(rbId,rbData),0)
2390        ResidueRBSizer.Add(rbResidues(rbId,rbData),0)
2391        ResidueRBSizer.Add((5,5),0)
2392        if rbData['rbSeq']:
2393            slideSizer,angSlide = SlideSizer()
2394        if len(rbData['rbSeq']):
2395            ResidueRBSizer.Add(wx.StaticText(ResidueRBDisplay,-1,
2396                'Sel  Del  Bond             Angle      Riding atoms'),
2397                0,wx.ALIGN_CENTER_VERTICAL)                       
2398        for iSeq,Seq in enumerate(rbData['rbSeq']):
2399            ResidueRBSizer.Add(SeqSizer(angSlide,rbId,iSeq,Seq,rbData['atNames']))
2400        if rbData['rbSeq']:
2401            ResidueRBSizer.Add(slideSizer,)
2402
2403        ResidueRBSizer.Add((5,25),)
2404        ResidueRBSizer.Layout()   
2405        ResidueRBDisplay.SetSizer(ResidueRBSizer,True)
2406        ResidueRBDisplay.SetAutoLayout(True)
2407        Size = ResidueRBSizer.GetMinSize()
2408        ResidueRBDisplay.SetSize(Size)
2409       
2410        Size[0] += 40
2411        Size[1] = max(Size[1],450) + 20
2412        ResidueRB.SetSize(Size)
2413        ResidueRB.SetScrollbars(10,10,Size[0]/10-4,Size[1]/10-1)
2414        G2frame.dataWindow.SendSizeEvent()
2415       
2416        ResidueRBDisplay.Show()
2417       
2418    def SetStatusLine(text):
2419        G2frame.GetStatusBar().SetStatusText(text,1)                                     
2420
2421    G2gd.SetDataMenuBar(G2frame,G2frame.dataWindow.RigidBodyMenu)
2422    SetStatusLine('')
2423    UpdateVectorRB()
2424    G2frame.rbBook.Bind(wx.aui.EVT_AUINOTEBOOK_PAGE_CHANGED, OnPageChanged)
2425    wx.CallAfter(OnPageChanged,None)
2426
2427def ShowIsoDistortCalc(G2frame,phase=None):
2428    '''Compute the ISODISTORT mode values from the current coordinates.
2429    Called in response to the (Phase/Atoms tab) AtomCompute or
2430    Constraints/Edit Constr. "Show ISODISTORT modes" menu item, which
2431    should be enabled only when Phase['ISODISTORT'] is defined.
2432    '''
2433    def _onClose(event):
2434        dlg.EndModal(wx.ID_CANCEL)
2435    def fmtHelp(item,fullname):
2436        helptext = "A new variable"
2437        if item[-3]:
2438            helptext += " named "+str(item[-3])
2439        helptext += " is a linear combination of the following parameters:\n"
2440        first = True
2441        for term in item[:-3]:
2442            line = ''
2443            var = str(term[1])
2444            m = term[0]
2445            if first:
2446                first = False
2447                line += ' = '
2448            else:
2449                if m >= 0:
2450                    line += ' + '
2451                else:
2452                    line += ' - '
2453                m = abs(m)
2454            line += '%.3f*%s '%(m,var)
2455            varMean = G2obj.fmtVarDescr(var)
2456            helptext += "\n" + line + " ("+ varMean + ")"
2457        helptext += '\n\nISODISTORT full name: '+str(fullname)
2458        return helptext
2459
2460    Histograms,Phases = G2frame.GetUsedHistogramsAndPhasesfromTree() # init for constraint
2461    isophases = [p for p in Phases if 'ISODISTORT' in Phases[p]]
2462   
2463    if not isophases:
2464        G2frame.ErrorDialog('no ISODISTORT phases',
2465                            'Unexpected error: no ISODISTORT phases')
2466        return
2467    if phase and phase not in isophases:
2468        G2frame.ErrorDialog('not ISODISTORT phase',
2469                            'Unexpected error: selected phase is not an ISODISTORT phase')
2470        print('Unexpected error: selected phase is not an ISODISTORT phase',
2471                  phase,isophases)
2472    elif not phase and len(isophases) == 1:
2473        phase = isophases[0]
2474    elif not phase:
2475        dlg = wx.SingleChoiceDialog(G2frame,'Select phase from ISODISTORT phases',
2476                                        'Select Phase',isophases)
2477        if dlg.ShowModal() == wx.ID_OK:
2478            sel = dlg.GetSelection()
2479            phase = isophases[sel]
2480        else:
2481            return
2482    # if len(data.get('Histograms',[])) == 0:
2483    #     G2frame.ErrorDialog(
2484    #         'No data',
2485    #         'Sorry, this computation requires that a histogram first be added to the phase'
2486    #         )
2487    #     return
2488   
2489    covdata = G2frame.GPXtree.GetItemPyData(
2490        G2gd.GetGPXtreeItemId(G2frame,G2frame.root,'Covariance'))
2491    # make a lookup table for named NewVar Phase constraints
2492    sub = G2gd.GetGPXtreeItemId(G2frame,G2frame.root,'Constraints') 
2493    Constraints = G2frame.GPXtree.GetItemPyData(sub)
2494    constDict = {}
2495    for c in Constraints['Phase']:
2496        if c[-1] != 'f' or not c[-3]: continue
2497        constDict[str(c[-3])] = c
2498
2499    parmDict,varyList = G2frame.MakeLSParmDict()
2500
2501    dlg = wx.Dialog(G2frame,wx.ID_ANY,'ISODISTORT mode values',#size=(630,400),
2502                       style=wx.DEFAULT_DIALOG_STYLE|wx.RESIZE_BORDER)
2503    mainSizer = wx.BoxSizer(wx.VERTICAL)
2504    if 'ISODISTORT' not in Phases[phase]:
2505        G2frame.ErrorDialog('not ISODISTORT phase',
2506                            'Unexpected error: selected phase is not an ISODISTORT phase')
2507        return
2508    data = Phases[phase]
2509    ISO = data['ISODISTORT']
2510    mainSizer.Add(wx.StaticText(dlg,wx.ID_ANY,
2511                                'ISODISTORT mode computation for cordinates in phase '+
2512                                str(data['General'].get('Name'))))
2513    aSizer = wx.BoxSizer(wx.HORIZONTAL)
2514    panel1 = wxscroll.ScrolledPanel(
2515        dlg, wx.ID_ANY,#size=(100,200),
2516        style = wx.TAB_TRAVERSAL|wx.SUNKEN_BORDER)
2517    subSizer1 = wx.FlexGridSizer(cols=2,hgap=5,vgap=2)
2518    panel2 = wxscroll.ScrolledPanel(
2519        dlg, wx.ID_ANY,#size=(100,200),
2520        style = wx.TAB_TRAVERSAL|wx.SUNKEN_BORDER)
2521    subSizer2 = wx.FlexGridSizer(cols=3,hgap=5,vgap=2)
2522    subSizer1.Add(wx.StaticText(panel1,wx.ID_ANY,'Parameter name  '))
2523    subSizer1.Add(wx.StaticText(panel1,wx.ID_ANY,' value'),0,wx.ALIGN_RIGHT)
2524    subSizer2.Add((-1,-1))
2525    subSizer2.Add(wx.StaticText(panel2,wx.ID_ANY,'Mode name  '))
2526    subSizer2.Add(wx.StaticText(panel2,wx.ID_ANY,' value'),0,wx.ALIGN_RIGHT)
2527    # ISODISTORT displacive modes
2528    if 'G2VarList' in ISO:
2529        deltaList = []
2530        for gv,Ilbl in zip(ISO['G2VarList'],ISO['IsoVarList']):
2531            dvar = gv.varname()
2532            var = dvar.replace('::dA','::A')
2533            albl = Ilbl[:Ilbl.rfind('_')]
2534            v = Ilbl[Ilbl.rfind('_')+1:]
2535            pval = ISO['ParentStructure'][albl][['dx','dy','dz'].index(v)]
2536            if var in parmDict:
2537                cval = parmDict[var][0]
2538            else:
2539                dlg.EndModal(wx.ID_CANCEL)
2540                G2frame.ErrorDialog('Atom not found',"No value found for parameter "+str(var))
2541                return
2542            deltaList.append(cval-pval)
2543        modeVals = np.inner(ISO['Var2ModeMatrix'],deltaList)
2544        for lbl,xyz,var,val,norm,G2mode in zip(
2545                ISO['IsoVarList'],deltaList,
2546                ISO['IsoModeList'],modeVals,ISO['NormList'],ISO['G2ModeList'] ):
2547            #GSASIIpath.IPyBreak()
2548            if str(G2mode) in constDict:
2549                ch = G2G.HelpButton(panel2,fmtHelp(constDict[str(G2mode)],var))
2550                subSizer2.Add(ch,0,wx.LEFT|wx.RIGHT|wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_CENTER,1)
2551            else:
2552                subSizer2.Add((-1,-1))
2553            subSizer1.Add(wx.StaticText(panel1,wx.ID_ANY,str(lbl)))
2554            try:
2555                value = G2py3.FormatSigFigs(xyz)
2556            except TypeError:
2557                value = str(xyz)           
2558            subSizer1.Add(wx.StaticText(panel1,wx.ID_ANY,value),0,wx.ALIGN_RIGHT)
2559            subSizer2.Add(wx.StaticText(panel2,wx.ID_ANY,str(var)))
2560            try:
2561                value = G2py3.FormatSigFigs(val/norm)
2562                if 'varyList' in covdata:
2563                    if str(G2mode) in covdata['varyList']:
2564                        sig = covdata['sig'][covdata['varyList'].index(str(G2mode))]
2565                        value = G2mth.ValEsd(val/norm,sig/norm)
2566            except TypeError:
2567                value = '?'
2568            subSizer2.Add(wx.StaticText(panel2,wx.ID_ANY,value),0,wx.ALIGN_RIGHT)
2569            #GSASIIpath.IPyBreak()
2570    # ISODISTORT occupancy modes
2571    if 'G2OccVarList' in ISO:
2572        deltaList = []
2573        for gv,Ilbl in zip(ISO['G2OccVarList'],ISO['OccVarList']):
2574            var = gv.varname()
2575            albl = Ilbl[:Ilbl.rfind('_')]
2576            pval = ISO['BaseOcc'][albl]
2577            if var in parmDict:
2578                cval = parmDict[var][0]
2579            else:
2580                dlg.EndModal(wx.ID_CANCEL)
2581                G2frame.ErrorDialog('Atom not found',"No value found for parameter "+str(var))
2582                return
2583            deltaList.append(cval-pval)
2584        modeVals = np.inner(ISO['Var2OccMatrix'],deltaList)
2585        for lbl,delocc,var,val,norm,G2mode in zip(
2586                ISO['OccVarList'],deltaList,
2587                ISO['OccModeList'],modeVals,ISO['OccNormList'],ISO['G2OccModeList']):
2588            if str(G2mode) in constDict:
2589                ch = G2G.HelpButton(panel2,fmtHelp(constDict[str(G2mode)],var))
2590                subSizer2.Add(ch,0,wx.LEFT|wx.RIGHT|wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_CENTER,1)
2591            else:
2592                subSizer2.Add((-1,-1))
2593            subSizer1.Add(wx.StaticText(panel1,wx.ID_ANY,str(lbl)))
2594            try:
2595                value = G2py3.FormatSigFigs(delocc)
2596            except TypeError:
2597                value = str(delocc)
2598            subSizer1.Add(wx.StaticText(panel1,wx.ID_ANY,value),0,wx.ALIGN_RIGHT)
2599            #subSizer.Add((10,-1))
2600            subSizer2.Add(wx.StaticText(panel2,wx.ID_ANY,str(var)))
2601            try:
2602                value = G2py3.FormatSigFigs(val/norm)
2603                if 'varyList' in covdata:
2604                    if str(G2mode) in covdata['varyList']:
2605                        sig = covdata['sig'][covdata['varyList'].index(str(G2mode))]
2606                        value = G2mth.ValEsd(val/norm,sig/norm)
2607            except TypeError:
2608                value = '?'
2609            subSizer2.Add(wx.StaticText(panel2,wx.ID_ANY,value),0,wx.ALIGN_RIGHT)
2610
2611    # finish up ScrolledPanel
2612    panel1.SetSizer(subSizer1)
2613    panel2.SetSizer(subSizer2)
2614    panel1.SetAutoLayout(1)
2615    panel1.SetupScrolling()
2616    panel2.SetAutoLayout(1)
2617    panel2.SetupScrolling()
2618    # Allow window to be enlarged but not made smaller
2619    dlg.SetSizer(mainSizer)
2620    w1,l1 = subSizer1.GetSize()
2621    w2,l2 = subSizer2.GetSize()
2622    panel1.SetMinSize((w1+10,200))
2623    panel2.SetMinSize((w2+20,200))
2624    aSizer.Add(panel1,1, wx.ALL|wx.EXPAND,1)
2625    aSizer.Add(panel2,2, wx.ALL|wx.EXPAND,1)
2626    mainSizer.Add(aSizer,1, wx.ALL|wx.EXPAND,1)
2627
2628    # make OK button
2629    btnsizer = wx.BoxSizer(wx.HORIZONTAL)
2630    btn = wx.Button(dlg, wx.ID_CLOSE) 
2631    btn.Bind(wx.EVT_BUTTON,_onClose)
2632    btnsizer.Add(btn)
2633    mainSizer.Add(btnsizer, 0, wx.ALIGN_CENTER|wx.ALL, 5)
2634
2635    mainSizer.Fit(dlg)
2636    dlg.SetMinSize(dlg.GetSize())
2637    dlg.ShowModal()
2638    dlg.Destroy()
Note: See TracBrowser for help on using the repository browser.