source: trunk/GSASIIexprGUI.py @ 2868

Last change on this file since 2868 was 2868, checked in by toby, 6 years ago

fix bug in Expr Edit; add math import (should not be needed, but will not hurt)

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