source: trunk/GSASIIexprGUI.py @ 2801

Last change on this file since 2801 was 2801, checked in by toby, 5 years ago

at long last, tracked down Mac bug in Expression Editor

  • Property svn:eol-style set to native
  • Property svn:keywords set to Date Author Revision URL Id
File size: 39.9 KB
Line 
1# -*- coding: utf-8 -*-
2#GSASIIexprGUI - Expression Definition and Evaluation
3########### SVN repository information ###################
4# $Date: 2017-04-22 23:50:33 +0000 (Sat, 22 Apr 2017) $
5# $Author: toby $
6# $Revision: 2801 $
7# $URL: trunk/GSASIIexprGUI.py $
8# $Id: GSASIIexprGUI.py 2801 2017-04-22 23:50:33Z toby $
9########### SVN repository information ###################
10'''
11*GSASIIexprGUI: Expression Handling*
12-------------------------------------
13
14This module defines a class for defining an expression in terms of values
15in a parameter dictionary via a wx.Dialog. The dialog creates a :class:`GSASII.ExpressionObj`
16which is used to evaluate the expression against a supplied parameter dictionary.
17
18The expression is parsed to find variables used in the expression and then
19the user is asked to assign parameters from the dictionary to each variable.
20
21Default expressions are read from file DefaultExpressions.txt using
22:func:`GSASIIpath.LoadConfigFile`.
23
24'''
25import re
26import wx
27import wx.lib.scrolledpanel as wxscroll
28import numpy as np
29import GSASIIpath
30GSASIIpath.SetVersionNumber("$Revision: 2801 $")
31import GSASIIgrid as G2gd
32import GSASIIctrls as G2G
33import GSASIIpy3 as G2py3
34import GSASIIobj as G2obj
35import GSASIImath as G2mth
36
37# Define a short name for convenience
38WACV = wx.ALIGN_CENTER_VERTICAL
39
40def IndexParmDict(parmDict,wildcard):
41    '''Separate the parameters in parmDict into list of keys by parameter
42    type.
43   
44    :param dict parmDict: a dict with GSAS-II parameters
45    :param bool wildcard: True if wildcard versions of parameters should
46      be generated and added to the lists
47    :returns: a dict of lists where key 1 is a list of phase parameters,
48      2 is histogram/phase parms, 3 is histogram parms and 4 are global parameters
49    '''
50    prex = re.compile('[0-9]+::.*')
51    hrex = re.compile(':[0-9]+:.*')
52    parmLists = {}
53    for i in (1,2,3,4):
54        parmLists[i] = []
55    for i in sorted(parmDict.keys()):
56        if i.startswith("::") or i.find(':') == -1: # globals
57            parmLists[4].append(i)
58        elif prex.match(i):
59            parmLists[1].append(i)
60        elif hrex.match(i):
61            parmLists[3].append(i)
62        else:
63            parmLists[2].append(i)
64    if wildcard:
65        for i in (1,2,3,4):
66            parmLists[i] += G2obj.GenWildCard(parmLists[i]) # generate wildcard versions
67    for i in (1,2,3,4):
68        parmLists[i].sort()
69    return parmLists
70
71#==========================================================================
72defaultExpressions = None
73def LoadDefaultExpressions():
74    '''Read a configuration file with default expressions from all files named
75    DefaultExpressions.txt found in the path. Duplicates are removed and
76    expressions are sorted alphabetically
77    '''
78    global defaultExpressions
79    if defaultExpressions is not None: return # run this routine only once
80    defaultExpressions = sorted(list(set(GSASIIpath.LoadConfigFile('DefaultExpressions.txt'))))
81   
82#==========================================================================
83class ExpressionDialog(wx.Dialog):
84    '''A wx.Dialog that allows a user to input an arbitrary expression
85    to be evaluated and possibly minimized.
86
87    To do this, the user assigns a new (free) or existing
88    GSAS-II parameter to each parameter label used in the expression.
89    The free parameters can optionally be designated to be refined.
90    For example, is an expression is used such as::
91
92    'A*np.exp(-B/C)'
93
94    then A, B and C can each be assigned as Free parameter with a user-selected
95    value or to any existing GSAS-II variable in the parameter dictionary.
96    As the expression is entered it is checked for validity.
97
98    After the :class:`ExpressionDialog` object is created, use :meth:`Show` to
99    run it and obtain a :class:`GSASIIobj.ExpressionObj` object with the user
100    input.
101
102    :param wx.Frame parent: The parent of the Dialog. Can be None,
103      but better is to provide the name of the Frame where the dialog
104      is called.
105    :param dict parmDict: a dict with defined parameters and their values. Each value
106      may be a list with parameter values and a refine flag or may just contain
107      the parameter value (non-float/int values in dict are ignored)
108    :param str exprObj: a :class:`GSASIIobj.ExpressionObj` object with an expression and
109      label assignments or None (default)
110    :param str wintitle: String placed on title bar of dialog;
111      defaults to "Expression Editor"
112    :param str header: String placed at top of dialog to tell the user
113      what they will do here; default is "Enter restraint expression here"
114    :param bool fit: determines if the expression will be used in fitting (default=True).
115      If set to False, and refinement flags are not shown
116      and Free parameters are not offered as an assignment option.
117    :param str VarLabel: an optional variable label to include before the expression
118      input. Ignored if None (default)
119    :param list depVarDict: a dict of choices for the dependent variable to be
120      fitted to the expression and their values. Ignored if None (default).
121    :param list ExtraButton: a list with two terms that define [0]: the label
122      for an extra button and [1] the callback routine to be used when the
123      button is pressed. The button will only be enabled when the OK button can be
124      used (meaning the equation/expression is valid). The default is None, meaning this
125      will not be used.
126    :param list usedVars: defines a list of previously used variable names. These names
127      will not be reused as defaults for new free variables.
128      (The default is an empty list).
129    '''
130    def __init__(self, parent, parmDict, exprObj=None,
131                 header='Enter restraint expression here',
132                 wintitle='Expression Editor',
133                 fit=True,VarLabel=None,depVarDict=None,
134                 ExtraButton=None,usedVars=[]):
135        self.fit = fit
136        self.depVarDict = depVarDict
137        'dict for dependent variables'
138        self.parmDict = {}
139        '''A copy of the G2 parameter dict (parmDict) except only numerical
140        values are included and only the value (not the vary flag, if present)
141        is included.
142        '''
143        self.exprVarLst = []
144        '''A list containing the variables utilized in the current expression.
145        Placed into a :class:`GSASIIobj.ExpressionObj` object when the dialog is closed
146        with "OK", saving any changes.
147        '''
148        self.varSelect = {}
149        '''A dict that shows the variable type for each label
150        found in the expression.
151
152        * If the value is None or is not defined, the value is not assigned.
153        * If the value is 0, then the varible is "free" -- a new refineable
154          parameter.
155        * Values above 1 determine what variables will be shown
156          when the option is selected.
157        '''
158        self.varName = {}
159        'Name assigned to each variable'
160        self.varValue = {}
161        'Value for a variable (Free parameters only)'
162        self.varRefflag = {}
163        'Refinement flag for a variable (Free parameters only)'
164        self.expr = ''
165        'Expression as a text string'
166        self.dependentVar = None
167        'name for dependent variable selection, when depVarDict is specified'
168        self.usedVars = usedVars
169        'variable names that have been used and should not be reused by default'
170        defSize = (620,340) # seems like a good size
171        'Starting size for dialog'
172
173        # process dictionary of values and create an index
174        for key in parmDict:
175            try: # deal with values that are in lists
176                val = parmDict[key][0]
177            except KeyError:
178                continue # there were dicts in parmDict (should be gone now)
179            except (TypeError,IndexError):
180                val = parmDict[key]
181            if isinstance(val, basestring): continue
182            try:
183                self.parmDict[key] = float(val)
184            except:
185                pass
186        # separate the variables by type
187        self.parmLists = IndexParmDict(self.parmDict,self.fit)
188        self.timer = wx.Timer()
189        self.timer.Bind(wx.EVT_TIMER,self.OnValidate)
190        LoadDefaultExpressions()
191        style=wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER
192        wx.Dialog.__init__(self, parent, wx.ID_ANY, wintitle, style=style, size=defSize)
193        self.mainsizer = wx.BoxSizer(wx.VERTICAL)
194        label = wx.StaticText(self,  wx.ID_ANY, header)
195        self.mainsizer.Add(label, 0, wx.ALIGN_CENTRE|wx.ALL, 5)
196
197        self.exsizer = wx.BoxSizer(wx.HORIZONTAL)
198        if VarLabel:
199            label = wx.StaticText(self,  wx.ID_ANY, VarLabel + ' = ')
200            self.exsizer.Add(label, 0, wx.ALL|wx.EXPAND|wx.ALIGN_CENTER_VERTICAL, 0)
201        elif depVarDict:
202            self.depParmLists = IndexParmDict(self.depVarDict,False)
203            choices = ['','Phase','Hist./Phase','Hist.','Global']
204            for i in range(1,len(choices)): # remove empty menus from choice list
205                if not len(self.depParmLists[i]): choices[i] = ''
206            self.depChoices = [i for i in range(len(choices)) if choices[i]]
207            choice = wx.Choice(
208                self, wx.ID_ANY,
209                choices = [choices[i] for i in self.depChoices]
210                )
211            choice.SetSelection(wx.NOT_FOUND)
212            choice.Bind(wx.EVT_CHOICE,self.OnDepChoice)
213            self.exsizer.Add(choice, 0, wx.ALL|wx.EXPAND|wx.ALIGN_CENTER_VERTICAL, 0)
214            self.exsizer.Add((5,5))
215            self.depLabel = wx.StaticText(self,  wx.ID_ANY, ' ')
216            self.exsizer.Add(self.depLabel, 0, wx.ALL|wx.EXPAND|wx.ALIGN_CENTER_VERTICAL, 0)
217            label = wx.StaticText(self,  wx.ID_ANY, ' = ')
218            self.exsizer.Add(label, 0, wx.ALL|wx.EXPAND|wx.ALIGN_CENTER_VERTICAL, 0)
219
220        #self.exCtrl = wx.TextCtrl(self,  wx.ID_ANY, size=(150,-1),style=wx.TE_PROCESS_ENTER)
221        self.exCtrl = wx.ComboBox(self, wx.ID_ANY, "", (90, 50), (160, -1),
222            defaultExpressions,wx.CB_DROPDOWN| wx.TE_PROCESS_ENTER)
223        self.exCtrl.Bind(wx.EVT_CHAR, self.OnChar)
224        self.exCtrl.Bind(wx.EVT_COMBOBOX, self.OnValidate)
225        self.exCtrl.Bind(wx.EVT_TEXT_ENTER, self.OnValidate)
226        self.exsizer.Add(self.exCtrl, 1, wx.ALL|wx.EXPAND|wx.ALIGN_CENTER_VERTICAL, 0)
227        #self.mainsizer.Add(self.exCtrl, 0, wx.ALL|wx.EXPAND, 5)
228        self.mainsizer.Add(self.exsizer, 0, wx.ALL|wx.EXPAND, 5)
229        self.mainsizer.Add((-1,5),0,wx.EXPAND,1)
230
231        evalSizer = wx.BoxSizer(wx.HORIZONTAL)
232        self.mainsizer.Add(evalSizer,0,wx.ALL|wx.EXPAND,0)
233        btn = wx.Button(self, wx.ID_ANY,"Validate")
234        btn.Bind(wx.EVT_BUTTON,self.OnValidate)
235        evalSizer.Add(btn,0,wx.LEFT|wx.RIGHT,5)
236        self.result = wx.StaticText(self,  wx.ID_ANY, '')
237        evalSizer.Add(self.result, 0, wx.ALIGN_CENTRE|wx.ALL, 5)
238
239        self.varSizer = wx.BoxSizer(wx.HORIZONTAL)
240        self.mainsizer.Add(self.varSizer,1,wx.ALL|wx.EXPAND,1)
241        self.mainsizer.Add((-1,5),0,wx.EXPAND,1)
242
243        bSizer = wx.BoxSizer(wx.HORIZONTAL)
244        btnsizer = wx.StdDialogButtonSizer()
245        if ExtraButton:
246            self.ExtraBtn = wx.Button(self, wx.ID_ANY, ExtraButton[0])
247            self.ExtraBtn.Bind(wx.EVT_BUTTON,self.OnExtra)
248            self.ExtraCallBack = ExtraButton[1]
249            self.ExtraBtn.Disable()
250            bSizer.Add(self.ExtraBtn, 0, wx.ALL|wx.ALIGN_CENTER_VERTICAL, 2)
251        else:
252            self.ExtraBtn = None
253        bSizer.Add((1,1), 1, wx.ALIGN_CENTER|wx.ALL|wx.EXPAND, 0)
254        self.OKbtn = wx.Button(self, wx.ID_OK)
255        self.OKbtn.SetDefault()
256        self.OKbtn.Disable()
257        btnsizer.AddButton(self.OKbtn)
258        btn = wx.Button(self, wx.ID_CANCEL)
259        btnsizer.AddButton(btn)
260        btnsizer.Realize()
261        bSizer.Add(btnsizer, 0, wx.ALIGN_CENTER_VERTICAL|wx.ALL, 5)
262        self.mainsizer.Add(bSizer, 0, wx.ALIGN_CENTER_VERTICAL|wx.ALL|wx.EXPAND, 5)
263        self.SetSizer(self.mainsizer)
264        self.CenterOnParent()
265        if exprObj:
266            self.expr = exprObj.EditExpression(
267                self.exprVarLst,
268                self.varSelect,
269                self.varName,
270                self.varValue,
271                self.varRefflag,
272                )
273            # set the initial value for the dependent value
274            if self.depVarDict:
275                var = exprObj.GetDepVar()
276                if var in self.depVarDict:
277                    self.depLabel.SetLabel(var)
278                    self.dependentVar = var
279                   
280        self.exCtrl.SetValue(self.expr)
281        self.OnValidate(None)
282        self.SetMinSize(defSize) 
283        #self.errbox.SetAutoLayout(1)
284        #self.errbox.SetupScrolling()
285        #self.varbox.SetAutoLayout(1)
286        #self.varbox.SetupScrolling()
287        #self.mainsizer.Fit(self)
288
289    def OnExtra(self,event):
290        exprObj = G2obj.ExpressionObj()
291        exprObj.LoadExpression(
292            self.expr,
293            self.exprVarLst,
294            self.varSelect,
295            self.varName,
296            self.varValue,
297            self.varRefflag,
298            )
299        if self.depVarDict:
300            exprObj.SetDepVar(self.dependentVar)
301        self.ExtraCallBack(exprObj)
302        # put results back into displayed dialog
303        resDict = dict(exprObj.GetVariedVarVal())
304        for v,var in self.varName.items():
305            varname = "::" + var.lstrip(':').replace(' ','_').replace(':',';')
306            val =  resDict.get(varname)
307            if val:
308                self.varValue[v] = val
309        wx.CallAfter(self.Repaint)
310
311    def Show(self,mode=True):
312        '''Call to use the dialog after it is created.
313
314        :returns: None (On Cancel) or a new :class:`~GSASIIobj.ExpressionObj`
315        '''
316        self.Layout()
317        #self.mainsizer.Fit(self)
318        self.SendSizeEvent() # force repaint
319        if self.ShowModal() == wx.ID_OK:
320            # store the edit results in the object and return it
321            exprObj = G2obj.ExpressionObj()
322            exprObj.LoadExpression(
323                self.expr,
324                self.exprVarLst,
325                self.varSelect,
326                self.varName,
327                self.varValue,
328                self.varRefflag,
329                )
330            if self.depVarDict:
331                exprObj.SetDepVar(self.dependentVar)
332            return exprObj
333        else:
334            return None
335       
336    def setEvalResult(self,msg):
337        'Show a string in the expression result area'
338        self.result.SetLabel(msg)
339
340    def RestartTimer(self):
341        '''Cancels any running timer and starts a new one.
342        The timer causes a check of syntax after 2 seconds unless there is further input.
343        Disables the OK button until a validity check is complete.
344        '''
345        if self.timer.IsRunning():
346            self.timer.Stop()
347        self.timer.Start(2000,oneShot=True)
348       
349    def OnChar(self,event):
350        '''Called as each character is entered. Cancels any running timer
351        and starts a new one. The timer causes a check of syntax after 2 seconds
352        without input.
353        Disables the OK button until a validity check is complete.
354        '''
355        self.RestartTimer()
356        self.OKbtn.Disable()
357        if self.ExtraBtn: self.ExtraBtn.Disable()
358        event.Skip()
359        return
360   
361    def CheckVars(self):
362        '''Check that appropriate variables are defined for each
363        symbol used in :data:`self.expr`
364
365        :returns: a text error message or None if all needed input is present       
366        '''
367        invalid = 0
368        for v in self.exprVarLst:
369            if self.varSelect.get(v) is None:
370                invalid += 1
371        if invalid==1:
372            return '(a variable is not assigned)'
373        elif invalid:
374            return '('+str(invalid)+' variables are not assigned)'
375        msg = ''
376        for v in self.exprVarLst:
377            varname = self.varName.get(v)
378            if not varname:
379                invalid += 1
380                if msg: msg += "; "
381                msg += 'No variable for '+str(v)
382            elif self.varSelect.get(v) > 0:
383               if '*' in varname:
384                   l = G2obj.LookupWildCard(varname,self.parmDict.keys())
385                   if len(l) == 0:
386                       invalid += 1
387                       if msg: msg += "; "
388                       msg += 'No variables match '+str(varname)
389               elif varname not in self.parmDict.keys():
390                   invalid += 1
391                   if msg: msg += "; "
392                   msg += 'No variables match '+str(varname)
393            else:
394                # value assignment: this check is likely unneeded
395                val = self.varValue.get(v)
396                try:
397                    float(val)
398                except (ValueError,TypeError):
399                    invalid += 1
400                    if msg: msg += "; "
401                    if val is None:
402                        msg += 'No value for '+str(v)
403                    else:
404                        msg += 'Value '+str(val)+' invalid for '+str(v)
405        if invalid:
406            return '('+msg+')'       
407        return
408
409    def OnDepChoice(self,event):
410        '''Respond to a selection of a variable type for a label in
411        an expression
412        '''
413        if event: event.Skip()
414        sel = self.depChoices[event.GetEventObject().GetSelection()]
415        var = self.SelectG2var(sel,'Dependent variable',self.depParmLists[sel])
416        if var is None:
417            self.dependentVar = None
418            self.OnValidate(None)
419            event.GetEventObject().SetSelection(wx.NOT_FOUND)
420            return
421        self.dependentVar = var
422        self.depLabel.SetLabel(var)
423        self.OnValidate(None)
424        self.Layout()
425
426    def GetDepVar(self):
427        '''Returns the name of the dependent variable, when depVarDict is used.
428        '''
429        return self.dependentVar
430       
431    def OnChoice(self,event):
432        '''Respond to a selection of a variable type for a label in
433        an expression
434        '''
435        if event: event.Skip()
436        v = event.GetEventObject().label
437        sel = self.AllowedChoices[event.GetEventObject().GetSelection()]
438        if sel == 0:
439            sv = G2obj.MakeUniqueLabel(v,self.usedVars)
440            self.varSelect[v] = sel
441            self.varName[v] = sv
442            self.varValue[v] = self.varValue.get(v,0.0)
443        else:
444            var = self.SelectG2var(sel,v,self.parmLists[sel])
445            if var is None:
446                self.OnValidate(None)
447                return
448            self.varSelect[v] = sel
449            self.varName[v] = var
450        self.OnValidate(None)
451
452    def SelectG2var(self,sel,var,parmList):
453        '''Offer a selection of a GSAS-II variable.
454
455        :param int sel: Determines the type of variable to be selected.
456          where 1 is used for Phase variables, and 2 for Histogram/Phase vars,
457          3 for Histogram vars and 4 for Global vars.
458        :returns: a variable name or None (if Cancel is pressed)
459        '''
460        if not parmList:
461            return None
462        l2 = l1 = 1
463        for i in parmList:
464            l1 = max(l1,len(i))
465            loc,desc = G2obj.VarDescr(i)
466            l2 = max(l2,len(loc))
467        fmt = u"{:"+str(l1)+"s} {:"+str(l2)+"s} {:s}"
468        varListlbl = [fmt.format(i,*G2obj.VarDescr(i)) for i in parmList]
469
470        dlg = G2G.G2SingleChoiceDialog(
471            self,'Select GSAS-II variable for '+str(var)+':',
472            'Select variable',
473            varListlbl,monoFont=True)
474        dlg.SetSize((625,250))
475        dlg.CenterOnParent()
476        var = None
477        if dlg.ShowModal() == wx.ID_OK:
478            i = dlg.GetSelection()
479            var = parmList[i]
480        dlg.Destroy()
481        return var
482
483    def showError(self,msg1,msg2='',msg3=''):
484        '''Show an error message of 1 to 3 sections. The second
485        section is shown in an equally-spaced font.
486       
487        :param str msg1: msg1 is shown in a the standard font
488        :param str msg2: msg2 is shown in a equally-spaced (wx.MODERN) font
489        :param str msg3: msg3 is shown in a the standard font
490        '''
491        self.varSizer.Clear(True)
492        self.OKbtn.Disable()
493        if self.ExtraBtn: self.ExtraBtn.Disable()
494        self.varSizer.Clear(True)
495        self.errbox = wxscroll.ScrolledPanel(self,style=wx.HSCROLL)
496        self.errbox.SetMinSize((200,130))
497        self.varSizer.Add(self.errbox,1,wx.ALL|wx.EXPAND,1)
498        Siz = wx.BoxSizer(wx.VERTICAL)
499        errMsg1 = wx.StaticText(self.errbox, wx.ID_ANY,"")
500        Siz.Add(errMsg1, 0, wx.ALIGN_LEFT|wx.LEFT|wx.EXPAND, 5)
501        errMsg2 = wx.StaticText(self.errbox, wx.ID_ANY,"\n\n")
502        font1 = wx.Font(errMsg2.GetFont().GetPointSize(),
503                        wx.MODERN, wx.NORMAL, wx.NORMAL, False)
504        errMsg2.SetFont(font1)
505        Siz.Add(errMsg2, 0, wx.ALIGN_LEFT|wx.LEFT|wx.EXPAND, 5)
506        errMsg3 = wx.StaticText(self.errbox, wx.ID_ANY,"")
507        Siz.Add(errMsg3, 0, wx.ALIGN_LEFT|wx.LEFT|wx.EXPAND, 5)
508        self.errbox.SetSizer(Siz,True)
509        Siz.Fit(self.errbox)
510        errMsg1.SetLabel(msg1)
511        errMsg2.SetLabel("  "+msg2)
512        errMsg2.Wrap(-1)
513        errMsg3.SetLabel(msg3)
514        self.Layout()
515
516    def OnValidate(self,event):
517        '''Respond to a press of the Validate button or when a variable
518        is associated with a label (in :meth:`OnChoice`)
519        '''
520        if event: event.Skip()
521        self.setEvalResult('(expression cannot be evaluated)')
522        self.timer.Stop()
523        self.expr = self.exCtrl.GetValue().strip()
524        if not self.expr: 
525            wx.CallAfter(self.showError,
526                "Invalid Expression:","","      (an expression must be entered)")
527            return
528        exprObj = G2obj.ExpressionObj()
529        ret = exprObj.ParseExpression(self.expr)
530        if not ret:
531            wx.CallAfter(self.showError,*exprObj.lastError)
532            return
533        self.exprVarLst,pkgdict = ret
534        wx.CallLater(100,self.Repaint,exprObj)
535
536    def Repaint(self,exprObj):
537        'Redisplay the variables and continue the validation'
538        # create widgets to associate vars with labels and/or show messages
539        self.varSizer.Clear(True)
540        self.errbox = wxscroll.ScrolledPanel(self,style=wx.HSCROLL)
541        self.errbox.SetMinSize((100,130))
542        self.varSizer.Add(self.errbox,0,wx.ALL|wx.EXPAND,1)
543        self.varbox = wxscroll.ScrolledPanel(self,style=wx.HSCROLL)
544        self.varSizer.Add(self.varbox,1,wx.ALL|wx.EXPAND,1)
545        Siz = wx.BoxSizer(wx.VERTICAL)
546        Siz.Add(
547            wx.StaticText(self.varbox,wx.ID_ANY,
548                          'Assignment of variables to labels:'),
549            0,wx.EXPAND|wx.ALIGN_CENTER,0)
550        GridSiz = wx.FlexGridSizer(0,5,2,2)
551        GridSiz.Add(
552            wx.StaticText(self.varbox,wx.ID_ANY,'label',style=wx.CENTER),
553            0,wx.ALIGN_CENTER)
554        lbls = ('varib. type\nselection','variable\nname','value')
555        choices = ['Free','Phase','Hist./Phase','Hist.','Global']
556        if self.fit:
557            lbls += ('refine\nflag',)
558        else:
559            lbls += ('',)
560            choices[0] = ''
561        for i in range(1,len(choices)): # remove empty menus from choice list
562            if not len(self.parmLists[i]): choices[i] = ''
563        self.AllowedChoices = [i for i in range(len(choices)) if choices[i]]
564        for lbl in lbls:
565            w = wx.StaticText(self.varbox,wx.ID_ANY,lbl,style=wx.CENTER)
566            w.SetMinSize((80,-1))
567            GridSiz.Add(w,0,wx.ALIGN_CENTER)
568
569        # show input for each var in expression.
570        for v in self.exprVarLst:
571            # label
572            GridSiz.Add(wx.StaticText(self.varbox,wx.ID_ANY,v),0,wx.ALIGN_CENTER,0)
573            # assignment type
574            ch = wx.Choice(
575                self.varbox, wx.ID_ANY,
576                choices = [choices[i] for i in self.AllowedChoices]
577                )
578            GridSiz.Add(ch,0,wx.ALIGN_LEFT,0)
579            if v in self.varSelect and self.varSelect.get(v) in self.AllowedChoices:
580                i = self.AllowedChoices.index(self.varSelect[v])
581                ch.SetSelection(i)
582            else:
583                ch.SetSelection(wx.NOT_FOUND)
584            ch.label = v
585            ch.Bind(wx.EVT_CHOICE,self.OnChoice)
586
587            # var name/var assignment
588            if self.varSelect.get(v) is None:
589                GridSiz.Add((-1,-1),0,wx.ALIGN_LEFT|wx.EXPAND,0)
590            elif self.varSelect.get(v) == 0:
591                wid = G2G.ValidatedTxtCtrl(self.varbox,self.varName,v,
592                                            #OnLeave=self.OnTxtLeave,
593                                            size=(50,-1))
594                GridSiz.Add(wid,0,wx.ALIGN_LEFT|wx.EXPAND,0)
595            else:
596                wid = wx.StaticText(self.varbox,wx.ID_ANY,self.varName[v])
597                GridSiz.Add(wid,0,wx.ALIGN_LEFT,0)
598
599            # value
600            if self.varSelect.get(v) is None:
601                GridSiz.Add((-1,-1),0,wx.ALIGN_RIGHT|wx.EXPAND,0)
602            elif self.varSelect.get(v) == 0:
603                wid = G2G.ValidatedTxtCtrl(self.varbox,self.varValue,v,
604                                            #OnLeave=self.OnTxtLeave,
605                                            size=(75,-1))
606                GridSiz.Add(wid,0,wx.ALIGN_LEFT|wx.EXPAND,0)
607                wid.Bind(wx.EVT_CHAR,self.OnChar)
608            else:
609                var = self.varName[v]
610                if '*' in var:
611                    #[self.parmDict[v] for v in LookupWildCard(var,self.parmDict.keys())]
612                    #print self.varValue[v]
613                    vs = G2obj.LookupWildCard(var,self.parmDict.keys())
614                    s = '('+str(len(vs))+' values)'
615                elif var in self.parmDict:
616                    val = self.parmDict[var]
617                    s = G2py3.FormatSigFigs(val).rstrip('0')
618                else:
619                    s = '?'
620                wid = wx.StaticText(self.varbox,wx.ID_ANY,s)
621                GridSiz.Add(wid,0,wx.ALIGN_LEFT,0)
622
623            # show a refine flag for Free Vars only
624            if self.varSelect.get(v) == 0 and self.fit:
625                self.varRefflag[v] = self.varRefflag.get(v,True)
626                wid = G2G.G2CheckBox(self.varbox,'',self.varRefflag,v)
627                GridSiz.Add(wid,0,wx.ALIGN_LEFT|wx.EXPAND,0)
628            else:
629                wid = (-1,-1)
630                GridSiz.Add(wid,0,wx.ALIGN_LEFT|wx.EXPAND,0)
631
632        Siz.Add(GridSiz)
633        self.varbox.SetSizer(Siz,True)
634
635        # evaluate the expression, displaying errors or the expression value
636        try:
637            msg = self.CheckVars() 
638            if msg:
639                self.setEvalResult(msg)
640                return
641            exprObj.LoadExpression(
642                self.expr,
643                self.exprVarLst,
644                self.varSelect,
645                self.varName,
646                self.varValue,
647                self.varRefflag,
648                )
649            try:
650                calcobj = G2obj.ExpressionCalcObj(exprObj)
651                calcobj.SetupCalc(self.parmDict)
652                val = calcobj.EvalExpression()
653            except Exception as msg:
654                self.setEvalResult("Error in evaluation: "+str(msg))
655                return
656            if not np.isfinite(val):
657                self.setEvalResult("Expression value is infinite or out-of-bounds")
658                return
659            s = G2py3.FormatSigFigs(val).rstrip('0')
660            depVal = ""
661            if self.depVarDict:
662                if not self.dependentVar:
663                    self.setEvalResult("A dependent variable must be selected.")
664                    return
665                depVal = '; Variable "' + self.dependentVar + '" = ' + str(
666                    self.depVarDict.get(self.dependentVar,'?')
667                    )
668            self.setEvalResult("Expression evaluates to: "+str(s)+depVal)
669            self.OKbtn.Enable()
670            if self.ExtraBtn: self.ExtraBtn.Enable()
671        finally: 
672            xwid,yhgt = Siz.Fit(self.varbox)
673            self.varbox.SetMinSize((xwid,130))
674            self.varbox.SetAutoLayout(1)
675            self.varbox.SetupScrolling()
676            self.varbox.Refresh()
677            self.Layout()
678            self.SendSizeEvent() # force repaint
679           
680#==========================================================================
681class BondDialog(wx.Dialog):
682    '''A wx.Dialog that allows a user to select a bond length to be evaluated.
683    What needs to be done here? Need phase info for atoms
684    0. Select phase
685    1. Select 1st atom from dialog
686    2. Find neighbors & select one from dialog
687    3. Set up distance equation & save it - has to look like result from Show in above ExpressionDialog       
688    Use existing bond & esd calculate routines
689    '''
690    def __init__(self, parent, Phases, parmDict, exprObj=None,
691                 header='Enter restraint expression here',
692                 wintitle='Expression Editor',
693                 VarLabel=None,depVarDict=None,
694                 ExtraButton=None,usedVars=[]):
695        wx.Dialog.__init__(self,parent,wx.ID_ANY,wintitle, 
696            pos=wx.DefaultPosition,style=wx.DEFAULT_DIALOG_STYLE)
697        self.panel = wx.Panel(self)         #just a dummy - gets destroyed in Draw!
698        self.Phases = Phases
699        self.parmDict = parmDict
700        self.header = header
701        self.pName = Phases.keys()[0]
702        DisAglCtls = {}
703        dlg = G2gd.DisAglDialog(self.panel,DisAglCtls,self.Phases[self.pName]['General'],Reset=False)
704        if dlg.ShowModal() == wx.ID_OK:
705            Phases[self.pName]['General']['DisAglCtls'] = dlg.GetData()
706        dlg.Destroy()
707        self.Oatom = ''
708        self.Tatom = ''
709        self.Draw()
710       
711    def Draw(self):
712       
713        def OnPhase(event):
714            Obj = event.GetEventObject()
715            self.pName = Obj.GetValue()
716            self.Oatom = ''
717            DisAglCtls = {}
718            dlg = G2gd.DisAglDialog(self.panel,DisAglCtls,self.Phases[self.pName]['General'],Reset=False)
719            if dlg.ShowModal() == wx.ID_OK:
720                self.Phases[self.pName]['General']['DisAglCtls'] = dlg.GetData()
721            dlg.Destroy()
722            wx.CallAfter(self.Draw)
723           
724        def OnOrigAtom(event):
725            Obj = event.GetEventObject()
726            self.Oatom = Obj.GetValue()
727            wx.CallAfter(self.Draw)
728           
729        def OnTargAtom(event):
730            Obj = event.GetEventObject()
731            self.Tatom = Obj.GetValue()
732            wx.CallAfter(self.Draw)
733
734        self.panel.Destroy()
735        self.panel = wx.Panel(self)
736        mainSizer = wx.BoxSizer(wx.VERTICAL)
737        mainSizer.Add(wx.StaticText(self.panel,label=self.header),0,WACV)
738        pNames = self.Phases.keys()
739        phaseSizer = wx.BoxSizer(wx.HORIZONTAL)
740        phaseSizer.Add(wx.StaticText(self.panel,label=' Select phase: '),0,WACV)
741        phase = wx.ComboBox(self.panel,value=self.pName,choices=pNames,
742            style=wx.CB_READONLY|wx.CB_DROPDOWN)
743        phase.Bind(wx.EVT_COMBOBOX,OnPhase)
744        phaseSizer.Add(phase,0,WACV)
745        mainSizer.Add(phaseSizer)
746        Phase = self.Phases[self.pName]
747        cx,ct = Phase['General']['AtomPtrs'][:2]
748        Atoms = Phase['Atoms']
749        aNames = [atom[ct-1] for atom in Atoms]
750        atomSizer = wx.BoxSizer(wx.HORIZONTAL)
751        atomSizer.Add(wx.StaticText(self.panel,label=' Origin atom: '),0,WACV)
752        origAtom = wx.ComboBox(self.panel,value=self.Oatom,choices=aNames,
753            style=wx.CB_READONLY|wx.CB_DROPDOWN)
754        origAtom.Bind(wx.EVT_COMBOBOX,OnOrigAtom)
755        atomSizer.Add(origAtom,0,WACV)
756        atomSizer.Add(wx.StaticText(self.panel,label=' distance to: '),0,WACV)
757        neigh = []
758        if self.Oatom:
759#            GSASIIpath.IPyBreak()
760            neigh = G2mth.FindAllNeighbors(Phase,self.Oatom,aNames)
761        bNames = ['',]
762        if neigh:
763            bNames = [item[0]+' d=%.3f'%(item[1]) for item in neigh[0]]
764        targAtom = wx.ComboBox(self.panel,value=self.Tatom,choices=bNames,
765            style=wx.CB_READONLY|wx.CB_DROPDOWN)
766        targAtom.Bind(wx.EVT_COMBOBOX,OnTargAtom)
767        atomSizer.Add(targAtom,0,WACV)
768       
769        mainSizer.Add(atomSizer)
770
771
772        OkBtn = wx.Button(self.panel,-1,"Ok")
773        OkBtn.Bind(wx.EVT_BUTTON, self.OnOk)
774        cancelBtn = wx.Button(self.panel,-1,"Cancel")
775        cancelBtn.Bind(wx.EVT_BUTTON, self.OnCancel)
776        btnSizer = wx.BoxSizer(wx.HORIZONTAL)
777        btnSizer.Add((20,20),1)
778        btnSizer.Add(OkBtn)
779        btnSizer.Add((20,20),1)
780        btnSizer.Add(cancelBtn)
781        btnSizer.Add((20,20),1)
782       
783        mainSizer.Add(btnSizer,0,wx.EXPAND|wx.BOTTOM|wx.TOP, 10)
784        self.panel.SetSizer(mainSizer)
785        self.panel.Fit()
786        self.Fit()
787
788    def GetSelection(self):
789        return self.pName,self.Oatom,self.Tatom
790
791    def OnOk(self,event):
792        parent = self.GetParent()
793        parent.Raise()
794        self.EndModal(wx.ID_OK)
795
796    def OnCancel(self,event):
797        parent = self.GetParent()
798        parent.Raise()
799        self.EndModal(wx.ID_CANCEL)       
800           
801#==========================================================================
802class AngleDialog(wx.Dialog):
803    '''A wx.Dialog that allows a user to select a bond angle to be evaluated.
804    What needs to be done here? Need phase info for atom
805    0. Select phase
806    1. Select 1st atom from dialog
807    2. Find neighbors & select two from dialog
808    3. Set up angle equation & save it - has to look like result from Show in above ExpressionDialog       
809    Use existing angle & esd calculate routines
810    '''
811    def __init__(self, parent, Phases, parmDict, exprObj=None,
812                 header='Enter restraint expression here',
813                 wintitle='Expression Editor',
814                 VarLabel=None,depVarDict=None,
815                 ExtraButton=None,usedVars=[]):
816        wx.Dialog.__init__(self,parent,wx.ID_ANY,wintitle, 
817            pos=wx.DefaultPosition,style=wx.DEFAULT_DIALOG_STYLE)
818        self.panel = wx.Panel(self)         #just a dummy - gets destroyed in Draw!
819        self.Phases = Phases
820        self.parmDict = parmDict
821        self.header = header
822        self.pName = Phases.keys()[0]
823        DisAglCtls = {}
824        dlg = G2gd.DisAglDialog(self.panel,DisAglCtls,self.Phases[self.pName]['General'],Reset=False)
825        if dlg.ShowModal() == wx.ID_OK:
826            Phases[self.pName]['General']['DisAglCtls'] = dlg.GetData()
827        dlg.Destroy()
828        self.Oatom = ''
829        self.Tatoms = ''
830        self.Draw()
831
832    def Draw(self):
833       
834        def OnPhase(event):
835            Obj = event.GetEventObject()
836            self.pName = Obj.GetValue()
837            self.Oatom = ''
838            DisAglCtls = {}
839            dlg = G2gd.DisAglDialog(self.panel,DisAglCtls,self.Phases[self.pName]['General'],Reset=False)
840            if dlg.ShowModal() == wx.ID_OK:
841                self.Phases[self.pName]['General']['DisAglCtls'] = dlg.GetData()
842            dlg.Destroy()
843            wx.CallAfter(self.Draw)
844           
845        def OnOrigAtom(event):
846            Obj = event.GetEventObject()
847            self.Oatom = Obj.GetValue()
848            wx.CallAfter(self.Draw)           
849
850        def OnTargAtoms(event):
851            Obj = event.GetEventObject()
852            self.Tatoms = Obj.GetValue()
853            wx.CallAfter(self.Draw)
854
855        self.panel.Destroy()
856        self.panel = wx.Panel(self)
857        mainSizer = wx.BoxSizer(wx.VERTICAL)
858        mainSizer.Add(wx.StaticText(self.panel,label=self.header),0,WACV)
859        pNames = self.Phases.keys()
860        phaseSizer = wx.BoxSizer(wx.HORIZONTAL)
861        phaseSizer.Add(wx.StaticText(self.panel,label=' Select phase: '),0,WACV)
862        phase = wx.ComboBox(self.panel,value=self.pName,choices=pNames,
863            style=wx.CB_READONLY|wx.CB_DROPDOWN)
864        phase.Bind(wx.EVT_COMBOBOX,OnPhase)
865        phaseSizer.Add(phase,0,WACV)
866        mainSizer.Add(phaseSizer)
867        Phase = self.Phases[self.pName]
868        cx,ct = Phase['General']['AtomPtrs'][:2]
869        Atoms = Phase['Atoms']
870        aNames = [atom[ct-1] for atom in Atoms]
871#        GSASIIpath.IPyBreak()
872        atomSizer = wx.BoxSizer(wx.HORIZONTAL)
873        atomSizer.Add(wx.StaticText(self.panel,label=' Origin atom (O in A-O-B): '),0,WACV)
874        origAtom = wx.ComboBox(self.panel,value=self.Oatom,choices=aNames,
875            style=wx.CB_READONLY|wx.CB_DROPDOWN)
876        origAtom.Bind(wx.EVT_COMBOBOX,OnOrigAtom)
877        atomSizer.Add(origAtom,0,WACV)       
878        mainSizer.Add(atomSizer)
879        neigh = []
880        if self.Oatom:
881            neigh = G2mth.FindAllNeighbors(Phase,self.Oatom,aNames)[0]
882            mainSizer.Add(wx.StaticText(self.panel,label=' A-O-B angle for A,B: '),0,WACV)
883            bNames = ['',]
884            if neigh:
885#                GSASIIpath.IPyBreak()
886                for iA,aName in enumerate(neigh):
887                    for cName in neigh[iA+1:]:
888                        bNames.append('%s;%s'%(aName[0].replace(' ',''),cName[0].replace(' ','')))
889                targAtoms = wx.ComboBox(self.panel,value=self.Tatoms,choices=bNames,
890                    style=wx.CB_READONLY|wx.CB_DROPDOWN)
891                targAtoms.Bind(wx.EVT_COMBOBOX,OnTargAtoms)
892                mainSizer.Add(targAtoms,0,WACV)
893
894
895        OkBtn = wx.Button(self.panel,-1,"Ok")
896        OkBtn.Bind(wx.EVT_BUTTON, self.OnOk)
897        cancelBtn = wx.Button(self.panel,-1,"Cancel")
898        cancelBtn.Bind(wx.EVT_BUTTON, self.OnCancel)
899        btnSizer = wx.BoxSizer(wx.HORIZONTAL)
900        btnSizer.Add((20,20),1)
901        btnSizer.Add(OkBtn)
902        btnSizer.Add((20,20),1)
903        btnSizer.Add(cancelBtn)
904        btnSizer.Add((20,20),1)
905       
906        mainSizer.Add(btnSizer,0,wx.EXPAND|wx.BOTTOM|wx.TOP, 10)
907        self.panel.SetSizer(mainSizer)
908        self.panel.Fit()
909        self.Fit()
910
911    def GetSelection(self):
912        return self.pName,self.Oatom,self.Tatoms
913
914    def OnOk(self,event):
915        parent = self.GetParent()
916        parent.Raise()
917        self.EndModal(wx.ID_OK)
918
919    def OnCancel(self,event):
920        parent = self.GetParent()
921        parent.Raise()
922        self.EndModal(wx.ID_CANCEL)       
923                   
924       
925if __name__ == "__main__":
926    app = wx.PySimpleApp() # create the App
927    frm = wx.Frame(None)
928    frm.Show()
929    PSvarDict = {'::a':1.0,'::b':1.1,'0::c':1.2,'::AlongVaraiableName':1000.}
930    #PSvars = PSvarDict.keys()
931    indepvarDict = {'Temperature':1.0,'Pressure':1.1,'Phase of Moon':1.2,'1:1:HAP':1.3}
932    dlg = ExpressionDialog(frm,indepvarDict,
933                           header="Edit the PseudoVar expression",
934                           fit=False,
935                           depVarDict=PSvarDict,
936                           #VarLabel="New PseudoVar",                           
937                           )
938    newobj = dlg.Show(True)
939    dlg = ExpressionDialog(frm,indepvarDict,newobj,
940                           header="Edit the PseudoVar expression",
941                           fit=False,
942                           depVarDict=PSvarDict,
943                           #VarLabel="New PseudoVar",                           
944                           )
945    newobj = dlg.Show(True)
946    print dlg.GetDepVar()
947    dlg = ExpressionDialog(frm,PSvarDict,
948                           header="Edit the PseudoVar expression",
949                           fit=True)
950    newobj = dlg.Show(True)
951    print dlg.GetDepVar()
952
953    #app.MainLoop()
954
955
956    import cPickle
957    def showEQ(calcobj):
958        print
959        print calcobj.eObj.expression
960        for v in sorted(calcobj.eObj.freeVars.keys()+calcobj.eObj.assgnVars.keys()):
961            print "  ",v,'=',calcobj.exprDict[v]
962        print calcobj.EvalExpression()
963    print "starting test"
964    obj = G2obj.ExpressionObj()
965    obj.expression = "A*np.exp(B)"
966    obj.assgnVars =  {'B': '0::Afrac:*'}
967    obj.freeVars =  {'A': [u'A', 0.5, True]}
968    obj.CheckVars()
969    parmDict2 = {'0::Afrac:0':1.0, '0::Afrac:1': 1.0}
970    calcobj = G2obj.ExpressionCalcObj(obj)
971    calcobj.SetupCalc(parmDict2)
972    showEQ(calcobj)
973    fp = open('/tmp/obj.pickle','w')
974    cPickle.dump(obj,fp)
975    fp.close()
976   
977    obj.expression = "A*np.exp(-2/B)"
978    obj.assgnVars =  {'A': '0::Afrac:0', 'B': '0::Afrac:1'}
979    obj.freeVars =  {}
980    parmDict1 = {'0::Afrac:0':1.0, '0::Afrac:1': -2.0}
981    calcobj = G2obj.ExpressionCalcObj(obj)
982    calcobj.SetupCalc(parmDict1)
983    showEQ(calcobj)
984
985    fp = open('/tmp/obj.pickle','r')
986    obj = cPickle.load(fp)
987    fp.close()
988    parmDict2 = {'0::Afrac:0':0.0, '0::Afrac:1': 1.0}
989    calcobj = G2obj.ExpressionCalcObj(obj)
990    calcobj.SetupCalc(parmDict2)
991    showEQ(calcobj)
992
993    parmDict2 = {'0::Afrac:0':1.0, '0::Afrac:1': 1.0}
994    calcobj = G2obj.ExpressionCalcObj(obj)
995    calcobj.SetupCalc(parmDict2)
996    showEQ(calcobj)
997   
Note: See TracBrowser for help on using the repository browser.