Changeset 1209


Ignore:
Timestamp:
Jan 30, 2014 11:45:40 PM (8 years ago)
Author:
toby
Message:

line up columns in parameter selection boxes; add a filter for parameter selection; start RB parm support; new: expression objects

Location:
trunk
Files:
3 edited

Legend:

Unmodified
Added
Removed
  • trunk/GSASIIconstrGUI.py

    r1160 r1209  
    310310          :ref:`GSASIIobj <Constraint_definitions_table>`
    311311        '''
    312         atchoice = ["("+i+") "+G2obj.fmtVarDescr(i) for i in varList]
     312        choices = [[i]+list(G2obj.VarDescr(i)) for i in varList]
    313313        meaning = G2obj.getDescr(FrstVarb.name)
    314314        if not meaning:
     
    355355                        if var == str(FrstVarb) or var in varList: continue
    356356                        varList += [var]
    357                         atchoice += ['('+var+') '+plbl+": "+meaning]
     357                        choices.append([var,plbl,meaning])
    358358            else:
    359359                for nam in nameList:
     
    370370                            if var == str(FrstVarb) or var in varList: continue
    371371                            varList += [var]
    372                             atchoice += ['('+var+') '+albl+plbl+": "+meaning]
     372                            choices.append([var,albl+plbl,meaning])
    373373        elif page[1] == 'hap':
    374374            for nam in nameList:
     
    384384                            if var == str(FrstVarb) or var in varList: continue
    385385                            varList += [var]
    386                             atchoice += ['('+var+') '+plbl+hlbl+": "+meaning]
     386                            choices.append([var,plbl+hlbl,meaning])
    387387        elif page[1] == 'hst':
    388388            for nam in nameList:
     
    393393                        if var == str(FrstVarb) or var in varList: continue
    394394                        varList += [var]
    395                         atchoice += ['('+var+') '+hlbl+": "+meaning]
     395                        choices.append([var,hlbl,meaning])
    396396        elif page[1] == 'glb':
    397397            pass
    398398        else:
    399399            raise Exception, 'Unknown constraint page '+ page[1]                   
    400         if len(atchoice):
    401             dlg = wx.MultiChoiceDialog(
     400        if len(choices):
     401            l1 = l2 = 1
     402            for i1,i2,i3 in choices:
     403                l1 = max(l1,len(i1))
     404                l2 = max(l2,len(i2))
     405            fmt = "{:"+str(l1)+"s} {:"+str(l2)+"s} {:s}"
     406            atchoice = [fmt.format(*i) for i in choices]
     407            dlg = G2gd.G2MultiChoiceDialog(
    402408                G2frame.dataFrame,legend,
    403                 'Constrain '+str(FrstVarb)+' with...',atchoice)
    404             dlg.SetSize((625,250))
     409                'Constrain '+str(FrstVarb)+' with...',atchoice,
     410                toggle=False,size=(625,400),monoFont=True)
    405411            dlg.CenterOnParent()
    406412            res = dlg.ShowModal()
     
    591597                                parent=G2frame.dataFrame)
    592598            return
    593         varListlbl = ["("+i+") "+G2obj.fmtVarDescr(i) for i in varList]
     599        l2 = l1 = 1
     600        for i in varList:
     601            l1 = max(l1,len(i))
     602            loc,desc = G2obj.VarDescr(i)
     603            l2 = max(l2,len(loc))
     604        fmt = "{:"+str(l1)+"s} {:"+str(l2)+"s} {:s}"
     605        varListlbl = [fmt.format(i,*G2obj.VarDescr(i)) for i in varList]
     606        #varListlbl = ["("+i+") "+G2obj.fmtVarDescr(i) for i in varList]
    594607        legend = "Select variables to hold (Will not be varied, even if vary flag is set)"
    595608        dlg = G2gd.G2MultiChoiceDialog(
    596609            G2frame.dataFrame,
    597             legend,title1,varListlbl,toggle=False,size=(625,250))
     610            legend,title1,varListlbl,toggle=False,size=(625,400),monoFont=True)
    598611        dlg.CenterOnParent()
    599612        if dlg.ShowModal() == wx.ID_OK:
     
    617630                                parent=G2frame.dataFrame)
    618631            return
    619         varListlbl = ["("+i+") "+G2obj.fmtVarDescr(i) for i in varList]
    620632        legend = "Select variables to make equivalent (only one of the variables will be varied when all are set to be varied)"
    621         GetAddVars(page,title1,title2,varList,varListlbl,constrDictEnt,'equivalence')
     633        GetAddVars(page,title1,title2,varList,constrDictEnt,'equivalence')
    622634   
    623635    def OnAddFunction(event):
     
    631643                                parent=G2frame.dataFrame)
    632644            return
    633         varListlbl = ["("+i+") "+G2obj.fmtVarDescr(i) for i in varList]
    634645        legend = "Select variables to include in a new variable (the new variable will be varied when all included variables are varied)"
    635         GetAddVars(page,title1,title2,varList,varListlbl,constrDictEnt,'function')
     646        GetAddVars(page,title1,title2,varList,constrDictEnt,'function')
    636647                       
    637648    def OnAddConstraint(event):
     
    645656                                parent=G2frame.dataFrame)
    646657            return
    647         varListlbl = ["("+i+") "+G2obj.fmtVarDescr(i) for i in varList]
    648658        legend = "Select variables to include in a constraint equation (the values will be constrainted to equal a specified constant)"
    649         GetAddVars(page,title1,title2,varList,varListlbl,constrDictEnt,'constraint')
    650 
    651     def GetAddVars(page,title1,title2,varList,varListlbl,constrDictEnt,constType):
     659        GetAddVars(page,title1,title2,varList,constrDictEnt,'constraint')
     660
     661    def GetAddVars(page,title1,title2,varList,constrDictEnt,constType):
    652662        '''Get the variables to be added for OnAddEquivalence, OnAddFunction,
    653663        and OnAddConstraint. Then create and check the constraint.
    654664        '''
    655         dlg = wx.SingleChoiceDialog(G2frame.dataFrame,'Select 1st variable:',title1,varListlbl)
    656         dlg.SetSize((625,250))
     665        #varListlbl = ["("+i+") "+G2obj.fmtVarDescr(i) for i in varList]
     666        l2 = l1 = 1
     667        for i in varList:
     668            l1 = max(l1,len(i))
     669            loc,desc = G2obj.VarDescr(i)
     670            l2 = max(l2,len(loc))
     671        fmt = "{:"+str(l1)+"s} {:"+str(l2)+"s} {:s}"
     672        varListlbl = [fmt.format(i,*G2obj.VarDescr(i)) for i in varList]       
     673        dlg = G2gd.G2SingleChoiceDialog(G2frame.dataFrame,'Select 1st variable:',
     674                                      title1,varListlbl,
     675                                      monoFont=True,size=(625,400))
    657676        dlg.CenterOnParent()
    658677        if dlg.ShowModal() == wx.ID_OK:
  • trunk/GSASIIgrid.py

    r1208 r1209  
    211211         * tc:      (*wx.TextCtrl*)  the TextCtrl name
    212212
    213       The number of keyword arguments may be increased in the future, if needs arise,
     213      The number of keyword arguments may be increased in the future should needs arise,
    214214      so it is best to code these functions with a \*\*kwargs argument so they will
    215215      continue to run without errors
     
    301301            raise Exception,("ValidatedTxtCtrl error: Unknown element ("+str(key)+
    302302                             ") type: "+str(type(val)))
    303         # When the mouse is moved away or the widget loses focus
     303        # When the mouse is moved away or the widget loses focus,
    304304        # display the last saved value, if an expression
    305305        self.Bind(wx.EVT_LEAVE_WINDOW, self._onLeaveWindow)
     
    332332                wx.TextCtrl.SetValue(self,str(G2py3.FormatValue(val,self.nDig)))
    333333            else:
    334                 wx.TextCtrl.SetValue(self,str(G2py3.FormatSigFigs(val)))
     334                wx.TextCtrl.SetValue(self,str(G2py3.FormatSigFigs(val)).rstrip('0'))
    335335        else:
    336336            wx.TextCtrl.SetValue(self,str(val))
     
    13741374
    13751375################################################################################
     1376
    13761377class G2MultiChoiceDialog(wx.Dialog):
    13771378    '''A dialog similar to MultiChoiceDialog except that buttons are
     
    13841385    :param bool toggle: If True (default) the toggle and select all buttons
    13851386      are displayed
    1386 
     1387    :param bool monoFont: If False (default), use a variable-spaced font;
     1388      if True use a equally-spaced font.
     1389    :param bool filterBox: If True (default) an input widget is placed on
     1390      the window and only entries matching the entered text are shown.
    13871391    :param kw: optional keyword parameters for the wx.Dialog may
    13881392      be included such as size [which defaults to `(320,310)`] and
     
    13921396    :returns: the name of the created dialog 
    13931397    '''
    1394     def __init__(self,parent, title, header, ChoiceList, toggle=True, **kw):
    1395         # process keyword parameters, notably Style
     1398    def __init__(self,parent, title, header, ChoiceList, toggle=True,
     1399                 monoFont=False, filterBox=True, **kw):
     1400        # process keyword parameters, notably style
    13961401        options = {'size':(320,310), # default Frame keywords
    13971402                   'style':wx.DEFAULT_DIALOG_STYLE|wx.RESIZE_BORDER|wx.CENTRE| wx.OK | wx.CANCEL,
    13981403                   }
    13991404        options.update(kw)
     1405        self.ChoiceList = ChoiceList
     1406        self.filterlist = range(len(self.ChoiceList))
    14001407        if options['style'] & wx.OK:
    14011408            useOK = True
     
    14121419        # fill the dialog
    14131420        Sizer = wx.BoxSizer(wx.VERTICAL)
    1414         Sizer.Add(wx.StaticText(self,wx.ID_ANY,title),0,wx.ALL,12)
     1421        topSizer = wx.BoxSizer(wx.HORIZONTAL)
     1422        topSizer.Add(wx.StaticText(self,wx.ID_ANY,title),1,wx.ALL|wx.EXPAND,0)
     1423        if filterBox:
     1424            self.timer = wx.Timer()
     1425            self.timer.Bind(wx.EVT_TIMER,self.Filter)
     1426            topSizer.Add(wx.StaticText(self,wx.ID_ANY,'Filter: '),0,wx.ALL,1)
     1427            self.txt = wx.TextCtrl(self, wx.ID_ANY, size=(80,-1))
     1428            self.txt.Bind(wx.EVT_CHAR,self.onChar)
     1429        topSizer.Add(self.txt,0,wx.ALL,0)
     1430        Sizer.Add(topSizer,0,wx.ALL|wx.EXPAND,8)
    14151431        self.clb = wx.CheckListBox(self, wx.ID_ANY, (30,30), wx.DefaultSize, ChoiceList)
     1432        if monoFont:
     1433            font1 = wx.Font(self.clb.GetFont().GetPointSize()-1,
     1434                            wx.MODERN, wx.NORMAL, wx.NORMAL, False)
     1435            self.clb.SetFont(font1)
    14161436        self.numchoices = len(ChoiceList)
    14171437        Sizer.Add(self.clb,1,wx.LEFT|wx.RIGHT|wx.EXPAND,10)
     
    14451465    def GetSelections(self):
    14461466        'Returns a list of the indices for the selected choices'
    1447         return [i for i in range(self.numchoices) if self.clb.IsChecked(i)]
     1467        return [self.filterlist[i] for i in range(self.numchoices) if self.clb.IsChecked(i)]
    14481468    def _SetAll(self,event):
    14491469        'Set all choices on'
     
    14531473        for i in range(self.numchoices):
    14541474            self.clb.Check(i,not self.clb.IsChecked(i))
     1475    def onChar(self,event):
     1476        if self.timer.IsRunning():
     1477            self.timer.Stop()
     1478        self.timer.Start(1000,oneShot=True)
     1479        event.Skip()
     1480    def Filter(self,event):
     1481        txt = self.txt.GetValue()
     1482        self.clb.Clear()
     1483        self.Update()
     1484        self.filterlist = []
     1485        if txt:
     1486            txt = txt.lower()
     1487            ChoiceList = []
     1488            for i,item in enumerate(self.ChoiceList):
     1489                if item.lower().find(txt) != -1:
     1490                    ChoiceList.append(item)
     1491                    self.filterlist.append(i)
     1492        else:
     1493            self.filterlist = range(len(self.ChoiceList))
     1494            ChoiceList = self.ChoiceList
     1495        self.clb.AppendItems(ChoiceList)
     1496
     1497################################################################################
     1498
     1499class G2SingleChoiceDialog(wx.Dialog):
     1500    '''A dialog similar to wx.SingleChoiceDialog except that a filter can be
     1501    added.
     1502
     1503    :param wx.Frame ParentFrame: reference to parent frame
     1504    :param str title: heading above list of choices
     1505    :param str header: Title to place on window frame
     1506    :param list ChoiceList: a list of choices where one will be selected
     1507    :param bool monoFont: If False (default), use a variable-spaced font;
     1508      if True use a equally-spaced font.
     1509    :param bool filterBox: If True (default) an input widget is placed on
     1510      the window and only entries matching the entered text are shown.
     1511    :param kw: optional keyword parameters for the wx.Dialog may
     1512      be included such as size [which defaults to `(320,310)`] and
     1513      style (which defaults to ``wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER | wx.CENTRE | wx.OK | wx.CANCEL``);
     1514      note that ``wx.OK`` and ``wx.CANCEL`` controls
     1515      the presence of the eponymous buttons in the dialog.
     1516    :returns: the name of the created dialog
     1517    '''
     1518    def __init__(self,parent, title, header, ChoiceList,
     1519                 monoFont=False, filterBox=True, **kw):
     1520        # process keyword parameters, notably style
     1521        options = {'size':(320,310), # default Frame keywords
     1522                   'style':wx.DEFAULT_DIALOG_STYLE|wx.RESIZE_BORDER|wx.CENTRE| wx.OK | wx.CANCEL,
     1523                   }
     1524        options.update(kw)
     1525        self.ChoiceList = ChoiceList
     1526        self.filterlist = range(len(self.ChoiceList))
     1527        if options['style'] & wx.OK:
     1528            useOK = True
     1529            options['style'] ^= wx.OK
     1530        else:
     1531            useOK = False
     1532        if options['style'] & wx.CANCEL:
     1533            useCANCEL = True
     1534            options['style'] ^= wx.CANCEL
     1535        else:
     1536            useCANCEL = False       
     1537        # create the dialog frame
     1538        wx.Dialog.__init__(self,parent,wx.ID_ANY,header,**options)
     1539        # fill the dialog
     1540        Sizer = wx.BoxSizer(wx.VERTICAL)
     1541        topSizer = wx.BoxSizer(wx.HORIZONTAL)
     1542        topSizer.Add(wx.StaticText(self,wx.ID_ANY,title),1,wx.ALL|wx.EXPAND,0)
     1543        if filterBox:
     1544            self.timer = wx.Timer()
     1545            self.timer.Bind(wx.EVT_TIMER,self.Filter)
     1546            topSizer.Add(wx.StaticText(self,wx.ID_ANY,'Filter: '),0,wx.ALL,1)
     1547            self.txt = wx.TextCtrl(self, wx.ID_ANY, size=(80,-1))
     1548            self.txt.Bind(wx.EVT_CHAR,self.onChar)
     1549        topSizer.Add(self.txt,0,wx.ALL,0)
     1550        Sizer.Add(topSizer,0,wx.ALL|wx.EXPAND,8)
     1551        self.clb = wx.ListBox(self, wx.ID_ANY, (30,30), wx.DefaultSize, ChoiceList)
     1552        self.clb.Bind(wx.EVT_LEFT_DCLICK,self.onDoubleClick)
     1553        if monoFont:
     1554            font1 = wx.Font(self.clb.GetFont().GetPointSize()-1,
     1555                            wx.MODERN, wx.NORMAL, wx.NORMAL, False)
     1556            self.clb.SetFont(font1)
     1557        self.numchoices = len(ChoiceList)
     1558        Sizer.Add(self.clb,1,wx.LEFT|wx.RIGHT|wx.EXPAND,10)
     1559        Sizer.Add((-1,10))
     1560        # OK/Cancel buttons
     1561        btnsizer = wx.StdDialogButtonSizer()
     1562        if useOK:
     1563            OKbtn = wx.Button(self, wx.ID_OK)
     1564            OKbtn.SetDefault()
     1565            btnsizer.AddButton(OKbtn)
     1566        if useCANCEL:
     1567            btn = wx.Button(self, wx.ID_CANCEL)
     1568            btnsizer.AddButton(btn)
     1569        btnsizer.Realize()
     1570        Sizer.Add((-1,5))
     1571        Sizer.Add(btnsizer,0,wx.ALIGN_RIGHT,50)
     1572        Sizer.Add((-1,20))
     1573        # OK done, let's get outa here
     1574        self.SetSizer(Sizer)
     1575    def GetSelection(self):
     1576        'Returns the index of the selected choice'
     1577        i = self.clb.GetSelection()
     1578        if i < 0 or i >= len(self.filterlist):
     1579            return wx.NOT_FOUND
     1580        return self.filterlist[i]
     1581    def onChar(self,event):
     1582        if self.timer.IsRunning():
     1583            self.timer.Stop()
     1584        self.timer.Start(1000,oneShot=True)
     1585        event.Skip()
     1586    def Filter(self,event):
     1587        txt = self.txt.GetValue()
     1588        self.clb.Clear()
     1589        self.Update()
     1590        self.filterlist = []
     1591        if txt:
     1592            txt = txt.lower()
     1593            ChoiceList = []
     1594            for i,item in enumerate(self.ChoiceList):
     1595                if item.lower().find(txt) != -1:
     1596                    ChoiceList.append(item)
     1597                    self.filterlist.append(i)
     1598        else:
     1599            self.filterlist = range(len(self.ChoiceList))
     1600            ChoiceList = self.ChoiceList
     1601        self.clb.AppendItems(ChoiceList)
     1602    def onDoubleClick(self,event):
     1603        self.EndModal(wx.ID_OK)
     1604
     1605################################################################################
    14551606
    14561607def ItemSelector(ChoiceList, ParentFrame=None,
  • trunk/GSASIIobj.py

    r1202 r1209  
    836836
    837837'''
     838import re
     839import imp
    838840import random as ran
    839841import sys
    840842import GSASIIpath
    841843import GSASIImath as G2mth
     844import numpy as np
    842845
    843846GSASIIpath.SetVersionNumber("$Revision$")
     
    10981101    '''Return a string with a more complete description for a GSAS-II variable
    10991102
    1100     TODO: This will not handle rigid body parameters yet
    1101 
    1102     :param str name: A full G2 variable name with 2 or 3
    1103        colons (<p>:<h>:name[:<a>])
     1103    :param str name: A full G2 variable name with 2 or 3 or 4
     1104       colons (<p>:<h>:name[:<a>] or <p>::RBname:<?>:<?>])
    11041105       
    11051106    :returns: a string with the description
     1107    '''
     1108    s,l = VarDescr(varname)
     1109    return s+": "+l
     1110
     1111def VarDescr(varname):
     1112    '''Return two strings with a more complete description for a GSAS-II variable
     1113
     1114    :param str name: A full G2 variable name with 2 or 3 or 4
     1115       colons (<p>:<h>:name[:<a>] or <p>::RBname:<?>:<?>])
     1116       
     1117    :returns: (loc,meaning) where loc describes what item the variable is mapped
     1118      (phase, histogram, etc.) and meaning describes what the variable does.
    11061119    '''
    11071120   
     
    11101123        return "invalid variable name ("+str(varname)+")!"
    11111124
    1112     if not l[4]:
    1113         l[4] = "(variable needs a definition!)"
     1125    if not l[-1]:
     1126        l[-1] = "(variable needs a definition!)"
    11141127
    11151128    s = ""
     
    11231136        else:
    11241137            hlbl = 'Hist='+hlbl
    1125         s = "Ph="+str(lbl)+" * "+str(hlbl)+": "
     1138        s = "Ph="+str(lbl)+" * "+str(hlbl)
     1139    elif l[4] is not None: # rigid body parameter
     1140        lbl = ShortPhaseNames.get(l[0],'phase?')
     1141        s = "Res #"+str(l[3])+" body #"+str(l[4])+" in "+str(lbl)
    11261142    elif l[3] is not None: # atom parameter,
    11271143        lbl = ShortPhaseNames.get(l[0],'phase?')
     
    11301146        except KeyError:
    11311147            albl = 'Atom?'
    1132         s = "Atom "+str(albl)+" in "+str(lbl)+": "
     1148        s = "Atom "+str(albl)+" in "+str(lbl)
    11331149    elif l[0] is not None:
    11341150        lbl = ShortPhaseNames.get(l[0],'phase?')
    1135         s = "Phase "+str(lbl)+": "
     1151        s = "Phase "+str(lbl)
    11361152    elif l[1] is not None:
    11371153        hlbl = ShortHistNames.get(l[1],'? #'+str(l[1]))
     
    11421158        else:
    11431159            hlbl = 'Hist='+hlbl
    1144         s = str(hlbl)+": "   
     1160        s = str(hlbl)
    11451161    if not s:
    1146         s = 'Global: '
    1147     s += l[4]
    1148     return s
     1162        s = 'Global'
     1163    return s,l[-1]
    11491164
    11501165def getVarDescr(varname):
    11511166    '''Return a short description for a GSAS-II variable
    11521167
    1153     :param str name: A full G2 variable name with 2 or 3
    1154        colons (<p>:<h>:name[:<a>])
     1168    :param str name: A full G2 variable name with 2 or 3 or 4
     1169       colons (<p>:<h>:name[:<a1>][:<a2>])
    11551170     
    1156     :returns: a five element list as [`p`,`h`,`name`,`a`,`description`],
    1157       where `p`, `h`, `a` are str values or `None`, for the phase number,
     1171    :returns: a six element list as [`p`,`h`,`name`,`a1`,`a2`,`description`],
     1172      where `p`, `h`, `a1`, `a2` are str values or `None`, for the phase number,
    11581173      the histogram number and the atom number; `name` will always be
    11591174      an str; and `description` is str or `None`.
     
    11631178    l = varname.split(':')
    11641179    if len(l) == 3:
     1180        l += [None,None]
     1181    elif len(l) == 4:
    11651182        l += [None]
    1166     if len(l) != 4:
     1183    elif len(l) != 5:
    11671184        return None
    1168     for i in (0,1,3):
     1185    for i in (0,1,3,4):
    11691186        if l[i] == "":
    11701187            l[i] = None
     
    11931210   
    11941211    '''
    1195     import re
    11961212    if reVarDesc: return # already done
    11971213    for key,value in {
     
    12251241        'nDebye' : 'Debye model background corr. terms',
    12261242        'nPeaks' : 'Fixed peak background corr. terms',
     1243        'RBV.*' : 'Vector rigid body parameter',
     1244        'RBR.*' : 'Residue rigid body parameter',
     1245        'RBRO([aijk])' : 'Residue rigid body orientation parameter',
     1246        'RBRP([xyz])' : 'Residue rigid body position parameter',
     1247        'RBRTr;.*' : 'Residue rigid body torsion parameter',
     1248        'RBR([TLS])([123AB][123AB])' : 'Residue rigid body group disp. param.',
    12271249        # Global vars (::<var>)
    12281250        }.items():
     
    12441266            return m.expand(reVarDesc[key])
    12451267    return None
     1268
     1269def GenWildCard(varlist):
     1270    '''Generate wildcard versions of G2 variables. These introduce '*'
     1271    for a phase, histogram or atom number (but only for one of these
     1272    fields) but only when there is more than one matching variable in the
     1273    input variable list. So if the input is this::
     1274   
     1275      varlist = ['0::AUiso:0', '0::AUiso:1', '1::AUiso:0']
     1276
     1277    then the output will be this::
     1278   
     1279       wildList = ['*::AUiso:0', '0::AUiso:*']
     1280
     1281    :param list varlist: an input list of GSAS-II variable names
     1282      (such as 0::AUiso:0)
     1283
     1284    :returns: wildList, the generated list of wild card variable names.
     1285    '''
     1286    wild = []
     1287    for i in (0,1,3):
     1288        currentL = varlist[:]
     1289        while currentL:
     1290            item1 = currentL.pop(0)
     1291            i1splt = item1.split(':')
     1292            if i >= len(i1splt): continue
     1293            if i1splt[i]:
     1294                nextL = []
     1295                i1splt[i] = '[0-9]+'
     1296                rexp = re.compile(':'.join(i1splt))
     1297                matchlist = [item1]
     1298                for nxtitem in currentL:
     1299                    if rexp.match(nxtitem):
     1300                        matchlist += [nxtitem]
     1301                    else:
     1302                        nextL.append(nxtitem)
     1303                if len(matchlist) > 1:
     1304                    i1splt[i] = '*'
     1305                    wild.append(':'.join(i1splt))
     1306                currentL = nextL
     1307    return wild
     1308
     1309def LookupWildCard(varname,varlist):
     1310    '''returns a list of variable names from list varname
     1311    that match wildcard name in varname
     1312   
     1313    :param str varname: a G2 variable name containing a wildcard
     1314      (such as \*::var)
     1315    :param list varlist: the list of all variable names used in
     1316      the current project
     1317    :returns: a list of matching GSAS-II variables (may be empty) 
     1318    '''
     1319    rexp = re.compile(varname.replace('*','[0-9]+'))
     1320    return sorted([var for var in varlist if rexp.match(var)])
     1321
    12461322
    12471323def _lookup(dic,key):
     
    12621338    Note that :func:`LoadID` should be used to (re)load the current Ids
    12631339    before creating or later using the G2VarObj object.
     1340
     1341    TODO: This does not handle rigid body variables at present
    12641342
    12651343    A :class:`G2VarObj` object can be created with a single parameter:
     
    14061484        print 'atomDict', self.IDdict['atoms']
    14071485
     1486#==========================================================================
     1487# shortcut routines
     1488exp = np.exp
     1489sind = lambda x: np.sin(x*np.pi/180.)
     1490tand = lambda x: np.tan(x*np.pi/180.)
     1491cosd = lambda x: np.cos(x*np.pi/180.)
     1492sind = sin = s = lambda x: np.sin(x*np.pi/180.)
     1493cosd = cos = c = lambda x: np.cos(x*np.pi/180.)
     1494tand = tan = t = lambda x: np.tan(x*np.pi/180.)
     1495sqrt = sq = lambda x: np.sqrt(x)
     1496pi = lambda: np.pi
     1497class ExpressionObj(object):
     1498    '''Defines an object with a user-defined expression, to be used for
     1499    secondary fits or restraints. Object is created null, but is changed
     1500    using :meth:`LoadExpression`. This contains only the minimum
     1501    information that needs to be stored to save and load the expression
     1502    and how it is mapped to GSAS-II variables.
     1503    '''
     1504    def __init__(self):
     1505        self.expression = ''
     1506        'The expression as a text string'
     1507        self.assgnVars = {}
     1508        '''A dict where keys are label names in the expression mapping to a GSAS-II
     1509        variable. The value is a list with a G2 variable name and derivative step size.
     1510        Note that the G2 variable name may contain a wild-card and correspond to
     1511        multiple values.
     1512        '''
     1513        self.freeVars = {}
     1514        '''A dict where keys are label names in the expression mapping to a free
     1515        parameter. The value is a list with:
     1516
     1517         * a name assigned to the parameter
     1518         * a value for to the parameter
     1519         * a derivative step size and
     1520         * a flag to determine if the variable is refined.
     1521        '''
     1522
     1523        self.lastError = ('','')
     1524        '''Shows last encountered error in processing expression
     1525        (list of 1-3 str values)'''
     1526
     1527    def LoadExpression(self,expr,exprVarLst,varSelect,varName,varValue,varStep,varRefflag):
     1528        '''Load the expression and associated settings into the object. Raises
     1529        an exception if the expression is not parsed, if not all functions
     1530        are defined or if not all needed parameter labels in the expression
     1531        are defined.
     1532
     1533        This will not test if the variable referenced in these definitions
     1534        are actually in the parameter dictionary. This is checked when the
     1535        computation for the expression is done in :meth:`SetupCalc`.
     1536       
     1537        :param str expr: the expression
     1538        :param list exprVarLst: parameter labels found in the expression
     1539        :param dict varSelect: this will be 0 for Free parameters
     1540          and non-zero for expression labels linked to G2 variables.
     1541        :param dict varName: Defines a name (str) associated with each free parameter
     1542        :param dict varValue: Defines a value (float) associated with each free parameter
     1543        :param dict varStep: Defines a derivative step size (float) for each
     1544          parameter labels found in the expression
     1545        :param dict varRefflag: Defines a refinement flag (bool)
     1546          associated with each free parameter
     1547        '''
     1548        self.expression = expr
     1549        self.compiledExpr = None
     1550        self.freeVars = {}
     1551        self.assgnVars = {}
     1552        for v in exprVarLst:
     1553            if varSelect[v] == 0:
     1554                self.freeVars[v] = [
     1555                    varName.get(v),
     1556                    varValue.get(v),
     1557                    varStep.get(v),
     1558                    varRefflag.get(v),
     1559                    ]
     1560            else:
     1561                self.assgnVars[v] = [
     1562                    varName[v],
     1563                    varStep.get(v),
     1564                    ]
     1565        self.CheckVars()
     1566
     1567    def EditExpression(self,exprVarLst,varSelect,varName,varValue,varStep,varRefflag):
     1568        '''Load the expression and associated settings from the object into
     1569        arrays used for editing.
     1570
     1571        :param list exprVarLst: parameter labels found in the expression
     1572        :param dict varSelect: this will be 0 for Free parameters
     1573          and non-zero for expression labels linked to G2 variables.
     1574        :param dict varName: Defines a name (str) associated with each free parameter
     1575        :param dict varValue: Defines a value (float) associated with each free parameter
     1576        :param dict varStep: Defines a derivative step size (float) for each
     1577          parameter labels found in the expression
     1578        :param dict varRefflag: Defines a refinement flag (bool)
     1579          associated with each free parameter
     1580
     1581        :returns: the expression as a str
     1582        '''
     1583        for v in self.freeVars:
     1584            varSelect[v] = 0
     1585            varName[v] = self.freeVars[v][0]
     1586            varValue[v] = self.freeVars[v][1]
     1587            varStep[v] = self.freeVars[v][2]
     1588            varRefflag[v] = self.freeVars[v][3]
     1589        for v in self.assgnVars:
     1590            varSelect[v] = 1
     1591            varName[v] = self.assgnVars[v][0]
     1592            varStep[v] = self.assgnVars[v][1]
     1593        return self.expression
     1594
     1595    def GetVaried(self):
     1596        'Returns the names of the free parameters that will be refined'
     1597        return ["::"+self.freeVars[v][0] for v in self.freeVars if self.freeVars[v][3]]
     1598
     1599    def CheckVars(self):
     1600        '''Check that the expression can be parsed, all functions are
     1601        defined and that input loaded into the object is internally
     1602        consistent. If not an Exception is raised.
     1603
     1604        :returns: a dict with references to packages needed to
     1605          find functions referenced in the expression.
     1606        '''
     1607        ret = self.ParseExpression(self.expression)
     1608        if not ret:
     1609            raise Exception("Expression parse error")
     1610        exprLblList,fxnpkgdict = ret
     1611        # check each var used in expression is defined
     1612        defined = self.assgnVars.keys() + self.freeVars.keys()
     1613        notfound = []
     1614        for var in exprLblList:
     1615            if var not in defined:
     1616                notfound.append(var)
     1617        if notfound:
     1618            msg = 'Not all variables defined'
     1619            msg1 = 'The following variables were not defined: '
     1620            msg2 = ''
     1621            for var in notfound:
     1622                if msg: msg += ', '
     1623                msg += var
     1624            self.lastError = (msg1,'  '+msg2)
     1625            raise Exception(msg)
     1626        return fxnpkgdict
     1627
     1628    def ParseExpression(self,expr):
     1629        '''Parse an expression and return a dict of called functions and
     1630        the variables used in the expression. Returns None in case an error
     1631        is encountered. If packages are referenced in functions, they are loaded
     1632        and the functions are looked up into the modules global
     1633        workspace.
     1634       
     1635        Note that no changes are made to the object other than
     1636        saving an error message, so that this can be used for testing prior
     1637        to the save.
     1638
     1639        :returns: a list of variables used variables
     1640        '''
     1641        self.lastError = ('','')
     1642        import ast
     1643        def FindFunction(f):
     1644            '''Find the object corresponding to function f
     1645            :param str f: a function name such as 'numpy.exp'
     1646            :returns: (pkgdict,pkgobj) where pkgdict contains a dict
     1647              that defines the package location(s) and where pkgobj
     1648              defines the object associated with the function.
     1649              If the function is not found, pkgobj is None.
     1650            '''
     1651            df = f.split('.')
     1652            pkgdict = {}
     1653            # no listed package, try in current namespace
     1654            if len(df) == 1:
     1655                try:
     1656                    fxnobj = eval(f)
     1657                    return pkgdict,fxnobj
     1658                except (AttributeError, NameError):
     1659                    return None
     1660            else:
     1661                try:
     1662                    fxnobj = eval(f)
     1663                    pkgdict[df[0]] = eval(df[0])
     1664                    return pkgdict,fxnobj
     1665                except (AttributeError, NameError):
     1666                    pass
     1667            # includes a package, lets try to load the packages
     1668            pkgname = ''
     1669            path = sys.path
     1670            for pkg in f.split('.')[:-1]: # if needed, descend down the tree
     1671                if pkgname:
     1672                    pkgname += '.' + pkg
     1673                else:
     1674                    pkgname = pkg
     1675                fp = None
     1676                try:
     1677                    fp, fppath,desc = imp.find_module(pkg,path)
     1678                    pkgobj = imp.load_module(pkg,fp,fppath,desc)
     1679                    pkgdict[pkgname] = pkgobj
     1680                    path = [fppath]
     1681                except Exception as msg:
     1682                    print('load of '+pkgname+' failed with error='+str(msg))
     1683                    return {},None
     1684                finally:
     1685                    if fp: fp.close()
     1686                try:
     1687                    #print 'before',pkgdict.keys()
     1688                    fxnobj = eval(f,globals(),pkgdict)
     1689                    #print 'after 1',pkgdict.keys()
     1690                    #fxnobj = eval(f,pkgdict)
     1691                    #print 'after 2',pkgdict.keys()
     1692                    return pkgdict,fxnobj
     1693                except:
     1694                    continue
     1695            return None # not found
     1696        def ASTtransverse(node,fxn=False):
     1697            '''Transverse a AST-parsed expresson, compiling a list of variables
     1698            referenced in the expression. This routine is used recursively.
     1699
     1700            :returns: varlist,fxnlist where
     1701              varlist is a list of referenced variable names and
     1702              fxnlist is a list of used functions
     1703            '''
     1704            varlist = []
     1705            fxnlist = []
     1706            if isinstance(node, list):
     1707                for b in node:
     1708                    v,f = ASTtransverse(b,fxn)
     1709                    varlist += v
     1710                    fxnlist += f
     1711            elif isinstance(node, ast.AST):
     1712                for a, b in ast.iter_fields(node):
     1713                    if isinstance(b, ast.AST):
     1714                        if a == 'func':
     1715                            fxnlist += ['.'.join(ASTtransverse(b,True)[0])]
     1716                            continue
     1717                        v,f = ASTtransverse(b,fxn)
     1718                        varlist += v
     1719                        fxnlist += f
     1720                    elif isinstance(b, list):
     1721                        v,f = ASTtransverse(b,fxn)
     1722                        varlist += v
     1723                        fxnlist += f
     1724                    elif node.__class__.__name__ == "Name":
     1725                        varlist += [b]
     1726                    elif fxn and node.__class__.__name__ == "Attribute":
     1727                        varlist += [b]
     1728            return varlist,fxnlist
     1729        try:
     1730            exprast = ast.parse(expr)
     1731        except SyntaxError as err:
     1732            s = ''
     1733            for i in traceback.format_exc().splitlines()[-3:-1]:
     1734                if s: s += "\n"
     1735                s += str(i)
     1736            self.lastError = ("Error parsing expression:",s)
     1737            return
     1738        # find the variables & functions
     1739        v,f = ASTtransverse(exprast)
     1740        varlist = sorted(list(set(v)))
     1741        fxnlist = list(set(f))
     1742        pkgdict = {}
     1743        # check the functions are defined
     1744        for fxn in fxnlist:
     1745            fxndict,fxnobj = FindFunction(fxn)
     1746            if not fxnobj:
     1747                self.lastError = ("Error: Invalid function",fxn,
     1748                                  "is not defined")
     1749                return
     1750            if not hasattr(fxnobj,'__call__'):
     1751                self.lastError = ("Error: Not a function.",fxn,
     1752                                  "cannot be called as a function")
     1753                return
     1754            pkgdict.update(fxndict)
     1755        return varlist,pkgdict
     1756
     1757#==========================================================================
     1758class ExpressionCalcObj(object):
     1759    '''An object used to evaluate an expression from a :class:`ExpressionObj`
     1760    object.
     1761   
     1762    :param ExpressionObj exprObj: a :class:`~ExpressionObj` expression object with
     1763      an expression string and mappings for the parameter labels in that object.
     1764    '''
     1765    def __init__(self,exprObj):
     1766        self.eObj = exprObj
     1767        'The expression and mappings; a :class:`ExpressionObj` object'
     1768        self.compiledExpr = None
     1769        'The expression as compiled byte-code'
     1770        self.exprDict = {}
     1771        '''dict that defines values for labels used in expression and packages
     1772        referenced by functions
     1773        '''
     1774        self.derivStep = {}
     1775        '''Contains step sizes for derivatives for variables used in the expression;
     1776        if a variable is not included in this the derivatives will be computed as zero
     1777        '''
     1778        self.lblLookup = {}
     1779        '''Lookup table that specifies the expression label name that is
     1780        tied to a particular GSAS-II parameters in the parmDict.
     1781        '''
     1782        self.fxnpkgdict = {}
     1783        '''a dict with references to packages needed to
     1784        find functions referenced in the expression.
     1785        '''
     1786        self.varLookup = {}
     1787        '''Lookup table that specifies the GSAS-II variable(s)
     1788        indexed by the expression label name. (Used for only for diagnostics
     1789        not evaluation of expression.)
     1790        '''
     1791       
     1792    def SetupCalc(self,parmDict):
     1793        '''Do all preparations to use the expression for computation.
     1794        Adds the free parameter values to the parameter dict (parmDict).
     1795        '''
     1796        self.fxnpkgdict = self.eObj.CheckVars()
     1797        # all is OK, compile the expression
     1798        self.compiledExpr = compile(self.eObj.expression,'','eval')
     1799
     1800        # look at first value in parmDict to determine its type
     1801        parmsInList = True
     1802        for key in parmDict:
     1803            val = parmDict[key]
     1804            if isinstance(val, basestring):
     1805                parmsInList = False
     1806                break
     1807            try: # check if values are in lists
     1808                val = parmDict[key][0]
     1809            except TypeError:
     1810                parmsInList = False
     1811            break
     1812           
     1813        # set up the dicts needed to speed computations
     1814        self.exprDict = {}
     1815        self.derivStep = {}
     1816        self.lblLookup = {}
     1817        self.varLookup = {}
     1818        for v in self.eObj.freeVars:
     1819            varname = self.eObj.freeVars[v][0]
     1820            varname = "::" + varname.lstrip(':').replace(' ','_').replace(':',';')
     1821            self.lblLookup[varname] = v
     1822            self.varLookup[v] = varname
     1823            if parmsInList:
     1824                parmDict[varname] = [self.eObj.freeVars[v][1],self.eObj.freeVars[v][3]]
     1825            else:
     1826                parmDict[varname] = self.eObj.freeVars[v][1]
     1827            self.exprDict[v] = self.eObj.freeVars[v][1]
     1828            if self.eObj.freeVars[v][3]:
     1829                self.derivStep[varname] = self.eObj.freeVars[v][2]
     1830        for v in self.eObj.assgnVars:
     1831            step = self.eObj.assgnVars[v][1]
     1832            varname = self.eObj.assgnVars[v][0]
     1833            if '*' in varname:
     1834                varlist = LookupWildCard(varname,parmDict.keys())
     1835                if len(varlist) == 0:
     1836                    raise Exception,"No variables match "+str(v)
     1837                for var in varlist:
     1838                    self.lblLookup[var] = v
     1839                    self.derivStep[var] = np.array(
     1840                        [step if var1 == var else 0 for var1 in varlist]
     1841                        )
     1842                if parmsInList:
     1843                    self.exprDict[v] = np.array([parmDict[var][0] for var in varlist])
     1844                else:
     1845                    self.exprDict[v] = np.array([parmDict[var] for var in varlist])
     1846                self.varLookup[v] = [var for var in varlist]
     1847            elif varname in parmDict:
     1848                self.lblLookup[varname] = v
     1849                self.varLookup[v] = varname
     1850                if parmsInList:
     1851                    self.exprDict[v] = parmDict[varname][0]
     1852                else:
     1853                    self.exprDict[v] = parmDict[varname]
     1854                self.derivStep[varname] = step
     1855            else:
     1856                raise Exception,"No value for variable "+str(v)
     1857        self.exprDict.update(self.fxnpkgdict)
     1858
     1859    def EvalExpression(self):
     1860        '''Evaluate an expression. Note that the expression
     1861        and mapping are taken from the :class:`ExpressionObj` expression object
     1862        and the parameter values were specified in :meth:`SetupCalc`.
     1863        :returns: a single value for the expression. If parameter
     1864        values are arrays (for example, from wild-carded variable names),
     1865        the sum of the resulting expression is return.
     1866
     1867        For example, if the expression is ``'A*B'``,
     1868        where A is 2.0 and B maps to ``'1::Afrac:*'``, which evaluates to::
     1869
     1870        [0.5, 1, 0.5]
     1871
     1872        then the result will be ``4.0``.
     1873        '''
     1874        if self.compiledExpr is None:
     1875            raise Exception,"EvalExpression called before SetupCalc"
     1876        val = eval(self.compiledExpr,globals(),self.exprDict)
     1877        if not np.isscalar(val):
     1878            val = np.sum(val)
     1879        return val
     1880
     1881    def EvalDeriv(self,varname):
     1882        '''Evaluate the expression derivative with respect to a
     1883        GSAS-II variable name.
     1884
     1885        :param str varname: a G2 variable name (will not have a wild-card)
     1886        :returns: the derivative
     1887        '''
     1888        if varname not in self.derivStep: return 0.0
     1889        if varname not in self.lblLookup: return 0.0
     1890        step = self.derivStep[varname]
     1891        lbl = self.lblLookup[varname] # what label does this map to in expression?
     1892        origval = self.exprDict[lbl]
     1893
     1894        self.exprDict[lbl] = origval + step
     1895        val1 = eval(self.compiledExpr,globals(),self.exprDict)
     1896
     1897        self.exprDict[lbl] = origval - step
     1898        val2 = eval(self.compiledExpr,globals(),self.exprDict)
     1899
     1900        self.exprDict[lbl] = origval # reset back to central value
     1901
     1902        val = (val1 - val2) / (2.*np.max(step))
     1903        if not np.isscalar(val):
     1904            val = np.sum(val)
     1905        return val
     1906
     1907if __name__ == "__main__":
     1908    # test equation evaluation
     1909    def showEQ(calcobj):
     1910        print 50*'='
     1911        print calcobj.eObj.expression,'=',calcobj.EvalExpression()
     1912        for v in sorted(calcobj.varLookup):
     1913            print "  ",v,'=',calcobj.exprDict[v],'=',calcobj.varLookup[v]
     1914        print '  Derivatives'
     1915        for v in calcobj.derivStep.keys():
     1916            print '    d(Expr)/d('+v+') =',calcobj.EvalDeriv(v)
     1917
     1918    obj = ExpressionObj()
     1919
     1920    obj.expression = "A*np.exp(B)"
     1921    obj.assgnVars =  {'B': ['0::Afrac:1', 0.0001]}
     1922    obj.freeVars =  {'A': [u'A', 0.5, 0.0001, True]}
     1923    #obj.CheckVars()
     1924    parmDict2 = {'0::Afrac:0':[0.0,True], '0::Afrac:1': [1.0,False]}
     1925    calcobj = ExpressionCalcObj(obj)
     1926    calcobj.SetupCalc(parmDict2)
     1927    showEQ(calcobj)
     1928
     1929    obj.expression = "A*np.exp(B)"
     1930    obj.assgnVars =  {'B': ['0::Afrac:*', 0.0001]}
     1931    obj.freeVars =  {'A': [u'Free Prm A', 0.5, 0.0001, True]}
     1932    #obj.CheckVars()
     1933    parmDict1 = {'0::Afrac:0':1.0, '0::Afrac:1': 1.0}
     1934    calcobj = ExpressionCalcObj(obj)
     1935    calcobj.SetupCalc(parmDict1)
     1936    showEQ(calcobj)
     1937
     1938    calcobj.SetupCalc(parmDict2)
     1939    showEQ(calcobj)
Note: See TracChangeset for help on using the changeset viewer.