source: trunk/GSASIIexprGUI.py @ 3772

Last change on this file since 3772 was 3772, checked in by toby, 5 years ago

add GUI (seen in debug mode only) for general (equation specified) restraints

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