source: trunk/GSASIIexprGUI.py @ 2569

Last change on this file since 2569 was 2569, checked in by vondreele, 6 years ago

add 'f' or 'g' to maxdigits in FormatValue? in G2py3 - should be revised some more
do a lot of TextCtrl? --> ValidatedTextCtrl? replacements
note that ValidatedTextCtrl? only checks val <= max & not val < max
added tc.event to ValidatedTextCtrl? as some things needed that
G2imgGui - mostly done
G2restrGUI - done
G2pwdGUI - started

  • Property svn:eol-style set to native
  • Property svn:keywords set to Date Author Revision URL Id
File size: 39.6 KB
Line 
1# -*- coding: utf-8 -*-
2#GSASIIexprGUI - Expression Definition and Evaluation
3########### SVN repository information ###################
4# $Date: 2016-12-09 21:17:43 +0000 (Fri, 09 Dec 2016) $
5# $Author: vondreele $
6# $Revision: 2569 $
7# $URL: trunk/GSASIIexprGUI.py $
8# $Id: GSASIIexprGUI.py 2569 2016-12-09 21:17:43Z vondreele $
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: 2569 $")
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.ShowVars)
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 ShowVars(self):
410        # create widgets to associate vars with labels and/or show messages
411        self.varSizer.Clear(True)
412        self.errbox = wxscroll.ScrolledPanel(self,style=wx.HSCROLL)
413        self.errbox.SetMinSize((100,130))
414        self.varSizer.Add(self.errbox,0,wx.ALL|wx.EXPAND,1)
415        self.varbox = wxscroll.ScrolledPanel(self,style=wx.HSCROLL)
416        self.varSizer.Add(self.varbox,1,wx.ALL|wx.EXPAND,1)
417        Siz = wx.BoxSizer(wx.VERTICAL)
418        Siz.Add(
419            wx.StaticText(self.varbox,wx.ID_ANY,
420                          'Assignment of variables to labels:'),
421            0,wx.EXPAND|wx.ALIGN_CENTER,0)
422        GridSiz = wx.FlexGridSizer(0,5,2,2)
423        GridSiz.Add(
424            wx.StaticText(self.varbox,wx.ID_ANY,'label',style=wx.CENTER),
425            0,wx.ALIGN_CENTER)
426        lbls = ('varib. type\nselection','variable\nname','value')
427        choices = ['Free','Phase','Hist./Phase','Hist.','Global']
428        if self.fit:
429            lbls += ('refine\nflag',)
430        else:
431            lbls += ('',)
432            choices[0] = ''
433        for i in range(1,len(choices)): # remove empty menus from choice list
434            if not len(self.parmLists[i]): choices[i] = ''
435        self.AllowedChoices = [i for i in range(len(choices)) if choices[i]]
436        for lbl in lbls:
437            w = wx.StaticText(self.varbox,wx.ID_ANY,lbl,style=wx.CENTER)
438            w.SetMinSize((80,-1))
439            GridSiz.Add(w,0,wx.ALIGN_CENTER)
440
441        # show input for each var in expression.
442        for v in self.exprVarLst:
443            # label
444            GridSiz.Add(wx.StaticText(self.varbox,wx.ID_ANY,v),0,wx.ALIGN_CENTER,0)
445            # assignment type
446            ch = wx.Choice(
447                self.varbox, wx.ID_ANY,
448                choices = [choices[i] for i in self.AllowedChoices]
449                )
450            GridSiz.Add(ch,0,wx.ALIGN_LEFT,0)
451            if v in self.varSelect and self.varSelect.get(v) in self.AllowedChoices:
452                i = self.AllowedChoices.index(self.varSelect[v])
453                ch.SetSelection(i)
454            else:
455                ch.SetSelection(wx.NOT_FOUND)
456            ch.label = v
457            ch.Bind(wx.EVT_CHOICE,self.OnChoice)
458
459            # var name/var assignment
460            if self.varSelect.get(v) is None:
461                GridSiz.Add((-1,-1),0,wx.ALIGN_LEFT|wx.EXPAND,0)
462            elif self.varSelect.get(v) == 0:
463                wid = G2G.ValidatedTxtCtrl(self.varbox,self.varName,v,
464                                            #OnLeave=self.OnTxtLeave,
465                                            size=(50,-1))
466                GridSiz.Add(wid,0,wx.ALIGN_LEFT|wx.EXPAND,0)
467            else:
468                wid = wx.StaticText(self.varbox,wx.ID_ANY,self.varName[v])
469                GridSiz.Add(wid,0,wx.ALIGN_LEFT,0)
470
471            # value
472            if self.varSelect.get(v) is None:
473                GridSiz.Add((-1,-1),0,wx.ALIGN_RIGHT|wx.EXPAND,0)
474            elif self.varSelect.get(v) == 0:
475                wid = G2G.ValidatedTxtCtrl(self.varbox,self.varValue,v,
476                                            #OnLeave=self.OnTxtLeave,
477                                            size=(75,-1))
478                GridSiz.Add(wid,0,wx.ALIGN_LEFT|wx.EXPAND,0)
479                wid.Bind(wx.EVT_CHAR,self.OnChar)
480            else:
481                var = self.varName[v]
482                if '*' in var:
483                    #[self.parmDict[v] for v in LookupWildCard(var,self.parmDict.keys())]
484                    #print self.varValue[v]
485                    vs = G2obj.LookupWildCard(var,self.parmDict.keys())
486                    s = '('+str(len(vs))+' values)'
487                elif var in self.parmDict:
488                    val = self.parmDict[var]
489                    s = G2py3.FormatSigFigs(val).rstrip('0')
490                else:
491                    s = '?'
492                wid = wx.StaticText(self.varbox,wx.ID_ANY,s)
493                GridSiz.Add(wid,0,wx.ALIGN_LEFT,0)
494
495            # show a refine flag for Free Vars only
496            if self.varSelect.get(v) == 0 and self.fit:
497                self.varRefflag[v] = self.varRefflag.get(v,True)
498                wid = G2G.G2CheckBox(self.varbox,'',self.varRefflag,v)
499                GridSiz.Add(wid,0,wx.ALIGN_LEFT|wx.EXPAND,0)
500            else:
501                wid = (-1,-1)
502                GridSiz.Add(wid,0,wx.ALIGN_LEFT|wx.EXPAND,0)
503
504        Siz.Add(GridSiz)
505        self.varbox.SetSizer(Siz,True)
506        xwid,yhgt = Siz.Fit(self.varbox)
507        self.varbox.SetMinSize((xwid,130))
508        self.varbox.SetAutoLayout(1)
509        self.varbox.SetupScrolling()
510        self.varbox.Refresh()
511        self.Layout()
512        #self.mainsizer.Fit(self)
513        self.SendSizeEvent() # force repaint
514        return
515
516    def OnDepChoice(self,event):
517        '''Respond to a selection of a variable type for a label in
518        an expression
519        '''
520        sel = self.depChoices[event.GetEventObject().GetSelection()]
521        var = self.SelectG2var(sel,'Dependent variable',self.depParmLists[sel])
522        if var is None:
523            self.dependentVar = None
524            self.OnValidate(None)
525            event.GetEventObject().SetSelection(wx.NOT_FOUND)
526            return
527        self.dependentVar = var
528        self.depLabel.SetLabel(var)
529        self.OnValidate(None)
530        self.Layout()
531
532    def GetDepVar(self):
533        '''Returns the name of the dependent variable, when depVarDict is used.
534        '''
535        return self.dependentVar
536       
537    def OnChoice(self,event):
538        '''Respond to a selection of a variable type for a label in
539        an expression
540        '''
541        v = event.GetEventObject().label
542        sel = self.AllowedChoices[event.GetEventObject().GetSelection()]
543        if sel == 0:
544            sv = G2obj.MakeUniqueLabel(v,self.usedVars)
545            self.varSelect[v] = sel
546            self.varName[v] = sv
547            self.varValue[v] = self.varValue.get(v,0.0)
548        else:
549            var = self.SelectG2var(sel,v,self.parmLists[sel])
550            if var is None:
551                self.OnValidate(None)
552                return
553            self.varSelect[v] = sel
554            self.varName[v] = var
555        self.OnValidate(None)
556
557    def SelectG2var(self,sel,var,parmList):
558        '''Offer a selection of a GSAS-II variable.
559
560        :param int sel: Determines the type of variable to be selected.
561          where 1 is used for Phase variables, and 2 for Histogram/Phase vars,
562          3 for Histogram vars and 4 for Global vars.
563        :returns: a variable name or None (if Cancel is pressed)
564        '''
565        if not parmList:
566            return None
567        l2 = l1 = 1
568        for i in parmList:
569            l1 = max(l1,len(i))
570            loc,desc = G2obj.VarDescr(i)
571            l2 = max(l2,len(loc))
572        fmt = u"{:"+str(l1)+"s} {:"+str(l2)+"s} {:s}"
573        varListlbl = [fmt.format(i,*G2obj.VarDescr(i)) for i in parmList]
574
575        dlg = G2G.G2SingleChoiceDialog(
576            self,'Select GSAS-II variable for '+str(var)+':',
577            'Select variable',
578            varListlbl,monoFont=True)
579        dlg.SetSize((625,250))
580        dlg.CenterOnParent()
581        var = None
582        if dlg.ShowModal() == wx.ID_OK:
583            i = dlg.GetSelection()
584            var = parmList[i]
585        dlg.Destroy()
586        return var
587
588    def showError(self,msg1,msg2='',msg3=''):
589        '''Show an error message of 1 to 3 sections. The second
590        section is shown in an equally-spaced font.
591       
592        :param str msg1: msg1 is shown in a the standard font
593        :param str msg2: msg2 is shown in a equally-spaced (wx.MODERN) font
594        :param str msg3: msg3 is shown in a the standard font
595        '''
596        self.OKbtn.Disable()
597        if self.ExtraBtn: self.ExtraBtn.Disable()
598        self.varSizer.Clear(True)
599        self.errbox = wxscroll.ScrolledPanel(self,style=wx.HSCROLL)
600        self.errbox.SetMinSize((200,130))
601        self.varSizer.Add(self.errbox,1,wx.ALL|wx.EXPAND,1)
602        Siz = wx.BoxSizer(wx.VERTICAL)
603        errMsg1 = wx.StaticText(self.errbox, wx.ID_ANY,"")
604        Siz.Add(errMsg1, 0, wx.ALIGN_LEFT|wx.LEFT|wx.EXPAND, 5)
605        errMsg2 = wx.StaticText(self.errbox, wx.ID_ANY,"\n\n")
606        font1 = wx.Font(errMsg2.GetFont().GetPointSize(),
607                        wx.MODERN, wx.NORMAL, wx.NORMAL, False)
608        errMsg2.SetFont(font1)
609        Siz.Add(errMsg2, 0, wx.ALIGN_LEFT|wx.LEFT|wx.EXPAND, 5)
610        errMsg3 = wx.StaticText(self.errbox, wx.ID_ANY,"")
611        Siz.Add(errMsg3, 0, wx.ALIGN_LEFT|wx.LEFT|wx.EXPAND, 5)
612        self.errbox.SetSizer(Siz,True)
613        Siz.Fit(self.errbox)
614        errMsg1.SetLabel(msg1)
615        errMsg2.SetLabel("  "+msg2)
616        errMsg2.Wrap(-1)
617        errMsg3.SetLabel(msg3)
618        self.Layout()
619
620    def OnValidate(self,event):
621        '''Respond to a press of the Validate button or when a variable
622        is associated with a label (in :meth:`OnChoice`)
623        '''
624        self.setEvalResult('(expression cannot be evaluated)')
625        self.timer.Stop()
626        self.expr = self.exCtrl.GetValue().strip()
627        self.varSizer.Clear(True)
628        if not self.expr: 
629            self.showError(
630                "Invalid Expression:","",
631                "(an expression must be entered)")
632            return
633        exprObj = G2obj.ExpressionObj()
634        ret = exprObj.ParseExpression(self.expr)
635        if not ret:
636            self.showError(*exprObj.lastError)
637            return
638        self.exprVarLst,pkgdict = ret
639        wx.CallAfter(self.Repaint,exprObj)
640           
641    def Repaint(self,exprObj):
642        'Redisplay the variables and continue the validation'
643        self.ShowVars() # show widgets to set vars
644        msg = self.CheckVars() 
645        if msg:
646            self.setEvalResult(msg)
647            return
648        exprObj.LoadExpression(
649            self.expr,
650            self.exprVarLst,
651            self.varSelect,
652            self.varName,
653            self.varValue,
654            self.varRefflag,
655            )
656        try:
657            calcobj = G2obj.ExpressionCalcObj(exprObj)
658            calcobj.SetupCalc(self.parmDict)
659            val = calcobj.EvalExpression()
660        except Exception as msg:
661            self.setEvalResult("Error in evaluation: "+str(msg))
662            return
663        if not np.isfinite(val):
664            self.setEvalResult("Expression value is infinite or out-of-bounds")
665            return
666        s = G2py3.FormatSigFigs(val).rstrip('0')
667        depVal = ""
668        if self.depVarDict:
669            if not self.dependentVar:
670                self.setEvalResult("A dependent variable must be selected.")
671                return
672            depVal = '; Variable "' + self.dependentVar + '" = ' + str(
673                self.depVarDict.get(self.dependentVar,'?')
674                )
675        self.setEvalResult("Expression evaluates to: "+str(s)+depVal)
676        self.OKbtn.Enable()
677        if self.ExtraBtn: self.ExtraBtn.Enable()
678           
679#==========================================================================
680class BondDialog(wx.Dialog):
681    '''A wx.Dialog that allows a user to select a bond length to be evaluated.
682    What needs to be done here? Need phase info for atoms
683    0. Select phase
684    1. Select 1st atom from dialog
685    2. Find neighbors & select one from dialog
686    3. Set up distance equation & save it - has to look like result from Show in above ExpressionDialog       
687    Use existing bond & esd calculate routines
688    '''
689    def __init__(self, parent, Phases, parmDict, exprObj=None,
690                 header='Enter restraint expression here',
691                 wintitle='Expression Editor',
692                 VarLabel=None,depVarDict=None,
693                 ExtraButton=None,usedVars=[]):
694        wx.Dialog.__init__(self,parent,wx.ID_ANY,wintitle, 
695            pos=wx.DefaultPosition,style=wx.DEFAULT_DIALOG_STYLE)
696        self.panel = wx.Panel(self)         #just a dummy - gets destroyed in Draw!
697        self.Phases = Phases
698        self.parmDict = parmDict
699        self.header = header
700        self.pName = Phases.keys()[0]
701        DisAglCtls = {}
702        dlg = G2gd.DisAglDialog(self.panel,DisAglCtls,self.Phases[self.pName]['General'],Reset=False)
703        if dlg.ShowModal() == wx.ID_OK:
704            Phases[self.pName]['General']['DisAglCtls'] = dlg.GetData()
705        dlg.Destroy()
706        self.Oatom = ''
707        self.Tatom = ''
708        self.Draw()
709       
710    def Draw(self):
711       
712        def OnPhase(event):
713            Obj = event.GetEventObject()
714            self.pName = Obj.GetValue()
715            self.Oatom = ''
716            DisAglCtls = {}
717            dlg = G2gd.DisAglDialog(self.panel,DisAglCtls,self.Phases[self.pName]['General'],Reset=False)
718            if dlg.ShowModal() == wx.ID_OK:
719                self.Phases[self.pName]['General']['DisAglCtls'] = dlg.GetData()
720            dlg.Destroy()
721            wx.CallAfter(self.Draw)
722           
723        def OnOrigAtom(event):
724            Obj = event.GetEventObject()
725            self.Oatom = Obj.GetValue()
726            wx.CallAfter(self.Draw)
727           
728        def OnTargAtom(event):
729            Obj = event.GetEventObject()
730            self.Tatom = Obj.GetValue()
731            wx.CallAfter(self.Draw)
732
733        self.panel.Destroy()
734        self.panel = wx.Panel(self)
735        mainSizer = wx.BoxSizer(wx.VERTICAL)
736        mainSizer.Add(wx.StaticText(self.panel,label=self.header),0,WACV)
737        pNames = self.Phases.keys()
738        phaseSizer = wx.BoxSizer(wx.HORIZONTAL)
739        phaseSizer.Add(wx.StaticText(self.panel,label=' Select phase: '),0,WACV)
740        phase = wx.ComboBox(self.panel,value=self.pName,choices=pNames,
741            style=wx.CB_READONLY|wx.CB_DROPDOWN)
742        phase.Bind(wx.EVT_COMBOBOX,OnPhase)
743        phaseSizer.Add(phase,0,WACV)
744        mainSizer.Add(phaseSizer)
745        Phase = self.Phases[self.pName]
746        cx,ct = Phase['General']['AtomPtrs'][:2]
747        Atoms = Phase['Atoms']
748        aNames = [atom[ct-1] for atom in Atoms]
749        atomSizer = wx.BoxSizer(wx.HORIZONTAL)
750        atomSizer.Add(wx.StaticText(self.panel,label=' Origin atom: '),0,WACV)
751        origAtom = wx.ComboBox(self.panel,value=self.Oatom,choices=aNames,
752            style=wx.CB_READONLY|wx.CB_DROPDOWN)
753        origAtom.Bind(wx.EVT_COMBOBOX,OnOrigAtom)
754        atomSizer.Add(origAtom,0,WACV)
755        atomSizer.Add(wx.StaticText(self.panel,label=' distance to: '),0,WACV)
756        neigh = []
757        if self.Oatom:
758#            GSASIIpath.IPyBreak()
759            neigh = G2mth.FindAllNeighbors(Phase,self.Oatom,aNames)
760        bNames = ['',]
761        if neigh:
762            bNames = [item[0]+' d=%.3f'%(item[1]) for item in neigh[0]]
763        targAtom = wx.ComboBox(self.panel,value=self.Tatom,choices=bNames,
764            style=wx.CB_READONLY|wx.CB_DROPDOWN)
765        targAtom.Bind(wx.EVT_COMBOBOX,OnTargAtom)
766        atomSizer.Add(targAtom,0,WACV)
767       
768        mainSizer.Add(atomSizer)
769
770
771        OkBtn = wx.Button(self.panel,-1,"Ok")
772        OkBtn.Bind(wx.EVT_BUTTON, self.OnOk)
773        cancelBtn = wx.Button(self.panel,-1,"Cancel")
774        cancelBtn.Bind(wx.EVT_BUTTON, self.OnCancel)
775        btnSizer = wx.BoxSizer(wx.HORIZONTAL)
776        btnSizer.Add((20,20),1)
777        btnSizer.Add(OkBtn)
778        btnSizer.Add((20,20),1)
779        btnSizer.Add(cancelBtn)
780        btnSizer.Add((20,20),1)
781       
782        mainSizer.Add(btnSizer,0,wx.EXPAND|wx.BOTTOM|wx.TOP, 10)
783        self.panel.SetSizer(mainSizer)
784        self.panel.Fit()
785        self.Fit()
786
787    def GetSelection(self):
788        return self.pName,self.Oatom,self.Tatom
789
790    def OnOk(self,event):
791        parent = self.GetParent()
792        parent.Raise()
793        self.EndModal(wx.ID_OK)
794
795    def OnCancel(self,event):
796        parent = self.GetParent()
797        parent.Raise()
798        self.EndModal(wx.ID_CANCEL)       
799           
800#==========================================================================
801class AngleDialog(wx.Dialog):
802    '''A wx.Dialog that allows a user to select a bond angle to be evaluated.
803    What needs to be done here? Need phase info for atom
804    0. Select phase
805    1. Select 1st atom from dialog
806    2. Find neighbors & select two from dialog
807    3. Set up angle equation & save it - has to look like result from Show in above ExpressionDialog       
808    Use existing angle & esd calculate routines
809    '''
810    def __init__(self, parent, Phases, parmDict, exprObj=None,
811                 header='Enter restraint expression here',
812                 wintitle='Expression Editor',
813                 VarLabel=None,depVarDict=None,
814                 ExtraButton=None,usedVars=[]):
815        wx.Dialog.__init__(self,parent,wx.ID_ANY,wintitle, 
816            pos=wx.DefaultPosition,style=wx.DEFAULT_DIALOG_STYLE)
817        self.panel = wx.Panel(self)         #just a dummy - gets destroyed in Draw!
818        self.Phases = Phases
819        self.parmDict = parmDict
820        self.header = header
821        self.pName = Phases.keys()[0]
822        DisAglCtls = {}
823        dlg = G2gd.DisAglDialog(self.panel,DisAglCtls,self.Phases[self.pName]['General'],Reset=False)
824        if dlg.ShowModal() == wx.ID_OK:
825            Phases[self.pName]['General']['DisAglCtls'] = dlg.GetData()
826        dlg.Destroy()
827        self.Oatom = ''
828        self.Tatoms = ''
829        self.Draw()
830
831    def Draw(self):
832       
833        def OnPhase(event):
834            Obj = event.GetEventObject()
835            self.pName = Obj.GetValue()
836            self.Oatom = ''
837            DisAglCtls = {}
838            dlg = G2gd.DisAglDialog(self.panel,DisAglCtls,self.Phases[self.pName]['General'],Reset=False)
839            if dlg.ShowModal() == wx.ID_OK:
840                self.Phases[self.pName]['General']['DisAglCtls'] = dlg.GetData()
841            dlg.Destroy()
842            wx.CallAfter(self.Draw)
843           
844        def OnOrigAtom(event):
845            Obj = event.GetEventObject()
846            self.Oatom = Obj.GetValue()
847            wx.CallAfter(self.Draw)           
848
849        def OnTargAtoms(event):
850            Obj = event.GetEventObject()
851            self.Tatoms = Obj.GetValue()
852            wx.CallAfter(self.Draw)
853
854        self.panel.Destroy()
855        self.panel = wx.Panel(self)
856        mainSizer = wx.BoxSizer(wx.VERTICAL)
857        mainSizer.Add(wx.StaticText(self.panel,label=self.header),0,WACV)
858        pNames = self.Phases.keys()
859        phaseSizer = wx.BoxSizer(wx.HORIZONTAL)
860        phaseSizer.Add(wx.StaticText(self.panel,label=' Select phase: '),0,WACV)
861        phase = wx.ComboBox(self.panel,value=self.pName,choices=pNames,
862            style=wx.CB_READONLY|wx.CB_DROPDOWN)
863        phase.Bind(wx.EVT_COMBOBOX,OnPhase)
864        phaseSizer.Add(phase,0,WACV)
865        mainSizer.Add(phaseSizer)
866        Phase = self.Phases[self.pName]
867        cx,ct = Phase['General']['AtomPtrs'][:2]
868        Atoms = Phase['Atoms']
869        aNames = [atom[ct-1] for atom in Atoms]
870#        GSASIIpath.IPyBreak()
871        atomSizer = wx.BoxSizer(wx.HORIZONTAL)
872        atomSizer.Add(wx.StaticText(self.panel,label=' Origin atom (O in A-O-B): '),0,WACV)
873        origAtom = wx.ComboBox(self.panel,value=self.Oatom,choices=aNames,
874            style=wx.CB_READONLY|wx.CB_DROPDOWN)
875        origAtom.Bind(wx.EVT_COMBOBOX,OnOrigAtom)
876        atomSizer.Add(origAtom,0,WACV)       
877        mainSizer.Add(atomSizer)
878        neigh = []
879        if self.Oatom:
880            neigh = G2mth.FindAllNeighbors(Phase,self.Oatom,aNames)[0]
881            mainSizer.Add(wx.StaticText(self.panel,label=' A-O-B angle for A,B: '),0,WACV)
882            bNames = ['',]
883            if neigh:
884#                GSASIIpath.IPyBreak()
885                for iA,aName in enumerate(neigh):
886                    for cName in neigh[iA+1:]:
887                        bNames.append('%s;%s'%(aName[0].replace(' ',''),cName[0].replace(' ','')))
888                targAtoms = wx.ComboBox(self.panel,value=self.Tatoms,choices=bNames,
889                    style=wx.CB_READONLY|wx.CB_DROPDOWN)
890                targAtoms.Bind(wx.EVT_COMBOBOX,OnTargAtoms)
891                mainSizer.Add(targAtoms,0,WACV)
892
893
894        OkBtn = wx.Button(self.panel,-1,"Ok")
895        OkBtn.Bind(wx.EVT_BUTTON, self.OnOk)
896        cancelBtn = wx.Button(self.panel,-1,"Cancel")
897        cancelBtn.Bind(wx.EVT_BUTTON, self.OnCancel)
898        btnSizer = wx.BoxSizer(wx.HORIZONTAL)
899        btnSizer.Add((20,20),1)
900        btnSizer.Add(OkBtn)
901        btnSizer.Add((20,20),1)
902        btnSizer.Add(cancelBtn)
903        btnSizer.Add((20,20),1)
904       
905        mainSizer.Add(btnSizer,0,wx.EXPAND|wx.BOTTOM|wx.TOP, 10)
906        self.panel.SetSizer(mainSizer)
907        self.panel.Fit()
908        self.Fit()
909
910    def GetSelection(self):
911        return self.pName,self.Oatom,self.Tatoms
912
913    def OnOk(self,event):
914        parent = self.GetParent()
915        parent.Raise()
916        self.EndModal(wx.ID_OK)
917
918    def OnCancel(self,event):
919        parent = self.GetParent()
920        parent.Raise()
921        self.EndModal(wx.ID_CANCEL)       
922                   
923       
924if __name__ == "__main__":
925    app = wx.PySimpleApp() # create the App
926    frm = wx.Frame(None)
927    frm.Show()
928    PSvarDict = {'::a':1.0,'::b':1.1,'0::c':1.2,'::AlongVaraiableName':1000.}
929    #PSvars = PSvarDict.keys()
930    indepvarDict = {'Temperature':1.0,'Pressure':1.1,'Phase of Moon':1.2,'1:1:HAP':1.3}
931    dlg = ExpressionDialog(frm,indepvarDict,
932                           header="Edit the PseudoVar expression",
933                           fit=False,
934                           depVarDict=PSvarDict,
935                           #VarLabel="New PseudoVar",                           
936                           )
937    newobj = dlg.Show(True)
938    dlg = ExpressionDialog(frm,indepvarDict,newobj,
939                           header="Edit the PseudoVar expression",
940                           fit=False,
941                           depVarDict=PSvarDict,
942                           #VarLabel="New PseudoVar",                           
943                           )
944    newobj = dlg.Show(True)
945    print dlg.GetDepVar()
946    dlg = ExpressionDialog(frm,PSvarDict,
947                           header="Edit the PseudoVar expression",
948                           fit=True)
949    newobj = dlg.Show(True)
950    print dlg.GetDepVar()
951
952    #app.MainLoop()
953
954
955    import cPickle
956    def showEQ(calcobj):
957        print
958        print calcobj.eObj.expression
959        for v in sorted(calcobj.eObj.freeVars.keys()+calcobj.eObj.assgnVars.keys()):
960            print "  ",v,'=',calcobj.exprDict[v]
961        print calcobj.EvalExpression()
962    print "starting test"
963    obj = G2obj.ExpressionObj()
964    obj.expression = "A*np.exp(B)"
965    obj.assgnVars =  {'B': '0::Afrac:*'}
966    obj.freeVars =  {'A': [u'A', 0.5, True]}
967    obj.CheckVars()
968    parmDict2 = {'0::Afrac:0':1.0, '0::Afrac:1': 1.0}
969    calcobj = G2obj.ExpressionCalcObj(obj)
970    calcobj.SetupCalc(parmDict2)
971    showEQ(calcobj)
972    fp = open('/tmp/obj.pickle','w')
973    cPickle.dump(obj,fp)
974    fp.close()
975   
976    obj.expression = "A*np.exp(-2/B)"
977    obj.assgnVars =  {'A': '0::Afrac:0', 'B': '0::Afrac:1'}
978    obj.freeVars =  {}
979    parmDict1 = {'0::Afrac:0':1.0, '0::Afrac:1': -2.0}
980    calcobj = G2obj.ExpressionCalcObj(obj)
981    calcobj.SetupCalc(parmDict1)
982    showEQ(calcobj)
983
984    fp = open('/tmp/obj.pickle','r')
985    obj = cPickle.load(fp)
986    fp.close()
987    parmDict2 = {'0::Afrac:0':0.0, '0::Afrac:1': 1.0}
988    calcobj = G2obj.ExpressionCalcObj(obj)
989    calcobj.SetupCalc(parmDict2)
990    showEQ(calcobj)
991
992    parmDict2 = {'0::Afrac:0':1.0, '0::Afrac:1': 1.0}
993    calcobj = G2obj.ExpressionCalcObj(obj)
994    calcobj.SetupCalc(parmDict2)
995    showEQ(calcobj)
996   
Note: See TracBrowser for help on using the repository browser.