source: trunk/GSASIIexprGUI.py

Last change on this file was 5191, checked in by toby, 13 months ago

pseudovars & parametric fitting fixes; add G2 version to CIFs

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