source: trunk/GSASIIseqGUI.py @ 5452

Last change on this file since 5452 was 5446, checked in by toby, 3 years ago

Slider & Dark Mode fixes; remove position from graph toolbar; remove OpenGL install (old)

  • Property svn:eol-style set to native
  • Property svn:keywords set to Date Author Revision URL Id
File size: 97.7 KB
Line 
1# -*- coding: utf-8 -*-
2#GSASIIseqGUI - Sequential Results Display routines
3########### SVN repository information ###################
4# $Date: 2022-12-25 20:51:17 +0000 (Sun, 25 Dec 2022) $
5# $Author: toby $
6# $Revision: 5446 $
7# $URL: trunk/GSASIIseqGUI.py $
8# $Id: GSASIIseqGUI.py 5446 2022-12-25 20:51:17Z toby $
9########### SVN repository information ###################
10'''
11*GSASIIseqGUI: Sequential Results GUI*
12----------------------------------------
13
14Module that defines GUI routines and classes for the various sequential result GUI Frames (window)
15Also defines GUI routines for Cluster Analysis results
16'''
17from __future__ import division, print_function
18import platform
19import copy
20import re
21import numpy as np
22import numpy.ma as ma
23import numpy.linalg as nl
24import scipy.optimize as so
25try:
26    import wx
27    import wx.grid as wg
28except ImportError:
29    pass
30import GSASIIpath
31GSASIIpath.SetVersionNumber("$Revision: 5446 $")
32import GSASIImath as G2mth
33import GSASIIIO as G2IO
34import GSASIIdataGUI as G2gd
35import GSASIIstrIO as G2stIO
36import GSASIIlattice as G2lat
37import GSASIIplot as G2plt
38import GSASIImapvars as G2mv
39import GSASIIobj as G2obj
40import GSASIIexprGUI as G2exG
41import GSASIIctrlGUI as G2G
42WACV = wx.ALIGN_CENTER_VERTICAL
43
44#####  Display of Sequential Results ##########################################
45def UpdateSeqResults(G2frame,data,prevSize=None):
46    """
47    Called when any data tree entry is selected that has 'Sequential' in the name
48    to show results from any sequential analysis.
49   
50    :param wx.Frame G2frame: main GSAS-II data tree windows
51
52    :param dict data: a dictionary containing the following items: 
53
54            * 'histNames' - list of histogram names in order as processed by Sequential Refinement
55            * 'varyList' - list of variables - identical over all refinements in sequence
56              note that this is the original list of variables, prior to processing
57              constraints.
58            * 'variableLabels' -- a dict of labels to be applied to each parameter
59              (this is created as an empty dict if not present in data).
60            * keyed by histName - dictionaries for all data sets processed, which contains:
61
62              * 'variables'- result[0] from leastsq call
63              * 'varyList' - list of variables passed to leastsq call (not same as above)
64              * 'sig' - esds for variables
65              * 'covMatrix' - covariance matrix from individual refinement
66              * 'title' - histogram name; same as dict item name
67              * 'newAtomDict' - new atom parameters after shifts applied
68              * 'newCellDict' - refined cell parameters after shifts to A0-A5 from Dij terms applied'
69    """
70    def GetSampleParms():
71        '''Make a dictionary of the sample parameters that are not the same over the
72        refinement series. Controls here is local
73        '''
74        if 'IMG' in histNames[0]:
75            sampleParmDict = {'Sample load':[],}
76        elif 'PDF' in histNames[0]:
77            sampleParmDict = {'Temperature':[]}
78        else:
79            sampleParmDict = {'Temperature':[],'Pressure':[],'Time':[],
80                'FreePrm1':[],'FreePrm2':[],'FreePrm3':[],'Omega':[],
81                'Chi':[],'Phi':[],'Azimuth':[],}
82        Controls = G2frame.GPXtree.GetItemPyData(
83            G2gd.GetGPXtreeItemId(G2frame,G2frame.root, 'Controls'))
84        sampleParm = {}
85        for name in histNames:
86            if 'IMG' in name or 'PDF' in name:
87                if name not in data:
88                    continue
89                for item in sampleParmDict:
90                    sampleParmDict[item].append(data[name]['parmDict'].get(item,0))
91            else:
92                if 'PDF' in name:
93                    name = 'PWDR' + name[4:]
94                Id = G2gd.GetGPXtreeItemId(G2frame,G2frame.root,name)
95                if Id:
96                    sampleData = G2frame.GPXtree.GetItemPyData(G2gd.GetGPXtreeItemId(G2frame,Id,'Sample Parameters'))
97                else:  # item missing from tree! stick in NaN's!
98                    sampleData = {}
99                for item in sampleParmDict:
100                    sampleParmDict[item].append(sampleData.get(item,np.NaN))
101        for item in sampleParmDict:
102            if sampleParmDict[item]:
103                frstValue = sampleParmDict[item][0]
104                if np.any(np.array(sampleParmDict[item])-frstValue):
105                    if item.startswith('FreePrm'):
106                        sampleParm[Controls[item]] = sampleParmDict[item]
107                    else:
108                        sampleParm[item] = sampleParmDict[item]
109        return sampleParm
110
111    def GetColumnInfo(col):
112        '''returns column label, lists of values and errors (or None) for each column in the table
113        for plotting. The column label is reformatted from Unicode to MatPlotLib encoding
114        '''
115        colName = G2frame.SeqTable.GetColLabelValue(col)
116        plotName = variableLabels.get(colName,colName)
117        plotName = plotSpCharFix(plotName)
118        return plotName,G2frame.colList[col],G2frame.colSigs[col]
119           
120    def PlotSelectedColRow(calltyp='',event=None):
121        '''Called to plot a selected column or row by clicking
122        on a row or column label. N.B. This is called for LB click
123        after the event is processed so that the column or row has been
124        selected. For RB clicks, the event is processed here
125
126        :param str calltyp: ='left'/'right', specifies if this was
127          a left- or right-click, where a left click on row
128          plots histogram; Right click on row plots V-C matrix;
129          Left or right click on column: plots values in column
130        :param obj event: from  wx.EVT_GRID_LABEL_RIGHT_CLICK
131        '''
132        rows = []
133        cols = []
134        if calltyp == 'left':
135            cols = G2frame.dataDisplay.GetSelectedCols()
136            rows = G2frame.dataDisplay.GetSelectedRows()
137        elif calltyp == 'right':
138            r,c = event.GetRow(),event.GetCol()
139            if r > -1:
140                rows = [r,]
141            elif c > -1:
142                cols =  [c,]
143        if cols and calltyp == 'left':
144            G2plt.PlotSelectedSequence(G2frame,cols,GetColumnInfo,SelectXaxis)
145        elif cols and calltyp == 'right':
146            SetLabelString(cols[0])  #only the 1st one selected!
147        elif rows and calltyp == 'left':
148            name = histNames[rows[0]]       #only does 1st one selected
149            if name.startswith('PWDR'):
150                pickId = G2frame.PickId
151                G2frame.PickId = G2frame.PatternId = G2gd.GetGPXtreeItemId(G2frame, G2frame.root, name)
152                G2plt.PlotPatterns(G2frame,newPlot=False,plotType='PWDR')
153                G2frame.PickId = pickId
154            elif name.startswith('PDF'):
155                pickId = G2frame.PickId
156                G2frame.PickId = G2frame.PatternId = G2gd.GetGPXtreeItemId(G2frame, G2frame.root, name)
157                PFdata = G2frame.GPXtree.GetItemPyData(G2gd.GetGPXtreeItemId(G2frame,G2frame.PickId,'PDF Controls'))
158                G2plt.PlotISFG(G2frame,PFdata,plotType='G(R)')
159                G2frame.PickId = pickId               
160            else:
161                return
162        elif rows and calltyp == 'right':
163            name = histNames[rows[0]]       #only does 1st one selected
164            if name.startswith('PWDR'):
165                if len(data[name].get('covMatrix',[])):
166                    G2plt.PlotCovariance(G2frame,data[name])
167            elif name.startswith('PDF'):
168                print('make structure plot')
169        else:
170            G2frame.ErrorDialog(
171                'Select row or columns',
172                'Nothing selected in table. Click on column or row label(s) to plot. N.B. Grid selection can be a bit funky.'
173                )
174       
175    def SetLabelString(col):
176        '''Define or edit the label for a column in the table, to be used
177        as a tooltip and for plotting
178        '''
179        if col < 0 or col > len(colLabels):
180            return
181        var = colLabels[col]
182        lbl = variableLabels.get(var,G2obj.fmtVarDescr(var))
183        head = u'Set a new name for variable {} (column {})'.format(var,col)
184        dlg = G2G.SingleStringDialog(G2frame,'Set variable label',
185                                 head,lbl,size=(400,-1))
186        if dlg.Show():
187            variableLabels[var] = dlg.GetValue()
188            dlg.Destroy()
189            wx.CallAfter(UpdateSeqResults,G2frame,data) # redisplay variables
190        else:
191            dlg.Destroy()
192
193    def PlotLeftSelect(event):
194        'Called by a left MB click on a row or column label. '
195        event.Skip()
196        wx.CallAfter(PlotSelectedColRow,'left')
197       
198    def PlotRightSelect(event):
199        'Called by a right MB click on a row or column label'
200        PlotSelectedColRow('right',event)
201       
202    def OnPlotSelSeq(event):
203        'plot the selected columns or row from menu command'
204        cols = sorted(G2frame.dataDisplay.GetSelectedCols()) # ignore selection order
205        rows = G2frame.dataDisplay.GetSelectedRows()
206        if cols:
207            G2plt.PlotSelectedSequence(G2frame,cols,GetColumnInfo,SelectXaxis)
208        elif rows:
209            name = histNames[rows[0]]       #only does 1st one selected
210            G2plt.PlotCovariance(G2frame,data[name])
211        else:
212            G2frame.ErrorDialog('Select columns',
213                'No columns or rows selected in table. Click on row or column labels to select fields for plotting.')
214               
215    def OnAveSelSeq(event):
216        'average the selected columns from menu command'
217        cols = sorted(G2frame.dataDisplay.GetSelectedCols()) # ignore selection order
218        useCol =  ~np.array(G2frame.SeqTable.GetColValues(1),dtype=bool)
219        if cols:
220            for col in cols:
221                items = GetColumnInfo(col)[1]
222                noneMask = np.array([item is None for item in items])
223                info = ma.array(items,mask=useCol+noneMask)
224                ave = ma.mean(ma.compressed(info))
225                sig = ma.std(ma.compressed(info))
226                print (u' Average for '+G2frame.SeqTable.GetColLabelValue(col)+u': '+'%.6g'%(ave)+u' +/- '+u'%.6g'%(sig))
227        else:
228            G2frame.ErrorDialog('Select columns',
229                'No columns selected in table. Click on column labels to select fields for averaging.')
230           
231    def OnSelectUse(event):
232        dlg = G2G.G2MultiChoiceDialog(G2frame, 'Select rows to use','Select rows',histNames)
233        sellist = [i for i,item in enumerate(G2frame.colList[1]) if item]
234        dlg.SetSelections(sellist)
235        if dlg.ShowModal() == wx.ID_OK:
236            sellist = dlg.GetSelections()
237            for row in range(G2frame.SeqTable.GetNumberRows()):
238                G2frame.SeqTable.SetValue(row,1,False)
239                G2frame.colList[1][row] = False
240            for row in sellist:
241                G2frame.SeqTable.SetValue(row,1,True)
242                G2frame.colList[1][row] = True
243            G2frame.dataDisplay.ForceRefresh()
244        dlg.Destroy()
245               
246    def OnRenameSelSeq(event):
247        cols = sorted(G2frame.dataDisplay.GetSelectedCols()) # ignore selection order
248        colNames = [G2frame.SeqTable.GetColLabelValue(c) for c in cols]
249        newNames = colNames[:]
250        for i,name in enumerate(colNames):
251            if name in variableLabels:
252                newNames[i] = variableLabels[name]
253        if not cols:
254            G2frame.ErrorDialog('Select columns',
255                'No columns selected in table. Click on column labels to select fields for rename.')
256            return
257        dlg = G2G.MultiStringDialog(G2frame.dataDisplay,'Set column names',colNames,newNames)
258        if dlg.Show():
259            newNames = dlg.GetValues()           
260            variableLabels.update(dict(zip(colNames,newNames)))
261        data['variableLabels'] = variableLabels
262        dlg.Destroy()
263        UpdateSeqResults(G2frame,data,G2frame.dataDisplay.GetSize()) # redisplay variables
264        G2plt.PlotSelectedSequence(G2frame,cols,GetColumnInfo,SelectXaxis)
265           
266    def OnSaveSelSeqCSV(event):
267        'export the selected columns to a .csv file from menu command'
268        OnSaveSelSeq(event,csv=True)
269       
270    def OnSaveSeqCSV(event):
271        'export all columns to a .csv file from menu command'
272        OnSaveSelSeq(event,csv=True,allcols=True)
273       
274    def OnSaveSelSeq(event,csv=False,allcols=False):
275        'export the selected columns to a .txt or .csv file from menu command'
276        def WriteLine(line):
277            if '2' in platform.python_version_tuple()[0]:
278                SeqFile.write(G2obj.StripUnicode(line))
279            else:
280                SeqFile.write(line)
281               
282        def WriteCSV():
283            def WriteList(headerItems):
284                line = ''
285                for lbl in headerItems:
286                    if line: line += ','
287                    line += '"'+lbl+'"'
288                return line
289            head = ['name']
290            for col in cols:
291                # Excel does not like unicode
292                item = G2obj.StripUnicode(G2frame.SeqTable.GetColLabelValue(col))
293                if col in havesig:
294                    head += [item,'esd-'+item]
295                else:
296                    head += [item]
297            WriteLine(WriteList(head)+'\n')
298            for row,name in enumerate(saveNames):
299                line = '"'+saveNames[row]+'"'
300                for col in cols:
301                    if saveData[col][row] is None:
302                        if col in havesig:
303#                            line += ',0.0,0.0'
304                            line += ',,'
305                        else:
306#                            line += ',0.0'
307                            line += ','
308                    else:
309                        if col in havesig:
310                            line += ','+str(saveData[col][row])+','+str(saveSigs[col][row])
311                        else:   
312                            line += ','+str(saveData[col][row])
313                WriteLine(line+'\n')
314        def WriteSeq():
315            lenName = len(saveNames[0])
316            line = %s  '%('name'.center(lenName))
317            for col in cols:
318                item = G2frame.SeqTable.GetColLabelValue(col)
319                if col in havesig:
320                    line += ' %12s %12s '%(item.center(12),'esd'.center(12))
321                else:
322                    line += ' %12s '%(item.center(12))
323            WriteLine(line+'\n')
324            for row,name in enumerate(saveNames):
325                line = " '%s' "%(saveNames[row])
326                for col in cols:
327                    if col in havesig:
328                        try:
329                            line += ' %12.6f %12.6f '%(saveData[col][row],saveSigs[col][row])
330                        except TypeError:
331                            line += '                           '
332                    else:
333                        try:
334                            line += ' %12.6f '%saveData[col][row]
335                        except TypeError:
336                            line += '              '
337                WriteLine(line+'\n')
338
339        # start of OnSaveSelSeq code
340        if allcols:
341            cols = range(G2frame.SeqTable.GetNumberCols())
342        else:
343            cols = sorted(G2frame.dataDisplay.GetSelectedCols()) # ignore selection order
344        nrows = G2frame.SeqTable.GetNumberRows()
345        if not cols:
346            choices = [G2frame.SeqTable.GetColLabelValue(r) for r in range(G2frame.SeqTable.GetNumberCols())]
347            dlg = G2G.G2MultiChoiceDialog(G2frame, 'Select columns to write',
348                'select columns',choices)
349            #dlg.SetSelections()
350            if dlg.ShowModal() == wx.ID_OK:
351                cols = dlg.GetSelections()
352                dlg.Destroy()
353            else:
354                dlg.Destroy()
355                return
356            #G2frame.ErrorDialog('Select columns',
357            #                 'No columns selected in table. Click on column labels to select fields for output.')
358            #return
359        saveNames = [G2frame.SeqTable.GetRowLabelValue(r) for r in range(nrows)]
360        saveData = {}
361        saveSigs = {}
362        havesig = []
363        for col in cols:
364            name,vals,sigs = GetColumnInfo(col)
365            saveData[col] = vals
366            if sigs:
367                havesig.append(col)
368                saveSigs[col] = sigs
369        if csv:
370            wild = 'CSV output file (*.csv)|*.csv'
371        else:
372            wild = 'Text output file (*.txt)|*.txt'
373        pth = G2G.GetExportPath(G2frame)
374        dlg = wx.FileDialog(
375            G2frame,
376            'Choose text output file for your selection', pth, '', 
377            wild,wx.FD_SAVE|wx.FD_OVERWRITE_PROMPT)
378        try:
379            if dlg.ShowModal() == wx.ID_OK:
380                SeqTextFile = dlg.GetPath()
381                SeqTextFile = G2IO.FileDlgFixExt(dlg,SeqTextFile) 
382                SeqFile = open(SeqTextFile,'w')
383                if csv:
384                    WriteCSV()
385                else:
386                    WriteSeq()
387                SeqFile.close()
388        finally:
389            dlg.Destroy()
390               
391    def striphist(var,insChar=''):
392        'strip a histogram number from a var name'
393        sv = var.split(':')
394        if len(sv) <= 1: return var
395        if sv[1]:
396            sv[1] = insChar
397        return ':'.join(sv)
398       
399    def plotSpCharFix(lbl):
400        'Change selected unicode characters to their matplotlib equivalent'
401        for u,p in [
402            (u'\u03B1',r'$\alpha$'),
403            (u'\u03B2',r'$\beta$'),
404            (u'\u03B3',r'$\gamma$'),
405            (u'\u0394\u03C7',r'$\Delta\chi$'),
406            ]:
407            lbl = lbl.replace(u,p)
408        return lbl
409   
410    def SelectXaxis():
411        'returns a selected column number (or None) as the X-axis selection'
412        ncols = G2frame.SeqTable.GetNumberCols()
413        colNames = [G2frame.SeqTable.GetColLabelValue(r) for r in range(ncols)]
414        dlg = G2G.G2SingleChoiceDialog(
415            G2frame.dataDisplay,
416            'Select x-axis parameter for\nplot (Cancel=sequence #)',
417            'Select X-axis',
418            colNames)
419        try:
420            if dlg.ShowModal() == wx.ID_OK:
421                col = dlg.GetSelection()
422            else:
423                col = None
424        finally:
425            dlg.Destroy()
426        return col
427   
428    def EnablePseudoVarMenus():
429        'Enables or disables the PseudoVar menu items that require existing defs'
430        if data['SeqPseudoVars']:
431            val = True
432        else:
433            val = False
434        G2frame.dataWindow.SequentialPvars.Enable(G2G.wxID_DELSEQVAR,val)
435        G2frame.dataWindow.SequentialPvars.Enable(G2G.wxID_EDITSEQVAR,val)
436
437    def DelPseudoVar(event):
438        'Ask the user to select a pseudo var expression to delete'
439        choices = list(data['SeqPseudoVars'].keys())
440        selected = G2G.ItemSelector(
441            choices,G2frame,
442            multiple=True,
443            title='Select expressions to remove',
444            header='Delete expression')
445        if selected is None: return
446        for item in selected:
447            del data['SeqPseudoVars'][choices[item]]
448        if selected:
449            UpdateSeqResults(G2frame,data,G2frame.dataDisplay.GetSize()) # redisplay variables
450
451    def EditPseudoVar(event):
452        'Edit an existing pseudo var expression'
453        choices = list(data['SeqPseudoVars'].keys())
454        if len(choices) == 1:
455            selected = 0
456        else:
457            selected = G2G.ItemSelector(
458                choices,G2frame,
459                multiple=False,
460                title='Select an expression to edit',
461                header='Edit expression')
462        if selected is not None:
463            dlg = G2exG.ExpressionDialog(
464                G2frame.dataDisplay,PSvarDict,
465                data['SeqPseudoVars'][choices[selected]],
466                header="Edit the PseudoVar expression",
467                VarLabel="PseudoVar #"+str(selected+1),
468                fit=False)
469            newobj = dlg.Show(True)
470            if newobj:
471                calcobj = G2obj.ExpressionCalcObj(newobj)
472                del data['SeqPseudoVars'][choices[selected]]
473                data['SeqPseudoVars'][calcobj.eObj.expression] = newobj
474                UpdateSeqResults(G2frame,data,G2frame.dataDisplay.GetSize()) # redisplay variables
475       
476    def AddNewPseudoVar(event):
477        'Create a new pseudo var expression'
478        dlg = G2exG.ExpressionDialog(G2frame.dataDisplay,PSvarDict,
479            header='Enter an expression for a PseudoVar here',
480            VarLabel = "New PseudoVar",fit=False)
481        obj = dlg.Show(True)
482        dlg.Destroy()
483        if obj:
484            calcobj = G2obj.ExpressionCalcObj(obj)
485            data['SeqPseudoVars'][calcobj.eObj.expression] = obj
486            UpdateSeqResults(G2frame,data,G2frame.dataDisplay.GetSize()) # redisplay variables
487           
488    def AddNewDistPseudoVar(event):
489        obj = None
490        dlg = G2exG.BondDialog(
491            G2frame.dataDisplay,Phases,PSvarDict,
492            VarLabel = "New Bond")
493        if dlg.ShowModal() == wx.ID_OK:
494            pName,Oatom,Tatom = dlg.GetSelection()
495            if Tatom:
496                Phase = Phases[pName]
497                General = Phase['General']
498                cx,ct = General['AtomPtrs'][:2]
499                pId = Phase['pId']
500                SGData = General['SGData']
501                sB = Tatom.find('(')+1
502                symNo = 0
503                if sB:
504                    sF = Tatom.find(')')
505                    symNo = int(Tatom[sB:sF])
506                cellNo = [0,0,0]
507                cB = Tatom.find('[')
508                if cB>0:
509                    cF = Tatom.find(']')+1
510                    cellNo = eval(Tatom[cB:cF])
511                Atoms = Phase['Atoms']
512                aNames = [atom[ct-1] for atom in Atoms]
513                oId = aNames.index(Oatom)
514                tId = aNames.index(Tatom.split(' +')[0])
515                # create an expression object
516                obj = G2obj.ExpressionObj()
517                obj.expression = 'Dist(%s,\n%s)'%(Oatom,Tatom.split(' d=')[0].replace(' ',''))
518                obj.distance_dict = {'pId':pId,'SGData':SGData,'symNo':symNo,'cellNo':cellNo}
519                obj.distance_atoms = [oId,tId]
520        else: 
521            dlg.Destroy()
522            return
523        dlg.Destroy()
524        if obj:
525            data['SeqPseudoVars'][obj.expression] = obj
526            UpdateSeqResults(G2frame,data,G2frame.dataDisplay.GetSize()) # redisplay variables
527
528    def AddNewAnglePseudoVar(event):
529        obj = None
530        dlg = G2exG.AngleDialog(
531            G2frame.dataDisplay,Phases,PSvarDict,
532            header='Enter an Angle here',
533            VarLabel = "New Angle")
534        if dlg.ShowModal() == wx.ID_OK:
535            pName,Oatom,Tatoms = dlg.GetSelection()
536            if Tatoms:
537                obj = G2obj.makeAngleObj(Phases[pName],Oatom,Tatoms)
538        else: 
539            dlg.Destroy()
540            return
541        dlg.Destroy()
542        if obj:
543            data['SeqPseudoVars'][obj.expression] = obj
544            UpdateSeqResults(G2frame,data,G2frame.dataDisplay.GetSize()) # redisplay variables
545           
546    def UpdateParmDict(parmDict):
547        '''generate the atom positions and the direct & reciprocal cell values,
548        because they might be needed to evaluate the pseudovar
549        #TODO - effect of ISO modes?
550        '''
551        Ddict = dict(zip(['D11','D22','D33','D12','D13','D23'],
552                         ['A'+str(i) for i in range(6)])
553                     )
554        delList = []
555        phaselist = []
556        for item in parmDict: 
557            if ':' not in item: continue
558            key = item.split(':')
559            if len(key) < 3: continue
560            # remove the dA[xyz] terms, they would only bring confusion
561            if key[0] and key[0] not in phaselist: phaselist.append(key[0])
562            if key[2].startswith('dA'):
563                delList.append(item)
564            # compute and update the corrected reciprocal cell terms using the Dij values
565            elif key[2] in Ddict:
566                akey = key[0]+'::'+Ddict[key[2]]
567                parmDict[akey] += parmDict[item]
568                delList.append(item)
569        for item in delList:
570            del parmDict[item]               
571        for i in phaselist:
572            pId = int(i)
573            # apply cell symmetry
574            try:
575                A,zeros = G2stIO.cellFill(str(pId)+'::',SGdata[pId],parmDict,zeroDict[pId])
576                # convert to direct cell & add the unique terms to the dictionary
577                try:
578                    dcell = G2lat.A2cell(A)
579                except:
580                    print('phase',pId,'Invalid cell tensor',A)
581                    raise ValueError('Invalid cell tensor in phase '+str(pId))
582                for i,val in enumerate(dcell):
583                    if i in uniqCellIndx[pId]:
584                        lbl = str(pId)+'::'+G2lat.cellUlbl[i]
585                        parmDict[lbl] = val
586                lbl = str(pId)+'::'+'Vol'
587                parmDict[lbl] = G2lat.calc_V(A)
588            except KeyError:
589                pass
590        return parmDict
591
592    def EvalPSvarDeriv(calcobj,parmDict,sampleDict,var,ESD):
593        '''Evaluate an expression derivative with respect to a
594        GSAS-II variable name.
595
596        Note this likely could be faster if the loop over calcobjs were done
597        inside after the Dict was created.
598        '''
599        if not ESD:
600            return 0.
601        step = ESD/10
602        Ddict = dict(zip(['D11','D22','D33','D12','D13','D23'],
603                         ['A'+str(i) for i in range(6)])
604                     )
605        results = []
606        phaselist = []
607        VparmDict = sampleDict.copy()
608        for incr in step,-step:
609            VparmDict.update(parmDict.copy())           
610            # as saved, the parmDict has updated 'A[xyz]' values, but 'dA[xyz]'
611            # values are not zeroed: fix that!
612            VparmDict.update({item:0.0 for item in parmDict if 'dA' in item})
613            VparmDict[var] += incr
614            G2mv.Dict2Map(VparmDict) # apply constraints
615            # generate the atom positions and the direct & reciprocal cell values now, because they might
616            # needed to evaluate the pseudovar
617            for item in VparmDict:
618                if item in sampleDict:
619                    continue 
620                if ':' not in item: continue
621                key = item.split(':')
622                if len(key) < 3: continue
623                # apply any new shifts to atom positions
624                if key[2].startswith('dA'):
625                    VparmDict[''.join(item.split('d'))] += VparmDict[item]
626                    VparmDict[item] = 0.0
627                # compute and update the corrected reciprocal cell terms using the Dij values
628                if key[2] in Ddict:
629                    if key[0] not in phaselist: phaselist.append(key[0])
630                    akey = key[0]+'::'+Ddict[key[2]]
631                    VparmDict[akey] += VparmDict[item]
632            for i in phaselist:
633                pId = int(i)
634                # apply cell symmetry
635                try:
636                    A,zeros = G2stIO.cellFill(str(pId)+'::',SGdata[pId],VparmDict,zeroDict[pId])
637                    # convert to direct cell & add the unique terms to the dictionary
638                    for i,val in enumerate(G2lat.A2cell(A)):
639                        if i in uniqCellIndx[pId]:
640                            lbl = str(pId)+'::'+G2lat.cellUlbl[i]
641                            VparmDict[lbl] = val
642                    lbl = str(pId)+'::'+'Vol'
643                    VparmDict[lbl] = G2lat.calc_V(A)
644                except KeyError:
645                    pass
646            # dict should be fully updated, use it & calculate
647            calcobj.SetupCalc(VparmDict)
648            results.append(calcobj.EvalExpression())
649        if None in results:
650            return None
651        return (results[0] - results[1]) / (2.*step)
652       
653    def EnableParFitEqMenus():
654        'Enables or disables the Parametric Fit menu items that require existing defs'
655        if data['SeqParFitEqList']:
656            val = True
657        else:
658            val = False
659        G2frame.dataWindow.SequentialPfit.Enable(G2G.wxID_DELPARFIT,val)
660        G2frame.dataWindow.SequentialPfit.Enable(G2G.wxID_EDITPARFIT,val)
661        G2frame.dataWindow.SequentialPfit.Enable(G2G.wxID_DOPARFIT,val)
662
663    def ParEqEval(Values,calcObjList,varyList):
664        '''Evaluate the parametric expression(s)
665        :param list Values: a list of values for each variable parameter
666        :param list calcObjList: a list of :class:`GSASIIobj.ExpressionCalcObj`
667          expression objects to evaluate
668        :param list varyList: a list of variable names for each value in Values
669        '''
670        result = []
671        for calcobj in calcObjList:
672            calcobj.UpdateVars(varyList,Values)
673            if calcobj.depSig:
674                result.append((calcobj.depVal-calcobj.EvalExpression())/calcobj.depSig)
675            else:
676                result.append(calcobj.depVal-calcobj.EvalExpression())
677        return result
678
679    def DoParEqFit(event,eqObj=None):
680        'Parametric fit minimizer'
681        varyValueDict = {} # dict of variables and their initial values
682        calcObjList = [] # expression objects, ready to go for each data point
683        if eqObj is not None:
684            eqObjList = [eqObj,]
685        else:
686            eqObjList = data['SeqParFitEqList']
687        UseFlags = G2frame.SeqTable.GetColValues(1)
688        for obj in eqObjList:
689            # assemble refined vars for this equation
690            varyValueDict.update({var:val for var,val in obj.GetVariedVarVal()})
691            # lookup dependent var position
692            depVar = obj.GetDepVar()
693            if depVar in colLabels:
694                indx = colLabels.index(depVar)
695            else:
696                raise Exception('Dependent variable '+depVar+' not found')
697            # assemble a list of the independent variables
698            indepVars = obj.GetIndependentVars()
699            # loop over each datapoint
700            for j,row in enumerate(zip(*G2frame.colList)):
701                if not UseFlags[j]: continue
702                # assemble equations to fit
703                calcobj = G2obj.ExpressionCalcObj(obj)
704                # prepare a dict of needed independent vars for this expression
705                indepVarDict = {var:row[i] for i,var in enumerate(colLabels) if var in indepVars}
706                calcobj.SetupCalc(indepVarDict)               
707                # values and sigs for current value of dependent var
708                if row[indx] is None: continue
709                calcobj.depVal = row[indx]
710                if G2frame.colSigs[indx]:
711                    calcobj.depSig = G2frame.colSigs[indx][j]
712                else:
713                    calcobj.depSig = 1.
714                calcObjList.append(calcobj)
715        # varied parameters
716        varyList = varyValueDict.keys()
717        values = varyValues = [varyValueDict[key] for key in varyList]
718        if not varyList:
719            print ('no variables to refine!')
720            return
721        try:
722            result = so.leastsq(ParEqEval,varyValues,full_output=True,   #ftol=Ftol,
723                args=(calcObjList,varyList))
724            values = result[0]
725            covar = result[1]
726            if covar is None:
727                raise Exception
728            chisq = np.sum(result[2]['fvec']**2)
729            GOF = np.sqrt(chisq/(len(calcObjList)-len(varyList)))
730            esdDict = {}
731            for i,avar in enumerate(varyList):
732                esdDict[avar] = np.sqrt(covar[i,i])
733        except:
734            print('====> Fit failed')
735            return
736        print('==== Fit Results ====')
737        print ('  chisq =  %.2f, GOF = %.2f'%(chisq,GOF))
738        for obj in eqObjList:
739            obj.UpdateVariedVars(varyList,values)
740            ind = '      '
741            print(u'  '+obj.GetDepVar()+u' = '+obj.expression)
742            for var in obj.assgnVars:
743                print(ind+var+u' = '+obj.assgnVars[var])
744            for var in obj.freeVars:
745                avar = "::"+obj.freeVars[var][0]
746                val = obj.freeVars[var][1]
747                if obj.freeVars[var][2]:
748                    print(ind+var+u' = '+avar + " = " + G2mth.ValEsd(val,esdDict[avar]))
749                else:
750                    print(ind+var+u' = '+avar + u" =" + G2mth.ValEsd(val,0))
751        # create a plot for each parametric variable
752        for fitnum,obj in enumerate(eqObjList):
753            calcobj = G2obj.ExpressionCalcObj(obj)
754            # lookup dependent var position
755            indx = colLabels.index(obj.GetDepVar())
756            # assemble a list of the independent variables
757            indepVars = obj.GetIndependentVars()           
758            # loop over each datapoint
759            fitvals = []
760            for j,row in enumerate(zip(*G2frame.colList)):
761                calcobj.SetupCalc({var:row[i] for i,var in enumerate(colLabels) if var in indepVars})
762                fitvals.append(calcobj.EvalExpression())
763            G2plt.PlotSelectedSequence(G2frame,[indx],GetColumnInfo,SelectXaxis,fitnum,fitvals)
764
765    def SingleParEqFit(eqObj):
766        DoParEqFit(None,eqObj)
767
768    def DelParFitEq(event):
769        'Ask the user to select function to delete'
770        txtlst = [obj.GetDepVar()+' = '+obj.expression for obj in data['SeqParFitEqList']]
771        selected = G2G.ItemSelector(
772            txtlst,G2frame,
773            multiple=True,
774            title='Select a parametric equation(s) to remove',
775            header='Delete equation')
776        if selected is None: return
777        data['SeqParFitEqList'] = [obj for i,obj in enumerate(data['SeqParFitEqList']) if i not in selected]
778        EnableParFitEqMenus()
779        if data['SeqParFitEqList']: DoParEqFit(event)
780       
781    def EditParFitEq(event):
782        'Edit an existing parametric equation'
783        txtlst = [obj.GetDepVar()+' = '+obj.expression for obj in data['SeqParFitEqList']]
784        if len(txtlst) == 1:
785            selected = 0
786        else:
787            selected = G2G.ItemSelector(
788                txtlst,G2frame,
789                multiple=False,
790                title='Select a parametric equation to edit',
791                header='Edit equation')
792        if selected is not None:
793            dlg = G2exG.ExpressionDialog(G2frame.dataDisplay,VarDict,
794                data['SeqParFitEqList'][selected],depVarDict=VarDict,
795                header="Edit the formula for this minimization function",
796                ExtraButton=['Fit',SingleParEqFit],wildCard=False)
797            newobj = dlg.Show(True)
798            if newobj:
799                data['SeqParFitEqList'][selected] = newobj
800                EnableParFitEqMenus()
801            if data['SeqParFitEqList']: DoParEqFit(event)
802
803    def AddNewParFitEq(event):
804        'Create a new parametric equation to be fit to sequential results'
805
806        # compile the variable names used in previous freevars to avoid accidental name collisions
807        usedvarlist = []
808        for obj in data['SeqParFitEqList']:
809            for var in obj.freeVars:
810                if obj.freeVars[var][0] not in usedvarlist: usedvarlist.append(obj.freeVars[var][0])
811
812        dlg = G2exG.ExpressionDialog(G2frame.dataDisplay,VarDict,depVarDict=VarDict,
813            header='Define an equation to minimize in the parametric fit',
814            ExtraButton=['Fit',SingleParEqFit],usedVars=usedvarlist,wildCard=False)
815        obj = dlg.Show(True)
816        dlg.Destroy()
817        if obj:
818            data['SeqParFitEqList'].append(obj)
819            EnableParFitEqMenus()
820            if data['SeqParFitEqList']: DoParEqFit(event)
821               
822    def CopyParFitEq(event):
823        'Copy an existing parametric equation to be fit to sequential results'
824        # compile the variable names used in previous freevars to avoid accidental name collisions
825        usedvarlist = []
826        for obj in data['SeqParFitEqList']:
827            for var in obj.freeVars:
828                if obj.freeVars[var][0] not in usedvarlist: usedvarlist.append(obj.freeVars[var][0])
829        txtlst = [obj.GetDepVar()+' = '+obj.expression for obj in data['SeqParFitEqList']]
830        if len(txtlst) == 1:
831            selected = 0
832        else:
833            selected = G2G.ItemSelector(
834                txtlst,G2frame,
835                multiple=False,
836                title='Select a parametric equation to copy',
837                header='Copy equation')
838        if selected is not None:
839            newEqn = copy.deepcopy(data['SeqParFitEqList'][selected])
840            for var in newEqn.freeVars:
841                newEqn.freeVars[var][0] = G2obj.MakeUniqueLabel(newEqn.freeVars[var][0],usedvarlist)
842            dlg = G2exG.ExpressionDialog(
843                G2frame.dataDisplay,VarDict,newEqn,depVarDict=VarDict,
844                header="Edit the formula for this minimization function",
845                ExtraButton=['Fit',SingleParEqFit],wildCard=False)
846            newobj = dlg.Show(True)
847            if newobj:
848                data['SeqParFitEqList'].append(newobj)
849                EnableParFitEqMenus()
850            if data['SeqParFitEqList']: DoParEqFit(event)
851                                           
852    def GridSetToolTip(row,col):
853        '''Routine to show standard uncertainties for each element in table
854        as a tooltip
855        '''
856        if G2frame.colSigs[col]:
857            if G2frame.colSigs[col][row] == -0.1: return 'frozen'
858            return u'\u03c3 = '+str(G2frame.colSigs[col][row])
859        return ''
860       
861    def GridColLblToolTip(col):
862        '''Define a tooltip for a column. This will be the user-entered value
863        (from data['variableLabels']) or the default name
864        '''
865        if col < 0 or col > len(colLabels):
866            print ('Illegal column #%d'%col)
867            return
868        var = colLabels[col]
869        return variableLabels.get(var,G2obj.fmtVarDescr(var))
870       
871    def DoSequentialExport(event):
872        '''Event handler for all Sequential Export menu items
873        '''
874        if event.GetId() == G2G.wxID_XPORTSEQFCIF:
875            G2IO.ExportSequentialFullCIF(G2frame,data,Controls)
876            return
877        vals = G2frame.dataWindow.SeqExportLookup.get(event.GetId())
878        if vals is None:
879            print('Error: Id not found. This should not happen!')
880            return
881        G2IO.ExportSequential(G2frame,data,*vals)
882
883    def onSelectSeqVars(event):
884        '''Select which variables will be shown in table'''
885        hides = [saveColLabels[2:].index(item) for item in G2frame.SeqTblHideList if
886                     item in saveColLabels[2:]]
887        dlg = G2G.G2MultiChoiceDialog(G2frame, 'Select columns to hide',
888                'Hide columns',saveColLabels[2:])
889        dlg.SetSelections(hides)
890        if dlg.ShowModal() == wx.ID_OK:
891            G2frame.SeqTblHideList = [saveColLabels[2:][sel] for sel in dlg.GetSelections()]
892            dlg.Destroy()
893            UpdateSeqResults(G2frame,data,G2frame.dataDisplay.GetSize()) # redisplay variables
894        else:
895            dlg.Destroy()
896           
897    def OnCellChange(event):
898        c = event.GetCol()
899        if c != 1: return
900        r = event.GetRow()
901        val = G2frame.SeqTable.GetValue(r,c)
902        data['Use'][r] = val
903        G2frame.SeqTable.SetValue(r,c, val)
904       
905    def OnSelectUpdate(event):
906        '''Update all phase parameters from a selected column in the Sequential Table.
907        If no histogram is selected (or more than one), ask the user to make a selection.
908
909        Loosely based on :func:`GSASIIstrIO.SetPhaseData`
910        #TODO effect of ISO modes?
911        '''
912        rows = G2frame.dataDisplay.GetSelectedRows()
913        if len(rows) == 1:
914            sel = rows[0]
915        else:
916            dlg = G2G.G2SingleChoiceDialog(G2frame, 'Select a histogram to\nupdate phase from',
917                                           'Select row',histNames)
918            if dlg.ShowModal() == wx.ID_OK:
919                sel = dlg.GetSelection()
920                dlg.Destroy()
921            else:
922                dlg.Destroy()
923                return
924        parmDict = data[histNames[sel]]['parmDict']
925        Histograms,Phases = G2frame.GetUsedHistogramsAndPhasesfromTree()
926        for phase in Phases:
927            print('Updating {} from Seq. Ref. row {}'.format(phase,histNames[sel]))
928            Phase = Phases[phase]
929            General = Phase['General']
930            SGData = General['SGData']
931            Atoms = Phase['Atoms']
932            cell = General['Cell']
933            pId = Phase['pId']
934            pfx = str(pId)+'::'
935            # there should not be any changes to the cell because those terms are not refined
936            A,sigA = G2stIO.cellFill(pfx,SGData,parmDict,{})
937            cell[1:7] = G2lat.A2cell(A)
938            cell[7] = G2lat.calc_V(A)
939            textureData = General['SH Texture']   
940            if textureData['Order']:
941                for name in ['omega','chi','phi']:
942                    aname = pfx+'SH '+name
943                    textureData['Sample '+name][1] = parmDict[aname]
944                for name in textureData['SH Coeff'][1]:
945                    aname = pfx+name
946                    textureData['SH Coeff'][1][name] = parmDict[aname]
947            ik = 6  #for Pawley stuff below
948            if General.get('Modulated',False):
949                ik = 7
950            # how are these updated?
951            #General['SuperVec']
952            #RBModels = Phase['RBModels']
953            if Phase['General'].get('doPawley'):
954                pawleyRef = Phase['Pawley ref']
955                for i,refl in enumerate(pawleyRef):
956                    key = pfx+'PWLref:'+str(i)
957                    refl[ik] = parmDict[key]
958#                    if key in sigDict:  #TODO: error here sigDict not defined. What was intended?
959#                        refl[ik+1] = sigDict[key]
960#                    else:
961#                        refl[ik+1] = 0
962                continue
963            cx,ct,cs,cia = General['AtomPtrs']
964            for i,at in enumerate(Atoms):
965                names = {cx:pfx+'Ax:'+str(i),cx+1:pfx+'Ay:'+str(i),cx+2:pfx+'Az:'+str(i),cx+3:pfx+'Afrac:'+str(i),
966                    cia+1:pfx+'AUiso:'+str(i),cia+2:pfx+'AU11:'+str(i),cia+3:pfx+'AU22:'+str(i),cia+4:pfx+'AU33:'+str(i),
967                    cia+5:pfx+'AU12:'+str(i),cia+6:pfx+'AU13:'+str(i),cia+7:pfx+'AU23:'+str(i),
968                    cx+4:pfx+'AMx:'+str(i),cx+5:pfx+'AMy:'+str(i),cx+6:pfx+'AMz:'+str(i)}
969                for ind in range(cx,cx+4):
970                    at[ind] = parmDict[names[ind]]
971                if at[cia] == 'I':
972                    at[cia+1] = parmDict[names[cia+1]]
973                else:
974                    for ind in range(cia+2,cia+8):
975                        at[ind] = parmDict[names[ind]]
976                if General['Type'] == 'magnetic':
977                    for ind in range(cx+4,cx+7):
978                        at[ind] = parmDict[names[ind]]
979                ind = General['AtomTypes'].index(at[ct])
980                if General.get('Modulated',False):
981                    AtomSS = at[-1]['SS1']
982                    waveType = AtomSS['waveType']
983                    for Stype in ['Sfrac','Spos','Sadp','Smag']:
984                        Waves = AtomSS[Stype]
985                        for iw,wave in enumerate(Waves):
986                            stiw = str(i)+':'+str(iw)
987                            if Stype == 'Spos':
988                                if waveType in ['ZigZag','Block',] and not iw:
989                                    names = ['Tmin:'+stiw,'Tmax:'+stiw,'Xmax:'+stiw,'Ymax:'+stiw,'Zmax:'+stiw]
990                                else:
991                                    names = ['Xsin:'+stiw,'Ysin:'+stiw,'Zsin:'+stiw,
992                                        'Xcos:'+stiw,'Ycos:'+stiw,'Zcos:'+stiw]
993                            elif Stype == 'Sadp':
994                                names = ['U11sin:'+stiw,'U22sin:'+stiw,'U33sin:'+stiw,
995                                    'U12sin:'+stiw,'U13sin:'+stiw,'U23sin:'+stiw,
996                                    'U11cos:'+stiw,'U22cos:'+stiw,'U33cos:'+stiw,
997                                    'U12cos:'+stiw,'U13cos:'+stiw,'U23cos:'+stiw]
998                            elif Stype == 'Sfrac':
999                                if 'Crenel' in waveType and not iw:
1000                                    names = ['Fzero:'+stiw,'Fwid:'+stiw]
1001                                else:
1002                                    names = ['Fsin:'+stiw,'Fcos:'+stiw]
1003                            elif Stype == 'Smag':
1004                                names = ['MXsin:'+stiw,'MYsin:'+stiw,'MZsin:'+stiw,
1005                                    'MXcos:'+stiw,'MYcos:'+stiw,'MZcos:'+stiw]
1006                            for iname,name in enumerate(names):
1007                                AtomSS[Stype][iw][0][iname] = parmDict[pfx+name]
1008                               
1009    def OnEditSelectPhaseVars(event): 
1010        '''Select phase parameters in a selected histogram in a sequential
1011        fit. This allows the user to set their value(s)
1012        '''
1013        rows = G2frame.dataDisplay.GetSelectedRows()
1014        if len(rows) >= 1:
1015            selRows = rows
1016        else:
1017            dlg = G2G.G2MultiChoiceDialog(G2frame, 'Select histogram(s) to update\nphase parameters',
1018                                           'Select row',histNames)
1019            if dlg.ShowModal() == wx.ID_OK:
1020                selRows = dlg.GetSelections()
1021            else:
1022                selRows = []
1023            dlg.Destroy()
1024            if len(selRows) == 0: return
1025        parmDict = data[histNames[selRows[0]]]['parmDict']
1026        # narrow down to items w/o a histogram & having float values
1027        phaseKeys = [i for i in parmDict if ':' in i and i.split(':')[1] == '']
1028        phaseKeys = [i for i in phaseKeys if type(parmDict[i]) not in (int,str,bool)]
1029        if len(selRows) == 1:
1030            lbl = "\nin {}      ".format(histNames[selRows[0]])
1031        else:
1032            lbl = "\nin {} histograms".format(len(selRows))
1033        dlg = G2G.G2MultiChoiceDialog(G2frame, 'Choose phase parmDict item(s) to set'+lbl, 
1034                                      'Choose items to edit', phaseKeys)
1035        if dlg.ShowModal() == wx.ID_OK:
1036            select = dlg.GetSelections()
1037            dlg.Destroy()
1038        else:
1039            dlg.Destroy()
1040            return
1041        if len(select) == 0: return
1042        l = [phaseKeys[i] for i in select]
1043        d = {i:parmDict[i] for i in l}
1044        val = G2G.CallScrolledMultiEditor(G2frame,len(l)*[d],l,l,CopyButton=True)
1045        if val:
1046            for sel in selRows:
1047                parmDict = data[histNames[sel]]['parmDict']
1048                for key in d: # update values shown in table
1049                    if parmDict[key] == d[key]: continue
1050                    if key in data[histNames[sel]]['varyList']:
1051                        i = data[histNames[sel]]['varyList'].index(key)
1052                        data[histNames[sel]]['variables'][i] = d[key]
1053                        data[histNames[sel]]['sig'][i] = 0
1054                    if key in data[histNames[sel]].get('depParmDict',{}):
1055                        data[histNames[sel]]['depParmDict'][key] = (d[key],0)
1056                parmDict.update(d) # update values used in next fit
1057        wx.CallAfter(UpdateSeqResults,G2frame,data) # redisplay variables
1058        return
1059           
1060#---- UpdateSeqResults: start processing sequential results here ##########
1061    # lookup table for unique cell parameters by symmetry
1062
1063    if not data:
1064        print ('No sequential refinement results')
1065        return
1066    variableLabels = data.get('variableLabels',{})
1067    data['variableLabels'] = variableLabels
1068    Histograms,Phases = G2frame.GetUsedHistogramsAndPhasesfromTree()
1069    if not len(Histograms) and not len(Phases):   #PDF or IMG histogrms not PWDR
1070        histNames = [name for name in data['histNames']]
1071        Controls = {}
1072    else:       
1073        Controls = G2frame.GPXtree.GetItemPyData(G2gd.GetGPXtreeItemId(G2frame,G2frame.root,'Controls'))
1074        # create a place to store Pseudo Vars & Parametric Fit functions, if not present
1075        if 'SeqPseudoVars' not in data: data['SeqPseudoVars'] = {}
1076        if 'SeqParFitEqList' not in data: data['SeqParFitEqList'] = []
1077        histNames = [name for name in data['histNames'] if name in data]
1078    if G2frame.dataDisplay:
1079        G2frame.dataDisplay.Destroy()
1080    G2frame.GetStatusBar().SetStatusText("Select column to export; LMB/RMB column to plot data/change label; LMB/RMB on row for PWDR/Covariance plot",1)
1081    sampleParms = GetSampleParms()
1082
1083    # get unit cell & symmetry for all phases & initial stuff for later use
1084    RecpCellTerms = {}
1085    SGdata = {}
1086    uniqCellIndx = {}
1087    initialCell = {}
1088    RcellLbls = {}
1089    zeroDict = {}
1090    for phase in Phases:
1091        phasedict = Phases[phase]
1092        pId = phasedict['pId']
1093        pfx = str(pId)+'::' # prefix for A values from phase
1094        RcellLbls[pId] = [pfx+'A'+str(i) for i in range(6)]
1095        RecpCellTerms[pId] = G2lat.cell2A(phasedict['General']['Cell'][1:7])
1096        zeroDict[pId] = dict(zip(RcellLbls[pId],6*[0.,]))
1097        SGdata[pId] = phasedict['General']['SGData']
1098        laue = SGdata[pId]['SGLaue']
1099        if laue == '2/m':
1100            laue += SGdata[pId]['SGUniq']
1101        for symlist,celllist in G2lat.UniqueCellByLaue:
1102            if laue in symlist:
1103                uniqCellIndx[pId] = celllist
1104                break
1105        else: # should not happen
1106            uniqCellIndx[pId] = list(range(6))
1107        for i in uniqCellIndx[pId]:
1108            initialCell[str(pId)+'::A'+str(i)] =  RecpCellTerms[pId][i]
1109
1110    G2gd.SetDataMenuBar(G2frame,G2frame.dataWindow.SequentialMenu)
1111    G2frame.Bind(wx.EVT_MENU, OnSelectUse, id=G2G.wxID_SELECTUSE)
1112    G2frame.Bind(wx.EVT_MENU, OnRenameSelSeq, id=G2G.wxID_RENAMESEQSEL)
1113    G2frame.Bind(wx.EVT_MENU, OnSaveSelSeq, id=G2G.wxID_SAVESEQSEL)
1114    G2frame.Bind(wx.EVT_MENU, OnSaveSelSeqCSV, id=G2G.wxID_SAVESEQSELCSV)
1115    G2frame.Bind(wx.EVT_MENU, OnSaveSeqCSV, id=G2G.wxID_SAVESEQCSV)
1116    G2frame.Bind(wx.EVT_MENU, OnPlotSelSeq, id=G2G.wxID_PLOTSEQSEL)
1117    G2frame.Bind(wx.EVT_MENU, OnAveSelSeq, id=G2G.wxID_AVESEQSEL)
1118    G2frame.Bind(wx.EVT_MENU, OnSelectUpdate, id=G2G.wxID_UPDATESEQSEL)
1119    G2frame.Bind(wx.EVT_MENU, OnEditSelectPhaseVars, id=G2G.wxID_EDITSEQSELPHASE)
1120    G2frame.Bind(wx.EVT_MENU, onSelectSeqVars, id=G2G.wxID_ORGSEQINC)
1121    G2frame.Bind(wx.EVT_MENU, AddNewPseudoVar, id=G2G.wxID_ADDSEQVAR)
1122    G2frame.Bind(wx.EVT_MENU, AddNewDistPseudoVar, id=G2G.wxID_ADDSEQDIST)
1123    G2frame.Bind(wx.EVT_MENU, AddNewAnglePseudoVar, id=G2G.wxID_ADDSEQANGLE)
1124    G2frame.Bind(wx.EVT_MENU, DelPseudoVar, id=G2G.wxID_DELSEQVAR)
1125    G2frame.Bind(wx.EVT_MENU, EditPseudoVar, id=G2G.wxID_EDITSEQVAR)
1126    G2frame.Bind(wx.EVT_MENU, AddNewParFitEq, id=G2G.wxID_ADDPARFIT)
1127    G2frame.Bind(wx.EVT_MENU, CopyParFitEq, id=G2G.wxID_COPYPARFIT)
1128    G2frame.Bind(wx.EVT_MENU, DelParFitEq, id=G2G.wxID_DELPARFIT)
1129    G2frame.Bind(wx.EVT_MENU, EditParFitEq, id=G2G.wxID_EDITPARFIT)
1130    G2frame.Bind(wx.EVT_MENU, DoParEqFit, id=G2G.wxID_DOPARFIT)
1131
1132    for id in G2frame.dataWindow.SeqExportLookup:
1133        G2frame.Bind(wx.EVT_MENU, DoSequentialExport, id=id)
1134    G2frame.Bind(wx.EVT_MENU, OnSaveSeqCSV, id=G2G.wxID_XPORTSEQCSV)
1135    G2frame.Bind(wx.EVT_MENU, DoSequentialExport, id=G2G.wxID_XPORTSEQFCIF)
1136
1137    EnablePseudoVarMenus()
1138    EnableParFitEqMenus()
1139
1140    combinedVaryList = []  # list of all varied parameters ; used for column headings
1141    atomsVaryList = {}     # dict of atom coords varied in any histogram, includes dependent params
1142                           # key is atom param name, value is esd parm name
1143    firstValueDict = {}    # first value for each parameter; used to create VarDict for parametric fit pseudo vars GUI
1144    foundHistNames = []    # histograms to be used in sequential table
1145    maxPWL = 5             # number of Pawley vars to show
1146    missing = 0
1147    newCellDict = {}
1148    PSvarDict = {} # contains 1st value for each parameter in parmDict
1149                   # needed for creating & editing pseudovars
1150    PSvarDict.update(sampleParms)
1151
1152    # scan through histograms to see what are available and to make a
1153    # list of all varied parameters; also create a dict that has the
1154    for i,name in enumerate(histNames):
1155        if name not in data:
1156            if missing < 5:
1157                print(" Warning: "+name+" not found")
1158            elif missing == 5:
1159                print (' Warning: more are missing')
1160            missing += 1
1161            continue
1162        foundHistNames.append(name)
1163        for var,val,sig in zip(data[name]['varyList'],data[name]['variables'],data[name]['sig']):
1164            svar = striphist(var,'*') # wild-carded
1165            if 'PWL' in svar:
1166                if int(svar.split(':')[-1]) > maxPWL:
1167                    continue
1168            if svar not in combinedVaryList:
1169                # add variables to list as they appear
1170                combinedVaryList.append(svar)
1171                firstValueDict[svar] = (val,sig)
1172            if '::dA' in var: 
1173                atomsVaryList[var.replace('::dA','::A')] = var
1174                atomsVaryList.update({i.replace('::dA','::A'):i for i in data[name]['depParmDict'] if '::dA' in i})
1175        # get all refined cell terms
1176        newCellDict.update(data[name].get('newCellDict',{})) # N.B. These Dij vars are missing a histogram #
1177        # make sure 1st reference to each parm is in PseudoVar dict
1178        tmp = copy.deepcopy(data[name].get('parmDict',{}))
1179        tmp = {striphist(var,'*'):tmp[var] for var in tmp}  # replace histogram #s with "*"
1180        tmp.update(PSvarDict)
1181        PSvarDict = tmp
1182   
1183    if missing:
1184        print (' Warning: Total of %d data sets missing from sequential results'%(missing))
1185
1186    # make dict of varied cell parameters equivalents
1187    ESDlookup = {} # provides the Dij term for each Ak term (where terms are refined)
1188    Dlookup = {} # provides the Ak term for each Dij term (where terms are refined)
1189    cellAlist = []
1190    for item in newCellDict:
1191        cellAlist.append(newCellDict[item][0])
1192        if item in data.get('varyList',[]):
1193            ESDlookup[newCellDict[item][0]] = item
1194            Dlookup[item] = newCellDict[item][0]
1195    # add coordinate equivalents to lookup table
1196    for parm in atomsVaryList:
1197        Dlookup[atomsVaryList[parm]] = parm
1198        ESDlookup[parm] = atomsVaryList[parm]
1199    combinedVaryList.sort()
1200    histNames = foundHistNames
1201    # prevVaryList = []
1202    posdict = {}    # defines position for each entry in table; for inner
1203                    # dict key is column number & value is parameter name
1204    for i,name in enumerate(histNames):
1205        # if prevVaryList != data[name]['varyList']: # this refinement has a different refinement list from previous
1206        #     prevVaryList = data[name]['varyList']
1207            posdict[name] = {}
1208            for var in data[name]['varyList']:
1209                svar = striphist(var,'*')
1210                if 'PWL' in svar:
1211                    if int(svar.split(':')[-1]) > maxPWL:
1212                        continue
1213                posdict[name][combinedVaryList.index(svar)] = svar
1214    ####--  build up the data table by columns -----------------------------------------------
1215    nRows = len(histNames)
1216    G2frame.colList = [list(range(nRows))]
1217    if len(data.get('Use',[])) != nRows:
1218        data['Use'] = nRows*[True]
1219    G2frame.colList += [data['Use']]
1220    G2frame.colSigs = [None,None,]
1221    colLabels = ['No.','Use',]
1222    Types = [wg.GRID_VALUE_LONG,wg.GRID_VALUE_BOOL,]
1223    # start with Rwp values
1224    if 'IMG ' not in histNames[0][:4]:
1225        G2frame.colList += [[data[name]['Rvals']['Rwp'] for name in histNames]]
1226        G2frame.colSigs += [None]
1227        colLabels += ['Rwp']
1228        Types += [wg.GRID_VALUE_FLOAT+':10,3',]
1229    if histNames[0][:4] not in ['SASD','IMG ','REFD']:
1230        G2frame.colList += [[data[name]['Rvals']['GOF'] for name in histNames]]
1231        G2frame.colSigs += [None]
1232        colLabels += ['GOF']
1233        Types += [wg.GRID_VALUE_FLOAT+':10,3',]
1234    # add % change in Chi^2 in last cycle
1235    if histNames[0][:4] not in ['SASD','IMG ','REFD'] and Controls.get('ShowCell'):
1236        G2frame.colList += [[100.*data[name]['Rvals'].get('DelChi2',-1) for name in histNames]]
1237        G2frame.colSigs += [None]
1238        colLabels += [u'\u0394\u03C7\u00B2 (%)']
1239        Types += [wg.GRID_VALUE_FLOAT+':10,5',]
1240    deltaChiCol = len(colLabels)-1
1241    # frozen variables?
1242    if 'parmFrozen' in Controls:
1243        f = [len(Controls['parmFrozen'].get(h,[])) for h in histNames]
1244        if any(f):
1245            G2frame.colList += [f]
1246            G2frame.colSigs += [None]
1247            colLabels += ['frozen']
1248            Types += [wg.GRID_VALUE_LONG]
1249    # add changing sample parameters to table
1250    for key in sampleParms:
1251        G2frame.colList += [sampleParms[key]]
1252        G2frame.colSigs += [None]
1253        colLabels += [key]
1254        Types += [wg.GRID_VALUE_FLOAT,]
1255    sampleDict = {}
1256    for i,name in enumerate(histNames):
1257        sampleDict[name] = dict(zip(sampleParms.keys(),[sampleParms[key][i] for key in sampleParms.keys()])) 
1258    # add unique cell parameters 
1259    if Controls.get('ShowCell',False) and len(newCellDict):
1260        phaseLookup = {Phases[phase]['pId']:phase for phase in Phases}
1261        for pId in sorted(RecpCellTerms):
1262            pfx = str(pId)+'::' # prefix for A values from phase
1263            cells = []
1264            cellESDs = []
1265            colLabels += [pfx+G2lat.cellUlbl[i] for i in uniqCellIndx[pId]]
1266            colLabels += [pfx+'Vol']
1267            Types += (len(uniqCellIndx[pId]))*[wg.GRID_VALUE_FLOAT+':10,5',]
1268            Types += [wg.GRID_VALUE_FLOAT+':10,3',]
1269            Albls = [pfx+'A'+str(i) for i in range(6)]
1270            for name in histNames:
1271                if name not in Histograms: continue
1272                hId = Histograms[name]['hId']
1273                phfx = '%d:%d:'%(pId,hId)
1274                esdLookUp = {}
1275                dLookup = {}
1276                for item in data[name]['newCellDict']:
1277                    if phfx+item.split('::')[1] in data[name]['varyList']:
1278                        esdLookUp[newCellDict[item][0]] = item
1279                        dLookup[item] = newCellDict[item][0]
1280                covData = {'varyList': [dLookup.get(striphist(v),v) for v in data[name]['varyList']],
1281                    'covMatrix': data[name]['covMatrix']}
1282                A = RecpCellTerms[pId][:] # make copy of starting A values
1283                # update with refined values
1284                for i,j in enumerate(('D11','D22','D33','D12','D13','D23')):
1285                    var = str(pId)+'::A'+str(i)
1286                    Dvar = str(pId)+':'+str(hId)+':'+j
1287                    # apply Dij value if non-zero
1288                    if Dvar in data[name]['parmDict']:
1289                        if data[name]['parmDict'][Dvar] != 0.0:
1290                            A[i] += data[name]['parmDict'][Dvar]
1291                    # override with fit result if is Dij varied
1292                    if var in cellAlist:
1293                        try:
1294                            A[i] = data[name]['newCellDict'][esdLookUp[var]][1] # get refined value
1295                        except KeyError:
1296                            pass
1297                # apply symmetry
1298                cellDict = dict(zip(Albls,A))
1299                try:    # convert to direct cell
1300                    A,zeros = G2stIO.cellFill(pfx,SGdata[pId],cellDict,zeroDict[pId])
1301                    c = G2lat.A2cell(A)
1302                    vol = G2lat.calc_V(A)
1303                    cE = G2stIO.getCellEsd(pfx,SGdata[pId],A,covData)
1304                except:
1305                    c = 6*[None]
1306                    cE = 6*[None]
1307                    vol = None
1308                # add only unique values to table
1309                if name in Phases[phaseLookup[pId]]['Histograms']:
1310                    cells += [[c[i] for i in uniqCellIndx[pId]]+[vol]]
1311                    cellESDs += [[cE[i] for i in uniqCellIndx[pId]]+[cE[-1]]]
1312                    # add in direct cell terms to PseudoVar dict
1313                    tmp = dict(zip([pfx+G2lat.cellUlbl[i] for i in uniqCellIndx[pId]]+[pfx+'Vol'],
1314                                     [c[i] for i in uniqCellIndx[pId]]+[vol]))
1315                    tmp.update(PSvarDict)
1316                    PSvarDict = tmp
1317                else:
1318                    cells += [[None for i in uniqCellIndx[pId]]+[None]]
1319                    cellESDs += [[None for i in uniqCellIndx[pId]]+[None]]
1320            G2frame.colList += zip(*cells)
1321            G2frame.colSigs += zip(*cellESDs)
1322
1323    # get ISODISTORT labels
1324    ISOlist = []
1325    for phase in Phases:
1326        ISOlist += [i.varname() for i in Phases[phase].get('ISODISTORT',{}).get('G2ModeList',[])
1327                       if i.varname() not in ISOlist]
1328    # set labels for columns of data table
1329    ISOcols = {}  # ISODISTORT modes
1330    for i,lbl in enumerate(combinedVaryList):
1331        if 'nv-' in lbl:
1332            nlbl = lbl.replace('::nv-','::')
1333            if nlbl in ISOlist:
1334                lbl = nlbl
1335                ISOcols[lbl] = i
1336        colLabels.append(lbl)
1337    Types += len(combinedVaryList)*[wg.GRID_VALUE_FLOAT,]
1338    vals = []
1339    esds = []
1340    varsellist = None        # will be a list of variable names in the order they are selected to appear
1341    # tabulate values for each hist, leaving None for blank columns
1342    for ih,name in enumerate(histNames):
1343        varsellist = [posdict[name].get(i) for i in range(len(combinedVaryList))]
1344        # translate variable names to how they will be used in the headings
1345        vs = [striphist(v,'*') for v in data[name]['varyList']]
1346        # determine the index for each column (or None) in the data[]['variables'] and ['sig'] lists
1347        sellist = [vs.index(v) if v is not None else None for v in varsellist]
1348        #sellist = [i if striphist(v,'*') in varsellist else None for i,v in enumerate(data[name]['varyList'])]
1349        if not varsellist: raise Exception()
1350        vals.append([data[name]['variables'][s] if s is not None else None for s in sellist])
1351        #replace mode displacement shift with value; esd applies to both
1352        for pname in ISOcols:
1353            if pname in data[name]['parmDict']:
1354                vals[ih][ISOcols[pname]] = data[name]['parmDict'][pname]
1355        esds.append([data[name]['sig'][s] if s is not None else None for s in sellist])
1356    G2frame.colList += zip(*vals)
1357    G2frame.colSigs += zip(*esds)
1358
1359    # add refined atom parameters to table
1360    for parm in sorted(atomsVaryList):
1361        vals = []
1362        sigs = []
1363        aprm = atomsVaryList[parm]
1364        for name in histNames:
1365            if aprm in data[name]['varyList']:
1366                i = data[name]['parmDict'][parm]
1367                j = data[name]['sig'][data[name]['varyList'].index(aprm)]
1368            elif aprm in data[name]['depParmDict']:
1369                i = data[name]['parmDict'][parm]
1370                j = data[name]['depParmDict'][aprm][1]
1371            else:
1372                i = j = None
1373            vals.append(i)
1374            sigs.append(j)
1375        colLabels.append(parm)
1376        Types += [wg.GRID_VALUE_FLOAT+':10,5',]
1377        G2frame.colSigs += [sigs]
1378        G2frame.colList += [vals]
1379           
1380    # tabulate dependent parameters from constraints, removing histogram numbers from
1381    # parm label, if needed. Add the dependent variables to the table
1382    depValDict = {}
1383    for name in histNames:
1384        for var in data[name].get('depParmDict',{}):
1385            if '::dA' in var: continue
1386            val,sig = data[name]['depParmDict'][var]
1387            svar = striphist(var,'*')
1388            if svar not in depValDict:
1389               depValDict[svar] = {}
1390            depValDict[svar][name] = (val,sig)
1391    for svar in sorted(depValDict):
1392        vals = []
1393        sigs = []
1394        for name in histNames:
1395            if name in depValDict[svar]:
1396                i,j = depValDict[svar][name]
1397            else:
1398                i = j = None
1399            vals.append(i)
1400            sigs.append(j)
1401        colLabels.append(svar)
1402        Types += [wg.GRID_VALUE_FLOAT+':10,5',]
1403        G2frame.colSigs += [sigs]
1404        G2frame.colList += [vals]
1405
1406    # evaluate Pseudovars, their ESDs and add them to grid
1407    # this should be reworked so that the eval dict is created once and as noted below
1408    for expr in data['SeqPseudoVars']:
1409        obj = data['SeqPseudoVars'][expr]
1410        calcobj = G2obj.ExpressionCalcObj(obj)
1411        valList = []
1412        esdList = []
1413        for seqnum,name in enumerate(histNames):
1414            sigs = data[name]['sig']
1415            G2mv.InitVars()
1416#            parmDict = data[name].get('parmDict')
1417            parmDict = data[name]['parmDict']
1418            constraintInfo = data[name].get('constraintInfo',[[],[],{},[],seqnum])
1419            groups,parmlist,constrDict,fixedList,ihst = constraintInfo
1420            varyList = data[name]['varyList']
1421            msg = G2mv.EvaluateMultipliers(constrDict,parmDict)
1422            if msg:
1423                print('Unable to interpret multiplier(s) for',name,':',msg)
1424                continue
1425            G2mv.GenerateConstraints(varyList,constrDict,fixedList,parmDict,
1426                                     seqHistNum=ihst,raiseException=False)
1427            if 'Dist' in expr:
1428                derivs = G2mth.CalcDistDeriv(obj.distance_dict,obj.distance_atoms, parmDict)
1429                pId = obj.distance_dict['pId']
1430                aId,bId = obj.distance_atoms
1431                varyNames = ['%d::dA%s:%d'%(pId,ip,aId) for ip in ['x','y','z']]
1432                varyNames += ['%d::dA%s:%d'%(pId,ip,bId) for ip in ['x','y','z']]
1433                VCoV = G2mth.getVCov(varyNames,varyList,data[name]['covMatrix'])
1434                esdList.append(np.sqrt(np.inner(derivs,np.inner(VCoV,derivs.T)) ))
1435            elif 'Angle' in expr:
1436                derivs = G2mth.CalcAngleDeriv(obj.angle_dict,obj.angle_atoms, parmDict)
1437                pId = obj.angle_dict['pId']
1438                aId,bId = obj.angle_atoms
1439                varyNames = ['%d::dA%s:%d'%(pId,ip,aId) for ip in ['x','y','z']]
1440                varyNames += ['%d::dA%s:%d'%(pId,ip,bId[0]) for ip in ['x','y','z']]
1441                varyNames += ['%d::dA%s:%d'%(pId,ip,bId[1]) for ip in ['x','y','z']]
1442                VCoV = G2mth.getVCov(varyNames,varyList,data[name]['covMatrix'])
1443                esdList.append(np.sqrt(np.inner(derivs,np.inner(VCoV,derivs.T)) ))
1444            else:
1445                derivs = np.array(  # TODO: this needs to be reworked
1446                    [EvalPSvarDeriv(calcobj,parmDict.copy(),sampleDict[name],var,ESD)
1447                     for var,ESD in zip(varyList,sigs)])
1448                # needs work: calls calcobj.SetupCalc each call time
1449                # integrate into G2obj.ExpressionCalcObj
1450                if None in list(derivs):
1451                    esdList.append(None)
1452                else:
1453                    esdList.append(np.sqrt(
1454                        np.inner(derivs,np.inner(data[name]['covMatrix'],derivs.T)) ))
1455            psDict = parmDict.copy()
1456            psDict.update(sampleDict[name])
1457            try:
1458                UpdateParmDict(psDict)
1459            except:
1460                print('UpdateParmDict error on histogram',name)
1461            calcobj.UpdateDict(psDict)
1462            valList.append(calcobj.EvalExpression())
1463#            if calcobj.su is not None: esdList[-1] = calcobj.su
1464        if not esdList:
1465            esdList = None
1466        G2frame.colList += [valList]
1467        G2frame.colSigs += [esdList]
1468        colLabels += [expr]
1469        Types += [wg.GRID_VALUE_FLOAT+':10,5']
1470    #---- table build done -------------------------------------------------------------
1471
1472    # clean up the PseudoVars dict by reomving dA[xyz] & Dij
1473    remDij =   re.compile('[0-9]+:[0-9]*:D[123][123]')
1474    remdAxyz = re.compile('[0-9]+::dA[xyz]:[0-9]+')
1475    PSvarDict = {i:PSvarDict[i] for i in PSvarDict if not (remDij.match(i) or remdAxyz.match(i))}
1476
1477    # remove selected columns from table
1478    saveColLabels = colLabels[:]
1479    if G2frame.SeqTblHideList is None:      #set default hides
1480        G2frame.SeqTblHideList = [item for item in saveColLabels if 'Back' in item]
1481        G2frame.SeqTblHideList += [item for item in saveColLabels if 'dA' in item]
1482        G2frame.SeqTblHideList += [item for item in saveColLabels if ':*:D' in item]
1483    #******************************************************************************
1484    # create a set of values for example evaluation of parametric equation fitting
1485    VarDict = {}
1486    for i,var in enumerate(colLabels):
1487        if var in ['Use','Rwp',u'\u0394\u03C7\u00B2 (%)']: continue
1488        if G2frame.colList[i][0] is None:
1489            val,sig = firstValueDict.get(var,[None,None])
1490        elif G2frame.colSigs[i]:
1491            val,sig = G2frame.colList[i][0],G2frame.colSigs[i][0]
1492        else:
1493            val,sig = G2frame.colList[i][0],None
1494        if striphist(var) not in Dlookup:
1495            VarDict[var] = val
1496    # add recip cell coeff. values
1497    VarDict.update({var:val for var,val in newCellDict.values()})
1498
1499    # remove items to be hidden from table
1500    for l in reversed(range(len(colLabels))):
1501        if colLabels[l] in G2frame.SeqTblHideList:
1502            del colLabels[l]
1503            del Types[l]
1504            del G2frame.colList[l]
1505            del G2frame.colSigs[l]
1506            if deltaChiCol == l:
1507                deltaChiCol = None
1508
1509    # make a copy of the column labels substituting alternate labels when defined
1510    displayLabels = colLabels[:]
1511    for i,l in enumerate(colLabels):
1512        if l in variableLabels:
1513            displayLabels[i] = variableLabels[l]
1514           
1515    G2frame.dataWindow.ClearData()
1516    G2frame.dataWindow.currentGrids = []
1517    G2frame.dataDisplay = G2G.GSGrid(parent=G2frame.dataWindow)
1518    G2frame.dataDisplay.SetScrollRate(10,10)
1519    mainSizer = wx.BoxSizer(wx.VERTICAL)
1520    topSizer = wx.BoxSizer(wx.HORIZONTAL)
1521    topSizer.Add(wx.StaticText(G2frame.dataWindow,label='Sequential results:'),0,WACV)
1522    topSizer.Add((100,-1),1,wx.EXPAND)
1523    topSizer.Add(G2G.HelpButton(G2frame.dataWindow,helpIndex=G2frame.dataWindow.helpKey))
1524    mainSizer.Add(topSizer)
1525    mainSizer.Add(G2frame.dataDisplay)
1526    G2frame.dataWindow.GetSizer().Add(mainSizer)
1527    if histNames[0].startswith('PWDR'):
1528        #rowLabels = [str(i)+': '+l[5:30] for i,l in enumerate(histNames)]
1529        rowLabels = [l[5:] for i,l in enumerate(histNames)]
1530    else:
1531        rowLabels = histNames
1532    G2frame.SeqTable = G2G.Table([list(cl) for cl in zip(*G2frame.colList)],     # convert from columns to rows
1533        colLabels=displayLabels,rowLabels=rowLabels,types=Types)
1534    G2frame.dataDisplay.SetTable(G2frame.SeqTable, True)
1535    # make all Use editable all others ReadOnly
1536    for c in range(len(colLabels)):
1537        for r in range(nRows):
1538            if c == 1:
1539                G2frame.dataDisplay.SetReadOnly(r,c,isReadOnly=False)
1540            else:
1541                G2frame.dataDisplay.SetReadOnly(r,c,isReadOnly=True)
1542    if 'phoenix' in wx.version():
1543        G2frame.dataDisplay.Bind(wg.EVT_GRID_CELL_CHANGED, OnCellChange)
1544    else:
1545        G2frame.dataDisplay.Bind(wg.EVT_GRID_CELL_CHANGE, OnCellChange)
1546    G2frame.dataDisplay.Bind(wg.EVT_GRID_LABEL_LEFT_CLICK, PlotLeftSelect)
1547    G2frame.dataDisplay.Bind(wg.EVT_GRID_LABEL_RIGHT_CLICK, PlotRightSelect)
1548    G2frame.dataDisplay.SetRowLabelSize(8*len(histNames[0]))       #pretty arbitrary 8
1549    G2frame.dataDisplay.SetMargins(0,0)
1550    G2frame.dataDisplay.AutoSizeColumns(False)
1551    # highlight unconverged shifts
1552    if histNames[0][:4] not in ['SASD','IMG ','REFD',] and deltaChiCol is not None:
1553        for row,name in enumerate(histNames):
1554            deltaChi = G2frame.SeqTable.GetValue(row,deltaChiCol)
1555            try:
1556                if deltaChi > 10.:
1557                    G2frame.dataDisplay.SetCellStyle(row,deltaChiCol,color=wx.Colour(255,0,0))
1558                elif deltaChi > 1.0:
1559                    G2frame.dataDisplay.SetCellStyle(row,deltaChiCol,color=wx.Colour(255,255,0))
1560            except:
1561                pass
1562    G2frame.dataDisplay.InstallGridToolTip(GridSetToolTip,GridColLblToolTip)
1563    #G2frame.dataDisplay.SendSizeEvent() # resize needed on mac
1564    #G2frame.dataDisplay.Refresh() # shows colored text on mac
1565    G2frame.dataWindow.SetDataSize()
1566   
1567###############################################################################################################
1568#UpdateClusterAnalysis: results
1569###############################################################################################################
1570
1571def UpdateClusterAnalysis(G2frame,ClusData,shoNum=-1):
1572    import scipy.spatial.distance as SSD
1573    import scipy.cluster.hierarchy as SCH
1574    import scipy.cluster.vq as SCV
1575    try:
1576        import sklearn.cluster as SKC
1577        import sklearn.ensemble as SKE
1578        import sklearn.neighbors as SKN
1579        import sklearn.svm as SKVM
1580        import sklearn.metrics as SKM
1581        ClusData['SKLearn'] = True
1582    except:
1583        ClusData['SKLearn'] = False
1584       
1585    SKLearnCite = '''If you use Scikit-Learn Cluster Analysis, please cite:
1586    'Scikit-learn: Machine Learning in Python', Pedregosa, F., Varoquaux, G., Gramfort, A., Michel, V.,
1587    Thirion, B., Grisel, O., Blondel, M., Prettenhofer, P., Weiss, R., Dubourg, V., Vanderplas, J.,
1588    Passos, A., Cournapeau, D., Brucher, M., Perrot, M. and Duchesnay, E.,
1589    Journal of Machine Learning Research (2011) 12, 2825-2830.
1590'''
1591
1592    def FileSizer():
1593       
1594        def OnSelectData(event):
1595           
1596            def GetCaLimits(names):
1597                ''' scan through data selected for cluster analysis to find highest lower & lowest upper limits
1598                param: data dict: Cluster analysis info
1599                '''
1600                limits = [0.,1.e6]
1601                for name in names:
1602                    item = G2gd.GetGPXtreeItemId(G2frame,G2frame.root,name)
1603                    if 'PWDR' in name:
1604                        x = G2frame.GPXtree.GetItemPyData(item)[1][0]
1605                    else:
1606                        PDFControls = G2frame.GPXtree.GetItemPyData(G2gd.GetGPXtreeItemId(G2frame, item,'PDF Controls'))
1607                        x = PDFControls['G(R)'][1][0]
1608                    limits = [max(np.min(x),limits[0]),min(np.max(x),limits[1])]
1609                return limits                   
1610                   
1611            choices = G2gd.GetGPXtreeDataNames(G2frame,['PWDR','PDF '])
1612            if len(choices) == 0:
1613                G2G.G2MessageBox(G2frame,'No PWDR or PDF histograms found for cluster analysis.','No Histograms')
1614                return
1615            sel = []
1616            try:
1617                if 'Cluster Data' in ClusData:
1618                    sel = [choices.index(item) for item in ClusData['Cluster Data']['Files']]
1619            except ValueError:  #data changed somehow - start fresh
1620                sel = []
1621            dlg = G2G.G2MultiChoiceDialog(G2frame,
1622                'Select datasets to include.\n PWDR or PDF',
1623                'Cluster analysis data selection',choices)
1624            dlg.SetSelections(sel)
1625            names = []
1626            Type = ''
1627            if dlg.ShowModal() == wx.ID_OK:
1628                for sel in dlg.GetSelections():
1629                    if not Type:
1630                        Type = choices[sel].split()[0]
1631                    if Type != choices[sel].split()[0]:
1632                        G2G.G2MessageBox(G2frame,'Histogram types not all the same; revise selection','Histogram type mismatch')
1633                        return
1634                    names.append(choices[sel])
1635                ClusData['Files'] = names
1636                limits = GetCaLimits(names)
1637                ClusData['Limits'] = [copy.copy(limits),limits]
1638                ClusData['DataMatrix'] = []
1639                ClusData['ConDistMat'] = []
1640                ClusData['CLuZ'] = None
1641                ClusData['codes'] = None
1642                ClusData['plots'] = 'All'
1643               
1644            dlg.Destroy()
1645            G2frame.SetTitleByGPX()
1646           
1647            wx.CallAfter(UpdateClusterAnalysis,G2frame,ClusData)
1648           
1649        fileSizer = wx.BoxSizer(wx.HORIZONTAL)
1650        Type = 'PWDR'
1651        if len(ClusData['Files']):
1652            if 'PDF' in ClusData['Files'][0]:
1653                Type = 'PDF'
1654            lbl = 'Cluster Analysis with %d %s datasets: '%(len(ClusData['Files']),Type)
1655            ClusData['Type'] = Type
1656        else:
1657            lbl = 'No data selected for Cluster Analysis'
1658        fileSizer.Add(wx.StaticText(G2frame.dataWindow,label=lbl),0,WACV)
1659        selSeqData = wx.Button(G2frame.dataWindow,label=' Select datasets')           
1660        selSeqData.Bind(wx.EVT_BUTTON,OnSelectData)
1661        fileSizer.Add(selSeqData,0,WACV)
1662        return fileSizer
1663   
1664    def LimitSizer():
1665       
1666        def CheckLimits(invalid,value,tc):
1667            #TODO this needs a check on ultimate size of data array; loop over names & count points?
1668
1669            if ClusData['Limits'][1][1] < ClusData['Limits'][1][0]:
1670                ClusData['Limits'][1] = [ClusData['Limits'][1][1],ClusData['Limits'][1][0]]
1671            ClusData['DataMatrix'] = []
1672            ClusData['ConDistMat'] = []
1673            ClusData['CLuZ'] = None
1674           
1675            wx.CallAfter(UpdateClusterAnalysis,G2frame,ClusData)
1676           
1677        limitSizer = wx.BoxSizer(wx.HORIZONTAL)
1678        limitSizer.Add(wx.StaticText(G2frame.dataWindow,label='Enter cluster analysis data limits: '),0,WACV)
1679        limitSizer.Add(G2G.ValidatedTxtCtrl(G2frame.dataWindow,ClusData['Limits'][1],0,nDig=(10,3),
1680            xmin=ClusData['Limits'][0][0],xmax=ClusData['Limits'][0][1],OnLeave=CheckLimits),0,WACV)
1681        limitSizer.Add(G2G.ValidatedTxtCtrl(G2frame.dataWindow,ClusData['Limits'][1],1,nDig=(10,3),
1682            xmin=ClusData['Limits'][0][0],xmax=ClusData['Limits'][0][1],OnLeave=CheckLimits),0,WACV)
1683        return limitSizer
1684   
1685    def GetYMatSize():
1686        nData = 0
1687        for name in ClusData['Files']:
1688            item = G2gd.GetGPXtreeItemId(G2frame,G2frame.root,name)
1689            if 'PWDR' in name:
1690                x = G2frame.GPXtree.GetItemPyData(item)[1][0]
1691            else:
1692                PDFControls = G2frame.GPXtree.GetItemPyData(G2gd.GetGPXtreeItemId(G2frame, item,'PDF Controls'))
1693                x = PDFControls['G(R)'][1][0]
1694            iBeg = np.searchsorted(x,ClusData['Limits'][1][0])
1695            iFin = np.searchsorted(x,ClusData['Limits'][1][1])+1
1696            nData += (iFin-iBeg)
1697        return nData
1698
1699    def OnMakeArray(event):
1700        Limits = ClusData['Limits'][1]
1701        Start = True
1702        nFiles = len(ClusData['Files'])
1703        CAmatrix = []
1704        try:
1705            for iname,name in enumerate(ClusData['Files']):
1706                item = G2gd.GetGPXtreeItemId(G2frame,G2frame.root,name)
1707                if 'PWDR' in name:
1708                    x = G2frame.GPXtree.GetItemPyData(item)[1][0]
1709                    y = G2frame.GPXtree.GetItemPyData(item)[1][1]
1710                else:
1711                    PDFControls = G2frame.GPXtree.GetItemPyData(G2gd.GetGPXtreeItemId(G2frame, item,'PDF Controls'))
1712                    x = PDFControls['G(R)'][1][0]
1713                    y = PDFControls['G(R)'][1][1]
1714                iBeg = np.searchsorted(x,Limits[0])
1715                iFin = np.searchsorted(x,Limits[1])
1716                if Start:
1717                    CAmatrix = np.empty((nFiles,iFin-iBeg+1))
1718                    CAmatrix[iname] = y[iBeg:iFin+1]
1719                    Start = False
1720                else:
1721                    CAmatrix[iname] = y[iBeg:iFin+1]
1722        except ValueError:
1723            G2G.G2MessageBox(G2frame,
1724                'Data for %s is mismatched in length to those already processed or has different step size'%name,
1725                'No Cluster Analysis possible')
1726            return
1727        ClusData['DataMatrix'] = CAmatrix
1728        ClusData['ConDistMat'] = []
1729        ClusData['CLuZ'] = None
1730        wx.CallAfter(UpdateClusterAnalysis,G2frame,ClusData)
1731       
1732    def MethodSizer():
1733       
1734        def OnClusterMethod(event):
1735            ClusData['Method'] = method.GetValue()
1736            ClusData['ConDistMat'] = []
1737            ClusData['CLuZ'] = None
1738            OnCompute(event)
1739            wx.CallAfter(UpdateClusterAnalysis,G2frame,ClusData)
1740           
1741        def OnCompute(event):
1742            if 'minkowski' in ClusData['Method']:
1743                ClusData['ConDistMat'] = SSD.pdist(ClusData['DataMatrix'],ClusData['Method'],p=int(ClusData['MinkP']))
1744            else:
1745                ClusData['ConDistMat'] = SSD.pdist(ClusData['DataMatrix'],ClusData['Method'])
1746            wx.CallAfter(UpdateClusterAnalysis,G2frame,ClusData)
1747           
1748        def OnExponent(event):
1749            ClusData['MinkP'] = minp.GetValue()
1750            ClusData['ConDistMat'] = []
1751            ClusData['CLuZ'] = None
1752            OnCompute(event)
1753            wx.CallAfter(UpdateClusterAnalysis,G2frame,ClusData)
1754       
1755        choice = ['braycurtis', 'canberra', 'chebyshev', 'cityblock', 'correlation', 'cosine',  \
1756            'euclidean', 'jensenshannon', 'minkowski', 'seuclidean',  'sqeuclidean']
1757        methsizer = wx.BoxSizer(wx.HORIZONTAL)
1758        methsizer.Add(wx.StaticText(G2frame.dataWindow,label='Select cluster analysis distance method: '),0,WACV)
1759        method = wx.ComboBox(G2frame.dataWindow,choices=choice,style=wx.CB_READONLY|wx.CB_DROPDOWN)
1760        method.SetValue(ClusData['Method'])
1761        method.Bind(wx.EVT_COMBOBOX, OnClusterMethod)
1762        methsizer.Add(method,0,WACV)
1763        if 'minkowski' in ClusData['Method']:
1764            methsizer.Add(wx.StaticText(G2frame.dataWindow,label=' exponent: '),0,WACV)
1765            choicep = ['1','2','3','4','10']
1766            minp = wx.ComboBox(G2frame.dataWindow,choices=choicep,style=wx.CB_READONLY|wx.CB_DROPDOWN)
1767            minp.SetValue(ClusData['MinkP'])
1768            minp.Bind(wx.EVT_COMBOBOX, OnExponent)
1769            methsizer.Add(minp,0,WACV)
1770        compute = wx.Button(G2frame.dataWindow,label='Compute distance matrix')
1771        compute.Bind(wx.EVT_BUTTON,OnCompute)
1772        methsizer.Add(compute,0,WACV)
1773        return methsizer
1774               
1775    def HierSizer():
1776       
1777        def OnLinkMethod(event):
1778            ClusData['LinkMethod'] = method.GetValue()
1779            OnCompute(event)
1780       
1781        def OnOptOrd(event):
1782            ClusData['Opt Order'] = not ClusData['Opt Order']
1783            OnCompute(event)
1784       
1785        def OnCompute(event):
1786            ClusData['CLuZ'] = SCH.linkage(ClusData['ConDistMat'],method=ClusData['LinkMethod'],optimal_ordering=ClusData['Opt Order'])
1787            wx.CallAfter(UpdateClusterAnalysis,G2frame,ClusData)
1788       
1789        hierSizer = wx.BoxSizer(wx.HORIZONTAL)
1790        hierSizer.Add(wx.StaticText(G2frame.dataWindow,label='Hierarchical clustering: Select linkage method: '),0,WACV)
1791        choice = ['single','complete','average','weighted','centroid','median','ward',]
1792        method = wx.ComboBox(G2frame.dataWindow,choices=choice,style=wx.CB_READONLY|wx.CB_DROPDOWN)
1793        method.SetValue(ClusData['LinkMethod'])
1794        method.Bind(wx.EVT_COMBOBOX, OnLinkMethod)
1795        hierSizer.Add(method,0,WACV)
1796        optOrd = wx.CheckBox(G2frame.dataWindow,label=' Optimal order? ')
1797        optOrd.Bind(wx.EVT_CHECKBOX,OnOptOrd)
1798        optOrd.SetValue(ClusData['Opt Order'])
1799        hierSizer.Add(optOrd,0,WACV)
1800        compute = wx.Button(G2frame.dataWindow,label='Compute')
1801        compute.Bind(wx.EVT_BUTTON,OnCompute)
1802        hierSizer.Add(compute,0,WACV)
1803        return hierSizer
1804   
1805    def kmeanSizer():
1806       
1807        def OnClusNum(event):
1808            ClusData['NumClust'] = int(numclust.GetValue())
1809            OnCompute(event)
1810           
1811        def OnCompute(event):
1812            whitMat = SCV.whiten(ClusData['DataMatrix'])
1813            codebook,dist = SCV.kmeans2(whitMat,ClusData['NumClust'])   #use K-means++
1814            ClusData['codes'],ClusData['dists'] = SCV.vq(whitMat,codebook)
1815            wx.CallAfter(UpdateClusterAnalysis,G2frame,ClusData)
1816       
1817        kmeanssizer = wx.BoxSizer(wx.HORIZONTAL)
1818        choice = [str(i) for i in range(2,16)]
1819        kmeanssizer.Add(wx.StaticText(G2frame.dataWindow,label='K-means clustering: select number of clusters (2-15): '),0,WACV)
1820        numclust = wx.ComboBox(G2frame.dataWindow,choices=choice,style=wx.CB_READONLY|wx.CB_DROPDOWN)
1821        numclust.SetValue(str(ClusData['NumClust']))
1822        numclust.Bind(wx.EVT_COMBOBOX,OnClusNum)
1823        kmeanssizer.Add(numclust,0,WACV)
1824        compute = wx.Button(G2frame.dataWindow,label='Compute')
1825        compute.Bind(wx.EVT_BUTTON,OnCompute)
1826        kmeanssizer.Add(compute)
1827        return kmeanssizer
1828   
1829    def OnPlotSel(event):
1830        ClusData['plots'] = plotsel.GetValue()
1831        G2plt.PlotClusterXYZ(G2frame,YM,XYZ,ClusData,PlotName=ClusData['Method'],Title=ClusData['Method'])
1832       
1833    def ScikitSizer():
1834       
1835        def OnClusMethod(event):
1836            ClusData['Scikit'] = clusMethod.GetValue()
1837            OnCompute(event)
1838       
1839        def OnClusNum(event):
1840            ClusData['NumClust'] = int(numclust.GetValue())
1841            OnCompute(event)
1842           
1843        def OnCompute(event):
1844            whitMat = SCV.whiten(ClusData['DataMatrix'])
1845            if ClusData['Scikit'] == 'K-Means':
1846                result = SKC.KMeans(n_clusters=ClusData['NumClust'],algorithm='elkan',init='k-means++').fit(whitMat)
1847                print('K-Means sum squared dist. to means %.2f'%result.inertia_)
1848            elif ClusData['Scikit'] == 'Spectral clustering':
1849                result = SKC.SpectralClustering(n_clusters=ClusData['NumClust']).fit(whitMat)
1850            elif ClusData['Scikit'] == 'Mean-shift':
1851                result = SKC.MeanShift().fit(whitMat)
1852                print('Number of Mean-shift clusters found: %d'%(np.max(result.labels_)+1))
1853            elif ClusData['Scikit'] == 'Affinity propagation':
1854                result = SKC.AffinityPropagation(affinity='precomputed',damping=0.5).fit(SSD.squareform(ClusData['ConDistMat']))
1855                print('Number of Affinity propagation clusters found: %d'%(np.max(result.labels_)+1))
1856            elif ClusData['Scikit'] == 'Agglomerative clustering':
1857                result = SKC.AgglomerativeClustering(n_clusters=ClusData['NumClust'],
1858                    affinity='precomputed',linkage='average').fit(SSD.squareform(ClusData['ConDistMat']))
1859           
1860            ClusData['codes'] = result.labels_
1861            ClusData['Metrics'] = Metrics(whitMat,result)
1862            wx.CallAfter(UpdateClusterAnalysis,G2frame,ClusData)
1863           
1864        def Metrics(whitMat,result):
1865            if np.max(result.labels_) >= 1:
1866                Scoeff = SKM.silhouette_score(whitMat,result.labels_,metric='euclidean')
1867                print('Silhouette Coefficient: %.3f'%Scoeff)
1868                CHcoeff = SKM.calinski_harabasz_score(whitMat,result.labels_)
1869                print('Calinski-Harabasz index (Variance ratio): %.3f'%CHcoeff)
1870                DBcoeff = SKM.davies_bouldin_score(whitMat,result.labels_)
1871                print('Davies-Bouldin Index: %.3f'%DBcoeff)
1872                return Scoeff,CHcoeff,DBcoeff
1873            else:
1874                print('number of clusters found must be > 1 for metrics to be determined')
1875                return None
1876                               
1877        scikitSizer = wx.BoxSizer(wx.VERTICAL)
1878        scikitSizer.Add(wx.StaticText(G2frame.dataWindow,label=SKLearnCite))
1879        choice = ['K-Means','Affinity propagation','Mean-shift','Spectral clustering','Agglomerative clustering']
1880        clusSizer = wx.BoxSizer(wx.HORIZONTAL)
1881        clusSizer.Add(wx.StaticText(G2frame.dataWindow,label='Select clustering method: '),0,WACV)
1882        clusMethod = wx.ComboBox(G2frame.dataWindow,choices=choice,style=wx.CB_READONLY|wx.CB_DROPDOWN)
1883        clusMethod.SetValue(ClusData['Scikit'])
1884        clusMethod.Bind(wx.EVT_COMBOBOX,OnClusMethod)
1885        clusSizer.Add(clusMethod,0,WACV)
1886        if ClusData['Scikit'] in ['K-Means','Spectral clustering','Agglomerative clustering']:
1887            nchoice = [str(i) for i in range(2,16)]
1888            clusSizer.Add(wx.StaticText(G2frame.dataWindow,label=' Select number of clusters (2-15): '),0,WACV)
1889            numclust = wx.ComboBox(G2frame.dataWindow,choices=nchoice,style=wx.CB_READONLY|wx.CB_DROPDOWN)
1890            numclust.SetValue(str(ClusData['NumClust']))
1891            numclust.Bind(wx.EVT_COMBOBOX,OnClusNum)
1892            clusSizer.Add(numclust,0,WACV)
1893        compute = wx.Button(G2frame.dataWindow,label='Compute')
1894        compute.Bind(wx.EVT_BUTTON,OnCompute)
1895        clusSizer.Add(compute,0,WACV)
1896        scikitSizer.Add(clusSizer)
1897        useTxt = '%s used the whitened data matrix'%ClusData['Scikit']
1898        if ClusData['Scikit'] in ['Agglomerative clustering','Affinity propagation']:
1899            useTxt = '%s used %s for distance method'%(ClusData['Scikit'],ClusData['Method'])
1900        scikitSizer.Add(wx.StaticText(G2frame.dataWindow,label=useTxt))
1901        if ClusData.get('Metrics',None) is not None:
1902            metrics = ClusData['Metrics']
1903            scikitSizer.Add(wx.StaticText(G2frame.dataWindow,
1904                label='Metrics: Silhoutte: %.3f, Variance: %.3f, Davies-Bouldin: %.3f'%(metrics[0],metrics[1],metrics[2])))
1905        return scikitSizer
1906   
1907    def memberSizer():
1908       
1909        def OnClusNum(event):
1910            shoNum = int(numclust.GetValue())
1911            wx.CallAfter(UpdateClusterAnalysis,G2frame,ClusData,shoNum)
1912           
1913        def OnSelection(event):
1914            name = cluslist.GetStringSelection()
1915            item = G2gd.GetGPXtreeItemId(G2frame,G2frame.root,name)
1916            G2frame.PatternId = item
1917            if 'PWDR' in name:
1918                G2plt.PlotPatterns(G2frame,newPlot=False,plotType='PWDR')
1919            else: #PDF
1920                data = G2frame.GPXtree.GetItemPyData(G2gd.GetGPXtreeItemId(G2frame, item,'PDF Controls'))
1921                G2plt.PlotISFG(G2frame,data,plotType='G(R)')
1922               
1923        #need 15 colors; values adjusted to match xkcs/PCA plot colors. NB: RGB reverse order from xkcd values.   
1924        Colors = [['xkcd:blue',0xff0000],['xkcd:red',0x0000ff],['xkcd:green',0x00a000],['xkcd:cyan',0xd0d000], 
1925                  ['xkcd:magenta',0xa000a0],['xkcd:black',0x000000],['xkcd:pink',0xb469ff],['xkcd:brown',0x13458b],
1926                  ['xkcd:teal',0x808000],['xkcd:orange',0x008cff],['xkcd:grey',0x808080],['xkcd:violet',0xe22b8a],
1927                  ['xkcd:aqua',0xaaaa00],['xkcd:blueberry',0xcd5a6a],['xkcd:bordeaux',0x00008b]] 
1928        NClust = np.max(ClusData['codes'])
1929        memSizer = wx.BoxSizer(wx.VERTICAL)
1930        memSizer.Add(wx.StaticText(G2frame.dataWindow,label='Cluster populations (colors refer to cluster colors in PCA plot):'))       
1931        for i in range(NClust+1):
1932            nPop= len(ClusData['codes'])-np.count_nonzero(ClusData['codes']-i)
1933            txt = wx.StaticText(G2frame.dataWindow,label='Cluster #%d has %d members'%(i,nPop))
1934            txt.SetForegroundColour(wx.Colour(Colors[i][1]))
1935            if wx.Colour(Colors[i][1]).GetLuminance() > 0.5:
1936                txt.SetBackgroundColour(wx.Colour(50,50,50))
1937            else:
1938                txt.SetBackgroundColour(wx.Colour(200,200,200))
1939            memSizer.Add(txt)       
1940        headSizer = wx.BoxSizer(wx.HORIZONTAL)
1941        headSizer.Add(wx.StaticText(G2frame.dataWindow,label='Select cluster to list members: '),0,WACV)       
1942        choice = [str(i) for i in range(NClust+1)]
1943        numclust = wx.ComboBox(G2frame.dataWindow,choices=choice,value=str(shoNum),style=wx.CB_READONLY|wx.CB_DROPDOWN)
1944        numclust.Bind(wx.EVT_COMBOBOX,OnClusNum)
1945        headSizer.Add(numclust,0,WACV)
1946        memSizer.Add(headSizer)       
1947        if shoNum >= 0:
1948            memSizer.Add(wx.StaticText(G2frame.dataWindow,label='Members of cluster %d (select to show data plot):'%shoNum))
1949            ClusList = []
1950            for i,item in enumerate(ClusData['Files']):
1951                 if ClusData['codes'][i] == shoNum:
1952                     ClusList.append(item)               
1953            cluslist = wx.ListBox(G2frame.dataWindow, choices=ClusList)
1954            cluslist.SetForegroundColour(wx.Colour(Colors[shoNum][1]))
1955            if wx.Colour(Colors[shoNum][1]).GetLuminance() > 0.5:
1956                cluslist.SetBackgroundColour(wx.Colour(50,50,50))
1957            else:
1958                cluslist.SetBackgroundColour(wx.Colour(200,200,200))
1959            cluslist.Bind(wx.EVT_LISTBOX,OnSelection)
1960            memSizer.Add(cluslist)
1961        return memSizer
1962   
1963    def outlierSizer():
1964       
1965        def OnOutSel(event):
1966            ClusData['OutMethod'] = outsel.GetValue()
1967            OnCompute(event)
1968           
1969        def OnCompute(event):
1970            if ClusData['OutMethod'] == 'One-Class SVM':
1971                ClusData['codes'] = SKVM.OneClassSVM().fit_predict(ClusData['DataMatrix'])  #codes = 1 or -1
1972            elif ClusData['OutMethod'] == 'Isolation Forest':
1973                ClusData['codes'] = SKE.IsolationForest().fit_predict(ClusData['DataMatrix'])
1974            elif ClusData['OutMethod'] == 'Local Outlier Factor':
1975                ClusData['codes'] = SKN.LocalOutlierFactor().fit_predict(ClusData['DataMatrix'])
1976            wx.CallAfter(UpdateClusterAnalysis,G2frame,ClusData,shoNum)
1977           
1978        outSizer = wx.BoxSizer(wx.VERTICAL)
1979        outSizer.Add(wx.StaticText(G2frame.dataWindow,label='Outlier (bad data) analysis with Scikit-learn:'))
1980        choice = ['One-Class SVM','Isolation Forest','Local Outlier Factor']
1981        outline = wx.BoxSizer(wx.HORIZONTAL)
1982        outline.Add(wx.StaticText(G2frame.dataWindow,label='Select method: '),0,WACV)
1983        outsel = wx.ComboBox(G2frame.dataWindow,choices=choice,style=wx.CB_READONLY|wx.CB_DROPDOWN)
1984        outsel.SetValue(ClusData['OutMethod'])
1985        outsel.Bind(wx.EVT_COMBOBOX,OnOutSel)
1986        outline.Add(outsel,0,WACV)
1987        compute = wx.Button(G2frame.dataWindow,label='Compute')
1988        compute.Bind(wx.EVT_BUTTON,OnCompute)
1989        outline.Add(compute,0,WACV)
1990        outSizer.Add(outline)
1991        return outSizer
1992           
1993    def OnSelection(event):
1994        name = outlist.GetStringSelection()
1995        item = G2gd.GetGPXtreeItemId(G2frame,G2frame.root,name)
1996        G2frame.PatternId = item
1997        if 'PWDR' in name:
1998            G2frame.PatternId = G2gd.GetGPXtreeItemId(G2frame,G2frame.root,name)
1999            G2plt.PlotPatterns(G2frame,newPlot=False,plotType='PWDR')
2000        else:
2001            data = G2frame.GPXtree.GetItemPyData(G2gd.GetGPXtreeItemId(G2frame, item,'PDF Controls'))
2002            G2plt.PlotISFG(G2frame,data,plotType='G(R)')
2003
2004    #patch
2005    ClusData['SKLearn'] = ClusData.get('SKLearn',False)
2006    ClusData['plots'] = ClusData.get('plots','All')
2007    ClusData['Scikit'] = ClusData.get('Scikit','K-Means')
2008    ClusData['OutMethod'] = ClusData.get('OutMethod','Isolation Forest')
2009    ClusData['MinkP'] = ClusData.get('MinkP','2')
2010    #end patch
2011    G2frame.dataWindow.ClearData()
2012    bigSizer = wx.BoxSizer(wx.HORIZONTAL)
2013    mainSizer = wx.BoxSizer(wx.VERTICAL)
2014    mainSizer.Add((5,5),0)
2015    subSizer = wx.BoxSizer(wx.HORIZONTAL)
2016    subSizer.Add((-1,-1),1,wx.EXPAND)
2017    subSizer.Add(wx.StaticText(G2frame.dataWindow,label='Scipy Cluster Analysis: '),0,WACV)   
2018    subSizer.Add((-1,-1),1,wx.EXPAND)
2019    mainSizer.Add(subSizer,0,wx.EXPAND)
2020    mainSizer.Add((5,5),0)
2021   
2022    mainSizer.Add(FileSizer())
2023    if len(ClusData['Files']):
2024        mainSizer.Add(LimitSizer())
2025        mainSizer.Add(wx.StaticText(G2frame.dataWindow,label='Cluster Analysis input data size: %d'%(GetYMatSize())))
2026        mainSizer.Add(wx.StaticText(G2frame.dataWindow,label=
2027            '(Examine any %s plot for reasonable limits; any change will clear Cluster data matrix) '%ClusData['Type']))
2028        makeArray = wx.Button(G2frame.dataWindow,label='Make Cluster Analysis data array')
2029        makeArray.Bind(wx.EVT_BUTTON,OnMakeArray)
2030        mainSizer.Add(makeArray)
2031        if len(ClusData['DataMatrix']):           
2032
2033            G2G.HorizontalLine(mainSizer,G2frame.dataWindow)
2034            mainSizer.Add(wx.StaticText(G2frame.dataWindow,label='Distance Cluster Analysis:'))
2035            mainSizer.Add(MethodSizer())
2036            if len(ClusData['ConDistMat']):
2037                YM = SSD.squareform(ClusData['ConDistMat'])
2038                U,s,VT = nl.svd(YM) #s are the Eigenvalues
2039                ClusData['PCA'] = s
2040                s[3:] = 0.
2041                S = np.diag(s)
2042                XYZ = np.dot(S,VT)
2043                G2plt.PlotClusterXYZ(G2frame,YM,XYZ[:3,:],ClusData,PlotName=ClusData['Method'],Title=ClusData['Method'])
2044                G2G.HorizontalLine(mainSizer,G2frame.dataWindow)
2045                mainSizer.Add(wx.StaticText(G2frame.dataWindow,label='Hierarchical Cluster Analysis:'))
2046                mainSizer.Add(HierSizer())
2047               
2048                G2G.HorizontalLine(mainSizer,G2frame.dataWindow)
2049                mainSizer.Add(wx.StaticText(G2frame.dataWindow,label='K-means Cluster Analysis:'))
2050                mainSizer.Add(kmeanSizer())
2051                if 'dists' in ClusData:
2052                    kmeansres = wx.BoxSizer(wx.HORIZONTAL)
2053                    kmeansres.Add(wx.StaticText(G2frame.dataWindow,label='K-means ave. dist = %.2f'%np.mean(ClusData['dists'])))
2054                    mainSizer.Add(kmeansres)
2055            if ClusData['codes'] is not None:
2056                G2G.HorizontalLine(mainSizer,G2frame.dataWindow)
2057                mainSizer.Add(memberSizer())
2058            G2G.HorizontalLine(mainSizer,G2frame.dataWindow)
2059            plotSizer = wx.BoxSizer(wx.HORIZONTAL)
2060            plotSizer.Add(wx.StaticText(G2frame.dataWindow,label='Plot selection: '),0,WACV)
2061            if ClusData['CLuZ'] is None:
2062                choice = ['All','Distances','3D PCA','2D PCA','Diffs']
2063            else:
2064                choice = ['All','Distances','Dendrogram','2D PCA','3D PCA','Diffs']
2065            plotsel = wx.ComboBox(G2frame.dataWindow,choices=choice,style=wx.CB_READONLY|wx.CB_DROPDOWN)
2066            plotsel.SetValue(str(ClusData['plots']))
2067            plotsel.Bind(wx.EVT_COMBOBOX,OnPlotSel)
2068            plotSizer.Add(plotsel,0,WACV)
2069            mainSizer.Add(plotSizer)
2070           
2071            if ClusData['SKLearn'] and len(ClusData['ConDistMat']):
2072                G2G.HorizontalLine(mainSizer,G2frame.dataWindow)
2073                subSizer = wx.BoxSizer(wx.HORIZONTAL)
2074                subSizer.Add((-1,-1),1,wx.EXPAND)
2075                subSizer.Add(wx.StaticText(G2frame.dataWindow,label='Scikit-Learn Cluster Analysis: '),0,WACV)   
2076                subSizer.Add((-1,-1),1,wx.EXPAND)
2077                mainSizer.Add(subSizer,0,wx.EXPAND)
2078                mainSizer.Add(ScikitSizer())
2079               
2080        if ClusData['SKLearn'] and len(ClusData['DataMatrix']) > 15:
2081            G2G.HorizontalLine(mainSizer,G2frame.dataWindow)
2082            mainSizer.Add(outlierSizer())
2083            Nout = 0
2084            if ClusData['codes'] is not None:
2085                Nout = len(ClusData['codes'])-np.count_nonzero(ClusData['codes']+1)
2086            if Nout > 0:
2087                mainSizer.Add(wx.StaticText(G2frame.dataWindow,
2088                    label='%d Probable outlier data found by %s (select to show data plot):'%(Nout,ClusData['OutMethod'])))
2089                OutList = []
2090                for i,item in enumerate(ClusData['Files']):
2091                     if ClusData['codes'][i] < 0:
2092                         OutList.append(item)               
2093                outlist = wx.ListBox(G2frame.dataWindow, choices=OutList)
2094                outlist.Bind(wx.EVT_LISTBOX,OnSelection)
2095                mainSizer.Add(outlist)       
2096            else:
2097                mainSizer.Add(wx.StaticText(G2frame.dataWindow,label='No outlier data found'))
2098               
2099    bigSizer.Add(mainSizer)
2100       
2101    bigSizer.Add(G2G.HelpButton(G2frame.dataWindow,helpIndex=G2frame.dataWindow.helpKey))
2102    bigSizer.Layout()
2103    bigSizer.FitInside(G2frame.dataWindow)
2104    G2frame.dataWindow.SetSizer(bigSizer)
2105    G2frame.dataWindow.SetDataSize()
2106    G2frame.SendSizeEvent()
2107
2108
Note: See TracBrowser for help on using the repository browser.