source: trunk/GSASIIexprGUI.py @ 3193

Last change on this file since 3193 was 3136, checked in by vondreele, 8 years ago

make GSAS-II python 3.6 compliant & preserve python 2.7 use;changes:
do from future import division, print_function for all GSAS-II py sources
all menu items revised to be py 2.7/3.6 compliant
all wx.OPEN --> wx.FD_OPEN in file dialogs
all integer divides (typically for image pixel math) made explicit with ; ambiguous ones made floats as appropriate
all print "stuff" --> print (stuff)
all print >> pFile,'stuff' --> pFile.writeCIFtemplate('stuff')
all read file opens made explicit 'r' or 'rb'
all cPickle imports made for py2.7 or 3.6 as cPickle or _pickle; test for '2' platform.version_tuple[0] for py 2.7
define cPickleload to select load(fp) or load(fp,encoding='latin-1') for loading gpx files; provides cross compatibility between py 2.7/3.6 gpx files
make dict.keys() as explicit list(dict.keys()) as needed (NB: possible source of remaining py3.6 bugs)
make zip(a,b) as explicit list(zip(a,b)) as needed (NB: possible source of remaining py3.6 bugs)
select unichr/chr according test for '2' platform.version_tuple[0] for py 2.7 (G2pwdGUI * G2plot) for special characters
select wg.EVT_GRID_CELL_CHANGE (classic) or wg.EVT_GRID_CELL_CHANGED (phoenix) in grid Bind
maxint --> maxsize; used in random number stuff
raise Exception,"stuff" --> raise Exception("stuff")
wx 'classic' sizer.DeleteWindows?() or 'phoenix' sizer.Clear(True)
wx 'classic' SetToolTipString?(text) or 'phoenix' SetToolTip?(wx.ToolTip?(text)); define SetToolTipString?(self,text) to handle the choice in plots
status.SetFields? --> status.SetStatusText?
'classic' AddSimpleTool? or 'phoenix' self.AddTool? for plot toolbar; Bind different as well
define GetItemPydata? as it doesn't exist in wx 'phoenix'
allow python versions 2.7 & 3.6 to run GSAS-II
Bind override commented out - no logging capability (NB: remove all logging code?)
all import ContentsValidator? open filename & test if valid then close; filepointer removed from Reader
binary importers (mostly images) test for 'byte' type & convert as needed to satisfy py 3.6 str/byte rules

  • 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-10-23 16:39:16 +0000 (Mon, 23 Oct 2017) $
5# $Author: toby $
6# $Revision: 3136 $
7# $URL: trunk/GSASIIexprGUI.py $
8# $Id: GSASIIexprGUI.py 3136 2017-10-23 16:39:16Z 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'''
25from __future__ import division, print_function
26import re
27import wx
28import wx.lib.scrolledpanel as wxscroll
29import numpy as np
30import GSASIIpath
31GSASIIpath.SetVersionNumber("$Revision: 3136 $")
32import GSASIIctrlGUI 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.CallLater(100,self.Repaint,exprObj)
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,list(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                    vs = G2obj.LookupWildCard(var,self.parmDict.keys())
612                    s = '('+str(len(vs))+' values)'
613                elif var in self.parmDict:
614                    val = self.parmDict[var]
615                    s = G2py3.FormatSigFigs(val).rstrip('0')
616                else:
617                    s = '?'
618                wid = wx.StaticText(self.varbox,wx.ID_ANY,s)
619                GridSiz.Add(wid,0,wx.ALIGN_LEFT,0)
620
621            # show a refine flag for Free Vars only
622            if self.varSelect.get(v) == 0 and self.fit:
623                self.varRefflag[v] = self.varRefflag.get(v,True)
624                wid = G2G.G2CheckBox(self.varbox,'',self.varRefflag,v)
625                GridSiz.Add(wid,0,wx.ALIGN_LEFT|wx.EXPAND,0)
626            else:
627                wid = (-1,-1)
628                GridSiz.Add(wid,0,wx.ALIGN_LEFT|wx.EXPAND,0)
629
630        Siz.Add(GridSiz)
631        self.varbox.SetSizer(Siz,True)
632
633        # evaluate the expression, displaying errors or the expression value
634        try:
635            msg = self.CheckVars() 
636            if msg:
637                self.setEvalResult(msg)
638                return
639            exprObj.LoadExpression(
640                self.expr,
641                self.exprVarLst,
642                self.varSelect,
643                self.varName,
644                self.varValue,
645                self.varRefflag,
646                )
647            try:
648                calcobj = G2obj.ExpressionCalcObj(exprObj)
649                calcobj.SetupCalc(self.parmDict)
650                val = calcobj.EvalExpression()
651            except Exception as msg:
652                self.setEvalResult("Error in evaluation: "+str(msg))
653                return
654            if not np.isfinite(val):
655                self.setEvalResult("Expression value is infinite or out-of-bounds")
656                return
657            s = G2py3.FormatSigFigs(val).rstrip('0')
658            depVal = ""
659            if self.depVarDict:
660                if not self.dependentVar:
661                    self.setEvalResult("A dependent variable must be selected.")
662                    return
663                depVal = '; Variable "' + self.dependentVar + '" = ' + str(
664                    self.depVarDict.get(self.dependentVar,'?')
665                    )
666            self.setEvalResult("Expression evaluates to: "+str(s)+depVal)
667            self.OKbtn.Enable()
668            if self.ExtraBtn: self.ExtraBtn.Enable()
669        finally: 
670            xwid,yhgt = Siz.Fit(self.varbox)
671            self.varbox.SetMinSize((xwid,130))
672            self.varbox.SetAutoLayout(1)
673            self.varbox.SetupScrolling()
674            self.varbox.Refresh()
675            self.Layout()
676            self.SendSizeEvent() # force repaint
677           
678#==========================================================================
679class BondDialog(wx.Dialog):
680    '''A wx.Dialog that allows a user to select a bond length to be evaluated.
681    What needs to be done here? Need phase info for atoms
682    0. Select phase
683    1. Select 1st atom from dialog
684    2. Find neighbors & select one from dialog
685    3. Set up distance equation & save it - has to look like result from Show in above ExpressionDialog       
686    Use existing bond & esd calculate routines
687    '''
688    def __init__(self, parent, Phases, parmDict, exprObj=None,
689                 header='Enter restraint expression here',
690                 wintitle='Expression Editor',
691                 VarLabel=None,depVarDict=None,
692                 ExtraButton=None,usedVars=[]):
693        wx.Dialog.__init__(self,parent,wx.ID_ANY,wintitle, 
694            pos=wx.DefaultPosition,style=wx.DEFAULT_DIALOG_STYLE)
695        self.panel = wx.Panel(self)         #just a dummy - gets destroyed in Draw!
696        self.Phases = Phases
697        self.parmDict = parmDict
698        self.header = header
699        self.pName = list(Phases.keys())[0]
700        DisAglCtls = {}
701        dlg = G2G.DisAglDialog(self.panel,DisAglCtls,self.Phases[self.pName]['General'],Reset=False)
702        if dlg.ShowModal() == wx.ID_OK:
703            Phases[self.pName]['General']['DisAglCtls'] = dlg.GetData()
704        dlg.Destroy()
705        self.Oatom = ''
706        self.Tatom = ''
707        self.Draw()
708       
709    def Draw(self):
710       
711        def OnPhase(event):
712            Obj = event.GetEventObject()
713            self.pName = Obj.GetValue()
714            self.Oatom = ''
715            DisAglCtls = {}
716            dlg = G2G.DisAglDialog(self.panel,DisAglCtls,self.Phases[self.pName]['General'],Reset=False)
717            if dlg.ShowModal() == wx.ID_OK:
718                self.Phases[self.pName]['General']['DisAglCtls'] = dlg.GetData()
719            dlg.Destroy()
720            wx.CallAfter(self.Draw)
721           
722        def OnOrigAtom(event):
723            Obj = event.GetEventObject()
724            self.Oatom = Obj.GetValue()
725            wx.CallAfter(self.Draw)
726           
727        def OnTargAtom(event):
728            Obj = event.GetEventObject()
729            self.Tatom = Obj.GetValue()
730            wx.CallAfter(self.Draw)
731
732        self.panel.Destroy()
733        self.panel = wx.Panel(self)
734        mainSizer = wx.BoxSizer(wx.VERTICAL)
735        mainSizer.Add(wx.StaticText(self.panel,label=self.header),0,WACV)
736        pNames = list(self.Phases.keys())
737        phaseSizer = wx.BoxSizer(wx.HORIZONTAL)
738        phaseSizer.Add(wx.StaticText(self.panel,label=' Select phase: '),0,WACV)
739        phase = wx.ComboBox(self.panel,value=self.pName,choices=pNames,
740            style=wx.CB_READONLY|wx.CB_DROPDOWN)
741        phase.Bind(wx.EVT_COMBOBOX,OnPhase)
742        phaseSizer.Add(phase,0,WACV)
743        mainSizer.Add(phaseSizer)
744        Phase = self.Phases[self.pName]
745        cx,ct = Phase['General']['AtomPtrs'][:2]
746        Atoms = Phase['Atoms']
747        aNames = [atom[ct-1] for atom in Atoms]
748        atomSizer = wx.BoxSizer(wx.HORIZONTAL)
749        atomSizer.Add(wx.StaticText(self.panel,label=' Origin atom: '),0,WACV)
750        origAtom = wx.ComboBox(self.panel,value=self.Oatom,choices=aNames,
751            style=wx.CB_READONLY|wx.CB_DROPDOWN)
752        origAtom.Bind(wx.EVT_COMBOBOX,OnOrigAtom)
753        atomSizer.Add(origAtom,0,WACV)
754        atomSizer.Add(wx.StaticText(self.panel,label=' distance to: '),0,WACV)
755        neigh = []
756        if self.Oatom:
757#            GSASIIpath.IPyBreak()
758            neigh = G2mth.FindAllNeighbors(Phase,self.Oatom,aNames)
759        bNames = ['',]
760        if neigh:
761            bNames = [item[0]+' d=%.3f'%(item[1]) for item in neigh[0]]
762        targAtom = wx.ComboBox(self.panel,value=self.Tatom,choices=bNames,
763            style=wx.CB_READONLY|wx.CB_DROPDOWN)
764        targAtom.Bind(wx.EVT_COMBOBOX,OnTargAtom)
765        atomSizer.Add(targAtom,0,WACV)
766       
767        mainSizer.Add(atomSizer)
768
769
770        OkBtn = wx.Button(self.panel,-1,"Ok")
771        OkBtn.Bind(wx.EVT_BUTTON, self.OnOk)
772        cancelBtn = wx.Button(self.panel,-1,"Cancel")
773        cancelBtn.Bind(wx.EVT_BUTTON, self.OnCancel)
774        btnSizer = wx.BoxSizer(wx.HORIZONTAL)
775        btnSizer.Add((20,20),1)
776        btnSizer.Add(OkBtn)
777        btnSizer.Add((20,20),1)
778        btnSizer.Add(cancelBtn)
779        btnSizer.Add((20,20),1)
780       
781        mainSizer.Add(btnSizer,0,wx.EXPAND|wx.BOTTOM|wx.TOP, 10)
782        self.panel.SetSizer(mainSizer)
783        self.panel.Fit()
784        self.Fit()
785
786    def GetSelection(self):
787        return self.pName,self.Oatom,self.Tatom
788
789    def OnOk(self,event):
790        parent = self.GetParent()
791        parent.Raise()
792        self.EndModal(wx.ID_OK)
793
794    def OnCancel(self,event):
795        parent = self.GetParent()
796        parent.Raise()
797        self.EndModal(wx.ID_CANCEL)       
798           
799#==========================================================================
800class AngleDialog(wx.Dialog):
801    '''A wx.Dialog that allows a user to select a bond angle to be evaluated.
802    What needs to be done here? Need phase info for atom
803    0. Select phase
804    1. Select 1st atom from dialog
805    2. Find neighbors & select two from dialog
806    3. Set up angle equation & save it - has to look like result from Show in above ExpressionDialog       
807    Use existing angle & esd calculate routines
808    '''
809    def __init__(self, parent, Phases, parmDict, exprObj=None,
810                 header='Enter restraint expression here',
811                 wintitle='Expression Editor',
812                 VarLabel=None,depVarDict=None,
813                 ExtraButton=None,usedVars=[]):
814        wx.Dialog.__init__(self,parent,wx.ID_ANY,wintitle, 
815            pos=wx.DefaultPosition,style=wx.DEFAULT_DIALOG_STYLE)
816        self.panel = wx.Panel(self)         #just a dummy - gets destroyed in Draw!
817        self.Phases = Phases
818        self.parmDict = parmDict
819        self.header = header
820        self.pName = list(Phases.keys())[0]
821        DisAglCtls = {}
822        dlg = G2G.DisAglDialog(self.panel,DisAglCtls,self.Phases[self.pName]['General'],Reset=False)
823        if dlg.ShowModal() == wx.ID_OK:
824            Phases[self.pName]['General']['DisAglCtls'] = dlg.GetData()
825        dlg.Destroy()
826        self.Oatom = ''
827        self.Tatoms = ''
828        self.Draw()
829
830    def Draw(self):
831       
832        def OnPhase(event):
833            Obj = event.GetEventObject()
834            self.pName = Obj.GetValue()
835            self.Oatom = ''
836            DisAglCtls = {}
837            dlg = G2G.DisAglDialog(self.panel,DisAglCtls,self.Phases[self.pName]['General'],Reset=False)
838            if dlg.ShowModal() == wx.ID_OK:
839                self.Phases[self.pName]['General']['DisAglCtls'] = dlg.GetData()
840            dlg.Destroy()
841            wx.CallAfter(self.Draw)
842           
843        def OnOrigAtom(event):
844            Obj = event.GetEventObject()
845            self.Oatom = Obj.GetValue()
846            wx.CallAfter(self.Draw)           
847
848        def OnTargAtoms(event):
849            Obj = event.GetEventObject()
850            self.Tatoms = Obj.GetValue()
851            wx.CallAfter(self.Draw)
852
853        self.panel.Destroy()
854        self.panel = wx.Panel(self)
855        mainSizer = wx.BoxSizer(wx.VERTICAL)
856        mainSizer.Add(wx.StaticText(self.panel,label=self.header),0,WACV)
857        pNames = list(self.Phases.keys())
858        phaseSizer = wx.BoxSizer(wx.HORIZONTAL)
859        phaseSizer.Add(wx.StaticText(self.panel,label=' Select phase: '),0,WACV)
860        phase = wx.ComboBox(self.panel,value=self.pName,choices=pNames,
861            style=wx.CB_READONLY|wx.CB_DROPDOWN)
862        phase.Bind(wx.EVT_COMBOBOX,OnPhase)
863        phaseSizer.Add(phase,0,WACV)
864        mainSizer.Add(phaseSizer)
865        Phase = self.Phases[self.pName]
866        cx,ct = Phase['General']['AtomPtrs'][:2]
867        Atoms = Phase['Atoms']
868        aNames = [atom[ct-1] for atom in Atoms]
869#        GSASIIpath.IPyBreak()
870        atomSizer = wx.BoxSizer(wx.HORIZONTAL)
871        atomSizer.Add(wx.StaticText(self.panel,label=' Origin atom (O in A-O-B): '),0,WACV)
872        origAtom = wx.ComboBox(self.panel,value=self.Oatom,choices=aNames,
873            style=wx.CB_READONLY|wx.CB_DROPDOWN)
874        origAtom.Bind(wx.EVT_COMBOBOX,OnOrigAtom)
875        atomSizer.Add(origAtom,0,WACV)       
876        mainSizer.Add(atomSizer)
877        neigh = []
878        if self.Oatom:
879            neigh = G2mth.FindAllNeighbors(Phase,self.Oatom,aNames)[0]
880            mainSizer.Add(wx.StaticText(self.panel,label=' A-O-B angle for A,B: '),0,WACV)
881            bNames = ['',]
882            if neigh:
883#                GSASIIpath.IPyBreak()
884                for iA,aName in enumerate(neigh):
885                    for cName in neigh[iA+1:]:
886                        bNames.append('%s;%s'%(aName[0].replace(' ',''),cName[0].replace(' ','')))
887                targAtoms = wx.ComboBox(self.panel,value=self.Tatoms,choices=bNames,
888                    style=wx.CB_READONLY|wx.CB_DROPDOWN)
889                targAtoms.Bind(wx.EVT_COMBOBOX,OnTargAtoms)
890                mainSizer.Add(targAtoms,0,WACV)
891
892
893        OkBtn = wx.Button(self.panel,-1,"Ok")
894        OkBtn.Bind(wx.EVT_BUTTON, self.OnOk)
895        cancelBtn = wx.Button(self.panel,-1,"Cancel")
896        cancelBtn.Bind(wx.EVT_BUTTON, self.OnCancel)
897        btnSizer = wx.BoxSizer(wx.HORIZONTAL)
898        btnSizer.Add((20,20),1)
899        btnSizer.Add(OkBtn)
900        btnSizer.Add((20,20),1)
901        btnSizer.Add(cancelBtn)
902        btnSizer.Add((20,20),1)
903       
904        mainSizer.Add(btnSizer,0,wx.EXPAND|wx.BOTTOM|wx.TOP, 10)
905        self.panel.SetSizer(mainSizer)
906        self.panel.Fit()
907        self.Fit()
908
909    def GetSelection(self):
910        return self.pName,self.Oatom,self.Tatoms
911
912    def OnOk(self,event):
913        parent = self.GetParent()
914        parent.Raise()
915        self.EndModal(wx.ID_OK)
916
917    def OnCancel(self,event):
918        parent = self.GetParent()
919        parent.Raise()
920        self.EndModal(wx.ID_CANCEL)       
921                   
922       
923if __name__ == "__main__":
924    app = wx.PySimpleApp() # create the App
925    frm = wx.Frame(None)
926    frm.Show()
927    PSvarDict = {'::a':1.0,'::b':1.1,'0::c':1.2,'::AlongVaraiableName':1000.}
928    #PSvars = PSvarDict.keys()
929    indepvarDict = {'Temperature':1.0,'Pressure':1.1,'Phase of Moon':1.2,'1:1:HAP':1.3}
930    dlg = ExpressionDialog(frm,indepvarDict,
931                           header="Edit the PseudoVar expression",
932                           fit=False,
933                           depVarDict=PSvarDict,
934                           #VarLabel="New PseudoVar",                           
935                           )
936    newobj = dlg.Show(True)
937    dlg = ExpressionDialog(frm,indepvarDict,newobj,
938                           header="Edit the PseudoVar expression",
939                           fit=False,
940                           depVarDict=PSvarDict,
941                           #VarLabel="New PseudoVar",                           
942                           )
943    newobj = dlg.Show(True)
944    print (dlg.GetDepVar())
945    dlg = ExpressionDialog(frm,PSvarDict,
946                           header="Edit the PseudoVar expression",
947                           fit=True)
948    newobj = dlg.Show(True)
949    print (dlg.GetDepVar())
950
951    #app.MainLoop()
952
953    import platform
954    if '2' in platform.python_version_tuple()[0]:
955        import cPickle
956    else:
957        import pickle as 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.