source: trunk/G2compare.py @ 4801

Last change on this file since 4801 was 4801, checked in by toby, 2 years ago

Cleanup deleted hists in Phase/data; improve help button location; mac: use default browser for help; G2compare filter projects by histogram

  • Property svn:eol-style set to native
  • Property svn:keywords set to Date Author Revision URL Id
File size: 36.9 KB
Line 
1#!/usr/bin/env python
2# -*- coding: utf-8 -*-
3#GSAS-II Data/Model Comparison
4########### SVN repository information ###################
5# $Date: 2021-02-06 04:14:23 +0000 (Sat, 06 Feb 2021) $
6# $Author: toby $
7# $Revision: 4801 $
8# $URL: trunk/G2compare.py $
9# $Id: G2compare.py 4801 2021-02-06 04:14:23Z toby $
10########### SVN repository information ###################
11'''
12.. _G2compare:
13
14*G2compare: Tool for project comparison*
15---------------------------------------------
16'''
17
18#TODO:
19# Prince-test next
20# Make Phase unique? (need a phaselist)
21# more graphics
22# display more in datawindow
23
24import sys
25import os
26import platform
27import glob
28if '2' in platform.python_version_tuple()[0]:
29    import cPickle
30else:
31    try:
32        import _pickle as cPickle
33    except:
34        print('Warning: failed to import the optimized Py3 pickle (_pickle)')
35        import pickle as cPickle
36
37import wx
38import numpy as np
39import matplotlib as mpl
40try:
41    import OpenGL as ogl
42except ImportError:
43    pass
44import scipy as sp
45
46import GSASIIpath
47GSASIIpath.SetVersionNumber("$Revision: 4801 $")
48import GSASIIfiles as G2fil
49import GSASIIplot as G2plt
50import GSASIIdataGUI as G2gd
51import GSASIIctrlGUI as G2G
52import GSASIIobj as G2obj
53
54__version__ = '0.0.1'
55
56# math to do F-test
57def RC2Ftest(npts,RChiSq0,nvar0,RChiSq1,nvar1):
58    '''Compute the F-test probability that a model expanded with added
59    parameters (relaxed model) is statistically more likely than the
60    constrained (base) model
61    :param int npts: number of observed diffraction data points
62    :param float RChiSq0: Reduced Chi**2 for the base model
63    :param int nvar0: number of refined variables in the base model
64    :param float RChiSq0: Reduced Chi**2 for the relaxed model
65    :param int nvar1: number of refined variables in the relaxed model
66    '''
67    if nvar1 == nvar0:
68        raise Exception("parameter # agree, test is not valid")
69    elif nvar1 < nvar0:
70        print("Warning: RwFtest used with base/relaxed models reversed")
71        RChiSq0,nvar0,RChiSq1,nvar1 = RChiSq1,nvar1,RChiSq0,nvar0
72    ratio = RChiSq0 / RChiSq1
73    nu1 = float(nvar1 - nvar0)
74    nu2 = float(npts - nvar1)
75    F = (ratio - 1.) * nu2 / nu1
76    import scipy.stats
77    return scipy.stats.f.cdf(F,nu1,nu2)
78
79def RwFtest(npts,Rwp0,nvar0,Rwp1,nvar1):
80    '''Compute the F-test probability that a model expanded with added
81    parameters (relaxed model) is statistically more likely than the
82    constrained (base) model
83    :param int npts: number of observed diffraction data points
84    :param float Rwp0: Weighted profile R-factor or GOF for the base model
85    :param int nvar0: number of refined variables in the base model
86    :param float Rwp1: Weighted profile R-factor or GOF for the relaxed model
87    :param int nvar1: number of refined variables in the relaxed model
88    '''
89    if nvar1 == nvar0:
90        raise Exception("parameter # agree, test is not valid")
91    elif nvar1 < nvar0:
92        print("Warning: RwFtest used with base/relaxed models reversed")
93        Rwp0,nvar0,Rwp1,nvar1 = Rwp1,nvar1,Rwp0,nvar0
94    ratio = (Rwp0 / Rwp1)**2
95    nu1 = float(nvar1 - nvar0)
96    nu2 = float(npts - nvar1)
97    F = (ratio - 1.) * nu2 / nu1
98    import scipy.stats
99    return scipy.stats.f.cdf(F,nu1,nu2)
100
101def cPickleLoad(fp):
102    if '2' in platform.python_version_tuple()[0]:
103        return cPickle.load(fp)
104    else:
105       return cPickle.load(fp,encoding='latin-1')
106           
107def main(application):
108    '''Start up the GSAS-II GUI'''                       
109    knownVersions = ['3.6','3.7','3.8','3.9']
110    if platform.python_version()[:3] not in knownVersions: 
111        dlg = wx.MessageDialog(None, 
112                'GSAS-II requires Python 3.6+\n Yours is '+sys.version.split()[0],
113                'Python version error',  wx.OK)
114        try:
115            dlg.ShowModal()
116        finally:
117            dlg.Destroy()
118        sys.exit()
119           
120    application.main = MakeTopWindow(None)  # application.main is the main wx.Frame
121    application.SetTopWindow(application.main)
122    # save the current package versions
123    application.main.PackageVersions = G2fil.get_python_versions([wx, mpl, np, sp, ogl])
124    try:
125        application.SetAppDisplayName('GSAS-II Compare')
126    except:
127        pass
128    #application.GetTopWindow().SendSizeEvent()
129    application.GetTopWindow().Show(True)
130    return application.GetTopWindow()
131   
132class MakeTopWindow(wx.Frame):
133    '''Define the main frame and its associated menu items
134    '''
135    def __init__(self, parent):
136        size = wx.Size(700,450)
137        wx.Frame.__init__(self, name='dComp', parent=parent,
138            size=size,style=wx.DEFAULT_FRAME_STYLE, title='GSAS-II data/model comparison')
139        # misc vars
140        self.VcovColor = 'RdYlGn'       
141        # plot window
142        try:
143            size = GSASIIpath.GetConfigValue('Plot_Size')
144            if type(size) is tuple:
145                pass
146            elif type(size) is str:
147                size = eval(size)
148            else:
149                raise Exception
150        except:
151            size = wx.Size(700,600)               
152        self.plotFrame = wx.Frame(None,-1,'dComp Plots',size=size,
153            style=wx.DEFAULT_FRAME_STYLE ^ wx.CLOSE_BOX)
154        self.G2plotNB = G2plt.G2PlotNoteBook(self.plotFrame,G2frame=self)
155        #self.plotFrame.Show()
156        self.plotFrame.Show(False)  # until we have some graphics, hide the plot window
157        # menus
158        Frame = self.GetTopLevelParent() # same as self
159        self.MenuBar = wx.MenuBar()
160        File = wx.Menu(title='')
161        self.MenuBar.Append(menu=File, title='&File')
162        item = File.Append(wx.ID_ANY,'&Import single project...\tCtrl+O','Open a GSAS-II project file (*.gpx)')           
163        self.Bind(wx.EVT_MENU, self.onLoadGPX, id=item.GetId())
164        item = File.Append(wx.ID_ANY,'&Import multiple projects...\tCtrl+U','Open a GSAS-II project file (*.gpx)')
165        self.Bind(wx.EVT_MENU, self.onLoadMultGPX, id=item.GetId())
166        item = File.Append(wx.ID_ANY,'&Wildcard import of projects...\tCtrl+W','Open a GSAS-II project file (*.gpx)')           
167        self.Bind(wx.EVT_MENU, self.onLoadWildGPX, id=item.GetId())
168#        item = File.Append(wx.ID_ANY,'&Import selected...','Open a GSAS-II project file (*.gpx)')
169#        self.Bind(wx.EVT_MENU, self.onLoadSel, id=item.GetId())
170
171        self.Mode = wx.Menu(title='')
172        self.MenuBar.Append(menu=self.Mode, title='&Mode')
173        self.wxID_Mode = {}
174        for i,m in enumerate(("Histogram","Phase","Project")):
175            self.wxID_Mode[m] = i+1
176            item = self.Mode.AppendRadioItem(i+1,m+'\tCtrl+{}'.format(i+1),
177                                                 'Display {}s'.format(m))
178            self.Bind(wx.EVT_MENU, self.onRefresh, id=item.GetId())
179        self.hFilter = self.Mode.Append(wx.ID_ANY,'Filter by histogram\tCtrl+F','Only show selected histograms')
180        self.Bind(wx.EVT_MENU, self.onHistFilter, id=self.hFilter.GetId())
181        modeMenu = wx.Menu(title='')
182        self.MenuBar.Append(menu=modeMenu, title='TBD')
183        self.modeMenuPos = self.MenuBar.FindMenu('TBD')
184       
185        Frame.SetMenuBar(self.MenuBar)
186        # status bar
187        self.Status = self.CreateStatusBar()
188        self.Status.SetFieldsCount(2)
189        # split the frame and add the tree
190        self.mainPanel = wx.SplitterWindow(self, wx.ID_ANY, style=wx.SP_LIVE_UPDATE|wx.SP_3D)
191        self.mainPanel.SetMinimumPaneSize(100)
192        self.treePanel = wx.Panel(self.mainPanel, wx.ID_ANY,
193            style = wx.TAB_TRAVERSAL|wx.SUNKEN_BORDER)
194       
195#        self.dataWindow = G2DataWindow(self.mainPanel)
196        self.dataWindow = wx.ScrolledWindow(self.mainPanel)
197        dataSizer = wx.BoxSizer(wx.VERTICAL)
198        self.dataWindow.SetSizer(dataSizer)
199        self.mainPanel.SplitVertically(self.treePanel, self.dataWindow, 200)
200        self.Status.SetStatusWidths([200,-1])   # make these match?
201       
202        treeSizer = wx.BoxSizer(wx.VERTICAL)
203        self.treePanel.SetSizer(treeSizer)
204        self.GPXtree = G2G.G2TreeCtrl(id=wx.ID_ANY,
205            parent=self.treePanel, size=self.treePanel.GetClientSize(),style=wx.TR_DEFAULT_STYLE )
206        #TreeId = self.GPXtree.Id
207
208        treeSizer.Add(self.GPXtree,1,wx.EXPAND|wx.ALL,0)
209        #self.GPXtree.Bind(wx.EVT_TREE_SEL_CHANGED,self.OnDataTreeSelChanged)
210        self.GPXtree.Bind(wx.EVT_TREE_SEL_CHANGED,self.OnDataTreeSelChanged)
211        # self.GPXtree.Bind(wx.EVT_TREE_ITEM_RIGHT_CLICK,self.OnDataTreeSelChanged)
212        # self.GPXtree.Bind(wx.EVT_TREE_ITEM_COLLAPSED,
213        #     self.OnGPXtreeItemCollapsed, id=TreeId)
214        #self.GPXtree.Bind(wx.EVT_TREE_ITEM_EXPANDED,
215        #     self.OnGPXtreeItemExpanded, id=TreeId)
216        # self.GPXtree.Bind(wx.EVT_TREE_DELETE_ITEM,
217        #     self.OnGPXtreeItemDelete, id=TreeId)
218        # self.GPXtree.Bind(wx.EVT_TREE_KEY_DOWN,
219        #     self.OnGPXtreeKeyDown, id=TreeId)
220        # self.GPXtree.Bind(wx.EVT_TREE_BEGIN_RDRAG,
221        #     self.OnGPXtreeBeginRDrag, id=TreeId)       
222        # self.GPXtree.Bind(wx.EVT_TREE_END_DRAG,
223        #     self.OnGPXtreeEndDrag, id=TreeId)       
224        self.root = self.GPXtree.root       
225        self.Bind(wx.EVT_CLOSE, lambda event: sys.exit())
226
227        self.fileList = []  # list of files read for use in Reload
228        self.histListOrg = [] # list of histograms, before mods for unique naming
229        self.histList = []  # list of histograms, after mods for unique naming
230        self.projList = []
231
232        self.PWDRfilter = None
233        for win,var in ((self.plotFrame,'Plot_Pos'),):
234        #for win,var in ((self,'Main_Pos'),(self.plotFrame,'Plot_Pos')):
235            try:
236                pos = GSASIIpath.GetConfigValue(var)
237                if type(pos) is str: pos = eval(pos)
238                win.SetPosition(pos)
239                if G2gd.GetDisplay(pos) is None: win.Center()
240            except:
241                if GSASIIpath.GetConfigValue(var):
242                    print('Value for config {} {} is invalid'.format(var,GSASIIpath.GetConfigValue(var)))
243                    win.Center()
244        self.SetModeMenu()
245       
246    def SelectGPX(self):
247        '''Select a .GPX file to be read
248        '''
249        dlg = wx.FileDialog(self, 'Choose GSAS-II project file', 
250                wildcard='GSAS-II project file (*.gpx)|*.gpx',style=wx.FD_OPEN)
251        try:
252            if dlg.ShowModal() != wx.ID_OK: return
253            fil = os.path.splitext(dlg.GetPath())[0]+'.gpx'
254        finally:
255            dlg.Destroy()
256        if os.path.exists(fil):
257            #self.fileList.append([fil,'GPX'])
258            return fil
259        else:
260            print('File {} not found, skipping'.format(fil))
261            return
262
263    def SelectMultGPX(self):
264        '''Select multiple .GPX files to be read
265        '''
266        dlg = wx.FileDialog(self, 'Choose GSAS-II project file', 
267                wildcard='GSAS-II project file (*.gpx)|*.gpx',
268                style=wx.FD_OPEN|wx.FD_MULTIPLE)
269        try:
270            if dlg.ShowModal() != wx.ID_OK: return
271            files = dlg.GetPaths()
272        finally:
273            dlg.Destroy()
274        fileList = []
275        for f in files:
276            fil = os.path.splitext(f)[0]+'.gpx'
277            if os.path.exists(fil):
278                if fil not in fileList:
279                    fileList.append(fil)
280            else:
281                print('File {} not found, skipping'.format(fil))
282        return fileList
283       
284    def getMode(self):
285        '''returns the display mode (one of "Histogram","Phase","Project").
286        Could return '?' in case of an error.
287        '''
288        for m in self.wxID_Mode:
289            if self.Mode.FindItemById(self.wxID_Mode[m]).IsChecked():
290                break
291        else:
292            m = '?'
293        return m
294   
295    def onRefresh(self,event):
296        '''reread all files, in response to a change in mode, etc.
297        '''
298        self.GPXtree.DeleteChildren(self.root)  # delete tree contents
299        self.histList = []  # clear list of loaded histograms
300        self.histListOrg = []
301        self.projList = []
302        self.hFilter.Enable(not self.getMode() == "Phase") # Filter disabled for Phase display
303        for fil,mode in self.fileList:
304            self.loadFile(fil)
305        self.doneLoad()
306        self.SetModeMenu()
307           
308    def SetModeMenu(self):
309        '''Create the mode-specific menu and its contents
310        '''
311        modeMenu = wx.Menu(title='')
312        oldmenu = self.MenuBar.Replace(self.modeMenuPos,modeMenu,self.getMode())
313        wx.CallAfter(oldmenu.Destroy)
314        if self.getMode() == "Histogram":
315            item = modeMenu.Append(wx.ID_ANY,'Prince test')
316            self.Bind(wx.EVT_MENU, self.onHistPrinceTest, id=item.GetId())
317        elif self.getMode() == "Phase":
318            pass
319        elif self.getMode() == "Project":
320            item = modeMenu.Append(wx.ID_ANY,'F-test')
321            self.Bind(wx.EVT_MENU, self.onProjFtest, id=item.GetId())
322
323    def loadFile(self,fil):
324        '''read or reread a file
325        '''
326        if self.getMode() == "Histogram":
327            self.LoadPwdr(fil)
328        elif self.getMode() == "Phase":
329            self.LoadPhase(fil)
330        elif self.getMode() == "Project":
331            self.LoadProject(fil)
332        else:
333            print("mode not implemented")
334            #raise Exception("mode not implemented")
335
336    def doneLoad(self):
337        self.GPXtree.Expand(self.root)
338        if self.getMode() == "Project":
339            overId = self.GPXtree.InsertItem(pos=0,parent=self.root,text='Project Overview')
340            self.GPXtree.SelectItem(overId)
341
342    def onLoadGPX(self,event):
343        '''Initial load of GPX file in response to a menu command
344        '''
345        fil = self.SelectGPX()
346        if not fil: return
347        if not os.path.exists(fil): return
348        self.fileList.append([fil,'GPX'])
349        self.loadFile(fil)
350        self.doneLoad()
351
352    def onLoadMultGPX(self,event):
353        '''Initial load of multiple GPX files in response to a menu command
354        '''
355        for fil in self.SelectMultGPX():
356            if not os.path.exists(fil): continue
357            self.fileList.append([fil,'GPX'])
358            self.loadFile(fil)
359        self.doneLoad()
360
361    def onLoadWildGPX(self,event,wildcard=None):
362        '''Initial load of GPX file in response to a menu command
363        '''
364        home = os.path.abspath(os.getcwd())
365        hlp = '''Enter a wildcard version of a file name.
366The directory is assumed to be "{}" unless specified otherwise.
367Extensions will be set to .gpx and .bak files will be ignored unless
368the wildcard string includes "bak". For example, "*abc*" will match any
369.gpx file in that directory containing "abc". String "/tmp/A*" will match
370files in "/tmp" beginning with "A". Supplying two strings, "A*" and "B*bak*"
371will match files names beginning with "A" or "B", but ".bak" files will
372be included for the files beginning with "B" only.
373'''.format(home)
374        if wildcard is None:
375            dlg = G2G.MultiStringDialog(self, 'Enter wildcard file names', 
376                    ['wild-card 1'] , values=['*'], 
377                    lbl='Provide string(s) with "*" to find matching files',
378                    addRows=True, hlp=hlp)
379            res = dlg.Show()
380            wl = dlg.GetValues()
381            dlg.Destroy()
382            if not res: return
383        else:
384            wl = [wildcard]
385        for w in wl:
386            if not os.path.split(w)[0]:
387                w = os.path.join(home,w)
388            w = os.path.splitext(w)[0] + '.gpx'
389            for fil in glob.glob(w): 
390                if not os.path.exists(fil): continue
391                if '.bak' in fil and 'bak' not in w: continue
392                if fil in [i for i,j in self.fileList]: continue
393                self.fileList.append([fil,'GPX'])
394                self.loadFile(fil)
395        self.doneLoad()
396
397    def LoadPwdr(self,fil):
398        '''Load PWDR entries from a .GPX file to the tree.
399        see :func:`GSASIIIO.ProjFileOpen`
400        '''
401        G2frame = self
402        filep = open(fil,'rb')
403        shortname = os.path.splitext(os.path.split(fil)[1])[0]
404
405        wx.BeginBusyCursor()
406        histLoadList = []
407        try:
408            while True:
409                try:
410                    data = cPickleLoad(filep)
411                except EOFError:
412                    break
413                if not data[0][0].startswith('PWDR'): continue
414                self.histListOrg.append(data[0][0])
415                if self.PWDRfilter is not None: # implement filter
416                    if self.PWDRfilter not in data[0][0]: continue
417                data[0][0] += ' ('
418                data[0][0] += shortname
419                data[0][0] += ')'
420                histLoadList.append(data)
421                       
422        except Exception as errmsg:
423            if GSASIIpath.GetConfigValue('debug'):
424                print('\nError reading GPX file:',errmsg)
425                import traceback
426                print (traceback.format_exc())
427            msg = wx.MessageDialog(G2frame,message="Error reading file "+
428                str(fil)+". This is not a current GSAS-II .gpx file",
429                caption="Load Error",style=wx.ICON_ERROR | wx.OK | wx.STAY_ON_TOP)
430            msg.ShowModal()
431        finally:
432            filep.close()
433            wx.EndBusyCursor()
434
435        datum = None
436        for i,data in enumerate(histLoadList):
437            datum = data[0]
438            datum[0] = G2obj.MakeUniqueLabel(datum[0],self.histList)
439            Id = G2frame.GPXtree.AppendItem(parent=G2frame.root,text=datum[0])
440            self.histList.append(datum[0])
441            # if 'ranId' not in datum[1][0]: # patch: add random Id if not present
442            #     datum[1][0]['ranId'] = ran.randint(0,sys.maxsize)
443            G2frame.GPXtree.SetItemPyData(Id,datum[1][:3])  #temp. trim off junk (patch?)
444            for datus in data[1:]:
445                sub = G2frame.GPXtree.AppendItem(Id,datus[0])
446    #patch
447                if datus[0] == 'Instrument Parameters' and len(datus[1]) == 1:
448                    if datum[0].startswith('PWDR'):
449                        datus[1] = [dict(zip(datus[1][3],zip(datus[1][0],datus[1][1],datus[1][2]))),{}]
450                    else:
451                        datus[1] = [dict(zip(datus[1][2],zip(datus[1][0],datus[1][1]))),{}]
452                    for item in datus[1][0]:               #zip makes tuples - now make lists!
453                        datus[1][0][item] = list(datus[1][0][item])
454    #end patch
455                G2frame.GPXtree.SetItemPyData(sub,datus[1])
456        if datum: # was anything loaded?
457            print('data load successful for {}'.format(datum[0]))
458    #        G2frame.Status.SetStatusText('Mouse RB drag/drop to reorder',0)
459    #    G2frame.SetTitleByGPX()
460        self.GPXtree.Expand(self.root)
461       
462    def onHistFilter(self,event):
463        'Load a filter string via a dialog in response to a menu event'
464        defval = ''
465        if self.PWDRfilter is not None:
466            defval = self.PWDRfilter
467        uniqueHist = sorted(set(self.histListOrg))
468        dlg = G2G.SingleStringDialog(self,'Set string',
469                                'Set a string that must be in histogram name',
470                                defval, choices=uniqueHist, size=(400,-1))
471        if dlg.Show():
472            if dlg.GetValue().strip() == '':
473                self.PWDRfilter = None
474            else:
475                self.PWDRfilter = dlg.GetValue()
476            dlg.Destroy()
477            self.onRefresh(event)
478        else:
479            dlg.Destroy()
480
481    def LoadPhase(self,fil):
482        '''Load Phase entries from a .GPX file to the tree.
483        see :func:`GSASIIIO.ProjFileOpen`
484        '''
485        G2frame = self
486        filep = open(fil,'rb')
487        shortname = os.path.splitext(os.path.split(fil)[1])[0]
488
489        wx.BeginBusyCursor()
490        Phases = None
491        try:
492            while True:
493                try:
494                    data = cPickleLoad(filep)
495                except EOFError:
496                    break
497                if not data[0][0].startswith('Phase'): continue
498                Phases = data
499                #if self.PWDRfilter is not None: # implement filter
500                #    if self.PWDRfilter not in data[0][0]: continue
501                data[0][0] += ' ('
502                if Phases:
503                    data[0][0] += shortname
504                    data[0][0] += ')'
505                else: 
506                    data[0][0] += shortname
507                    data[0][0] += 'has no phases)'
508                Phases = data
509                break
510               
511        except Exception as errmsg:
512            if GSASIIpath.GetConfigValue('debug'):
513                print('\nError reading GPX file:',errmsg)
514                import traceback
515                print (traceback.format_exc())
516            msg = wx.MessageDialog(G2frame,message="Error reading file "+
517                str(fil)+". This is not a current GSAS-II .gpx file",
518                caption="Load Error",style=wx.ICON_ERROR | wx.OK | wx.STAY_ON_TOP)
519            msg.ShowModal()
520        finally:
521            filep.close()
522            wx.EndBusyCursor()
523
524        datum = None
525        if Phases:
526            datum = data[0]
527            #datum[0] = G2obj.MakeUniqueLabel(datum[0],self.histList)
528            Id = G2frame.GPXtree.AppendItem(parent=G2frame.root,text=datum[0])
529            G2frame.GPXtree.SetItemPyData(Id,datum[1])
530            for datus in data[1:]:
531                #datus[0] += ' ('
532                #datus[0] += shortname
533                #datus[0] += ')'
534                sub = G2frame.GPXtree.AppendItem(Id,datus[0])
535                G2frame.GPXtree.SetItemPyData(sub,datus[1])
536        if datum: # was anything loaded?
537            self.GPXtree.Expand(Id)
538            print('Phase load successful for {}'.format(datum[0]))
539    #        G2frame.Status.SetStatusText('Mouse RB drag/drop to reorder',0)
540    #    G2frame.SetTitleByGPX()
541        self.GPXtree.Expand(self.root)
542
543    def LoadProject(self,fil):
544        '''Load the Covariance entry from a .GPX file to the tree.
545        see :func:`GSASIIIO.ProjFileOpen`
546        '''
547        import datetime
548        G2frame = self
549        filep = open(fil,'rb')
550        saved = datetime.datetime.fromtimestamp(os.path.getmtime(fil)).strftime("%Y-%h-%d %H:%M")
551        shortname = os.path.splitext(os.path.split(fil)[1])[0]
552        projInfo = [shortname,saved]
553        wx.BeginBusyCursor()
554        #Phases = None
555        #G2frame.GPXtree.SetItemPyData(Id,Covar[1])
556        if self.PWDRfilter is None: # implement filter
557            match = True
558        else:
559            match = False
560        Covar = None
561        try:
562            while True:
563                try:
564                    data = cPickleLoad(filep)
565                except EOFError:
566                    break
567                if data[0][0].startswith('PWDR'): 
568                    self.histListOrg.append(data[0][0])
569                    if self.PWDRfilter is not None: # implement filter
570                        if self.PWDRfilter in data[0][0]: match = True
571                if not data[0][0].startswith('Covariance'): continue
572                Covar = data[0]
573                f = '{:d}'
574                if 'varyList' in data[0][1]:
575                    projInfo += [f.format(len(data[0][1]['varyList']))]
576                else:
577                    projInfo += ['?']
578                for v in 'Nobs','GOF':
579                    if 'Rvals' in data[0][1] and v in data[0][1]['Rvals']:
580                        projInfo += [f.format(data[0][1]['Rvals'][v])]
581                    else:
582                        projInfo += ['?']
583                    f = '{:6.2f}'
584                Covar[0] = shortname # + ' Covariance'
585            if match and Covar:
586                Id = G2frame.GPXtree.AppendItem(parent=G2frame.root,text=Covar[0])
587                G2frame.GPXtree.SetItemPyData(Id,Covar[1])
588                self.projList.append(projInfo)
589        except Exception as errmsg:
590            if GSASIIpath.GetConfigValue('debug'):
591                print('\nError reading GPX file:',errmsg)
592                import traceback
593                print (traceback.format_exc())
594            msg = wx.MessageDialog(G2frame,message="Error reading file "+
595                str(fil)+". This is not a current GSAS-II .gpx file",
596                caption="Load Error",style=wx.ICON_ERROR | wx.OK | wx.STAY_ON_TOP)
597            msg.ShowModal()
598        finally:
599            filep.close()
600            wx.EndBusyCursor()
601
602    def OnDataTreeSelChanged(self,event):
603        def ClearData(self):
604            '''Initializes the contents of the dataWindow panel
605            '''
606            self.Unbind(wx.EVT_SIZE)
607            self.SetBackgroundColour(wx.Colour(240,240,240))
608            Sizer = self.GetSizer()
609            if Sizer:
610                try:
611                    Sizer.Clear(True)
612                except:
613                    pass
614        G2frame = self
615        item = event.GetItem()
616        #print('selected',item)
617        lbl = G2frame.GPXtree.GetItemText(item)
618        if self.getMode() == "Project" and lbl == 'Project Overview':
619            ClearData(G2frame.dataWindow)
620            #import imp
621            #imp.reload(G2G)
622            pnl = G2G.SortableLstCtrl(G2frame.dataWindow)
623            h = ["File", "last saved", "vars", "Nobs", "GOF"]
624            j = [ 0,       0,           1,      1,      1]
625            pnl.PopulateHeader(h,j)
626            for i,line in enumerate(self.projList):
627                pnl.PopulateLine(i,line)
628            G2frame.dataWindow.GetSizer().Add(pnl,1,wx.EXPAND)
629            pnl.SetColWidth(0,maxwidth=170)
630            for i in range(1,len(h)):
631                pnl.SetColWidth(i,minwidth=50)
632            G2frame.dataWindow.SendSizeEvent()
633        elif self.getMode() == "Project":
634            ClearData(G2frame.dataWindow)
635            data = G2frame.GPXtree.GetItemPyData(item)
636            if data is None:
637                self.plotFrame.Show(False)
638                return
639            text = ''
640            if 'Rvals' in data:
641                Nvars = len(data['varyList'])
642                Rvals = data['Rvals']
643                text = ('Residuals after last refinement:\n'+
644                        '\twR = {:.3f}\n\tchi**2 = {:.1f}\n\tGOF = {:.2f}').format(
645                        Rvals['Rwp'],Rvals['chisq'],Rvals['GOF'])
646                text += '\n\tNobs = {}\n\tNvals = {}\n\tSVD zeros = {}'.format(
647                    Rvals['Nobs'],Nvars,Rvals.get('SVD0',0.))
648                text += '\n\tmax shift/esd = {:.3f}'.format(Rvals.get('Max shft/sig',0.0))
649                if 'lamMax' in Rvals:
650                    text += '\n\tlog10 MaxLambda = {:.1f}'.format(np.log10(Rvals['lamMax']))
651                text += '\n\tReduced χ**2 = {:.2f}'.format(Rvals['GOF']**2)
652                G2frame.dataWindow.GetSizer().Add(
653                    wx.StaticText(G2frame.dataWindow,wx.ID_ANY,text)
654                )
655                self.plotFrame.Show(True)
656                G2plt.PlotCovariance(G2frame,data)
657        else:
658            self.plotFrame.Show(False)
659            ClearData(G2frame.dataWindow)
660       
661        #print(self.GPXtree._getTreeItemsList(item))
662        # pltNum = self.G2plotNB.nb.GetSelection()
663        # print(pltNum)
664        # if pltNum >= 0:                         #to avoid the startup with no plot!
665        #     self.G2plotNB.nb.GetPage(pltNum)
666        #     NewPlot = False
667        # else:
668        #     NewPlot = True
669        #if self.getMode() == "Histogram":
670        #self.PatternId = self.PickId  = item
671        #G2plt.PlotPatterns(self,plotType='PWDR',newPlot=NewPlot)
672           
673    # def OnGPXtreeItemExpanded(self,event):
674    #     item = event.GetItem()
675    #     print('expanded',item)
676    #     print(self.GPXtree._getTreeItemsList(item))
677    #     if item == self.root:
678    #         event.StopPropagation()
679    #     else:
680    #         event.Skip(False)
681   
682    def onProjFtest(self,event):
683        '''Compare two projects (selected here if more than two are present)
684        using the statistical F-test (aka Hamilton R-factor test), see:
685
686            * Hamilton, R. W. (1965), Acta Crystallogr. 18, 502-510.
687            * Prince, E., Mathematical Techniques in Crystallography and Materials Science, Second ed. (Springer-Verlag, New York, 1994).
688        '''
689        items = []
690        item, cookie = self.GPXtree.GetFirstChild(self.root)
691        while item:
692            items.append(item)
693            item, cookie = self.GPXtree.GetNextChild(self.root, cookie)
694        if len(items) < 2:
695            G2G.G2MessageBox(self,'F-test requires two projects','Need more projects')
696            return
697        elif len(items) == 2:
698            s0 = items[0]
699            baseDict = self.GPXtree.GetItemPyData(s0)
700            s1 = items[1]           
701            relxDict = self.GPXtree.GetItemPyData(s1)
702            # sort out the dicts in order of number of refined variables
703            if len(baseDict['varyList']) > len(relxDict['varyList']):
704                s0,s1,baseDict,relxDict = s1,s0,relxDict,baseDict
705        else:
706            # need to make selection here
707            sel = []
708            for i in items:
709                sel.append(self.GPXtree.GetItemText(i))
710            dlg = G2G.G2SingleChoiceDialog(self,'Select constrained refinement',
711                                             'Choose refinement',sel)
712            if dlg.ShowModal() == wx.ID_OK:
713                s0 = dlg.GetSelection()
714                dlg.Destroy()
715            else:
716                dlg.Destroy()
717                return
718            inds = list(range(len(items)))
719            del sel[s0]
720            del inds[s0]
721            dlg = G2G.G2SingleChoiceDialog(self,'Select relaxed refinement',
722                                             'Choose refinement',sel)
723            if dlg.ShowModal() == wx.ID_OK:
724                s1 = dlg.GetSelection()
725                s1 = inds[s1]
726                dlg.Destroy()
727            else:
728                dlg.Destroy()
729                return
730            baseDict = self.GPXtree.GetItemPyData(items[s0])
731            relxDict = self.GPXtree.GetItemPyData(items[s1])
732            if len(baseDict['varyList']) > len(relxDict['varyList']):
733                G2G.G2MessageBox(self,
734                            'F-test warning: constrained refinement has more '+
735                            'variables ({}) than relaxed refinement ({}). Swapping'
736                            .format(len(baseDict['varyList']), len(relxDict['varyList'])),
737                            'Fits reversed?')
738                s0,s1,baseDict,relxDict = s1,s0,relxDict,baseDict
739                baseDict,relxDict = relxDict,baseDict
740        if len(baseDict['varyList']) == len(relxDict['varyList']):
741            G2G.G2MessageBox(self,'F-test requires differing numbers of variables','F-test not valid')
742            return
743        elif baseDict['Rvals']['Nobs'] != relxDict['Rvals']['Nobs']:
744            G2G.G2MessageBox(self,'F-test requires same number of observations in each refinement','F-test not valid')
745            return
746        missingVars = []
747        for var in baseDict['varyList']: 
748            if var not in relxDict['varyList']: 
749                missingVars.append(var)
750        txt = ''
751        postmsg = ''
752        if missingVars:
753            txt = ('*** Possible invalid use of F-test: '+
754                'The F-test requires that the constrained model be a subset '+
755                'of the relaxed model. Review the parameters shown below to '+
756                'confirm missing var(s) have new names in the relaxed model. '+
757                '***\n\n')
758            postmsg = ('\n\nThese parameters are in the constrained '+
759              'fit and are not in the relaxed fit:\n*  ')
760            for i,var in enumerate(missingVars):
761                if i > 0: postmsg += ', '
762                postmsg += var
763            postmsg += ('\nThese parameters are in the relaxed fit and not'+
764                ' in the constrained fit:\n*  ')
765            i = 0
766            for var in relxDict['varyList']: 
767                if var not in baseDict['varyList']: 
768                    if i > 0: postmsg += ', '
769                    i += 1
770                    postmsg += var
771        #GSASIIpath.IPyBreak_base()
772        prob = RwFtest(baseDict['Rvals']['Nobs'],
773                   baseDict['Rvals']['GOF'],len(baseDict['varyList']),
774                   relxDict['Rvals']['GOF'],len(relxDict['varyList']))
775        fmt = "{} model is \n{}\n{} variables and Reduced Chi**2 = {:.3f}"
776        msg = txt
777        msg += fmt.format('Constrained',self.GPXtree.GetItemText(s0)[:-11],
778                       len(baseDict['varyList']),
779                       baseDict['Rvals']['GOF']**2)
780        msg += '\n\n'
781        msg += fmt.format('Relaxed',self.GPXtree.GetItemText(s1)[:-11],
782                       len(relxDict['varyList']),
783                       relxDict['Rvals']['GOF']**2)
784        msg += '\n\nCumulative F-test probability {:.2f}%\n'.format(prob*100)
785        if prob > 0.95:
786            msg += "The relaxed model is statistically preferred to the constrained model."
787        elif prob > 0.75:
788            msg += "There is little ability to differentiate between the two models on a statistical basis."
789        else:
790            msg += "The constrained model is statistically preferred to the relaxed model."
791        msg += postmsg
792        G2G.G2MessageBox(self,msg,'F-test result')
793       
794    def onHistPrinceTest(self,event):
795        '''Compare two histograms (selected here if more than two are present)
796        using the statistical test proposed by Ted Prince in
797        Acta Cryst. B35 1099-1100. (1982). Also see Int. Tables Vol. C
798        (1st Ed.) chapter 8.4, 618-621 (1995).
799        '''
800        items = []
801        item, cookie = self.GPXtree.GetFirstChild(self.root)
802        while item:
803            items.append(item)
804            item, cookie = self.GPXtree.GetNextChild(self.root, cookie)
805
806        if len(items) < 2:
807            G2G.G2MessageBox(self,'Prince test requires two histograms','Need more')
808            return
809        elif len(items) == 2:
810            s0,s1 = items
811        else:
812            # need to make selection here
813            sel = []
814            for i in items:
815                sel.append(self.GPXtree.GetItemText(i))
816            dlg = G2G.G2SingleChoiceDialog(self,'Select one refinement',
817                                             'Choose refinement',sel)
818            if dlg.ShowModal() == wx.ID_OK:
819                s0 = dlg.GetSelection()
820                dlg.Destroy()
821            else:
822                dlg.Destroy()
823                return
824            inds = list(range(len(items)))
825            del sel[s0]
826            del inds[s0]
827            dlg = G2G.G2SingleChoiceDialog(self,'Select comparison refinement',
828                                             'Choose refinement',sel)
829            if dlg.ShowModal() == wx.ID_OK:
830                s1 = dlg.GetSelection()
831                s1 = inds[s1]
832                dlg.Destroy()
833            else:
834                dlg.Destroy()
835                return
836        model0 = self.GPXtree.GetItemPyData(s0)
837        data0 = model0[1]
838        model1 = self.GPXtree.GetItemPyData(s1)
839        data1 = model1[1]
840        if len(data0[0]) != len(data1[0]):
841            G2G.G2MessageBox(self,'Unable to test: differing numbers of data points','Comparison not valid')
842            return
843        if max(abs((data0[0]-data1[0])/data0[0])) > 0.01:
844            G2G.G2MessageBox(self,'Unable to use test: "X" values differ','Comparison not valid')
845            return
846        # X = data0[3] - data1[3]
847        # #Z = np.sqrt(data0[3]) * (data0[1] - (data0[3] + data1[3])/2)
848        # X = (data0[3] - data1[3]) / np.sqrt(data0[1])
849        # Z = (data0[1] - (data0[3] + data1[3])/2) / np.sqrt(data0[1])
850        # lam = np.sum(X*Z) / np.sum(X)
851        # sig = np.sqrt(
852        #     (np.sum(Z*Z) - lam*lam*np.sum(X*X)) /
853        #     ((len(data0[0]) - 1) * np.sum(X*X))
854        #     )
855           
856#    0 the x-postions (two-theta in degrees),
857#    1 the intensity values (Yobs),
858#    2 the weights for each Yobs value
859#    3 the computed intensity values (Ycalc)
860#    4 the background values
861#    5 Yobs-Ycalc
862       
863        GSASIIpath.IPyBreak_base()
864               
865#======================================================================   
866if __name__ == '__main__':
867    #if sys.platform == "darwin":
868    #    application = G2App(0) # create the GUI framework
869    #else:
870    application = wx.App(0) # create the GUI framework
871    try:
872        GSASIIpath.SetBinaryPath(True)
873    except:
874        print('Unable to run with current setup, do you want to update to the')
875        try:
876#            if '2' in platform.python_version_tuple()[0]:           
877#                ans = raw_input("latest GSAS-II version? Update ([Yes]/no): ")
878#            else:
879                ans = input("latest GSAS-II version? Update ([Yes]/no): ")               
880        except:
881            ans = 'no'
882        if ans.strip().lower() == "no":
883            print('Exiting')
884            sys.exit()
885        print('Updating...')
886        GSASIIpath.svnUpdateProcess()
887    GSASIIpath.InvokeDebugOpts()
888    Frame = main(application) # start the GUI
889    loadall = False
890    argLoadlist = sys.argv[1:]
891    for arg in argLoadlist:
892        fil = os.path.splitext(arg)[0] + '.gpx'
893        if os.path.exists(fil):
894            Frame.fileList.append([fil,'GPX'])
895            Frame.loadFile(fil)
896        elif '-d' in arg:
897            loadall = True
898        else:
899            print('File {} not found. Skipping'.format(fil))
900    Frame.doneLoad()
901    # debug code to select Project in initial mode
902    if loadall:
903        Frame.onLoadWildGPX(None,wildcard='*')
904        Frame.Mode.FindItemById(Frame.wxID_Mode['Project']).Check(True)
905        Frame.onRefresh(None)
906   
907    application.MainLoop()
Note: See TracBrowser for help on using the repository browser.