source: trunk/GSASIIrestrGUI.py @ 3180

Last change on this file since 3180 was 3137, checked in by vondreele, 8 years ago

replace old CifFile? with new py 2/7/3.6 compliant code
fix cif file import phase & powder file
fix CemComp? restraint editing

  • Property svn:eol-style set to native
  • Property svn:keywords set to Date Author Revision URL Id
File size: 90.0 KB
Line 
1# -*- coding: utf-8 -*-
2#GSASIIrestr - restraint GUI routines
3########### SVN repository information ###################
4# $Date: 2017-10-24 16:53:41 +0000 (Tue, 24 Oct 2017) $
5# $Author: toby $
6# $Revision: 3137 $
7# $URL: trunk/GSASIIrestrGUI.py $
8# $Id: GSASIIrestrGUI.py 3137 2017-10-24 16:53:41Z toby $
9########### SVN repository information ###################
10'''
11*GSASIIrestrGUI: Restraint GUI routines*
12----------------------------------------
13
14Used to define restraints.
15
16'''
17from __future__ import division, print_function
18import wx
19import wx.grid as wg
20import numpy as np
21import numpy.ma as ma
22import os.path
23import GSASIIpath
24GSASIIpath.SetVersionNumber("$Revision: 3137 $")
25import GSASIImath as G2mth
26import GSASIIlattice as G2lat
27import GSASIIspc as G2spc
28import GSASIIdataGUI as G2gd
29import GSASIIplot as G2plt
30import GSASIIdata as G2data
31import GSASIIctrlGUI as G2G
32import GSASIIphsGUI as G2phsGUI
33
34VERY_LIGHT_GREY = wx.Colour(235,235,235)
35TabSelectionIdDict = {}
36
37################################################################################
38#####  Restraints
39################################################################################           
40def GetSelectedRows(widget):
41    '''Returns a list of selected rows. Rows can be selected, blocks of cells
42    or individual cells can be selected. The column for selected cells is ignored.
43    '''
44    rows = widget.GetSelectedRows()
45    if not rows:
46        top = widget.GetSelectionBlockTopLeft()
47        bot = widget.GetSelectionBlockBottomRight()
48        if top and bot:
49            rows = range(top[0][0],bot[0][0]+1)
50    if not rows:
51        rows = sorted(list(set([cell[0] for cell in widget.GetSelectedCells()])))
52    return rows
53       
54def UpdateRestraints(G2frame,data,Phases,phaseName):
55    '''Respond to selection of the Restraints item on the
56    data tree
57    '''
58    global Pages
59    def OnSelectPhase(event):
60        pageName = Pages[G2frame.restrBook.GetSelection()]
61        dlg = wx.SingleChoiceDialog(G2frame,'Select','Phase',list(Phases.keys()))
62        try:
63            if dlg.ShowModal() == wx.ID_OK:
64                phaseName = list(Phases.keys())[dlg.GetSelection()]
65                UpdateRestraints(G2frame,data,Phases,phaseName)
66                newPage = G2frame.restrBook.FindPage(pageName)
67                G2frame.restrBook.SetSelection(newPage)
68        finally:
69            dlg.Destroy()
70   
71    def getMacroFile(macName):
72        defDir = os.path.join(os.path.split(__file__)[0],'GSASIImacros')
73        dlg = wx.FileDialog(G2frame,message='Choose '+macName+' restraint macro file',
74            defaultDir=defDir,defaultFile="",wildcard="GSAS-II macro file (*.mac)|*.mac",
75            style=wx.FD_OPEN | wx.CHANGE_DIR)
76        try:
77            macro = ''
78            if dlg.ShowModal() == wx.ID_OK:
79                macfile = dlg.GetPath()
80                macro = open(macfile,'Ur')
81                head = macro.readline()
82                if macName not in head:
83                    print (head)
84                    print ('**** ERROR - wrong restraint macro file selected, try again ****')
85                    macro = []
86        finally:
87            dlg.Destroy()
88        return macro        #advanced past 1st line
89       
90    def OnPlotAARestraint(event):
91        page = G2frame.restrBook.GetSelection()
92        if 'Torsion' in G2frame.restrBook.GetPageText(page):
93            torNames = []
94            torNames += list(restrData['Torsion']['Coeff'].keys())
95            dlg = wx.SingleChoiceDialog(G2frame,'Select','Torsion data',torNames)
96            try:
97                if dlg.ShowModal() == wx.ID_OK:
98                    torName = torNames[dlg.GetSelection()]
99                    torsion = G2data.torsionDist[torName]
100                    torCoeff = restrData['Torsion']['Coeff'][torName]
101                    torList = restrData['Torsion']['Torsions']
102                    Names = []
103                    Angles = []
104                    for i,[indx,ops,cofName,esd] in enumerate(torList):
105                        if cofName == torName:
106                            atoms = G2mth.GetAtomItemsById(Atoms,AtLookUp,indx,0,4)
107                            name = '('+atoms[2][1]+atoms[2][0].strip()+atoms[2][2]+')'
108                            for atom in atoms:
109                                name += '  '+atom[3]
110                            XYZ = np.array(G2mth.GetAtomItemsById(Atoms,AtLookUp,indx,cx,3))
111                            angle = G2mth.getRestTorsion(XYZ,Amat)
112                            Angles.append(angle)
113                            Names.append(name) 
114                    G2plt.PlotTorsion(G2frame,phaseName,torsion,torName,Names,np.array(Angles),torCoeff)
115            finally:
116                dlg.Destroy()
117           
118        elif 'Rama' in G2frame.restrBook.GetPageText(page):
119            ramaNames = ['All',]
120            ramaNames += list(restrData['Rama']['Coeff'].keys())
121            dlg = wx.SingleChoiceDialog(G2frame,'Select','Ramachandran data',ramaNames)
122            try:
123                if dlg.ShowModal() == wx.ID_OK:
124                    ramaName = ramaNames[dlg.GetSelection()]
125                    rama = G2data.ramachandranDist[ramaName]
126                    ramaCoeff = []
127                    if ramaName != 'All':
128                        ramaCoeff = restrData['Rama']['Coeff'][ramaName]
129                    ramaList = restrData['Rama']['Ramas']
130                    Names = []
131                    PhiPsi = []
132                    for i,[indx,ops,cofName,esd] in enumerate(ramaList):
133                        if cofName == ramaName or (ramaName == 'All' and '-1' in cofName):
134                            atoms = G2mth.GetAtomItemsById(Atoms,AtLookUp,indx,0,4)
135                            name = '('+atoms[3][1]+atoms[3][0].strip()+atoms[3][2]+')'
136                            for atom in atoms:
137                                name += '  '+atom[3]
138                            XYZ = np.array(G2mth.GetAtomItemsById(Atoms,AtLookUp,indx,cx,3))
139                            phi,psi = G2mth.getRestRama(XYZ,Amat)
140                            PhiPsi.append([phi,psi])
141                            Names.append(name) 
142                    G2plt.PlotRama(G2frame,phaseName,rama,ramaName,Names,np.array(PhiPsi),ramaCoeff)
143            finally:
144                dlg.Destroy()
145           
146    def OnAddRestraint(event):
147        page = G2frame.restrBook.GetSelection()
148        if 'Bond' in G2frame.restrBook.GetPageText(page):
149            AddBondRestraint(restrData['Bond'])
150        elif 'Angle' in G2frame.restrBook.GetPageText(page):
151            AddAngleRestraint(restrData['Angle'])
152        elif 'Plane' in G2frame.restrBook.GetPageText(page):
153            AddPlaneRestraint(restrData['Plane'])
154        elif 'Chem' in G2frame.restrBook.GetPageText(page):
155            AddChemCompRestraint(restrData['ChemComp'])
156        elif 'Texture' in G2frame.restrBook.GetPageText(page):
157            AddTextureRestraint(restrData['Texture'])
158           
159    def OnAddAARestraint(event):
160        page = G2frame.restrBook.GetSelection()
161        if 'Bond' in G2frame.restrBook.GetPageText(page):
162            AddAABondRestraint(restrData['Bond'])
163        elif 'Angle' in G2frame.restrBook.GetPageText(page):
164            AddAAAngleRestraint(restrData['Angle'])
165        elif 'Plane' in G2frame.restrBook.GetPageText(page):
166            AddAAPlaneRestraint(restrData['Plane'])
167        elif 'Chiral' in G2frame.restrBook.GetPageText(page):
168            AddAAChiralRestraint(restrData['Chiral'])
169        elif 'Torsion' in G2frame.restrBook.GetPageText(page):
170            AddAATorsionRestraint(restrData['Torsion'])
171        elif 'Rama' in G2frame.restrBook.GetPageText(page):
172            AddAARamaRestraint(restrData['Rama'])
173           
174    def AddBondRestraint(bondRestData):
175        Lists = {'origin':[],'target':[]}
176        for listName in ['origin','target']:
177            dlg = wx.MultiChoiceDialog(G2frame,'Bond restraint '+listName+' for '+General['Name'],
178                    'Select bond restraint '+listName+' atoms',Names)
179            if dlg.ShowModal() == wx.ID_OK:
180                sel = dlg.GetSelections()
181                for x in sel:
182                    if 'all' in Names[x]:
183                        allType = Types[x]
184                        for name,Type,coords,id in zip(Names,Types,Coords,Ids):
185                            if Type == allType and 'all' not in name:
186                                Lists[listName].append([id,Type,coords])
187                    else:
188                        Lists[listName].append([Ids[x],Types[x],Coords[x],])
189            else:
190                break
191        if len(Lists['origin']) and len(Lists['target']):
192            bond = 1.54
193            dlg = G2G.SingleFloatDialog(G2frame,'Distance','Enter restraint distance for bond',bond,[0.01,4.],'%.4f')
194            if dlg.ShowModal() == wx.ID_OK:
195                bond = dlg.GetValue()
196            dlg.Destroy()
197        Factor = bondRestData['Range']
198        indices = (-2,-1,0,1,2)
199        Units = np.array([[h,k,l] for h in indices for k in indices for l in indices])
200        origAtoms = Lists['origin']
201        targAtoms = Lists['target']
202        dlg = wx.ProgressDialog("Generating bond restraints","Processed origin atoms",len(origAtoms), 
203            style = wx.PD_ELAPSED_TIME|wx.PD_AUTO_HIDE|wx.PD_REMAINING_TIME)
204        try:
205            Norig = 0
206            for Oid,Otype,Ocoord in origAtoms:
207                Norig += 1
208                dlg.Update(Norig)
209                for Tid,Ttype,Tcoord in targAtoms:
210                    if 'macro' in General['Type']:
211                        result = [[Tcoord,1,[0,0,0]],]
212                    else:
213                        result = G2spc.GenAtom(Tcoord,SGData,False,Move=False)
214                    for Txyz,Top,Tunit in result:
215                        Dx = (Txyz-np.array(Ocoord))+Units
216                        dx = np.inner(Amat,Dx)
217                        dist = ma.masked_less(np.sqrt(np.sum(dx**2,axis=0)),bond/Factor)
218                        IndB = ma.nonzero(ma.masked_greater(dist,bond*Factor))
219                        if np.any(IndB):
220                            for indb in IndB:
221                                for i in range(len(indb)):
222                                    unit = Units[indb][i]+Tunit
223                                    if np.any(unit):
224                                        Topstr = '%d+%d,%d,%d'%(Top,unit[0],unit[1],unit[2])
225                                    else:
226                                        Topstr = str(Top)
227                                    newBond = [[Oid,Tid],['1',Topstr],bond,0.01]
228                                    if newBond not in bondRestData['Bonds']:
229                                        bondRestData['Bonds'].append(newBond)
230        finally:
231            dlg.Destroy()
232        UpdateBondRestr(bondRestData)               
233
234    def AddAABondRestraint(bondRestData):
235        macro = getMacroFile('bond')
236        if not macro:
237            return
238        macStr = macro.readline()
239        atoms = zip(Names,Coords,Ids)
240       
241        Factor = bondRestData['Range']
242        while macStr:
243            items = macStr.split()
244            if 'F' in items[0]:
245                restrData['Bond']['wtFactor'] = float(items[1])
246            elif 'S' in items[0]:
247                oIds = []
248                oCoords = []
249                tIds = []
250                tCoords = []
251                res = items[1]
252                dist = float(items[2])
253                esd = float(items[3])
254                oAtm,tAtm = items[4:6]
255                for Name,coords,Id in atoms:
256                    names = Name.split()
257                    if res == '*' or res in names[0]:
258                        if oAtm == names[2]:
259                            oIds.append(Id)
260                            oCoords.append(np.array(coords))
261                        if tAtm == names[2]:
262                            tIds.append(Id)
263                            tCoords.append(np.array(coords))
264                for i,[oId,oCoord] in enumerate(zip(oIds,oCoords)):
265                    for tId,tCoord in zip(tIds,tCoords)[i:]:
266                        obsd = np.sqrt(np.sum(np.inner(Amat,tCoord-oCoord)**2))
267                        if dist/Factor < obsd < dist*Factor:
268                            newBond = [[oId,tId],['1','1'],dist,esd]
269                            if newBond not in bondRestData['Bonds']:
270                                bondRestData['Bonds'].append(newBond)                         
271            macStr = macro.readline()
272        macro.close()
273        UpdateBondRestr(bondRestData)               
274           
275    def AddAngleRestraint(angleRestData):
276        Radii = dict(zip(General['AtomTypes'],zip(General['BondRadii'],General['AngleRadii'])))
277        Lists = {'A-atom':[],'B-atom':[],'C-atom':[]}
278        for listName in ['A-atom','B-atom']:
279            dlg = wx.MultiChoiceDialog(G2frame,'Select '+listName+' for angle A-B-C for '+General['Name']                                                                           ,
280                    'Select angle restraint '+listName,Names)
281            if dlg.ShowModal() == wx.ID_OK:
282                sel = dlg.GetSelections()
283                for x in sel:
284                    if 'all' in Names[x]:
285                        allType = Types[x]
286                        for name,Type,coords,id in zip(Names,Types,Coords,Ids):
287                            if Type == allType and 'all' not in name:
288                                if 'A' in listName:
289                                    Lists[listName].append(Type)
290                                else:
291                                    Lists[listName].append([id,Type,coords])
292                    else:
293                        if 'A' in listName:
294                            Lists[listName].append(Types[x])
295                        else:
296                            Lists[listName].append([Ids[x],Types[x],Coords[x],])
297            else:
298                break
299            targAtoms = [[Ids[x+iBeg],Types[x+iBeg],Coords[x+iBeg]] for x in range(len(Names[iBeg:]))]
300        if len(Lists['B-atom']):
301            value = 109.54
302            dlg = G2G.SingleFloatDialog(G2frame,'Angle','Enter restraint angle ',value,[30.,180.],'%.2f')
303            if dlg.ShowModal() == wx.ID_OK:
304                value = dlg.GetValue()
305            dlg.Destroy()
306
307        Factor = angleRestData['Range']
308        indices = (-2,-1,0,1,2)
309        Units = np.array([[h,k,l] for h in indices for k in indices for l in indices])
310        VectA = []
311        for Oid,Otype,Ocoord in Lists['B-atom']:
312            IndBlist = []
313            VectB = []
314            for Tid,Ttype,Tcoord in targAtoms:
315                result = G2spc.GenAtom(Tcoord,SGData,False,Move=False)
316                BsumR = (Radii[Otype][0]+Radii[Ttype][0])*Factor
317                AsumR = (Radii[Otype][1]+Radii[Ttype][1])*Factor
318                for Txyz,Top,Tunit in result:
319                    Dx = (Txyz-Ocoord)+Units
320                    dx = np.inner(Amat,Dx)
321                    dist = ma.masked_less(np.sqrt(np.sum(dx**2,axis=0)),0.5)
322                    IndB = ma.nonzero(ma.masked_greater(dist,BsumR))
323                    if np.any(IndB):
324                        for indb in IndB:
325                            for i in range(len(indb)):
326                                if str(dx.T[indb][i]) not in IndBlist:
327                                    IndBlist.append(str(dx.T[indb][i]))
328                                    unit = Units[indb][i]+Tunit
329                                if np.any(unit):
330                                    Topstr = '%d+%d,%d,%d'%(Top,unit[0],unit[1],unit[2])
331                                else:
332                                    Topstr = str(Top)
333                                Dist = ma.getdata(dist[indb])[i]
334                                if (Dist-AsumR) <= 0.:
335                                    VectB.append([Oid,'1',Ocoord,Ttype,Tid,Topstr,Tcoord,Dist])
336            VectA.append(VectB)
337        for Vects in VectA:
338            for i,vecta in enumerate(Vects):                   
339                for vectb in Vects[:i]:
340                    if vecta[3] in Lists['A-atom']:
341                        ids = [vecta[4],vecta[0],vectb[4]]
342                        ops = [vecta[5],vecta[1],vectb[5]]
343                        angle = [ids,ops,value,1.0]
344                        if angle not in angleRestData['Angles']:
345                            angleRestData['Angles'].append(angle)
346        UpdateAngleRestr(angleRestData)               
347
348    def AddAAAngleRestraint(angleRestData):
349        macro = getMacroFile('angle')
350        if not macro:
351            return
352        atoms = zip(Names,Ids)
353        macStr = macro.readline()
354        while macStr:
355            items = macStr.split()
356            if 'F' in items[0]:
357                restrData['Angle']['wtFactor'] = float(items[1])
358            elif 'S' in items[0]:
359                res = items[1]
360                value = float(items[2])
361                esd = float(items[3])
362                Atms = items[4:7]
363                pAtms = ['','','']
364                for i,atm in enumerate(Atms):
365                    if '+' in atm:
366                        pAtms[i] = atm.strip('+')
367                ids = np.array([0,0,0])
368                rNum = -1
369                for name,id in atoms:
370                    names = name.split()
371                    tNum = int(names[0].split(':')[0])
372                    if res in names[0]:
373                        try:
374                            ipos = Atms.index(names[2])
375                            ids[ipos] = id
376                        except ValueError:
377                            continue
378                    elif res == '*':
379                        try:
380                            ipos = Atms.index(names[2])
381                            if not np.all(ids):
382                                rNum = int(names[0].split(':')[0])
383                            ids[ipos] = id
384                        except ValueError:
385                            try:
386                                if tNum == rNum+1:
387                                    ipos = pAtms.index(names[2])
388                                    ids[ipos] = id
389                            except ValueError:
390                                continue
391                    if np.all(ids):
392                        angle = [list(ids),['1','1','1'],value,esd]
393                        if angle not in angleRestData['Angles']:
394                            angleRestData['Angles'].append(angle)
395                        ids = np.array([0,0,0])
396            macStr = macro.readline()
397        macro.close()
398        UpdateAngleRestr(angleRestData)               
399       
400    def AddPlaneRestraint(restrData):
401        ids = []
402        dlg = wx.MultiChoiceDialog(G2frame,'Select 4 or more atoms for plane in '+General['Name'],
403                'Select 4+ atoms',Names[iBeg:])
404        if dlg.ShowModal() == wx.ID_OK:
405            sel = dlg.GetSelections()
406            if len(sel) > 3:
407                for x in sel:
408                    ids.append(Ids[x+iBeg])
409                ops = ['1' for i in range(len(sel))]
410                plane = [ids,ops,0.0,0.01]
411                if plane not in restrData['Planes']:
412                    restrData['Planes'].append(plane)
413            else:
414                print ('**** ERROR - not enough atoms for a plane restraint - try again ****')
415        UpdatePlaneRestr(restrData)               
416
417    def AddAAPlaneRestraint(planeRestData):
418        macro = getMacroFile('plane')
419        if not macro:
420            return
421        atoms = zip(Names,Ids)
422        macStr = macro.readline()
423        while macStr:
424            items = macStr.split()
425            if 'F' in items[0]:
426                restrData['Plane']['wtFactor'] = float(items[1])
427            elif 'S' in items[0]:
428                res = items[1]
429                esd = float(items[2])
430                Atms = items[3:]
431                pAtms = ['' for i in Atms]
432                for i,atm in enumerate(Atms):
433                    if '+' in atm:
434                        pAtms[i] = atm.strip('+')
435                rNum = -1
436                ids = np.zeros(len(Atms))
437                ops = ['1' for i in range(len(Atms))]
438                for name,id in atoms:
439                    names = name.split()
440                    tNum = int(names[0].split(':')[0])
441                    if res in names[0]:
442                        try:
443                            ipos = Atms.index(names[2])
444                            ids[ipos] = id
445                        except ValueError:
446                            continue
447                    elif res == '*':
448                        try:
449                            ipos = Atms.index(names[2])
450                            if not np.all(ids):
451                                rNum = int(names[0].split(':')[0])
452                            ids[ipos] = id
453                        except ValueError:
454                            try:
455                                if tNum == rNum+1:
456                                    ipos = pAtms.index(names[2])
457                                    ids[ipos] = id
458                            except ValueError:
459                                continue
460                    if np.all(ids):
461                        plane = [list(ids),ops,0.0,esd]
462                        if plane not in planeRestData['Planes']:
463                            planeRestData['Planes'].append(plane)
464                        ids = np.zeros(len(Atms))
465            macStr = macro.readline()
466        macro.close()
467        UpdatePlaneRestr(planeRestData)               
468
469    def AddAAChiralRestraint(chiralRestData):
470        macro = getMacroFile('chiral')
471        if not macro:
472            return
473        atoms = zip(Names,Ids)
474        macStr = macro.readline()
475        while macStr:
476            items = macStr.split()
477            if 'F' in items[0]:
478                restrData['Chiral']['wtFactor'] = float(items[1])
479            elif 'S' in items[0]:
480                res = items[1]
481                value = float(items[2])
482                esd = float(items[3])
483                Atms = items[4:8]
484                ids = np.array([0,0,0,0])
485                for name,id in atoms:
486                    names = name.split()
487                    if res in names[0]:
488                        try:
489                            ipos = Atms.index(names[2])
490                            ids[ipos] = id
491                        except ValueError:
492                            pass
493                        if np.all(ids):
494                            chiral = [list(ids),['1','1','1','1'],value,esd]
495                            if chiral not in chiralRestData['Volumes']:
496                                chiralRestData['Volumes'].append(chiral)
497                            ids = np.array([0,0,0,0])
498            macStr = macro.readline()
499        macro.close()
500        UpdateChiralRestr(chiralRestData)               
501       
502    def makeChains(Names,Ids):
503        Chains = {}
504        atoms = zip(Names,Ids)
505        for name,id in atoms:
506            items = name.split()
507            rnum,res = items[0].split(':')
508            if items[1] not in Chains:
509                Residues = {}
510                Chains[items[1]] = Residues
511            if int(rnum) not in Residues:
512                Residues[int(rnum)] = []
513            Residues[int(rnum)].append([res,items[2],id])
514        return Chains
515       
516    def AddAATorsionRestraint(torsionRestData):
517        macro = getMacroFile('torsion')
518        if not macro:
519            return
520        Chains = makeChains(Names,Ids)           
521        macStr = macro.readline()[:-1]
522        while macStr:
523            items = macStr.split()
524            if 'F' in items[0]:
525                restrData['Torsion']['wtFactor'] = float(items[1])
526            elif 'A' in items[0]:
527                name = items[10]
528                coeff = np.zeros(9)
529                for i,item in enumerate(items[1:10]):
530                    coeff[i] = float(item)
531                torsionRestData['Coeff'][name] = coeff
532            elif 'S' in items[0]:
533                Name = items[1]
534                Res = items[2]
535                Esd = float(items[3])
536                Atms = items[4:8]
537                pAtms = ['','','','']
538                for i,atm in enumerate(Atms):
539                    if '+' in atm:
540                        pAtms[i] = atm.strip('+')
541                ids = np.array([0,0,0,0])
542                chains = list(Chains.keys())
543                chains.sort()
544                for chain in chains:
545                    residues = list(Chains[chain].keys())
546                    residues.sort()
547                    for residue in residues:
548                        if residue == residues[-1] and Res == '*':
549                            continue
550                        if Res != '*':
551                            for res,name,id in Chains[chain][residue]:
552                                if Res == res:
553                                    try:
554                                        ipos = Atms.index(name)
555                                        ids[ipos] = id
556                                    except ValueError:
557                                        continue
558                        else:
559                            try:
560                                for res,name,id in Chains[chain][residue]:
561                                    try:
562                                        ipos = Atms.index(name)
563                                        ids[ipos] = id
564                                    except ValueError:
565                                        continue
566                                for res,name,id in Chains[chain][residue+1]:
567                                    try:
568                                        ipos = pAtms.index(name)
569                                        ids[ipos] = id
570                                    except ValueError:
571                                        continue
572                            except KeyError:
573                                continue
574                        if np.all(ids):
575                            torsion = [list(ids),['1','1','1','1'],Name,Esd]
576                            if torsion not in torsionRestData['Torsions']:
577                                torsionRestData['Torsions'].append(torsion)
578                            ids = np.array([0,0,0,0])                           
579            macStr = macro.readline()
580        macro.close()
581        UpdateTorsionRestr(torsionRestData)                       
582       
583    def AddAARamaRestraint(ramaRestData):
584        macro = getMacroFile('Ramachandran')
585        if not macro:
586            return
587        Chains = makeChains(Names,Ids)           
588        macStr = macro.readline()
589        while macStr:
590            items = macStr.split()
591            if 'F' in items[0]:
592                restrData['Rama']['wtFactor'] = float(items[1])
593            elif 'A' in items[0]:
594                nTerms = int(items[1])
595                name = items[2]
596                coeff = np.zeros((nTerms,6))
597                for i in range(nTerms):
598                    macStr = macro.readline()
599                    items = macStr.split()
600                    for j,val in enumerate(items):
601                        coeff[i][j] = float(val)
602                ramaRestData['Coeff'][name] = coeff
603            elif 'S' in items[0]:
604                Name = items[1]
605                Res = items[2]
606                Esd = float(items[3])
607                Atms = items[4:9]
608                mAtms = ['','','','','']
609                pAtms = ['','','','','']
610                for i,atm in enumerate(Atms):
611                    if '+' in atm:
612                        pAtms[i] = atm.strip('+')
613                    elif '-' in atm:
614                        mAtms[i] = atm.strip('-')
615                ids = np.array([0,0,0,0,0])
616                chains = list(Chains.keys())
617                chains.sort()
618                for chain in chains:
619                    residues = list(Chains[chain].keys())
620                    residues.sort()
621                    if not (any(mAtms) or any(pAtms)):
622                        for residue in residues:
623                            for res,name,id in Chains[chain][residue]:
624                                if Res == res:
625                                    try:
626                                        ipos = Atms.index(name)
627                                        ids[ipos] = id
628                                    except ValueError:
629                                        continue
630                            if np.all(ids):
631                                rama = [list(ids),['1','1','1','1','1'],Name,Esd]
632                                if rama not in ramaRestData['Ramas']:
633                                    ramaRestData['Ramas'].append(rama)
634                                ids = np.array([0,0,0,0,0])
635                    else:
636                        for residue in residues[1:-1]:
637                            try:
638                                for res,name,id in Chains[chain][residue-1]:
639                                    try:
640                                        ipos = mAtms.index(name)
641                                        ids[ipos] = id
642                                    except ValueError:
643                                        continue
644                                for res,name,id in Chains[chain][residue+1]:
645                                    try:
646                                        ipos = pAtms.index(name)
647                                        ids[ipos] = id
648                                    except ValueError:
649                                        continue
650                                for res,name,id in Chains[chain][residue]:
651                                    if Res == res:
652                                        try:
653                                            ipos = Atms.index(name)
654                                            ids[ipos] = id
655                                        except ValueError:
656                                            continue
657                                if np.all(ids):
658                                    rama = [list(ids),['1','1','1','1','1'],Name,Esd]
659                                    if rama not in ramaRestData['Ramas']:
660                                        ramaRestData['Ramas'].append(rama)
661                                    ids = np.array([0,0,0,0,0])
662                            except KeyError:
663                                continue
664            macStr = macro.readline()
665        macro.close()
666        UpdateRamaRestr(ramaRestData)
667       
668    def AddChemCompRestraint(chemcompRestData):
669        ids = []
670        factors = []
671        dlg = wx.MultiChoiceDialog(G2frame,'Select atoms for chemical restraint in '+General['Name'],
672                'Select atoms',Names)
673        if dlg.ShowModal() == wx.ID_OK:
674            sel = dlg.GetSelections()
675            for x in sel:
676                if 'all' in Names[x]:
677                    allType = Types[x]
678                    for name,Type,id in zip(Names,Types,Ids):
679                        if Type == allType and 'all' not in name:
680                            ids.append(id)
681                            factors.append(1.0)
682                else:
683                    ids.append(Ids[x])
684                    factors.append(1.0)
685            dlg.Destroy()
686            if len(ids) > 0:
687                value = 1.0
688                dlg = G2G.SingleFloatDialog(G2frame,'Angle','Enter unit cell sum ',value,[-1.e6,1.e6],'%.2f')
689                if dlg.ShowModal() == wx.ID_OK:
690                    value = dlg.GetValue()               
691                    comp = [ids,factors,value,0.01]
692                    if comp not in chemcompRestData['Sites']:
693                        chemcompRestData['Sites'].append(comp)
694                UpdateChemcompRestr(chemcompRestData)
695            else:
696                print ('**** ERROR - not enough atoms for a composition restraint - try again ****')
697       
698    def AddTextureRestraint(textureRestData):
699        dlg = wx.TextEntryDialog(G2frame,'Enter h k l for pole figure restraint','Enter HKL','')
700        if dlg.ShowModal() == wx.ID_OK:
701            text = dlg.GetValue()
702            vals = text.split()
703            try:
704                hkl = [int(vals[i]) for i in range(3)]
705                texture = [hkl,24,0.01,False,1.0]
706                if texture not in textureRestData['HKLs']:
707                        textureRestData['HKLs'].append(texture)
708                UpdateTextureRestr(textureRestData)
709            except (ValueError,IndexError):
710                print ('**** ERROR - bad input of h k l - try again ****')
711        dlg.Destroy()
712               
713    def WtBox(wind,restData):
714        if 'Range' not in restData: restData['Range'] = 1.1     #patch
715       
716        def OnUseData(event):
717            Obj = event.GetEventObject()
718            restData['Use'] = Obj.GetValue()
719
720        wtBox = wx.BoxSizer(wx.HORIZONTAL)
721        wtBox.Add(wx.StaticText(wind,-1,'Restraint weight factor: '),0,wx.ALIGN_CENTER_VERTICAL)
722        wtfactor = G2G.ValidatedTxtCtrl(wind,restData,'wtFactor',nDig=(10,2),typeHint=float)
723        wtBox.Add(wtfactor,0,wx.ALIGN_CENTER_VERTICAL)
724        useData = wx.CheckBox(wind,-1,label=' Use?')
725        useData.Bind(wx.EVT_CHECKBOX, OnUseData)
726        useData.SetValue(restData['Use'])       
727        wtBox.Add(useData,0,wx.ALIGN_CENTER_VERTICAL)
728        if 'Bonds' in restData or 'Angles' in restData:
729            wtBox.Add(wx.StaticText(wind,-1,'  Search range: '),0,wx.ALIGN_CENTER_VERTICAL)
730            sRange = G2G.ValidatedTxtCtrl(wind,restData,'Range',nDig=(10,2),typeHint=float)
731            wtBox.Add(sRange,0,wx.ALIGN_CENTER_VERTICAL)
732            wtBox.Add(wx.StaticText(wind,-1,' x sum(atom radii)'),0,wx.ALIGN_CENTER_VERTICAL)
733        return wtBox
734       
735    def OnRowSelect(event):
736        r,c =  event.GetRow(),event.GetCol()
737        Obj = event.GetEventObject()
738        if r < 0 and c < 0:
739            if Obj.IsSelection():
740                Obj.ClearSelection()
741            else:
742                for row in range(Obj.GetNumberRows()):
743                    Obj.SelectRow(row,True)
744        elif c < 0:                   #only row clicks
745            if event.ControlDown():                   
746                if r in Obj.GetSelectedRows():
747                    Obj.DeselectRow(r)
748                else:
749                    Obj.SelectRow(r,True)
750            elif event.ShiftDown():
751                indxs = Obj.GetSelectedRows()
752                Obj.ClearSelection()
753                ibeg = 0
754                if indxs:
755                    ibeg = indxs[-1]
756                for row in range(ibeg,r+1):
757                    Obj.SelectRow(row,True)
758            else:
759                Obj.ClearSelection()
760                Obj.SelectRow(r,True)
761               
762                   
763    def UpdateBondRestr(bondRestData):
764       
765        def OnCellChange(event):
766            r,c =  event.GetRow(),event.GetCol()
767            try:
768                new = float(bondTable.GetValue(r,c))
769                if new <= 0.:
770                    raise ValueError
771                bondRestData['Bonds'][r][c] = new
772            except ValueError:
773                pass           
774            wx.CallAfter(UpdateBondRestr,bondRestData)               
775
776        def OnChangeValue(event):
777            rows = GetSelectedRows(Bonds)
778            if not rows:
779                return
780            Bonds.ClearSelection()
781            val = bondList[rows[0]][2]
782            dlg = G2G.SingleFloatDialog(G2frame,'New value','Enter new value for bond',val,[0.,5.],'%.4f')
783            if dlg.ShowModal() == wx.ID_OK:
784                parm = dlg.GetValue()
785                for r in rows:
786                    bondRestData['Bonds'][r][2] = parm
787            dlg.Destroy()
788            UpdateBondRestr(bondRestData)               
789
790        def OnChangeEsd(event):
791            rows = GetSelectedRows(Bonds)
792            if not rows:
793                return
794            Bonds.ClearSelection()
795            val = bondList[rows[0]][3]
796            dlg = G2G.SingleFloatDialog(G2frame,'New value','Enter new esd for bond',val,[0.,1.],'%.4f')
797            if dlg.ShowModal() == wx.ID_OK:
798                parm = dlg.GetValue()
799                for r in rows:
800                    bondRestData['Bonds'][r][3] = parm
801            dlg.Destroy()
802            UpdateBondRestr(bondRestData)               
803                               
804        def OnDeleteRestraint(event):
805            rows = GetSelectedRows(Bonds)
806            if not rows:
807                return
808            Bonds.ClearSelection()
809            rows.sort()
810            rows.reverse()
811            for row in rows:
812                bondList.remove(bondList[row])
813            UpdateBondRestr(bondRestData)               
814           
815        if BondRestr.GetSizer(): BondRestr.GetSizer().Clear(True)
816        mainSizer = wx.BoxSizer(wx.VERTICAL)
817        mainSizer.Add((5,5),0)
818        mainSizer.Add(WtBox(BondRestr,bondRestData),0,wx.ALIGN_CENTER_VERTICAL)
819
820        bondList = bondRestData['Bonds']
821        if len(bondList) and len(bondList[0]) == 6:   #patch
822            bondList = bondRestData['Bonds'] = []
823        if len(bondList):
824            table = []
825            rowLabels = []
826            bad = []
827            chisq = 0.
828            Types = [wg.GRID_VALUE_STRING,]+4*[wg.GRID_VALUE_FLOAT+':10,3',]
829            if 'macro' in General['Type']:
830                colLabels = ['(res) A - (res) B','calc','target','esd','delt/sig']
831                for i,[indx,ops,obs,esd] in enumerate(bondList):
832                    try:
833                        atoms = G2mth.GetAtomItemsById(Atoms,AtLookUp,indx,0,4)
834                        name = ''
835                        for atom in atoms:
836                            name += '('+atom[1]+atom[0].strip()+atom[2]+') '+atom[3]+' - '
837                        XYZ = np.array(G2mth.GetAtomItemsById(Atoms,AtLookUp,indx,cx,3))
838                        calc = G2mth.getRestDist(XYZ,Amat)
839                        chisq += bondRestData['wtFactor']*((obs-calc)/esd)**2
840                        table.append([name[:-3],calc,obs,esd,(obs-calc)/esd])
841                        rowLabels.append(str(i))               
842                    except KeyError:
843                        print ('**** WARNING - missing atom - restraint deleted ****')
844                        bad.append(i)
845            else:
846                colLabels = ['A+SymOp - B+SymOp','calc','target','esd','delt/sig']
847                for i,[indx,ops,obs,esd] in enumerate(bondList):
848                    try:
849                        names = G2mth.GetAtomItemsById(Atoms,AtLookUp,indx,ct-1)
850                        XYZ = np.array(G2mth.GetAtomItemsById(Atoms,AtLookUp,indx,cx,3))
851                        XYZ = G2mth.getSyXYZ(XYZ,ops,SGData)
852                        calc = G2mth.getRestDist(XYZ,Amat)
853                        chisq += bondRestData['wtFactor']*((obs-calc)/esd)**2
854                        table.append([names[0]+'+('+ops[0]+') - '+names[1]+'+('+ops[1]+')',calc,obs,esd,(obs-calc)/esd])
855                        rowLabels.append(str(i))
856                    except KeyError:
857                        print ('**** WARNING - missing atom - restraint deleted ****')
858                        bad.append(i)
859            if len(bad):
860                bad.reverse()
861                for ibad in bad:
862                    del bondList[ibad]
863            if len(bondList):
864                bondTable = G2G.Table(table,rowLabels=rowLabels,colLabels=colLabels,types=Types)
865                Bonds = G2G.GSGrid(BondRestr)
866                Bonds.SetTable(bondTable, True)
867                Bonds.AutoSizeColumns(False)
868                for r in range(len(bondList)):
869                    for c in [0,1,4]:
870                        Bonds.SetReadOnly(r,c,True)
871                        Bonds.SetCellStyle(r,c,VERY_LIGHT_GREY,True)
872                Bonds.Bind(wg.EVT_GRID_LABEL_LEFT_CLICK,OnRowSelect)
873                if 'phoenix' in wx.version():
874                    Bonds.Bind(wg.EVT_GRID_CELL_CHANGED, OnCellChange)
875                else:
876                    Bonds.Bind(wg.EVT_GRID_CELL_CHANGE, OnCellChange)
877                G2frame.Bind(wx.EVT_MENU, OnDeleteRestraint, id=G2G.wxID_RESTDELETE)
878                G2frame.Bind(wx.EVT_MENU, OnChangeValue, id=G2G.wxID_RESRCHANGEVAL)
879                G2frame.Bind(wx.EVT_MENU, OnChangeEsd, id=G2G.wxID_RESTCHANGEESD)
880                mainSizer.Add(wx.StaticText(BondRestr,-1,
881                    'Bond restraints: sum(wt*(delt/sig)^2) =    %.2f, mean(wt*(delt/sig)^2) =    %.2f'    \
882                    %(chisq,chisq/len(bondList))),0,wx.ALIGN_CENTER_VERTICAL)
883                mainSizer.Add(Bonds,0,)
884            else:
885                mainSizer.Add(wx.StaticText(BondRestr,-1,'No bond distance restraints for this phase'),0,)
886        else:
887            mainSizer.Add(wx.StaticText(BondRestr,-1,'No bond distance restraints for this phase'),0,)
888
889        BondRestr.SetSizer(mainSizer)
890        G2phsGUI.SetPhaseWindow(BondRestr,mainSizer,Scroll=0)
891       
892    def UpdateAngleRestr(angleRestData):
893       
894        def OnCellChange(event):
895            r,c =  event.GetRow(),event.GetCol()
896            try:
897                new = float(angleTable.GetValue(r,c))
898                if new <= 0. or new > 180.:
899                    raise ValueError
900                angleRestData['Angles'][r][c] = new
901            except ValueError:
902                pass           
903            wx.CallAfter(UpdateAngleRestr,angleRestData)               
904           
905        def OnChangeValue(event):
906            rows = GetSelectedRows(Angles)
907            if not rows:
908                return
909            Angles.ClearSelection()
910            val = angleList[rows[0]][2]
911            dlg = G2G.SingleFloatDialog(G2frame,'New value','Enter new value for angle',val,[0.,360.],'%.2f')
912            if dlg.ShowModal() == wx.ID_OK:
913                parm = dlg.GetValue()
914                for r in rows:
915                    angleRestData['Angles'][r][2] = parm
916            dlg.Destroy()
917            UpdateAngleRestr(angleRestData)               
918
919        def OnChangeEsd(event):
920            rows = GetSelectedRows(Angles)
921            if not rows:
922                return
923            Angles.ClearSelection()
924            val = angleList[rows[0]][3]
925            dlg = G2G.SingleFloatDialog(G2frame,'New value','Enter new esd for angle',val,[0.,5.],'%.2f')
926            if dlg.ShowModal() == wx.ID_OK:
927                parm = dlg.GetValue()
928                for r in rows:
929                    angleRestData['Angles'][r][3] = parm
930            dlg.Destroy()
931            UpdateAngleRestr(angleRestData)               
932                                           
933        def OnDeleteRestraint(event):
934            rows = GetSelectedRows(Angles)
935            if not rows:
936                return
937            rows.sort()
938            rows.reverse()
939            for row in rows:
940                angleList.remove(angleList[row])
941            UpdateAngleRestr(angleRestData)               
942           
943        if AngleRestr.GetSizer():
944            AngleRestr.GetSizer().Clear(True)
945        mainSizer = wx.BoxSizer(wx.VERTICAL)
946        mainSizer.Add((5,5),0)
947        mainSizer.Add(WtBox(AngleRestr,angleRestData),0,wx.ALIGN_CENTER_VERTICAL)
948
949        angleList = angleRestData['Angles']
950        if len(angleList):
951            table = []
952            rowLabels = []
953            bad = []
954            chisq = 0.
955            Types = [wg.GRID_VALUE_STRING,]+4*[wg.GRID_VALUE_FLOAT+':10,2',]
956            if 'macro' in General['Type']:
957                colLabels = ['(res) A - (res) B - (res) C','calc','target','esd','delt/sig']
958                for i,[indx,ops,obs,esd] in enumerate(angleList):
959                    try:
960                        atoms = G2mth.GetAtomItemsById(Atoms,AtLookUp,indx,0,4)
961                        name = ''
962                        for atom in atoms:
963                            name += '('+atom[1]+atom[0].strip()+atom[2]+') '+atom[3]+' - '
964                        XYZ = np.array(G2mth.GetAtomItemsById(Atoms,AtLookUp,indx,cx,3))
965                        calc = G2mth.getRestAngle(XYZ,Amat)
966                        chisq += angleRestData['wtFactor']*((obs-calc)/esd)**2
967                        table.append([name[:-3],calc,obs,esd,(obs-calc)/esd])
968                        rowLabels.append(str(i))                               
969                    except KeyError:
970                        print ('**** WARNING - missing atom - restraint deleted ****')
971                        bad.append(i)
972            else:
973                colLabels = ['A+SymOp - B+SymOp - C+SymOp','calc','target','esd','delt/sig']
974                for i,[indx,ops,obs,esd] in enumerate(angleList):
975                    try:
976                        atoms = G2mth.GetAtomItemsById(Atoms,AtLookUp,indx,ct-1)
977                        name = atoms[0]+'+('+ops[0]+') - '+atoms[1]+'+('+ops[1]+') - '+atoms[2]+ \
978                        '+('+ops[2]+')'
979                        XYZ = np.array(G2mth.GetAtomItemsById(Atoms,AtLookUp,indx,cx,3))
980                        XYZ = G2mth.getSyXYZ(XYZ,ops,SGData)
981                        calc = G2mth.getRestAngle(XYZ,Amat)
982                        chisq += angleRestData['wtFactor']*((obs-calc)/esd)**2
983                        table.append([name,calc,obs,esd,(obs-calc)/esd])
984                        rowLabels.append(str(i))
985                    except KeyError:
986                        print ('**** WARNING - missing atom - restraint deleted ****')
987                        bad.append(i)
988            if len(bad):
989                bad.reverse()
990                for ibad in bad:
991                    del angleList[ibad]
992            if len(angleList):
993                angleTable = G2G.Table(table,rowLabels=rowLabels,colLabels=colLabels,types=Types)
994                Angles = G2G.GSGrid(AngleRestr)
995                Angles.SetTable(angleTable, True)
996                Angles.AutoSizeColumns(False)
997                for r in range(len(angleList)):
998                    for c in [0,1,4]:
999                        Angles.SetReadOnly(r,c,True)
1000                        Angles.SetCellStyle(r,c,VERY_LIGHT_GREY,True)
1001                Angles.Bind(wg.EVT_GRID_LABEL_LEFT_CLICK,OnRowSelect)
1002                if 'phoenix' in wx.version():
1003                    Angles.Bind(wg.EVT_GRID_CELL_CHANGED, OnCellChange)
1004                else:
1005                    Angles.Bind(wg.EVT_GRID_CELL_CHANGE, OnCellChange)
1006                G2frame.Bind(wx.EVT_MENU, OnDeleteRestraint, id=G2G.wxID_RESTDELETE)
1007                G2frame.Bind(wx.EVT_MENU, OnChangeValue, id=G2G.wxID_RESRCHANGEVAL)
1008                G2frame.Bind(wx.EVT_MENU, OnChangeEsd, id=G2G.wxID_RESTCHANGEESD)
1009                mainSizer.Add(wx.StaticText(AngleRestr,-1,
1010                    'Angle restraints: sum(wt*(delt/sig)^2) =    %.2f, mean(wt*(delt/sig)^2) =    %.2f'    \
1011                    %(chisq,chisq/len(angleList))),0,wx.ALIGN_CENTER_VERTICAL)
1012                mainSizer.Add(Angles,0,)
1013            else:
1014                mainSizer.Add(wx.StaticText(AngleRestr,-1,'No bond angle restraints for this phase'),0,)
1015        else:
1016            mainSizer.Add(wx.StaticText(AngleRestr,-1,'No bond angle restraints for this phase'),0,)
1017
1018        AngleRestr.SetSizer(mainSizer)
1019        Size = mainSizer.Fit(AngleRestr)
1020        Size[0] = 600
1021        Size[1] = min(Size[1]+50,500)       #make room for tab, but not too big
1022        AngleRestr.SetSize(Size)
1023        AngleRestr.SetScrollbars(10,10,Size[0]/10-4,Size[1]/10-1)
1024   
1025    def UpdatePlaneRestr(planeRestData):
1026       
1027        items = G2frame.dataWindow.RestraintEdit.GetMenuItems()
1028        for item in items:
1029            if item.GetLabel() in ['Change value']:
1030                item.Enable(False)
1031
1032        def OnCellChange(event):
1033            r,c =  event.GetRow(),event.GetCol()
1034            try:
1035                new = float(planeTable.GetValue(r,c))
1036                if new <= 0.:
1037                    raise ValueError
1038                planeRestData['Planes'][r][c] = new
1039            except ValueError:
1040                pass           
1041            wx.CallAfter(UpdatePlaneRestr,planeRestData)               
1042           
1043        def OnChangeEsd(event):
1044            rows = GetSelectedRows(Planes)
1045            if not rows:
1046                return
1047            Planes.ClearSelection()
1048            val = planeList[rows[0]][3]
1049            dlg = G2G.SingleFloatDialog(G2frame,'New value','Enter new esd for plane',val,[0.,5.],'%.2f')
1050            if dlg.ShowModal() == wx.ID_OK:
1051                parm = dlg.GetValue()
1052                for r in rows:
1053                    planeRestData['Planes'][r][3] = parm
1054            dlg.Destroy()
1055            UpdatePlaneRestr(planeRestData)               
1056                                           
1057        def OnDeleteRestraint(event):
1058            rows = GetSelectedRows(Planes)
1059            if not rows:
1060                return
1061            rows.sort()
1062            rows.reverse()
1063            for row in rows:
1064                planeList.remove(planeList[row])
1065            UpdatePlaneRestr(planeRestData)               
1066           
1067        if PlaneRestr.GetSizer():
1068            PlaneRestr.GetSizer().Clear(True)
1069        mainSizer = wx.BoxSizer(wx.VERTICAL)
1070        mainSizer.Add((5,5),0)
1071        mainSizer.Add(WtBox(PlaneRestr,planeRestData),0,wx.ALIGN_CENTER_VERTICAL)
1072
1073        planeList = planeRestData['Planes']
1074        if len(planeList):
1075            table = []
1076            rowLabels = []
1077            bad = []
1078            chisq = 0.
1079            Types = [wg.GRID_VALUE_STRING,]+3*[wg.GRID_VALUE_FLOAT+':10,2',]
1080            if 'macro' in General['Type']:
1081                colLabels = ['(res) atom','calc','target','esd']
1082                for i,[indx,ops,obs,esd] in enumerate(planeList):
1083                    try:
1084                        atoms = G2mth.GetAtomItemsById(Atoms,AtLookUp,indx,0,4)
1085                        name = ''
1086                        for a,atom in enumerate(atoms):
1087                            name += '('+atom[1]+atom[0].strip()+atom[2]+') '+atom[3]+' - '
1088                            if (a+1)%3 == 0:
1089                                name += '\n'
1090                        XYZ = np.array(G2mth.GetAtomItemsById(Atoms,AtLookUp,indx,cx,3))
1091                        calc = G2mth.getRestPlane(XYZ,Amat)
1092                        chisq += planeRestData['wtFactor']*((calc)/esd)**2
1093                        table.append([name[:-3],calc,obs,esd])
1094                        rowLabels.append(str(i))
1095                    except KeyError:
1096                        print ('**** WARNING - missing atom - restraint deleted ****')
1097                        bad.append(i)
1098            else:                               
1099                colLabels = ['atom+SymOp','calc','target','esd']
1100                for i,[indx,ops,obs,esd] in enumerate(planeList):
1101                    try:
1102                        atoms = G2mth.GetAtomItemsById(Atoms,AtLookUp,indx,ct-1)
1103                        XYZ = np.array(G2mth.GetAtomItemsById(Atoms,AtLookUp,indx,cx,3))
1104                        XYZ = G2mth.getSyXYZ(XYZ,ops,SGData)
1105                        calc = G2mth.getRestPlane(XYZ,Amat)
1106                        chisq += planeRestData['wtFactor']*((calc)/esd)**2
1107                        name = ''
1108                        for a,atom in enumerate(atoms):
1109                            name += atom+'+ ('+ops[a]+'),'
1110                            if (a+1)%3 == 0:
1111                                name += '\n'
1112                        table.append([name[:-1],calc,obs,esd])
1113                        rowLabels.append(str(i))
1114                    except KeyError:
1115                        print ('**** WARNING - missing atom - restraint deleted ****')
1116                        bad.append(i)
1117            if len(bad):
1118                bad.reverse()
1119                for ibad in bad:
1120                    del planeList[ibad]
1121            if len(planeList):
1122                planeTable = G2G.Table(table,rowLabels=rowLabels,colLabels=colLabels,types=Types)
1123                Planes = G2G.GSGrid(PlaneRestr)
1124                Planes.SetTable(planeTable, True)
1125                Planes.AutoSizeColumns(False)
1126                Planes.AutoSizeRows(False)
1127                for r in range(len(planeList)):
1128                    for c in range(3):
1129                        Planes.SetReadOnly(r,c,True)
1130                        Planes.SetCellStyle(r,c,VERY_LIGHT_GREY,True)
1131                Planes.Bind(wg.EVT_GRID_LABEL_LEFT_CLICK,OnRowSelect)
1132                if 'phoenix' in wx.version():
1133                    Planes.Bind(wg.EVT_GRID_CELL_CHANGED, OnCellChange)
1134                else:
1135                    Planes.Bind(wg.EVT_GRID_CELL_CHANGE, OnCellChange)
1136                G2frame.Bind(wx.EVT_MENU, OnDeleteRestraint, id=G2G.wxID_RESTDELETE)
1137                G2frame.Bind(wx.EVT_MENU, OnChangeEsd, id=G2G.wxID_RESTCHANGEESD)
1138                mainSizer.Add(wx.StaticText(PlaneRestr,-1,
1139                    'Plane restraints: sum(wt*(delt/sig)^2) =    %.2f, mean(wt*(delt/sig)^2) =    %.2f'    \
1140                    %(chisq,chisq/len(planeList))),0,wx.ALIGN_CENTER_VERTICAL)
1141                mainSizer.Add(Planes,0,)
1142            else:
1143                mainSizer.Add(wx.StaticText(PlaneRestr,-1,'No plane restraints for this phase'),0,)
1144        else:
1145            mainSizer.Add(wx.StaticText(PlaneRestr,-1,'No plane restraints for this phase'),0,)
1146
1147        PlaneRestr.SetSizer(mainSizer)
1148        Size = mainSizer.Fit(PlaneRestr)
1149        Size[0] = 600
1150        Size[1] = min(Size[1]+50,500)       #make room for tab, but not too big
1151        PlaneRestr.SetSize(Size)
1152        PlaneRestr.SetScrollbars(10,10,Size[0]/10-4,Size[1]/10-1)
1153   
1154    def UpdateChiralRestr(chiralRestData):
1155
1156        def OnCellChange(event):
1157            r,c =  event.GetRow(),event.GetCol()
1158            try:
1159                new = float(volumeTable.GetValue(r,c))
1160                if new <= 0.:
1161                    raise ValueError
1162                chiralRestData['Volumes'][r][c] = new
1163            except ValueError:
1164                pass           
1165            wx.CallAfter(UpdateChiralRestr,chiralRestData)               
1166           
1167        def OnDeleteRestraint(event):
1168            rows = GetSelectedRows(Volumes)
1169            if not rows:
1170                return
1171            rows.sort()
1172            rows.reverse()
1173            for row in rows:
1174                volumeList.remove(volumeList[row])
1175            UpdateChiralRestr(chiralRestData)               
1176           
1177        def OnChangeValue(event):
1178            rows = GetSelectedRows(Volumes)
1179            if not rows:
1180                return
1181            Volumes.ClearSelection()
1182            val = volumeList[rows[0]][2]
1183            dlg = G2G.SingleFloatDialog(G2frame,'New value','Enter new value for chiral volume',val,[0.,360.],'%.2f')
1184            if dlg.ShowModal() == wx.ID_OK:
1185                parm = dlg.GetValue()
1186                for r in rows:
1187                    chiralRestData['Volumes'][r][2] = parm
1188            dlg.Destroy()
1189            UpdateChiralRestr(chiralRestData)               
1190
1191        def OnChangeEsd(event):
1192            rows = GetSelectedRows(Volumes)
1193            if not rows:
1194                return
1195            Volumes.ClearSelection()
1196            val = volumeList[rows[0]][3]
1197            dlg = G2G.SingleFloatDialog(G2frame,'New value','Enter new esd for chiral volume',val,[0.,5.],'%.2f')
1198            if dlg.ShowModal() == wx.ID_OK:
1199                parm = dlg.GetValue()
1200                for r in rows:
1201                    chiralRestData['Volumes'][r][3] = parm
1202            dlg.Destroy()
1203            UpdateChiralRestr(chiralRestData)               
1204                                           
1205        if ChiralRestr.GetSizer(): ChiralRestr.GetSizer().Clear(True)
1206        mainSizer = wx.BoxSizer(wx.VERTICAL)
1207        mainSizer.Add((5,5),0)
1208        mainSizer.Add(WtBox(ChiralRestr,chiralRestData),0,wx.ALIGN_CENTER_VERTICAL)
1209
1210        volumeList = chiralRestData['Volumes']
1211        if len(volumeList):
1212            table = []
1213            rowLabels = []
1214            bad = []
1215            chisq = 0.
1216            Types = [wg.GRID_VALUE_STRING,]+4*[wg.GRID_VALUE_FLOAT+':10,2',]
1217            if 'macro' in General['Type']:
1218                colLabels = ['(res) O (res) A (res) B (res) C','calc','target','esd','delt/sig']
1219                for i,[indx,ops,obs,esd] in enumerate(volumeList):
1220                    try:
1221                        atoms = G2mth.GetAtomItemsById(Atoms,AtLookUp,indx,0,4)
1222                        name = ''
1223                        for atom in atoms:
1224                            name += '('+atom[1]+atom[0].strip()+atom[2]+') '+atom[3]+' '
1225                        XYZ = np.array(G2mth.GetAtomItemsById(Atoms,AtLookUp,indx,cx,3))
1226                        calc = G2mth.getRestChiral(XYZ,Amat)
1227                        chisq += chiralRestData['wtFactor']*((obs-calc)/esd)**2
1228                        table.append([name,calc,obs,esd,(obs-calc)/esd])
1229                        rowLabels.append(str(i))
1230                    except KeyError:
1231                        print ('**** WARNING - missing atom - restraint deleted ****')
1232                        bad.append(i)
1233            else:
1234                colLabels = ['O+SymOp  A+SymOp  B+SymOp  C+SymOp)','calc','target','esd','delt/sig']
1235                for i,[indx,ops,obs,esd] in enumerate(volumeList):
1236                    try:
1237                        atoms = G2mth.GetAtomItemsById(Atoms,AtLookUp,indx,ct-1)
1238                        name = atoms[0]+'+('+ops[0]+') '+atoms[1]+'+('+ops[1]+') '+atoms[2]+ \
1239                            '+('+ops[2]+') '+atoms[3]+'+('+ops[3]+')'
1240                        XYZ = np.array(G2mth.GetAtomItemsById(Atoms,AtLookUp,indx,cx,3))
1241                        XYZ = G2mth.getSyXYZ(XYZ,ops,SGData)
1242                        calc = G2mth.getRestChiral(XYZ,Amat)
1243                        chisq += chiralRestData['wtFactor']*((obs-calc)/esd)**2
1244                        table.append([name,calc,obs,esd,(obs-calc)/esd])
1245                        rowLabels.append(str(i))
1246                    except KeyError:
1247                        print ('**** WARNING - missing atom - restraint deleted ****')
1248                        bad.append(i)
1249            if len(bad):
1250                bad.reverse()
1251                for ibad in bad:
1252                    del volumeList[ibad]
1253            if len(volumeList):
1254                volumeTable = G2G.Table(table,rowLabels=rowLabels,colLabels=colLabels,types=Types)
1255                Volumes = G2G.GSGrid(ChiralRestr)
1256                Volumes.SetTable(volumeTable, True)
1257                Volumes.AutoSizeColumns(False)
1258                for r in range(len(volumeList)):
1259                    for c in [0,1,4]:
1260                        Volumes.SetReadOnly(r,c,True)
1261                        Volumes.SetCellStyle(r,c,VERY_LIGHT_GREY,True)
1262                Volumes.Bind(wg.EVT_GRID_LABEL_LEFT_CLICK,OnRowSelect)
1263                if 'phoenix' in wx.version():
1264                    Volumes.Bind(wg.EVT_GRID_CELL_CHANGED, OnCellChange)
1265                else:
1266                    Volumes.Bind(wg.EVT_GRID_CELL_CHANGE, OnCellChange)
1267                G2frame.Bind(wx.EVT_MENU, OnChangeValue, id=G2G.wxID_RESRCHANGEVAL)
1268                G2frame.Bind(wx.EVT_MENU, OnChangeEsd, id=G2G.wxID_RESTCHANGEESD)
1269                mainSizer.Add(wx.StaticText(ChiralRestr,-1,
1270                    'Chiral volume restraints: sum(wt*(delt/sig)^2) =    %.2f, mean(wt*(delt/sig)^2) =    %.2f'    \
1271                    %(chisq,chisq/len(volumeList))),0,wx.ALIGN_CENTER_VERTICAL)
1272                mainSizer.Add(Volumes,0,)
1273            else:
1274                mainSizer.Add(wx.StaticText(ChiralRestr,-1,'No chiral volume restraints for this phase'),0,)
1275        else:
1276            mainSizer.Add(wx.StaticText(ChiralRestr,-1,'No chiral volume restraints for this phase'),0,)
1277
1278        ChiralRestr.SetSizer(mainSizer)
1279        Size = mainSizer.Fit(ChiralRestr)
1280        Size[0] = 600
1281        Size[1] = min(Size[1]+50,500)       #make room for tab, but not too big
1282        ChiralRestr.SetSize(Size)
1283        ChiralRestr.SetScrollbars(10,10,Size[0]/10-4,Size[1]/10-1)
1284   
1285    def UpdateTorsionRestr(torsionRestData):
1286
1287        def OnCellChange(event):
1288            r,c =  event.GetRow(),event.GetCol()
1289            try:
1290                new = float(torsionTable.GetValue(r,c))
1291                if new <= 0. or new > 5.:
1292                    raise ValueError
1293                torsionRestData['Torsions'][r][3] = new     #only esd is editable
1294            except ValueError:
1295                pass           
1296            wx.CallAfter(UpdateTorsionRestr,torsionRestData)               
1297           
1298        def OnDeleteRestraint(event):
1299            rows = GetSelectedRows(TorsionRestr.Torsions)
1300            if not rows:
1301                return
1302            rows.sort()
1303            rows.reverse()
1304            for row in rows:
1305                torsionList.remove(torsionList[row])
1306            wx.CallAfter(UpdateTorsionRestr,torsionRestData)               
1307           
1308        def OnChangeEsd(event):
1309            rows = GetSelectedRows(TorsionRestr.Torsions)
1310            if not rows:
1311                return
1312            TorsionRestr.Torsions.ClearSelection()
1313            val = torsionList[rows[0]][4]
1314            dlg = G2G.SingleFloatDialog(G2frame,'New value','Enter new esd for torsion restraints',val,[0.,5.],'%.2f')
1315            if dlg.ShowModal() == wx.ID_OK:
1316                parm = dlg.GetValue()
1317                for r in rows:
1318                    torsionRestData['Torsions'][r][4] = parm
1319            dlg.Destroy()
1320            wx.CallAfter(UpdateTorsionRestr,torsionRestData)               
1321                                           
1322        if TorsionRestr.GetSizer(): TorsionRestr.GetSizer().Clear(True)
1323        mainSizer = wx.BoxSizer(wx.VERTICAL)
1324        mainSizer.Add((5,5),0)
1325        mainSizer.Add(WtBox(TorsionRestr,torsionRestData),0,wx.ALIGN_CENTER_VERTICAL)
1326       
1327        coeffDict = torsionRestData['Coeff']
1328        torsionList = torsionRestData['Torsions']
1329        if len(torsionList):
1330            table = []
1331            rowLabels = []
1332            bad = []
1333            chisq = 0.
1334            Types = 2*[wg.GRID_VALUE_STRING,]+4*[wg.GRID_VALUE_FLOAT+':10,2',]
1335            if 'macro' in General['Type']:
1336                colLabels = ['(res) A  B  C  D','coef name','torsion','obs E','restr','esd']
1337                for i,[indx,ops,cofName,esd] in enumerate(torsionList):
1338                    try:
1339                        atoms = G2mth.GetAtomItemsById(Atoms,AtLookUp,indx,0,4)
1340                        name = '('+atoms[2][1]+atoms[2][0].strip()+atoms[2][2]+')'
1341                        for atom in atoms:
1342                            name += '  '+atom[3]
1343                        XYZ = np.array(G2mth.GetAtomItemsById(Atoms,AtLookUp,indx,cx,3))
1344                        tor = G2mth.getRestTorsion(XYZ,Amat)
1345                        restr,calc = G2mth.calcTorsionEnergy(tor,coeffDict[cofName])
1346                        chisq += torsionRestData['wtFactor']*(restr/esd)**2
1347                        table.append([name,cofName,tor,calc,restr,esd])
1348                        rowLabels.append(str(i))
1349                    except KeyError:
1350                        print ('**** WARNING - missing atom - restraint deleted ****')
1351                        bad.append(i)
1352            if len(bad):
1353                bad.reverse()
1354                for ibad in bad:
1355                    del torsionList[ibad]
1356            if len(torsionList):
1357                torsionTable = G2G.Table(table,rowLabels=rowLabels,colLabels=colLabels,types=Types)
1358                TorsionRestr.Torsions = G2G.GSGrid(TorsionRestr)
1359                TorsionRestr.Torsions.SetTable(torsionTable, True)
1360                TorsionRestr.Torsions.AutoSizeColumns(False)
1361                for r in range(len(torsionList)):
1362                    for c in range(5):
1363                        TorsionRestr.Torsions.SetReadOnly(r,c,True)
1364                        TorsionRestr.Torsions.SetCellStyle(r,c,VERY_LIGHT_GREY,True)
1365                TorsionRestr.Torsions.Bind(wg.EVT_GRID_LABEL_LEFT_CLICK,OnRowSelect)
1366                if 'phoenix' in wx.vrsion():
1367                    TorsionRestr.Torsions.Bind(wg.EVT_GRID_CELL_CHANGED, OnCellChange)
1368                else:
1369                    TorsionRestr.Torsions.Bind(wg.EVT_GRID_CELL_CHANGE, OnCellChange)
1370                G2frame.Bind(wx.EVT_MENU, OnDeleteRestraint, id=G2G.wxID_RESTDELETE)
1371                G2frame.Bind(wx.EVT_MENU, OnChangeEsd, id=G2G.wxID_RESTCHANGEESD)
1372                mainSizer.Add(wx.StaticText(TorsionRestr,-1,
1373                    'Torsion restraints: sum(wt*(delt/sig)^2) =    %.2f, mean(wt*(delt/sig)^2) =    %.2f'    \
1374                    %(chisq,chisq/len(torsionList))),0,wx.ALIGN_CENTER_VERTICAL)
1375                mainSizer.Add(TorsionRestr.Torsions,0,)
1376               
1377                mainSizer.Add((5,5))
1378                mainSizer.Add(wx.StaticText(TorsionRestr,-1,'Torsion function coefficients:'),0,wx.ALIGN_CENTER_VERTICAL)
1379                table = []
1380                rowLabels = []
1381                Types = 9*[wg.GRID_VALUE_FLOAT+':10,4',]
1382                colLabels = ['Mag A','Pos A','Width A','Mag B','Pos B','Width B','Mag C','Pos C','Width C']
1383                for item in coeffDict:
1384                    rowLabels.append(item)
1385                    table.append(coeffDict[item])
1386                coeffTable = G2G.Table(table,rowLabels=rowLabels,colLabels=colLabels,types=Types)
1387                Coeff = G2G.GSGrid(TorsionRestr)
1388                Coeff.SetTable(coeffTable, True)
1389                Coeff.AutoSizeColumns(False)
1390                for r in range(len(coeffDict)):
1391                    for c in range(9):
1392                        Coeff.SetReadOnly(r,c,True)
1393                        Coeff.SetCellStyle(r,c,VERY_LIGHT_GREY,True)
1394                mainSizer.Add(Coeff,0,)
1395            else:
1396                mainSizer.Add(wx.StaticText(TorsionRestr,-1,'No torsion restraints for this phase'),0,)
1397        else:
1398            mainSizer.Add(wx.StaticText(TorsionRestr,-1,'No torsion restraints for this phase'),0,)
1399
1400        TorsionRestr.SetSizer(mainSizer)
1401        Size = mainSizer.Fit(TorsionRestr)
1402        Size[0] = 600
1403        Size[1] = min(Size[1]+50,500)       #make room for tab, but not too big
1404        TorsionRestr.SetSize(Size)
1405        TorsionRestr.SetScrollbars(10,10,Size[0]/10-4,Size[1]/10-1)
1406
1407    def UpdateRamaRestr(ramaRestData):
1408
1409        def OnCellChange(event):
1410            r,c =  event.GetRow(),event.GetCol()
1411            try:
1412                new = float(ramaTable.GetValue(r,c))
1413                if new <= 0. or new > 5.:
1414                    raise ValueError
1415                ramaRestData['Ramas'][r][4] = new     #only esd is editable
1416            except ValueError:
1417                pass           
1418            wx.CallAfter(UpdateRamaRestr,ramaRestData)               
1419           
1420        def OnDeleteRestraint(event):
1421            rows = GetSelectedRows(RamaRestr.Ramas)
1422            if not rows:
1423                return
1424            rows.sort()
1425            rows.reverse()
1426            for row in rows:
1427                ramaList.remove(ramaList[row])
1428            UpdateRamaRestr(ramaRestData)               
1429           
1430        def OnChangeEsd(event):
1431            rows = GetSelectedRows(RamaRestr.Ramas)
1432            if not rows:
1433                return
1434            RamaRestr.Ramas.ClearSelection()
1435            val = ramaList[rows[0]][4]
1436            dlg = G2G.SingleFloatDialog(G2frame,'New value','Enter new esd for energy',val,[0.,5.],'%.2f')
1437            if dlg.ShowModal() == wx.ID_OK:
1438                parm = dlg.GetValue()
1439                for r in rows:
1440                    ramaRestData['Ramas'][r][4] = parm
1441            dlg.Destroy()
1442            UpdateRamaRestr(ramaRestData)               
1443                                           
1444        if RamaRestr.GetSizer(): RamaRestr.GetSizer().Clear(True)
1445        mainSizer = wx.BoxSizer(wx.VERTICAL)
1446        mainSizer.Add((5,5),0)
1447        mainSizer.Add(WtBox(RamaRestr,ramaRestData),0,wx.ALIGN_CENTER_VERTICAL)
1448
1449        ramaList = ramaRestData['Ramas']
1450        coeffDict = ramaRestData['Coeff']
1451        if len(ramaList):
1452            mainSizer.Add(wx.StaticText(RamaRestr,-1,'Ramachandran restraints:'),0,wx.ALIGN_CENTER_VERTICAL)
1453            table = []
1454            rowLabels = []
1455            bad = []
1456            chisq = 0.
1457            Types = 2*[wg.GRID_VALUE_STRING,]+5*[wg.GRID_VALUE_FLOAT+':10,2',]
1458            if 'macro' in General['Type']:
1459                colLabels = ['(res) A  B  C  D  E','coef name','phi','psi','obs E','restr','esd']
1460                for i,[indx,ops,cofName,esd] in enumerate(ramaList):
1461                    try:
1462                        atoms = G2mth.GetAtomItemsById(Atoms,AtLookUp,indx,0,4)
1463                        name = '('+atoms[3][1]+atoms[3][0].strip()+atoms[3][2]+')'
1464                        for atom in atoms:
1465                            name += '  '+atom[3]
1466                        XYZ = np.array(G2mth.GetAtomItemsById(Atoms,AtLookUp,indx,cx,3))
1467                        phi,psi = G2mth.getRestRama(XYZ,Amat)
1468                        restr,calc = G2mth.calcRamaEnergy(phi,psi,coeffDict[cofName])
1469                        chisq += ramaRestData['wtFactor']*(restr/esd)**2
1470                        table.append([name,cofName,phi,psi,calc,restr,esd])
1471                        rowLabels.append(str(i))
1472                    except KeyError:
1473                        print ('**** WARNING - missing atom - restraint deleted ****')
1474                        bad.append(i)
1475            if len(bad):
1476                bad.reverse()
1477                for ibad in bad:
1478                    del ramaList[ibad]
1479            if len(ramaList):
1480                ramaTable = G2G.Table(table,rowLabels=rowLabels,colLabels=colLabels,types=Types)
1481                RamaRestr.Ramas = G2G.GSGrid(RamaRestr)
1482                RamaRestr.Ramas.SetTable(ramaTable, True)
1483                RamaRestr.Ramas.AutoSizeColumns(False)
1484                for r in range(len(ramaList)):
1485                    for c in range(6):
1486                        RamaRestr.Ramas.SetReadOnly(r,c,True)
1487                        RamaRestr.Ramas.SetCellStyle(r,c,VERY_LIGHT_GREY,True)
1488                RamaRestr.Ramas.Bind(wg.EVT_GRID_LABEL_LEFT_CLICK,OnRowSelect)
1489                if 'phoenix' in wx.version():
1490                    RamaRestr.Ramas.Bind(wg.EVT_GRID_CELL_CHANGED, OnCellChange)
1491                else:
1492                    RamaRestr.Ramas.Bind(wg.EVT_GRID_CELL_CHANGE, OnCellChange)
1493                G2frame.Bind(wx.EVT_MENU, OnDeleteRestraint, id=G2G.wxID_RESTDELETE)
1494                G2frame.Bind(wx.EVT_MENU, OnChangeEsd, id=G2G.wxID_RESTCHANGEESD)
1495                mainSizer.Add(wx.StaticText(RamaRestr,-1,
1496                    'Ramachandran restraints: sum(wt*(delt/sig)^2) =    %.2f, mean(wt*(delt/sig)^2) =    %.2f'    \
1497                    %(chisq,chisq/len(ramaList))),0,wx.ALIGN_CENTER_VERTICAL)
1498                mainSizer.Add(RamaRestr.Ramas,0,)
1499            else:
1500                mainSizer.Add(wx.StaticText(RamaRestr,-1,'No Ramachandran restraints for this phase'),0,)
1501        else:
1502            mainSizer.Add(wx.StaticText(RamaRestr,-1,'No Ramachandran restraints for this phase'),0,)
1503        mainSizer.Add((5,5))
1504        mainSizer.Add(wx.StaticText(RamaRestr,-1,'Ramachandran function coefficients:'),0,wx.ALIGN_CENTER_VERTICAL)
1505        if len(coeffDict):
1506            table = []
1507            rowLabels = []
1508            Types = 6*[wg.GRID_VALUE_FLOAT+':10,4',]
1509            colLabels = ['Mag','Pos phi','Pos psi','sig(phi)','sig(psi)','sig(cov)']
1510            for item in coeffDict:
1511                for i,term in enumerate(coeffDict[item]):
1512                    rowLabels.append(item+' term:'+str(i))
1513                    table.append(term)
1514            coeffTable = G2G.Table(table,rowLabels=rowLabels,colLabels=colLabels,types=Types)
1515            Coeff = G2G.GSGrid(RamaRestr)
1516            Coeff.SetTable(coeffTable, True)
1517            Coeff.AutoSizeColumns(False)
1518            for r in range(Coeff.GetNumberRows()):
1519                for c in range(6):
1520                    Coeff.SetReadOnly(r,c,True)
1521                    Coeff.SetCellStyle(r,c,VERY_LIGHT_GREY,True)
1522            mainSizer.Add(Coeff,0,)
1523
1524        RamaRestr.SetSizer(mainSizer)
1525        Size = mainSizer.Fit(RamaRestr)
1526        Size[0] = 600
1527        Size[1] = min(Size[1]+50,500)       #make room for tab, but not too big
1528        RamaRestr.SetSize(Size)
1529        RamaRestr.SetScrollbars(10,10,Size[0]/10-4,Size[1]/10-1)
1530
1531    def UpdateChemcompRestr(chemcompRestData):
1532       
1533        def OnCellChange(event):
1534            r,c =  event.GetRow(),event.GetCol()
1535            rowLabl = ChemComps.GetRowLabelValue(r)
1536            row = int(rowLabl.split(':')[1])
1537            if 'Restr' in rowLabl:
1538                try:
1539                    new = float(chemcompTable.GetValue(r,c))
1540                    chemcompRestData['Sites'][row][c-2] = new         #obsd or esd
1541                except ValueError:
1542                    pass
1543            else:
1544                try:
1545                    new = float(chemcompTable.GetValue(r,c))
1546                    id = int(rowLabl.split(':')[2])
1547                    chemcompRestData['Sites'][row][1][id] = new     #only factor
1548                except ValueError:
1549                    pass
1550            wx.CallAfter(UpdateChemcompRestr,chemcompRestData)               
1551           
1552        def OnDeleteRestraint(event):
1553            #rows = GetSelectedRows()
1554            rows = ChemComps.GetSelectedRows()
1555            if not rows:
1556                return
1557            rowLabl = ChemComps.GetRowLabelValue(r)
1558            row = int(rowLabl.split(':')[1])
1559            if 'Restr' in rowLabl:
1560                del chemcompList[row]
1561            else:
1562                term = int(rowLabl.split(':')[2])
1563                del chemcompList[row][0][term]
1564                del chemcompList[row][1][term]
1565            UpdateChemcompRestr(chemcompRestData)               
1566           
1567        def OnChangeValue(event):
1568            rows = GetSelectedRows(ChemComps)
1569            if not rows:
1570                return
1571            ChemComps.ClearSelection()
1572            dlg = G2G.SingleFloatDialog(G2frame,'New value',
1573                'Enter new value for restraint multiplier',1.0,[-1.e6,1.e6],'%.2f')
1574            if dlg.ShowModal() == wx.ID_OK:
1575                parm = dlg.GetValue()
1576                for r in rows:
1577                    rowLabl = ChemComps.GetRowLabelValue(r)
1578                    if 'term' in rowLabl:
1579                        items = rowLabl.split(':')
1580                        chemcompRestData['Sites'][int(items[1])][1][int(items[2])] = parm
1581            dlg.Destroy()
1582            UpdateChemcompRestr(chemcompRestData)               
1583
1584        if ChemCompRestr.GetSizer(): ChemCompRestr.GetSizer().Clear(True)
1585        mainSizer = wx.BoxSizer(wx.VERTICAL)
1586        mainSizer.Add((5,5),0)
1587        mainSizer.Add(WtBox(ChemCompRestr,chemcompRestData),0,wx.ALIGN_CENTER_VERTICAL)
1588        mainSizer.Add(wx.StaticText(ChemCompRestr,-1, 
1589            'NB: The chemical restraint sum is over the unit cell contents'),0,wx.ALIGN_CENTER_VERTICAL)
1590        mainSizer.Add((5,5),0)
1591
1592        chemcompList = chemcompRestData['Sites']
1593        if len(chemcompList):
1594            table = []
1595            rowLabels = []
1596            bad = []
1597            chisq = 0.
1598            Types = [wg.GRID_VALUE_STRING,]+5*[wg.GRID_VALUE_FLOAT+':10,2',]
1599            colLabels = ['Atoms','mul*frac','factor','calc','target','esd']
1600            for i,[indx,factors,obs,esd] in enumerate(chemcompList):
1601                try:
1602                    atoms = G2mth.GetAtomItemsById(Atoms,AtLookUp,indx,ct-1)
1603                    mul = np.array(G2mth.GetAtomItemsById(Atoms,AtLookUp,indx,cs+1))
1604                    frac = np.array(G2mth.GetAtomItemsById(Atoms,AtLookUp,indx,cs-1))
1605                    mulfrac = mul*frac
1606                    calcs = mul*frac*factors
1607                    chisq += chemcompRestData['wtFactor']*((obs-np.sum(calcs))/esd)**2
1608                    for iatm,[atom,mf,fr,clc] in enumerate(zip(atoms,mulfrac,factors,calcs)):
1609                        table.append([atom,mf,fr,clc,'',''])
1610                        rowLabels.append('term:'+str(i)+':'+str(iatm))
1611                    table.append(['Sum','','',np.sum(calcs),obs,esd])
1612                    rowLabels.append('Restr:'+str(i))
1613                except KeyError:
1614                    print ('**** WARNING - missing atom - restraint deleted ****')
1615                    bad.append(i)
1616            if len(bad):
1617                bad.reverse()
1618                for ibad in bad:
1619                    del chemcompList[ibad]
1620            if len(chemcompList):
1621                chemcompTable = G2G.Table(table,rowLabels=rowLabels,colLabels=colLabels,types=Types)
1622                ChemComps = G2G.GSGrid(ChemCompRestr)
1623                ChemComps.SetTable(chemcompTable, True)
1624                ChemComps.AutoSizeColumns(False)
1625                for r in range(chemcompTable.GetNumberRows()):
1626                    for c in range(2):
1627                        ChemComps.SetReadOnly(r,c,True)
1628                        ChemComps.SetCellStyle(r,c,VERY_LIGHT_GREY,True)
1629                    if 'Restr' in ChemComps.GetRowLabelValue(r):
1630                        for c in range(4):
1631                            ChemComps.SetReadOnly(r,c,True)
1632                            ChemComps.SetCellStyle(r,c,VERY_LIGHT_GREY,True)
1633                        for c in [1,2]:
1634                            ChemComps.SetCellTextColour(r,c,VERY_LIGHT_GREY)
1635                    else:
1636                        for c in [3,4,5]:
1637                            ChemComps.SetReadOnly(r,c,True)
1638                            ChemComps.SetCellStyle(r,c,VERY_LIGHT_GREY,True)
1639                        for c in [4,5]:
1640                            ChemComps.SetCellTextColour(r,c,VERY_LIGHT_GREY)
1641                           
1642                ChemComps.Bind(wg.EVT_GRID_LABEL_LEFT_CLICK,OnRowSelect)
1643                if 'phoenix' in wx.version():
1644                    ChemComps.Bind(wg.EVT_GRID_CELL_CHANGED, OnCellChange)
1645                else:
1646                    ChemComps.Bind(wg.EVT_GRID_CELL_CHANGE, OnCellChange)
1647                G2frame.Bind(wx.EVT_MENU, OnDeleteRestraint, id=G2G.wxID_RESTDELETE)
1648                G2frame.Bind(wx.EVT_MENU, OnChangeValue, id=G2G.wxID_RESRCHANGEVAL)
1649                mainSizer.Add(wx.StaticText(ChemCompRestr,-1,
1650                    'Chemical composition restraints: sum(wt*(delt/sig)^2) =    %.2f, mean(wt*(delt/sig)^2) =    %.2f'    \
1651                    %(chisq,chisq/len(chemcompList))),0,wx.ALIGN_CENTER_VERTICAL)
1652                mainSizer.Add(ChemComps,0,)
1653            else:
1654                mainSizer.Add(wx.StaticText(ChemCompRestr,-1,'No chemical composition restraints for this phase'),0,)
1655        else:
1656            mainSizer.Add(wx.StaticText(ChemCompRestr,-1,'No chemical composition restraints for this phase'),0,)
1657
1658        ChemCompRestr.SetSizer(mainSizer)
1659        Size = mainSizer.Fit(ChemCompRestr)
1660        Size[0] = 600
1661        Size[1] = min(Size[1]+50,500)       #make room for tab, but not too big
1662        ChemCompRestr.SetSize(Size)
1663        ChemCompRestr.SetScrollbars(10,10,Size[0]/10-4,Size[1]/10-1)
1664           
1665    def UpdateTextureRestr(textureRestData):
1666           
1667        def OnDeleteRestraint(event):
1668            rows = GetSelectedRows(Textures)
1669            if not rows:
1670                return
1671            rows.sort()
1672            rows.reverse()
1673            for row in rows:
1674                textureList.remove(textureList[row])
1675            wx.CallAfter(UpdateTextureRestr,textureRestData)               
1676           
1677        def OnCellChange(event):
1678            r,c = event.GetRow(),event.GetCol()
1679            try:
1680                if c == 1:  #grid size
1681                    new = int(textureTable.GetValue(r,c))
1682                    if new < 6 or new > 24:
1683                        raise ValueError
1684                elif c in [2,4]:   #esds
1685                    new = float(textureTable.GetValue(r,c))
1686                    if new < -1. or new > 2.:
1687                        raise ValueError
1688                else:
1689                    new = textureTable.GetValue(r,c)
1690                textureRestData['HKLs'][r][c] = new
1691            except ValueError:
1692                pass           
1693            wx.CallAfter(UpdateTextureRestr,textureRestData)               
1694
1695        if TextureRestr.GetSizer(): TextureRestr.GetSizer().Clear(True)
1696        mainSizer = wx.BoxSizer(wx.VERTICAL)
1697        mainSizer.Add((5,5),0)
1698        mainSizer.Add(WtBox(TextureRestr,textureRestData),0,wx.ALIGN_CENTER_VERTICAL)
1699        mainSizer.Add(wx.StaticText(TextureRestr,-1, 
1700            'NB: The texture restraints suppress negative pole figure values for the selected HKLs\n'
1701            '    "unit esd" gives a bias toward a flatter polefigure'),0,wx.ALIGN_CENTER_VERTICAL)
1702        mainSizer.Add((5,5),0)
1703
1704        textureList = textureRestData['HKLs']
1705        if len(textureList):
1706            table = []
1707            rowLabels = []
1708            Types = [wg.GRID_VALUE_STRING,wg.GRID_VALUE_LONG,wg.GRID_VALUE_FLOAT+':10,2',
1709                wg.GRID_VALUE_BOOL,wg.GRID_VALUE_FLOAT+':10,2']
1710            colLabels = ['HKL','grid','neg esd','use unit?','unit esd']
1711            for i,[hkl,grid,esd1,ifesd2,esd2] in enumerate(textureList):
1712                table.append(['%d %d %d'%(hkl[0],hkl[1],hkl[2]),grid,esd1,ifesd2,esd2])
1713                rowLabels.append(str(i))
1714            textureTable = G2G.Table(table,rowLabels=rowLabels,colLabels=colLabels,types=Types)
1715            Textures = G2G.GSGrid(TextureRestr)
1716            Textures.SetTable(textureTable, True)
1717            Textures.AutoSizeColumns(False)
1718            for r in range(len(textureList)):
1719                Textures.SetReadOnly(r,0,True)
1720                Textures.SetCellStyle(r,0,VERY_LIGHT_GREY,True)
1721                if not textureTable.GetValue(r,3):
1722                    Textures.SetReadOnly(r,4,True)
1723                    Textures.SetCellStyle(r,4,VERY_LIGHT_GREY,True)
1724                    Textures.SetCellTextColour(r,4,VERY_LIGHT_GREY)
1725            Textures.Bind(wg.EVT_GRID_LABEL_LEFT_CLICK,OnRowSelect)
1726            if 'phoenix' in wx.version():
1727                Textures.Bind(wg.EVT_GRID_CELL_CHANGED, OnCellChange)
1728            else:
1729                Textures.Bind(wg.EVT_GRID_CELL_CHANGE, OnCellChange)
1730            G2frame.Bind(wx.EVT_MENU, OnDeleteRestraint, id=G2G.wxID_RESTDELETE)
1731            mainSizer.Add(Textures,0,)
1732        else:
1733            mainSizer.Add(wx.StaticText(TextureRestr,-1,'No texture restraints for this phase'),0,)
1734        TextureRestr.SetSizer(mainSizer)
1735        Size = mainSizer.Fit(TextureRestr)
1736        Size[0] = 600
1737        Size[1] = min(Size[1]+50,500)       #make room for tab, but not too big
1738        TextureRestr.SetSize(Size)
1739        TextureRestr.SetScrollbars(10,10,Size[0]/10-4,Size[1]/10-1)
1740           
1741    def OnPageChanged(event):
1742        page = event.GetSelection()
1743        #G2frame.restrBook.SetSize(G2frame.dataWindow.GetClientSize())    #TODO -almost right
1744        text = G2frame.restrBook.GetPageText(page)
1745        G2frame.dataWindow.RestraintEdit.SetLabel(G2G.wxID_RESRCHANGEVAL,'Change value')
1746        if text == 'Bond':
1747            G2gd.SetDataMenuBar(G2frame,G2frame.dataWindow.RestraintMenu)
1748            G2frame.dataWindow.RestraintEdit.Enable(G2G.wxID_RESTRAINTADD,True)
1749            G2frame.dataWindow.RestraintEdit.Enable(G2G.wxID_RESRCHANGEVAL,True)
1750            bondRestData = restrData['Bond']
1751            UpdateBondRestr(bondRestData)
1752        elif text == 'Angle':
1753            G2gd.SetDataMenuBar(G2frame,G2frame.dataWindow.RestraintMenu)
1754            G2frame.dataWindow.RestraintEdit.Enable(G2G.wxID_RESTRAINTADD,True)
1755            G2frame.dataWindow.RestraintEdit.Enable(G2G.wxID_RESRCHANGEVAL,True)
1756            angleRestData = restrData['Angle']
1757            UpdateAngleRestr(angleRestData)
1758        elif text == 'Plane':
1759            G2gd.SetDataMenuBar(G2frame,G2frame.dataWindow.RestraintMenu)
1760            G2frame.dataWindow.RestraintEdit.Enable(G2G.wxID_RESTRAINTADD,True)
1761            G2frame.dataWindow.RestraintEdit.Enable(G2G.wxID_RESRCHANGEVAL,False)
1762            planeRestData = restrData['Plane']
1763            UpdatePlaneRestr(planeRestData)
1764        elif text == 'Chiral':
1765            G2gd.SetDataMenuBar(G2frame,G2frame.dataWindow.RestraintMenu)
1766            G2frame.dataWindow.RestraintEdit.Enable(G2G.wxID_RESTRAINTADD,False)
1767            G2frame.dataWindow.RestraintEdit.Enable(G2G.wxID_RESRCHANGEVAL,True)
1768            chiralRestData = restrData['Chiral']
1769            UpdateChiralRestr(chiralRestData)
1770        elif text == 'Torsion':
1771            G2gd.SetDataMenuBar(G2frame,G2frame.dataWindow.RestraintMenu)
1772            G2frame.dataWindow.RestraintEdit.Enable(G2G.wxID_RESTRAINTADD,False)
1773            G2frame.dataWindow.RestraintEdit.Enable(G2G.wxID_RESRCHANGEVAL,False)
1774            G2frame.dataWindow.RestraintEdit.Enable(G2G.wxID_AARESTRAINTPLOT,True)
1775            torsionRestData = restrData['Torsion']
1776            UpdateTorsionRestr(torsionRestData)
1777        elif text == 'Ramachandran':
1778            G2gd.SetDataMenuBar(G2frame,G2frame.dataWindow.RestraintMenu)
1779            G2frame.dataWindow.RestraintEdit.Enable(G2G.wxID_RESTRAINTADD,False)
1780            G2frame.dataWindow.RestraintEdit.Enable(G2G.wxID_RESRCHANGEVAL,False)
1781            G2frame.dataWindow.RestraintEdit.Enable(G2G.wxID_AARESTRAINTPLOT,True)
1782            ramaRestData = restrData['Rama']
1783            UpdateRamaRestr(ramaRestData)
1784            wx.CallAfter(G2plt.PlotRama,G2frame,phaseName,rama,ramaName)
1785        elif text == 'Chem. comp.':
1786            G2gd.SetDataMenuBar(G2frame,G2frame.dataWindow.RestraintMenu)
1787            G2frame.dataWindow.RestraintEdit.Enable(G2G.wxID_RESTRAINTADD,True)
1788            G2frame.dataWindow.RestraintEdit.SetLabel(G2G.wxID_RESRCHANGEVAL,'Change factor')
1789            G2frame.dataWindow.RestraintEdit.Enable(G2G.wxID_RESRCHANGEVAL,True)
1790            G2frame.dataWindow.RestraintEdit.Enable(G2G.wxID_RESTCHANGEESD,False)
1791            chemcompRestData = restrData['ChemComp']
1792            UpdateChemcompRestr(chemcompRestData)
1793        elif text == 'Texture':
1794            G2gd.SetDataMenuBar(G2frame,G2frame.dataWindow.RestraintMenu)
1795            G2frame.dataWindow.RestraintEdit.Enable(G2G.wxID_RESTRAINTADD,True)
1796            G2frame.dataWindow.RestraintEdit.Enable(G2G.wxID_RESRCHANGEVAL,True)
1797            textureRestData = restrData['Texture']
1798            UpdateTextureRestr(textureRestData)
1799        event.Skip()
1800
1801#    def RaisePage(event):
1802#        'Respond to a "select tab" menu button'
1803#        # class PseudoEvent(object):
1804#        #     def __init__(self,page): self.page = page
1805#        #     def Skip(self): pass
1806#        #     def GetSelection(self): return self.page
1807#        try:
1808#            i = tabIndex.get(event.GetId())
1809#            G2frame.restrBook.SetSelection(i)
1810#            #OnPageChanged(PseudoEvent(i))
1811#        except ValueError:
1812#            print('Unexpected event in RaisePage')
1813#
1814    def OnSelectPage(event):
1815        'Called when an item is selected from the Select page menu'
1816        # lookup the menu item that called us and get its text
1817        tabname = TabSelectionIdDict.get(event.GetId())
1818        if not tabname:
1819            print ('Warning: menu item not in dict! id= %d'%event.GetId())
1820            return
1821        # find the matching tab
1822        for PageNum in range(G2frame.restrBook.GetPageCount()):
1823            if tabname == G2frame.restrBook.GetPageText(PageNum):
1824                G2frame.restrBook.SetSelection(PageNum)
1825                return
1826        else:
1827            print ("Warning: tab "+tabname+" was not found")
1828
1829    # UpdateRestraints execution starts here
1830    if not Phases:
1831        print ('There are no phases to form restraints')
1832        return
1833    if not len(Phases):
1834        print ('There are no phases to form restraints')
1835        return
1836    phasedata = Phases[phaseName]
1837    if phaseName not in data:
1838        data[phaseName] = {}
1839    restrData = data[phaseName]
1840    if 'Bond' not in restrData:
1841        restrData['Bond'] = {'wtFactor':1.0,'Range':1.1,'Bonds':[],'Use':True}
1842    if 'Angle' not in restrData:
1843        restrData['Angle'] = {'wtFactor':1.0,'Range':0.85,'Angles':[],'Use':True}
1844    if 'Plane' not in restrData:
1845        restrData['Plane'] = {'wtFactor':1.0,'Planes':[],'Use':True}
1846    if 'Chiral' not in restrData:
1847        restrData['Chiral'] = {'wtFactor':1.0,'Volumes':[],'Use':True}
1848    if 'Torsion' not in restrData:
1849        restrData['Torsion'] = {'wtFactor':1.0,'Coeff':{},'Torsions':[],'Use':True}
1850    if 'Rama' not in restrData:
1851        restrData['Rama'] = {'wtFactor':1.0,'Coeff':{},'Ramas':[],'Use':True}
1852    if 'Texture' not in restrData:
1853        restrData['Texture'] = {'wtFactor':1.0,'HKLs':[],'Use':True}
1854    if 'ChemComp' not in restrData:
1855        restrData['ChemComp'] = {'wtFactor':1.0,'Sites':[],'Use':True}
1856    General = phasedata['General']
1857    Cell = General['Cell'][1:7]          #skip flag & volume   
1858    Amat,Bmat = G2lat.cell2AB(Cell)
1859    SGData = General['SGData']
1860    cx,ct,cs,cia = General['AtomPtrs']
1861    Atoms = phasedata['Atoms']
1862    AtLookUp = G2mth.FillAtomLookUp(Atoms,cia+8)
1863    if 'macro' in General['Type']:
1864        Names = [atom[0]+':'+atom[1]+atom[2]+' '+atom[3] for atom in Atoms]
1865        Ids = []
1866        Coords = []
1867        Types = []
1868    else:   
1869        Names = ['all '+ name for name in General['AtomTypes']]
1870        iBeg = len(Names)
1871        Types = [name for name in General['AtomTypes']]
1872        Coords = [ [] for type in Types]
1873        Ids = [ 0 for type in Types]
1874        Names += [atom[ct-1] for atom in Atoms]
1875    Types += [atom[ct] for atom in Atoms]
1876    Coords += [atom[cx:cx+3] for atom in Atoms]
1877    Ids += [atom[cia+8] for atom in Atoms]
1878    rama = G2data.ramachandranDist['All']
1879    ramaName = 'All'
1880    G2gd.SetDataMenuBar(G2frame,G2frame.dataWindow.RestraintMenu)   
1881    G2frame.dataWindow.RestraintEdit.Enable(G2G.wxID_RESTSELPHASE,False)
1882    if len(Phases) > 1:
1883        G2frame.dataWindow.RestraintEdit.Enable(G2G.wxID_RESTSELPHASE,True)
1884        G2frame.Bind(wx.EVT_MENU, OnSelectPhase, id=G2G.wxID_RESTSELPHASE)
1885    G2frame.Bind(wx.EVT_MENU, OnAddRestraint, id=G2G.wxID_RESTRAINTADD)
1886    if 'macro' in phasedata['General']['Type']:
1887        G2frame.dataWindow.RestraintEdit.Enable(G2G.wxID_AARESTRAINTADD,True)
1888        G2frame.Bind(wx.EVT_MENU, OnAddAARestraint, id=G2G.wxID_AARESTRAINTADD)
1889        G2frame.Bind(wx.EVT_MENU, OnPlotAARestraint, id=G2G.wxID_AARESTRAINTPLOT)
1890   
1891    # GUI defined here
1892    G2frame.SetTitle('restraints for '+phaseName)
1893    #G2frame.restrBook = G2G.GSNoteBook(parent=G2frame.dataWindow,size=G2frame.dataWindow.GetClientSize())
1894    G2frame.restrBook = G2G.GSNoteBook(parent=G2frame.dataWindow)
1895    G2frame.dataWindow.GetSizer().Add(G2frame.restrBook,1,wx.ALL|wx.EXPAND,1)
1896    # clear menu and menu pointers
1897    Pages = []   
1898
1899    txt = 'Bond'
1900    BondRestr = wx.ScrolledWindow(G2frame.restrBook)
1901    G2frame.restrBook.AddPage(BondRestr,txt)
1902    Pages.append(txt)
1903
1904    txt = 'Angle'
1905    AngleRestr = wx.ScrolledWindow(G2frame.restrBook)
1906    G2frame.restrBook.AddPage(AngleRestr,txt) 
1907    Pages.append(txt)
1908   
1909    txt = 'Plane'
1910    PlaneRestr = wx.ScrolledWindow(G2frame.restrBook)
1911    G2frame.restrBook.AddPage(PlaneRestr,txt)
1912    Pages.append(txt)
1913
1914    txt = 'Chiral'
1915    ChiralRestr = wx.ScrolledWindow(G2frame.restrBook)
1916    G2frame.restrBook.AddPage(ChiralRestr,txt)
1917    Pages.append(txt)
1918
1919    if 'macro' in General['Type']:
1920        txt = 'Torsion'
1921        TorsionRestr = wx.ScrolledWindow(G2frame.restrBook)
1922        G2frame.restrBook.AddPage(TorsionRestr,txt)
1923        Pages.append(txt)
1924
1925        txt = 'Ramachandran'
1926        RamaRestr = wx.ScrolledWindow(G2frame.restrBook)
1927        G2frame.restrBook.AddPage(RamaRestr,txt)
1928        Pages.append(txt)
1929
1930    txt = 'Chem. comp.'
1931    ChemCompRestr = wx.ScrolledWindow(G2frame.restrBook)
1932    G2frame.restrBook.AddPage(ChemCompRestr,txt)
1933    Pages.append(txt)
1934   
1935    if General['SH Texture']['Order']:
1936        txt = 'Texture'
1937        TextureRestr = wx.ScrolledWindow(G2frame.restrBook)
1938        G2frame.restrBook.AddPage(TextureRestr,txt)
1939        Pages.append(txt)
1940
1941    UpdateBondRestr(restrData['Bond'])
1942
1943    G2frame.restrBook.Bind(wx.aui.EVT_AUINOTEBOOK_PAGE_CHANGED, OnPageChanged)
1944
1945    # fill page selection menu
1946    menu = G2frame.dataWindow.RestraintTab
1947    for page in Pages:
1948        if menu.FindItem(page) >= 0: continue # is tab already in menu?
1949        Id = wx.NewId()
1950        TabSelectionIdDict[Id] = page
1951        menu.Append(Id,page,'')
1952        G2frame.Bind(wx.EVT_MENU, OnSelectPage, id=Id)
Note: See TracBrowser for help on using the repository browser.