source: trunk/GSASIIexprGUI.py @ 4353

Last change on this file since 4353 was 4353, checked in by toby, 21 months ago

fix bond distance monitor in sequential fit

  • Property svn:eol-style set to native
  • Property svn:keywords set to Date Author Revision URL Id
File size: 40.6 KB
Line 
1# -*- coding: utf-8 -*-
2#GSASIIexprGUI - Expression Definition and Evaluation
3########### SVN repository information ###################
4# $Date: 2020-03-09 22:19:01 +0000 (Mon, 09 Mar 2020) $
5# $Author: toby $
6# $Revision: 4353 $
7# $URL: trunk/GSASIIexprGUI.py $
8# $Id: GSASIIexprGUI.py 4353 2020-03-09 22:19:01Z 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: 4353 $")
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            neigh = G2mth.FindAllNeighbors(Phase,self.Oatom,aNames)
781        bNames = ['',]
782        if neigh:
783            bNames = [item[0]+' d=%.3f'%(item[2]) for item in neigh[0]]
784        targAtom = wx.ComboBox(self.panel,value=self.Tatom,choices=bNames,
785            style=wx.CB_READONLY|wx.CB_DROPDOWN)
786        targAtom.Bind(wx.EVT_COMBOBOX,OnTargAtom)
787        atomSizer.Add(targAtom,0,WACV)
788       
789        mainSizer.Add(atomSizer)
790
791
792        OkBtn = wx.Button(self.panel,-1,"Ok")
793        OkBtn.Bind(wx.EVT_BUTTON, self.OnOk)
794        cancelBtn = wx.Button(self.panel,-1,"Cancel")
795        cancelBtn.Bind(wx.EVT_BUTTON, self.OnCancel)
796        btnSizer = wx.BoxSizer(wx.HORIZONTAL)
797        btnSizer.Add((20,20),1)
798        btnSizer.Add(OkBtn)
799        btnSizer.Add((20,20),1)
800        btnSizer.Add(cancelBtn)
801        btnSizer.Add((20,20),1)
802       
803        mainSizer.Add(btnSizer,0,wx.EXPAND|wx.BOTTOM|wx.TOP, 10)
804        self.panel.SetSizer(mainSizer)
805        self.panel.Fit()
806        self.Fit()
807
808    def GetSelection(self):
809        return self.pName,self.Oatom,self.Tatom
810
811    def OnOk(self,event):
812        parent = self.GetParent()
813        parent.Raise()
814        self.EndModal(wx.ID_OK)
815
816    def OnCancel(self,event):
817        parent = self.GetParent()
818        parent.Raise()
819        self.EndModal(wx.ID_CANCEL)       
820           
821#==========================================================================
822class AngleDialog(wx.Dialog):
823    '''A wx.Dialog that allows a user to select a bond angle to be evaluated.
824    What needs to be done here? Need phase info for atom
825    0. Select phase
826    1. Select 1st atom from dialog
827    2. Find neighbors & select two from dialog
828    3. Set up angle equation & save it - has to look like result from Show in above ExpressionDialog       
829    Use existing angle & esd calculate routines
830    '''
831    def __init__(self, parent, Phases, parmDict, exprObj=None,
832                 header='Enter restraint expression here',
833                 wintitle='Expression Editor',
834                 VarLabel=None,depVarDict=None,
835                 ExtraButton=None,usedVars=[]):
836        wx.Dialog.__init__(self,parent,wx.ID_ANY,wintitle, 
837            pos=wx.DefaultPosition,style=wx.DEFAULT_DIALOG_STYLE)
838        self.panel = wx.Panel(self)         #just a dummy - gets destroyed in Draw!
839        self.Phases = Phases
840        self.parmDict = parmDict
841        self.header = header
842        self.pName = list(Phases.keys())[0]
843        DisAglCtls = {}
844        dlg = G2G.DisAglDialog(self.panel,DisAglCtls,self.Phases[self.pName]['General'],Reset=False)
845        if dlg.ShowModal() == wx.ID_OK:
846            Phases[self.pName]['General']['DisAglCtls'] = dlg.GetData()
847        dlg.Destroy()
848        self.Oatom = ''
849        self.Tatoms = ''
850        self.Draw()
851
852    def Draw(self):
853       
854        def OnPhase(event):
855            Obj = event.GetEventObject()
856            self.pName = Obj.GetValue()
857            self.Oatom = ''
858            DisAglCtls = {}
859            dlg = G2G.DisAglDialog(self.panel,DisAglCtls,self.Phases[self.pName]['General'],Reset=False)
860            if dlg.ShowModal() == wx.ID_OK:
861                self.Phases[self.pName]['General']['DisAglCtls'] = dlg.GetData()
862            dlg.Destroy()
863            wx.CallAfter(self.Draw)
864           
865        def OnOrigAtom(event):
866            Obj = event.GetEventObject()
867            self.Oatom = Obj.GetValue()
868            wx.CallAfter(self.Draw)           
869
870        def OnTargAtoms(event):
871            Obj = event.GetEventObject()
872            self.Tatoms = Obj.GetValue()
873            wx.CallAfter(self.Draw)
874
875        self.panel.Destroy()
876        self.panel = wx.Panel(self)
877        mainSizer = wx.BoxSizer(wx.VERTICAL)
878        mainSizer.Add(wx.StaticText(self.panel,label=self.header),0,WACV)
879        pNames = list(self.Phases.keys())
880        phaseSizer = wx.BoxSizer(wx.HORIZONTAL)
881        phaseSizer.Add(wx.StaticText(self.panel,label=' Select phase: '),0,WACV)
882        phase = wx.ComboBox(self.panel,value=self.pName,choices=pNames,
883            style=wx.CB_READONLY|wx.CB_DROPDOWN)
884        phase.Bind(wx.EVT_COMBOBOX,OnPhase)
885        phaseSizer.Add(phase,0,WACV)
886        mainSizer.Add(phaseSizer)
887        Phase = self.Phases[self.pName]
888        cx,ct = Phase['General']['AtomPtrs'][:2]
889        Atoms = Phase['Atoms']
890        aNames = [atom[ct-1] for atom in Atoms]
891        atomSizer = wx.BoxSizer(wx.HORIZONTAL)
892        atomSizer.Add(wx.StaticText(self.panel,label=' Origin atom (O in A-O-B): '),0,WACV)
893        origAtom = wx.ComboBox(self.panel,value=self.Oatom,choices=aNames,
894            style=wx.CB_READONLY|wx.CB_DROPDOWN)
895        origAtom.Bind(wx.EVT_COMBOBOX,OnOrigAtom)
896        atomSizer.Add(origAtom,0,WACV)       
897        mainSizer.Add(atomSizer)
898        neigh = []
899        if self.Oatom:
900            neigh = G2mth.FindAllNeighbors(Phase,self.Oatom,aNames)[0]
901            mainSizer.Add(wx.StaticText(self.panel,label=' A-O-B angle for A,B: '),0,WACV)
902            bNames = ['',]
903            if neigh:
904                for iA,aName in enumerate(neigh):
905                    for cName in neigh[iA+1:]:
906                        bNames.append('%s;%s'%(aName[0].replace(' ',''),cName[0].replace(' ','')))
907                targAtoms = wx.ComboBox(self.panel,value=self.Tatoms,choices=bNames,
908                    style=wx.CB_READONLY|wx.CB_DROPDOWN)
909                targAtoms.Bind(wx.EVT_COMBOBOX,OnTargAtoms)
910                mainSizer.Add(targAtoms,0,WACV)
911
912
913        OkBtn = wx.Button(self.panel,-1,"Ok")
914        OkBtn.Bind(wx.EVT_BUTTON, self.OnOk)
915        cancelBtn = wx.Button(self.panel,-1,"Cancel")
916        cancelBtn.Bind(wx.EVT_BUTTON, self.OnCancel)
917        btnSizer = wx.BoxSizer(wx.HORIZONTAL)
918        btnSizer.Add((20,20),1)
919        btnSizer.Add(OkBtn)
920        btnSizer.Add((20,20),1)
921        btnSizer.Add(cancelBtn)
922        btnSizer.Add((20,20),1)
923       
924        mainSizer.Add(btnSizer,0,wx.EXPAND|wx.BOTTOM|wx.TOP, 10)
925        self.panel.SetSizer(mainSizer)
926        self.panel.Fit()
927        self.Fit()
928
929    def GetSelection(self):
930        return self.pName,self.Oatom,self.Tatoms
931
932    def OnOk(self,event):
933        parent = self.GetParent()
934        parent.Raise()
935        self.EndModal(wx.ID_OK)
936
937    def OnCancel(self,event):
938        parent = self.GetParent()
939        parent.Raise()
940        self.EndModal(wx.ID_CANCEL)       
941                   
942       
943if __name__ == "__main__":
944    app = wx.PySimpleApp() # create the App
945    frm = wx.Frame(None)
946    frm.Show()
947    PSvarDict = {'::a':1.0,'::b':1.1,'0::c':1.2,'::AlongVaraiableName':1000.}
948    #PSvars = PSvarDict.keys()
949    indepvarDict = {'Temperature':1.0,'Pressure':1.1,'Phase of Moon':1.2,'1:1:HAP':1.3}
950    dlg = ExpressionDialog(frm,indepvarDict,
951                           header="Edit the PseudoVar expression",
952                           fit=False,
953                           depVarDict=PSvarDict,
954                           #VarLabel="New PseudoVar",                           
955                           )
956    newobj = dlg.Show(True)
957    dlg = ExpressionDialog(frm,indepvarDict,newobj,
958                           header="Edit the PseudoVar expression",
959                           fit=False,
960                           depVarDict=PSvarDict,
961                           #VarLabel="New PseudoVar",                           
962                           )
963    newobj = dlg.Show(True)
964    print (dlg.GetDepVar())
965    dlg = ExpressionDialog(frm,PSvarDict,
966                           header="Edit the PseudoVar expression",
967                           fit=True)
968    newobj = dlg.Show(True)
969    print (dlg.GetDepVar())
970
971    #app.MainLoop()
972
973    if '2' in platform.python_version_tuple()[0]:
974        import cPickle
975    else:
976        import pickle as cPickle
977    def showEQ(calcobj):
978        print
979        print (calcobj.eObj.expression)
980        for v in sorted(calcobj.eObj.freeVars.keys()+calcobj.eObj.assgnVars.keys()):
981            print ("  ",v,'=',calcobj.exprDict[v])
982        print (calcobj.EvalExpression())
983    print ("starting test")
984    obj = G2obj.ExpressionObj()
985    obj.expression = "A*np.exp(B)"
986    obj.assgnVars =  {'B': '0::Afrac:*'}
987    obj.freeVars =  {'A': [u'A', 0.5, True]}
988    obj.CheckVars()
989    parmDict2 = {'0::Afrac:0':1.0, '0::Afrac:1': 1.0}
990    calcobj = G2obj.ExpressionCalcObj(obj)
991    calcobj.SetupCalc(parmDict2)
992    showEQ(calcobj)
993    fp = open('/tmp/obj.pickle','w')
994    cPickle.dump(obj,fp)
995    fp.close()
996   
997    obj.expression = "A*np.exp(-2/B)"
998    obj.assgnVars =  {'A': '0::Afrac:0', 'B': '0::Afrac:1'}
999    obj.freeVars =  {}
1000    parmDict1 = {'0::Afrac:0':1.0, '0::Afrac:1': -2.0}
1001    calcobj = G2obj.ExpressionCalcObj(obj)
1002    calcobj.SetupCalc(parmDict1)
1003    showEQ(calcobj)
1004
1005    fp = open('/tmp/obj.pickle','r')
1006    obj = cPickle.load(fp)
1007    fp.close()
1008    parmDict2 = {'0::Afrac:0':0.0, '0::Afrac:1': 1.0}
1009    calcobj = G2obj.ExpressionCalcObj(obj)
1010    calcobj.SetupCalc(parmDict2)
1011    showEQ(calcobj)
1012
1013    parmDict2 = {'0::Afrac:0':1.0, '0::Afrac:1': 1.0}
1014    calcobj = G2obj.ExpressionCalcObj(obj)
1015    calcobj.SetupCalc(parmDict2)
1016    showEQ(calcobj)
1017   
Note: See TracBrowser for help on using the repository browser.