source: trunk/GSASIIexprGUI.py @ 2321

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

start on CalcDistSig? & AddAngleDialog? from tablet

  • Property svn:eol-style set to native
  • Property svn:keywords set to Date Author Revision URL Id
File size: 39.3 KB
Line 
1# -*- coding: utf-8 -*-
2#GSASIIexprGUI - Expression Definition and Evaluation
3########### SVN repository information ###################
4# $Date: 2016-06-15 12:10:38 +0000 (Wed, 15 Jun 2016) $
5# $Author: vondreele $
6# $Revision: 2321 $
7# $URL: trunk/GSASIIexprGUI.py $
8# $Id: GSASIIexprGUI.py 2321 2016-06-15 12:10:38Z 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: 2321 $")
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        self.Draw()
713       
714    def Draw(self):
715       
716        def OnPhase(event):
717            Obj = event.GetEventObject()
718            self.pName = Obj.GetValue()
719            self.Oatom = ''
720            DisAglCtls = {}
721            dlg = G2gd.DisAglDialog(self.panel,DisAglCtls,self.Phases[self.pName]['General'],Reset=False)
722            if dlg.ShowModal() == wx.ID_OK:
723                self.Phases[self.pName]['General']['DisAglCtls'] = dlg.GetData()
724            dlg.Destroy()
725            wx.CallAfter(self.Draw)
726           
727        def OnOrigAtom(event):
728            Obj = event.GetEventObject()
729            self.Oatom = Obj.GetValue()
730            wx.CallAfter(self.Draw)
731           
732        def OnTargAtom(event):
733            Obj = event.GetEventObject()
734            self.Tatom = Obj.GetValue()
735            wx.CallAfter(self.Draw)
736
737        self.panel.Destroy()
738        self.panel = wx.Panel(self)
739        mainSizer = wx.BoxSizer(wx.VERTICAL)
740        mainSizer.Add(wx.StaticText(self.panel,label=self.header),0,WACV)
741        pNames = self.Phases.keys()
742        phaseSizer = wx.BoxSizer(wx.HORIZONTAL)
743        phaseSizer.Add(wx.StaticText(self.panel,label=' Select phase: '),0,WACV)
744        phase = wx.ComboBox(self.panel,value=self.pName,choices=pNames,
745            style=wx.CB_READONLY|wx.CB_DROPDOWN)
746        phase.Bind(wx.EVT_COMBOBOX,OnPhase)
747        phaseSizer.Add(phase,0,WACV)
748        mainSizer.Add(phaseSizer)
749        Phase = self.Phases[self.pName]
750        cx,ct = Phase['General']['AtomPtrs'][:2]
751        Atoms = Phase['Atoms']
752        aNames = [atom[ct-1] for atom in Atoms]
753        atomSizer = wx.BoxSizer(wx.HORIZONTAL)
754        atomSizer.Add(wx.StaticText(self.panel,label=' Origin atom: '),0,WACV)
755        origAtom = wx.ComboBox(self.panel,value=self.Oatom,choices=aNames,
756            style=wx.CB_READONLY|wx.CB_DROPDOWN)
757        origAtom.Bind(wx.EVT_COMBOBOX,OnOrigAtom)
758        atomSizer.Add(origAtom,0,WACV)
759        atomSizer.Add(wx.StaticText(self.panel,label=' distance to: '),0,WACV)
760        neigh = []
761        if self.Oatom:
762#            GSASIIpath.IPyBreak()
763            neigh = G2mth.FindAllNeighbors(Phase,self.Oatom,aNames)
764        bNames = ['',]
765        if neigh:
766            bNames = [item[0]+' d=%.3f'%(item[1]) for item in neigh[0]]
767        targAtom = wx.ComboBox(self.panel,value=self.Tatom,choices=bNames,
768            style=wx.CB_READONLY|wx.CB_DROPDOWN)
769        targAtom.Bind(wx.EVT_COMBOBOX,OnTargAtom)
770        atomSizer.Add(targAtom,0,WACV)
771       
772        mainSizer.Add(atomSizer)
773
774
775        OkBtn = wx.Button(self.panel,-1,"Ok")
776        OkBtn.Bind(wx.EVT_BUTTON, self.OnOk)
777        cancelBtn = wx.Button(self.panel,-1,"Cancel")
778        cancelBtn.Bind(wx.EVT_BUTTON, self.OnCancel)
779        btnSizer = wx.BoxSizer(wx.HORIZONTAL)
780        btnSizer.Add((20,20),1)
781        btnSizer.Add(OkBtn)
782        btnSizer.Add((20,20),1)
783        btnSizer.Add(cancelBtn)
784        btnSizer.Add((20,20),1)
785       
786        mainSizer.Add(btnSizer,0,wx.EXPAND|wx.BOTTOM|wx.TOP, 10)
787        self.panel.SetSizer(mainSizer)
788        self.panel.Fit()
789        self.Fit()
790
791    def GetSelection(self):
792        return self.pName,self.Oatom,self.Tatom
793
794    def OnOk(self,event):
795        parent = self.GetParent()
796        parent.Raise()
797        self.EndModal(wx.ID_OK)
798
799    def OnCancel(self,event):
800        parent = self.GetParent()
801        parent.Raise()
802        self.EndModal(wx.ID_CANCEL)       
803           
804#==========================================================================
805class AngleDialog(wx.Dialog):
806    '''A wx.Dialog that allows a user to select a bond angle to be evaluated.
807    What needs to be done here? Need phase info for atom
808    0. Select phase
809    1. Select 1st atom from dialog
810    2. Find neighbors & select two from dialog
811    3. Set up angle equation & save it - has to look like result from Show in above ExpressionDialog       
812    Use existing angle & esd calculate routines
813    '''
814    def __init__(self, parent, Phases, parmDict, exprObj=None,
815                 header='Enter restraint expression here',
816                 wintitle='Expression Editor',
817                 VarLabel=None,depVarDict=None,
818                 ExtraButton=None,usedVars=[]):
819        wx.Dialog.__init__(self,parent,wx.ID_ANY,wintitle, 
820            pos=wx.DefaultPosition,style=wx.DEFAULT_DIALOG_STYLE)
821        self.panel = wx.Panel(self)         #just a dummy - gets destroyed in Draw!
822        self.Phases = Phases
823        self.parmDict = parmDict
824        self.header = header
825        self.pName = Phases.keys()[0]
826        DisAglCtls = {}
827        dlg = G2gd.DisAglDialog(self.panel,DisAglCtls,self.Phases[self.pName]['General'],Reset=False)
828        if dlg.ShowModal() == wx.ID_OK:
829            Phases[self.pName]['General']['DisAglCtls'] = dlg.GetData()
830        dlg.Destroy()
831        self.Oatom = ''
832        self.Tatoms = ['','']
833        self.Draw()
834
835    def Draw(self):
836       
837        def OnPhase(event):
838            Obj = event.GetEventObject()
839            self.pName = Obj.GetValue()
840            self.Oatom = ''
841            DisAglCtls = {}
842            dlg = G2gd.DisAglDialog(self.panel,DisAglCtls,self.Phases[self.pName]['General'],Reset=False)
843            if dlg.ShowModal() == wx.ID_OK:
844                self.Phases[self.pName]['General']['DisAglCtls'] = dlg.GetData()
845            dlg.Destroy()
846            wx.CallAfter(self.Draw)
847           
848        def OnOrigAtom(event):
849            Obj = event.GetEventObject()
850            self.Oatom = Obj.GetValue()
851            wx.CallAfter(self.Draw)           
852
853        def OnTargAtom(event):
854            Obj = event.GetEventObject()
855            self.Tatom = Obj.GetValue()
856            wx.CallAfter(self.Draw)
857
858        self.panel.Destroy()
859        self.panel = wx.Panel(self)
860        mainSizer = wx.BoxSizer(wx.VERTICAL)
861        mainSizer.Add(wx.StaticText(self.panel,label=self.header),0,WACV)
862        pNames = self.Phases.keys()
863        phaseSizer = wx.BoxSizer(wx.HORIZONTAL)
864        phaseSizer.Add(wx.StaticText(self.panel,label=' Select phase: '),0,WACV)
865        phase = wx.ComboBox(self.panel,value=self.pName,choices=pNames,
866            style=wx.CB_READONLY|wx.CB_DROPDOWN)
867        phase.Bind(wx.EVT_COMBOBOX,OnPhase)
868        phaseSizer.Add(phase,0,WACV)
869        mainSizer.Add(phaseSizer)
870        Phase = self.Phases[self.pName]
871        cx,ct = Phase['General']['AtomPtrs'][:2]
872        Atoms = Phase['Atoms']
873        aNames = [atom[ct-1] for atom in Atoms]
874#        GSASIIpath.IPyBreak()
875        atomSizer = wx.BoxSizer(wx.HORIZONTAL)
876        atomSizer.Add(wx.StaticText(self.panel,label=' Origin atom (O in A-O-B): '),0,WACV)
877        origAtom = wx.ComboBox(self.panel,value=self.Oatom,choices=aNames,
878            style=wx.CB_READONLY|wx.CB_DROPDOWN)
879        origAtom.Bind(wx.EVT_COMBOBOX,OnOrigAtom)
880        atomSizer.Add(origAtom,0,WACV)       
881        mainSizer.Add(atomSizer)
882        mainSizer.Add(wx.StaticText(self.panel,label=' A-O-B angle for A,B: '),0,WACV)
883        neigh = []
884        if self.Oatom:
885#            GSASIIpath.IPyBreak()
886            neigh = G2mth.FindAllNeighbors(Phase,self.Oatom,aNames)
887        bNames = ['',]
888        if neigh:
889            bNames = [item[0]+' d=%.3f'%(item[1]) for item in neigh[0]]
890
891
892        OkBtn = wx.Button(self.panel,-1,"Ok")
893        OkBtn.Bind(wx.EVT_BUTTON, self.OnOk)
894        cancelBtn = wx.Button(self.panel,-1,"Cancel")
895        cancelBtn.Bind(wx.EVT_BUTTON, self.OnCancel)
896        btnSizer = wx.BoxSizer(wx.HORIZONTAL)
897        btnSizer.Add((20,20),1)
898        btnSizer.Add(OkBtn)
899        btnSizer.Add((20,20),1)
900        btnSizer.Add(cancelBtn)
901        btnSizer.Add((20,20),1)
902       
903        mainSizer.Add(btnSizer,0,wx.EXPAND|wx.BOTTOM|wx.TOP, 10)
904        self.panel.SetSizer(mainSizer)
905        self.panel.Fit()
906        self.Fit()
907
908    def GetSelection(self):
909        return []
910
911    def OnOk(self,event):
912        parent = self.GetParent()
913        parent.Raise()
914        self.EndModal(wx.ID_OK)
915
916    def OnCancel(self,event):
917        parent = self.GetParent()
918        parent.Raise()
919        self.EndModal(wx.ID_CANCEL)       
920                   
921       
922if __name__ == "__main__":
923    app = wx.PySimpleApp() # create the App
924    frm = wx.Frame(None)
925    frm.Show()
926    PSvarDict = {'::a':1.0,'::b':1.1,'0::c':1.2,'::AlongVaraiableName':1000.}
927    #PSvars = PSvarDict.keys()
928    indepvarDict = {'Temperature':1.0,'Pressure':1.1,'Phase of Moon':1.2,'1:1:HAP':1.3}
929    dlg = ExpressionDialog(frm,indepvarDict,
930                           header="Edit the PseudoVar expression",
931                           fit=False,
932                           depVarDict=PSvarDict,
933                           #VarLabel="New PseudoVar",                           
934                           )
935    newobj = dlg.Show(True)
936    dlg = ExpressionDialog(frm,indepvarDict,newobj,
937                           header="Edit the PseudoVar expression",
938                           fit=False,
939                           depVarDict=PSvarDict,
940                           #VarLabel="New PseudoVar",                           
941                           )
942    newobj = dlg.Show(True)
943    print dlg.GetDepVar()
944    dlg = ExpressionDialog(frm,PSvarDict,
945                           header="Edit the PseudoVar expression",
946                           fit=True)
947    newobj = dlg.Show(True)
948    print dlg.GetDepVar()
949    import sys
950    #sys.exit()
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.