source: trunk/GSASIIexprGUI.py @ 3447

Last change on this file since 3447 was 3447, checked in by toby, 4 years ago

fix seq ref constraint numbering bug; fix use if HAP vars in PSvars & equations; complete RepaintHistogramInfo? w/Py3 + misc Py3 fixes; improve initialization after project read

  • 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-06-24 00:46:31 +0000 (Sun, 24 Jun 2018) $
5# $Author: toby $
6# $Revision: 3447 $
7# $URL: trunk/GSASIIexprGUI.py $
8# $Id: GSASIIexprGUI.py 3447 2018-06-24 00:46:31Z 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: 3447 $")
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 str 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, and 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' not in platform.python_version_tuple()[0]: basestring = str
187            if isinstance(val, basestring): continue
188            try:
189                self.parmDict[key] = float(val)
190            except:
191                pass
192        # separate the variables by type
193        self.parmLists = IndexParmDict(self.parmDict,self.fit)
194        self.timer = wx.Timer()
195        self.timer.Bind(wx.EVT_TIMER,self.OnValidate)
196        LoadDefaultExpressions()
197        style=wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER
198        wx.Dialog.__init__(self, parent, wx.ID_ANY, wintitle, style=style, size=defSize)
199        self.mainsizer = wx.BoxSizer(wx.VERTICAL)
200        label = wx.StaticText(self,  wx.ID_ANY, header)
201        self.mainsizer.Add(label, 0, wx.ALIGN_CENTRE|wx.ALL, 5)
202
203        self.exsizer = wx.BoxSizer(wx.HORIZONTAL)
204        if VarLabel:
205            label = wx.StaticText(self,  wx.ID_ANY, VarLabel + ' = ')
206            self.exsizer.Add(label, 0, wx.ALL|wx.EXPAND|wx.ALIGN_CENTER_VERTICAL, 0)
207        elif depVarDict:
208            self.depParmLists = IndexParmDict(self.depVarDict,False)
209            choices = ['','Phase','Hist./Phase','Hist.','Global']
210            for i in range(1,len(choices)): # remove empty menus from choice list
211                if not len(self.depParmLists[i]): choices[i] = ''
212            self.depChoices = [i for i in range(len(choices)) if choices[i]]
213            choice = wx.Choice(
214                self, wx.ID_ANY,
215                choices = [choices[i] for i in self.depChoices]
216                )
217            choice.SetSelection(wx.NOT_FOUND)
218            choice.Bind(wx.EVT_CHOICE,self.OnDepChoice)
219            self.exsizer.Add(choice, 0, wx.ALL|wx.EXPAND|wx.ALIGN_CENTER_VERTICAL, 0)
220            self.exsizer.Add((5,5))
221            self.depLabel = wx.StaticText(self,  wx.ID_ANY, ' ')
222            self.exsizer.Add(self.depLabel, 0, wx.ALL|wx.EXPAND|wx.ALIGN_CENTER_VERTICAL, 0)
223            label = wx.StaticText(self,  wx.ID_ANY, ' = ')
224            self.exsizer.Add(label, 0, wx.ALL|wx.EXPAND|wx.ALIGN_CENTER_VERTICAL, 0)
225
226        #self.exCtrl = wx.TextCtrl(self,  wx.ID_ANY, size=(150,-1),style=wx.TE_PROCESS_ENTER)
227        self.exCtrl = wx.ComboBox(self, wx.ID_ANY, "", (90, 50), (160, -1),
228            defaultExpressions,wx.CB_DROPDOWN| wx.TE_PROCESS_ENTER)
229        self.exCtrl.Bind(wx.EVT_CHAR, self.OnChar)
230        self.exCtrl.Bind(wx.EVT_COMBOBOX, self.OnValidate)
231        self.exCtrl.Bind(wx.EVT_TEXT_ENTER, self.OnValidate)
232        self.exsizer.Add(self.exCtrl, 1, wx.ALL|wx.EXPAND|wx.ALIGN_CENTER_VERTICAL, 0)
233        #self.mainsizer.Add(self.exCtrl, 0, wx.ALL|wx.EXPAND, 5)
234        self.mainsizer.Add(self.exsizer, 0, wx.ALL|wx.EXPAND, 5)
235        self.mainsizer.Add((-1,5),0,wx.EXPAND,1)
236
237        evalSizer = wx.BoxSizer(wx.HORIZONTAL)
238        self.mainsizer.Add(evalSizer,0,wx.ALL|wx.EXPAND,0)
239        btn = wx.Button(self, wx.ID_ANY,"Validate")
240        btn.Bind(wx.EVT_BUTTON,self.OnValidate)
241        evalSizer.Add(btn,0,wx.LEFT|wx.RIGHT,5)
242        self.result = wx.StaticText(self,  wx.ID_ANY, '')
243        evalSizer.Add(self.result, 0, wx.ALIGN_CENTRE|wx.ALL, 5)
244
245        self.varSizer = wx.BoxSizer(wx.HORIZONTAL)
246        self.mainsizer.Add(self.varSizer,1,wx.ALL|wx.EXPAND,1)
247        self.mainsizer.Add((-1,5),0,wx.EXPAND,1)
248
249        bSizer = wx.BoxSizer(wx.HORIZONTAL)
250        btnsizer = wx.StdDialogButtonSizer()
251        if ExtraButton:
252            self.ExtraBtn = wx.Button(self, wx.ID_ANY, ExtraButton[0])
253            self.ExtraBtn.Bind(wx.EVT_BUTTON,self.OnExtra)
254            self.ExtraCallBack = ExtraButton[1]
255            self.ExtraBtn.Disable()
256            bSizer.Add(self.ExtraBtn, 0, wx.ALL|wx.ALIGN_CENTER_VERTICAL, 2)
257        else:
258            self.ExtraBtn = None
259        bSizer.Add((1,1), 1, wx.ALIGN_CENTER|wx.ALL|wx.EXPAND, 0)
260        self.OKbtn = wx.Button(self, wx.ID_OK)
261        self.OKbtn.SetDefault()
262        self.OKbtn.Disable()
263        btnsizer.AddButton(self.OKbtn)
264        btn = wx.Button(self, wx.ID_CANCEL)
265        btnsizer.AddButton(btn)
266        btnsizer.Realize()
267        bSizer.Add(btnsizer, 0, wx.ALIGN_CENTER_VERTICAL|wx.ALL, 5)
268        self.mainsizer.Add(bSizer, 0, wx.ALIGN_CENTER_VERTICAL|wx.ALL|wx.EXPAND, 5)
269        self.SetSizer(self.mainsizer)
270        self.CenterOnParent()
271        if exprObj:
272            self.expr = exprObj.EditExpression(
273                self.exprVarLst,
274                self.varSelect,
275                self.varName,
276                self.varValue,
277                self.varRefflag,
278                )
279            # set the initial value for the dependent value
280            if self.depVarDict:
281                var = exprObj.GetDepVar()
282                if var in self.depVarDict:
283                    self.depLabel.SetLabel(var)
284                    self.dependentVar = var
285                   
286        self.exCtrl.SetValue(self.expr)
287        self.OnValidate(None)
288        self.SetMinSize(defSize) 
289        #self.errbox.SetAutoLayout(1)
290        #self.errbox.SetupScrolling()
291        #self.varbox.SetAutoLayout(1)
292        #self.varbox.SetupScrolling()
293        #self.mainsizer.Fit(self)
294
295    def OnExtra(self,event):
296        exprObj = G2obj.ExpressionObj()
297        exprObj.LoadExpression(
298            self.expr,
299            self.exprVarLst,
300            self.varSelect,
301            self.varName,
302            self.varValue,
303            self.varRefflag,
304            )
305        if self.depVarDict:
306            exprObj.SetDepVar(self.dependentVar)
307        self.ExtraCallBack(exprObj)
308        # put results back into displayed dialog
309        resDict = dict(exprObj.GetVariedVarVal())
310        for v,var in self.varName.items():
311            varname = "::" + var.lstrip(':').replace(' ','_').replace(':',';')
312            val =  resDict.get(varname)
313            if val:
314                self.varValue[v] = val
315        wx.CallLater(100,self.Repaint,exprObj)
316
317    def Show(self,mode=True):
318        '''Call to use the dialog after it is created.
319
320        :returns: None (On Cancel) or a new :class:`~GSASIIobj.ExpressionObj`
321        '''
322        self.Layout()
323        #self.mainsizer.Fit(self)
324        self.SendSizeEvent() # force repaint
325        if self.ShowModal() == wx.ID_OK:
326            # store the edit results in the object and return it
327            exprObj = G2obj.ExpressionObj()
328            exprObj.LoadExpression(
329                self.expr,
330                self.exprVarLst,
331                self.varSelect,
332                self.varName,
333                self.varValue,
334                self.varRefflag,
335                )
336            if self.depVarDict:
337                exprObj.SetDepVar(self.dependentVar)
338            return exprObj
339        else:
340            return None
341       
342    def setEvalResult(self,msg):
343        'Show a string in the expression result area'
344        self.result.SetLabel(msg)
345
346    def RestartTimer(self):
347        '''Cancels any running timer and starts a new one.
348        The timer causes a check of syntax after 2 seconds unless there is further input.
349        Disables the OK button until a validity check is complete.
350        '''
351        if self.timer.IsRunning():
352            self.timer.Stop()
353        self.timer.Start(2000,oneShot=True)
354       
355    def OnChar(self,event):
356        '''Called as each character is entered. Cancels any running timer
357        and starts a new one. The timer causes a check of syntax after 2 seconds
358        without input.
359        Disables the OK button until a validity check is complete.
360        '''
361        self.RestartTimer()
362        self.OKbtn.Disable()
363        if self.ExtraBtn: self.ExtraBtn.Disable()
364        event.Skip()
365        return
366   
367    def CheckVars(self):
368        '''Check that appropriate variables are defined for each
369        symbol used in :data:`self.expr`
370
371        :returns: a text error message or None if all needed input is present       
372        '''
373        invalid = 0
374        for v in self.exprVarLst:
375            if self.varSelect.get(v) is None:
376                invalid += 1
377        if invalid==1:
378            return '(a variable is not assigned)'
379        elif invalid:
380            return '('+str(invalid)+' variables are not assigned)'
381        msg = ''
382        for v in self.exprVarLst:
383            varname = self.varName.get(v)
384            if not varname:
385                invalid += 1
386                if msg: msg += "; "
387                msg += 'No variable for '+str(v)
388            elif self.varSelect.get(v,0) > 0:
389                if varname in self.parmDict:
390                    pass
391                elif '*' in varname:
392                   l = G2obj.LookupWildCard(varname,list(self.parmDict.keys()))
393                   if len(l) == 0:
394                       invalid += 1
395                       if msg: msg += "; "
396                       msg += 'No variables match '+str(varname)
397                elif varname not in self.parmDict:
398                   invalid += 1
399                   if msg: msg += "; "
400                   msg += 'No variables match '+str(varname)
401            else:
402                # value assignment: this check is likely unneeded
403                val = self.varValue.get(v)
404                try:
405                    float(val)
406                except (ValueError,TypeError):
407                    invalid += 1
408                    if msg: msg += "; "
409                    if val is None:
410                        msg += 'No value for '+str(v)
411                    else:
412                        msg += 'Value '+str(val)+' invalid for '+str(v)
413        if invalid:
414            return '('+msg+')'       
415        return
416
417    def OnDepChoice(self,event):
418        '''Respond to a selection of a variable type for a label in
419        an expression
420        '''
421        if event: event.Skip()
422        sel = self.depChoices[event.GetEventObject().GetSelection()]
423        var = self.SelectG2var(sel,'Dependent variable',self.depParmLists[sel])
424        if var is None:
425            self.dependentVar = None
426            self.OnValidate(None)
427            event.GetEventObject().SetSelection(wx.NOT_FOUND)
428            return
429        self.dependentVar = var
430        self.depLabel.SetLabel(var)
431        self.OnValidate(None)
432        self.Layout()
433
434    def GetDepVar(self):
435        '''Returns the name of the dependent variable, when depVarDict is used.
436        '''
437        return self.dependentVar
438       
439    def OnChoice(self,event):
440        '''Respond to a selection of a variable type for a label in
441        an expression
442        '''
443        if event: event.Skip()
444        v = event.GetEventObject().label
445        sel = self.AllowedChoices[event.GetEventObject().GetSelection()]
446        if sel == 0:
447            sv = G2obj.MakeUniqueLabel(v,self.usedVars)
448            self.varSelect[v] = sel
449            self.varName[v] = sv
450            self.varValue[v] = self.varValue.get(v,0.0)
451        else:
452            var = self.SelectG2var(sel,v,self.parmLists[sel])
453            if var is None:
454                self.OnValidate(None)
455                return
456            self.varSelect[v] = sel
457            self.varName[v] = var
458        self.OnValidate(None)
459
460    def SelectG2var(self,sel,var,parmList):
461        '''Offer a selection of a GSAS-II variable.
462
463        :param int sel: Determines the type of variable to be selected.
464          where 1 is used for Phase variables, and 2 for Histogram/Phase vars,
465          3 for Histogram vars and 4 for Global vars.
466        :returns: a variable name or None (if Cancel is pressed)
467        '''
468        def wildHist(var):
469            '''Replace a histogram number with a wild card
470            '''
471            slst = var.split(':')
472            if len(slst) > 2 and slst[1]:
473                slst[1] = '*'
474                return ':'.join(slst)
475            return var
476
477        if not parmList:
478            return None
479        l2 = l1 = 1
480        if self.wildCard:
481            wildList = [wildHist(i) for i in parmList]
482        else:
483            wildList = parmList
484        for i in wildList:
485            l1 = max(l1,len(i))
486            loc,desc = G2obj.VarDescr(i)
487            l2 = max(l2,len(loc))
488        fmt = u"{:"+str(l1)+"s} {:"+str(l2)+"s} {:s}"
489        varListlbl = [fmt.format(i,*G2obj.VarDescr(i)) for i in wildList]
490        dlg = G2G.G2SingleChoiceDialog(
491            self,'Select GSAS-II variable for '+str(var)+':',
492            'Select variable',
493            varListlbl,monoFont=True)
494        dlg.SetSize((625,250))
495        dlg.CenterOnParent()
496        var = None
497        if dlg.ShowModal() == wx.ID_OK:
498            i = dlg.GetSelection()
499            var = wildList[i]
500        dlg.Destroy()
501        return var
502
503    def showError(self,msg1,msg2='',msg3=''):
504        '''Show an error message of 1 to 3 sections. The second
505        section is shown in an equally-spaced font.
506       
507        :param str msg1: msg1 is shown in a the standard font
508        :param str msg2: msg2 is shown in a equally-spaced (wx.MODERN) font
509        :param str msg3: msg3 is shown in a the standard font
510        '''
511        self.varSizer.Clear(True)
512        self.OKbtn.Disable()
513        if self.ExtraBtn: self.ExtraBtn.Disable()
514        self.varSizer.Clear(True)
515        self.errbox = wxscroll.ScrolledPanel(self,style=wx.HSCROLL)
516        self.errbox.SetMinSize((200,130))
517        self.varSizer.Add(self.errbox,1,wx.ALL|wx.EXPAND,1)
518        Siz = wx.BoxSizer(wx.VERTICAL)
519        errMsg1 = wx.StaticText(self.errbox, wx.ID_ANY,"")
520        Siz.Add(errMsg1, 0, wx.ALIGN_LEFT|wx.LEFT|wx.EXPAND, 5)
521        errMsg2 = wx.StaticText(self.errbox, wx.ID_ANY,"\n\n")
522        font1 = wx.Font(errMsg2.GetFont().GetPointSize(),
523                        wx.MODERN, wx.NORMAL, wx.NORMAL, False)
524        errMsg2.SetFont(font1)
525        Siz.Add(errMsg2, 0, wx.ALIGN_LEFT|wx.LEFT|wx.EXPAND, 5)
526        errMsg3 = wx.StaticText(self.errbox, wx.ID_ANY,"")
527        Siz.Add(errMsg3, 0, wx.ALIGN_LEFT|wx.LEFT|wx.EXPAND, 5)
528        self.errbox.SetSizer(Siz,True)
529        Siz.Fit(self.errbox)
530        errMsg1.SetLabel(msg1)
531        errMsg2.SetLabel("  "+msg2)
532        errMsg2.Wrap(-1)
533        errMsg3.SetLabel(msg3)
534        self.Layout()
535
536    def OnValidate(self,event):
537        '''Respond to a press of the Validate button or when a variable
538        is associated with a label (in :meth:`OnChoice`)
539        '''
540        if event: event.Skip()
541        self.setEvalResult('(expression cannot be evaluated)')
542        self.timer.Stop()
543        self.expr = self.exCtrl.GetValue().strip()
544        if not self.expr: 
545            wx.CallAfter(self.showError,
546                "Invalid Expression:","","      (an expression must be entered)")
547            return
548        exprObj = G2obj.ExpressionObj()
549        ret = exprObj.ParseExpression(self.expr)
550        if not ret:
551            wx.CallAfter(self.showError,*exprObj.lastError)
552            return
553        self.exprVarLst,pkgdict = ret
554        wx.CallLater(100,self.Repaint,exprObj)
555
556    def Repaint(self,exprObj):
557        'Redisplay the variables and continue the validation'
558        # create widgets to associate vars with labels and/or show messages
559        self.varSizer.Clear(True)
560        self.errbox = wxscroll.ScrolledPanel(self,style=wx.HSCROLL)
561        self.errbox.SetMinSize((100,130))
562        self.varSizer.Add(self.errbox,0,wx.ALL|wx.EXPAND,1)
563        self.varbox = wxscroll.ScrolledPanel(self,style=wx.HSCROLL)
564        self.varSizer.Add(self.varbox,1,wx.ALL|wx.EXPAND,1)
565        Siz = wx.BoxSizer(wx.VERTICAL)
566        Siz.Add(
567            wx.StaticText(self.varbox,wx.ID_ANY,
568                          'Assignment of variables to labels:'),
569            0,wx.EXPAND|wx.ALIGN_CENTER,0)
570        GridSiz = wx.FlexGridSizer(0,5,2,2)
571        GridSiz.Add(
572            wx.StaticText(self.varbox,wx.ID_ANY,'label',style=wx.CENTER),
573            0,wx.ALIGN_CENTER)
574        lbls = ('varib. type\nselection','variable\nname','value')
575        choices = ['Free','Phase','Hist./Phase','Hist.','Global']
576        if self.fit:
577            lbls += ('refine\nflag',)
578        else:
579            lbls += ('',)
580            choices[0] = ''
581        for i in range(1,len(choices)): # remove empty menus from choice list
582            if not len(self.parmLists[i]): choices[i] = ''
583        self.AllowedChoices = [i for i in range(len(choices)) if choices[i]]
584        for lbl in lbls:
585            w = wx.StaticText(self.varbox,wx.ID_ANY,lbl,style=wx.CENTER)
586            w.SetMinSize((80,-1))
587            GridSiz.Add(w,0,wx.ALIGN_CENTER)
588
589        # show input for each var in expression.
590        for v in self.exprVarLst:
591            # label
592            GridSiz.Add(wx.StaticText(self.varbox,wx.ID_ANY,v),0,wx.ALIGN_CENTER,0)
593            # assignment type
594            ch = wx.Choice(
595                self.varbox, wx.ID_ANY,
596                choices = [choices[i] for i in self.AllowedChoices]
597                )
598            GridSiz.Add(ch,0,wx.ALIGN_LEFT,0)
599            if v in self.varSelect and self.varSelect.get(v) in self.AllowedChoices:
600                i = self.AllowedChoices.index(self.varSelect[v])
601                ch.SetSelection(i)
602            else:
603                ch.SetSelection(wx.NOT_FOUND)
604            ch.label = v
605            ch.Bind(wx.EVT_CHOICE,self.OnChoice)
606
607            # var name/var assignment
608            if self.varSelect.get(v) is None:
609                GridSiz.Add((-1,-1),0,wx.ALIGN_LEFT|wx.EXPAND,0)
610            elif self.varSelect.get(v) == 0:
611                wid = G2G.ValidatedTxtCtrl(self.varbox,self.varName,v,
612                                            #OnLeave=self.OnTxtLeave,
613                                            size=(50,-1))
614                GridSiz.Add(wid,0,wx.ALIGN_LEFT|wx.EXPAND,0)
615            else:
616                wid = wx.StaticText(self.varbox,wx.ID_ANY,self.varName[v])
617                GridSiz.Add(wid,0,wx.ALIGN_LEFT,0)
618
619            # value
620            if self.varSelect.get(v) is None:
621                GridSiz.Add((-1,-1),0,wx.ALIGN_RIGHT|wx.EXPAND,0)
622            elif self.varSelect.get(v) == 0:
623                wid = G2G.ValidatedTxtCtrl(self.varbox,self.varValue,v,
624                                            #OnLeave=self.OnTxtLeave,
625                                            size=(75,-1))
626                GridSiz.Add(wid,0,wx.ALIGN_LEFT|wx.EXPAND,0)
627                wid.Bind(wx.EVT_CHAR,self.OnChar)
628            else:
629                var = self.varName[v]
630                if var in self.parmDict:
631                    val = self.parmDict[var]
632                    s = G2py3.FormatSigFigs(val).rstrip('0')
633                elif '*' in var:
634                    vs = G2obj.LookupWildCard(var,self.parmDict.keys())
635                    s = '('+str(len(vs))+' values)'
636                else:
637                    s = '?'
638                wid = wx.StaticText(self.varbox,wx.ID_ANY,s)
639                GridSiz.Add(wid,0,wx.ALIGN_LEFT,0)
640
641            # show a refine flag for Free Vars only
642            if self.varSelect.get(v) == 0 and self.fit:
643                self.varRefflag[v] = self.varRefflag.get(v,True)
644                wid = G2G.G2CheckBox(self.varbox,'',self.varRefflag,v)
645                GridSiz.Add(wid,0,wx.ALIGN_LEFT|wx.EXPAND,0)
646            else:
647                wid = (-1,-1)
648                GridSiz.Add(wid,0,wx.ALIGN_LEFT|wx.EXPAND,0)
649
650        Siz.Add(GridSiz)
651        self.varbox.SetSizer(Siz,True)
652
653        # evaluate the expression, displaying errors or the expression value
654        try:
655            msg = self.CheckVars() 
656            if msg:
657                self.setEvalResult(msg)
658                return
659            exprObj.LoadExpression(
660                self.expr,
661                self.exprVarLst,
662                self.varSelect,
663                self.varName,
664                self.varValue,
665                self.varRefflag,
666                )
667            try:
668                calcobj = G2obj.ExpressionCalcObj(exprObj)
669                calcobj.SetupCalc(self.parmDict)
670                val = calcobj.EvalExpression()
671            except Exception as msg:
672                self.setEvalResult("Error in evaluation: "+str(msg))
673                return
674            if not np.isfinite(val):
675                self.setEvalResult("Expression value is infinite or out-of-bounds")
676                return
677            s = G2py3.FormatSigFigs(val).rstrip('0')
678            depVal = ""
679            if self.depVarDict:
680                if not self.dependentVar:
681                    self.setEvalResult("A dependent variable must be selected.")
682                    return
683                depVal = '; Variable "' + self.dependentVar + '" = ' + str(
684                    self.depVarDict.get(self.dependentVar,'?')
685                    )
686            self.setEvalResult("Expression evaluates to: "+str(s)+depVal)
687            self.OKbtn.Enable()
688            if self.ExtraBtn: self.ExtraBtn.Enable()
689        finally: 
690            xwid,yhgt = Siz.Fit(self.varbox)
691            self.varbox.SetMinSize((xwid,130))
692            self.varbox.SetAutoLayout(1)
693            self.varbox.SetupScrolling()
694            self.varbox.Refresh()
695            self.Layout()
696            self.SendSizeEvent() # force repaint
697           
698#==========================================================================
699class BondDialog(wx.Dialog):
700    '''A wx.Dialog that allows a user to select a bond length to be evaluated.
701    What needs to be done here? Need phase info for atoms
702    0. Select phase
703    1. Select 1st atom from dialog
704    2. Find neighbors & select one from dialog
705    3. Set up distance equation & save it - has to look like result from Show in above ExpressionDialog       
706    Use existing bond & esd calculate routines
707    '''
708    def __init__(self, parent, Phases, parmDict, exprObj=None,
709                 header='Enter restraint expression here',
710                 wintitle='Expression Editor',
711                 VarLabel=None,depVarDict=None,
712                 ExtraButton=None,usedVars=[]):
713        wx.Dialog.__init__(self,parent,wx.ID_ANY,wintitle, 
714            pos=wx.DefaultPosition,style=wx.DEFAULT_DIALOG_STYLE)
715        self.panel = wx.Panel(self)         #just a dummy - gets destroyed in Draw!
716        self.Phases = Phases
717        self.parmDict = parmDict
718        self.header = header
719        self.pName = list(Phases.keys())[0]
720        DisAglCtls = {}
721        dlg = G2G.DisAglDialog(self.panel,DisAglCtls,self.Phases[self.pName]['General'],Reset=False)
722        if dlg.ShowModal() == wx.ID_OK:
723            Phases[self.pName]['General']['DisAglCtls'] = dlg.GetData()
724        dlg.Destroy()
725        self.Oatom = ''
726        self.Tatom = ''
727        self.Draw()
728       
729    def Draw(self):
730       
731        def OnPhase(event):
732            Obj = event.GetEventObject()
733            self.pName = Obj.GetValue()
734            self.Oatom = ''
735            DisAglCtls = {}
736            dlg = G2G.DisAglDialog(self.panel,DisAglCtls,self.Phases[self.pName]['General'],Reset=False)
737            if dlg.ShowModal() == wx.ID_OK:
738                self.Phases[self.pName]['General']['DisAglCtls'] = dlg.GetData()
739            dlg.Destroy()
740            wx.CallAfter(self.Draw)
741           
742        def OnOrigAtom(event):
743            Obj = event.GetEventObject()
744            self.Oatom = Obj.GetValue()
745            wx.CallAfter(self.Draw)
746           
747        def OnTargAtom(event):
748            Obj = event.GetEventObject()
749            self.Tatom = Obj.GetValue()
750            wx.CallAfter(self.Draw)
751
752        self.panel.Destroy()
753        self.panel = wx.Panel(self)
754        mainSizer = wx.BoxSizer(wx.VERTICAL)
755        mainSizer.Add(wx.StaticText(self.panel,label=self.header),0,WACV)
756        pNames = list(self.Phases.keys())
757        phaseSizer = wx.BoxSizer(wx.HORIZONTAL)
758        phaseSizer.Add(wx.StaticText(self.panel,label=' Select phase: '),0,WACV)
759        phase = wx.ComboBox(self.panel,value=self.pName,choices=pNames,
760            style=wx.CB_READONLY|wx.CB_DROPDOWN)
761        phase.Bind(wx.EVT_COMBOBOX,OnPhase)
762        phaseSizer.Add(phase,0,WACV)
763        mainSizer.Add(phaseSizer)
764        Phase = self.Phases[self.pName]
765        cx,ct = Phase['General']['AtomPtrs'][:2]
766        Atoms = Phase['Atoms']
767        aNames = [atom[ct-1] for atom in Atoms]
768        atomSizer = wx.BoxSizer(wx.HORIZONTAL)
769        atomSizer.Add(wx.StaticText(self.panel,label=' Origin atom: '),0,WACV)
770        origAtom = wx.ComboBox(self.panel,value=self.Oatom,choices=aNames,
771            style=wx.CB_READONLY|wx.CB_DROPDOWN)
772        origAtom.Bind(wx.EVT_COMBOBOX,OnOrigAtom)
773        atomSizer.Add(origAtom,0,WACV)
774        atomSizer.Add(wx.StaticText(self.panel,label=' distance to: '),0,WACV)
775        neigh = []
776        if self.Oatom:
777#            GSASIIpath.IPyBreak()
778            neigh = G2mth.FindAllNeighbors(Phase,self.Oatom,aNames)
779        bNames = ['',]
780        if neigh:
781            bNames = [item[0]+' d=%.3f'%(item[1]) for item in neigh[0]]
782        targAtom = wx.ComboBox(self.panel,value=self.Tatom,choices=bNames,
783            style=wx.CB_READONLY|wx.CB_DROPDOWN)
784        targAtom.Bind(wx.EVT_COMBOBOX,OnTargAtom)
785        atomSizer.Add(targAtom,0,WACV)
786       
787        mainSizer.Add(atomSizer)
788
789
790        OkBtn = wx.Button(self.panel,-1,"Ok")
791        OkBtn.Bind(wx.EVT_BUTTON, self.OnOk)
792        cancelBtn = wx.Button(self.panel,-1,"Cancel")
793        cancelBtn.Bind(wx.EVT_BUTTON, self.OnCancel)
794        btnSizer = wx.BoxSizer(wx.HORIZONTAL)
795        btnSizer.Add((20,20),1)
796        btnSizer.Add(OkBtn)
797        btnSizer.Add((20,20),1)
798        btnSizer.Add(cancelBtn)
799        btnSizer.Add((20,20),1)
800       
801        mainSizer.Add(btnSizer,0,wx.EXPAND|wx.BOTTOM|wx.TOP, 10)
802        self.panel.SetSizer(mainSizer)
803        self.panel.Fit()
804        self.Fit()
805
806    def GetSelection(self):
807        return self.pName,self.Oatom,self.Tatom
808
809    def OnOk(self,event):
810        parent = self.GetParent()
811        parent.Raise()
812        self.EndModal(wx.ID_OK)
813
814    def OnCancel(self,event):
815        parent = self.GetParent()
816        parent.Raise()
817        self.EndModal(wx.ID_CANCEL)       
818           
819#==========================================================================
820class AngleDialog(wx.Dialog):
821    '''A wx.Dialog that allows a user to select a bond angle to be evaluated.
822    What needs to be done here? Need phase info for atom
823    0. Select phase
824    1. Select 1st atom from dialog
825    2. Find neighbors & select two from dialog
826    3. Set up angle equation & save it - has to look like result from Show in above ExpressionDialog       
827    Use existing angle & esd calculate routines
828    '''
829    def __init__(self, parent, Phases, parmDict, exprObj=None,
830                 header='Enter restraint expression here',
831                 wintitle='Expression Editor',
832                 VarLabel=None,depVarDict=None,
833                 ExtraButton=None,usedVars=[]):
834        wx.Dialog.__init__(self,parent,wx.ID_ANY,wintitle, 
835            pos=wx.DefaultPosition,style=wx.DEFAULT_DIALOG_STYLE)
836        self.panel = wx.Panel(self)         #just a dummy - gets destroyed in Draw!
837        self.Phases = Phases
838        self.parmDict = parmDict
839        self.header = header
840        self.pName = list(Phases.keys())[0]
841        DisAglCtls = {}
842        dlg = G2G.DisAglDialog(self.panel,DisAglCtls,self.Phases[self.pName]['General'],Reset=False)
843        if dlg.ShowModal() == wx.ID_OK:
844            Phases[self.pName]['General']['DisAglCtls'] = dlg.GetData()
845        dlg.Destroy()
846        self.Oatom = ''
847        self.Tatoms = ''
848        self.Draw()
849
850    def Draw(self):
851       
852        def OnPhase(event):
853            Obj = event.GetEventObject()
854            self.pName = Obj.GetValue()
855            self.Oatom = ''
856            DisAglCtls = {}
857            dlg = G2G.DisAglDialog(self.panel,DisAglCtls,self.Phases[self.pName]['General'],Reset=False)
858            if dlg.ShowModal() == wx.ID_OK:
859                self.Phases[self.pName]['General']['DisAglCtls'] = dlg.GetData()
860            dlg.Destroy()
861            wx.CallAfter(self.Draw)
862           
863        def OnOrigAtom(event):
864            Obj = event.GetEventObject()
865            self.Oatom = Obj.GetValue()
866            wx.CallAfter(self.Draw)           
867
868        def OnTargAtoms(event):
869            Obj = event.GetEventObject()
870            self.Tatoms = Obj.GetValue()
871            wx.CallAfter(self.Draw)
872
873        self.panel.Destroy()
874        self.panel = wx.Panel(self)
875        mainSizer = wx.BoxSizer(wx.VERTICAL)
876        mainSizer.Add(wx.StaticText(self.panel,label=self.header),0,WACV)
877        pNames = list(self.Phases.keys())
878        phaseSizer = wx.BoxSizer(wx.HORIZONTAL)
879        phaseSizer.Add(wx.StaticText(self.panel,label=' Select phase: '),0,WACV)
880        phase = wx.ComboBox(self.panel,value=self.pName,choices=pNames,
881            style=wx.CB_READONLY|wx.CB_DROPDOWN)
882        phase.Bind(wx.EVT_COMBOBOX,OnPhase)
883        phaseSizer.Add(phase,0,WACV)
884        mainSizer.Add(phaseSizer)
885        Phase = self.Phases[self.pName]
886        cx,ct = Phase['General']['AtomPtrs'][:2]
887        Atoms = Phase['Atoms']
888        aNames = [atom[ct-1] for atom in Atoms]
889#        GSASIIpath.IPyBreak()
890        atomSizer = wx.BoxSizer(wx.HORIZONTAL)
891        atomSizer.Add(wx.StaticText(self.panel,label=' Origin atom (O in A-O-B): '),0,WACV)
892        origAtom = wx.ComboBox(self.panel,value=self.Oatom,choices=aNames,
893            style=wx.CB_READONLY|wx.CB_DROPDOWN)
894        origAtom.Bind(wx.EVT_COMBOBOX,OnOrigAtom)
895        atomSizer.Add(origAtom,0,WACV)       
896        mainSizer.Add(atomSizer)
897        neigh = []
898        if self.Oatom:
899            neigh = G2mth.FindAllNeighbors(Phase,self.Oatom,aNames)[0]
900            mainSizer.Add(wx.StaticText(self.panel,label=' A-O-B angle for A,B: '),0,WACV)
901            bNames = ['',]
902            if neigh:
903#                GSASIIpath.IPyBreak()
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.