source: trunk/GSASIIexprGUI.py @ 2317

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

missing/exercise directories

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