source: trunk/GSASIIexprGUI.py @ 4837

Last change on this file since 4837 was 4672, checked in by vondreele, 4 years ago

remove all wx.ALIGN from wx Add with wx.EXPAND - invalid combination ignored in older wx; now flagged in current wx 4.1.x
make ax as self.ax in Absorb.py - fix matplotlib warning about reusing a subaxis
put floors on pink beam alp & bet. Fix typo in GetPinkAlpBet? function - now works better.

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