# -*- coding: utf-8 -*-
#GSASIIexprGUI - Expression Definition and Evaluation
########### SVN repository information ###################
# $Date: 2023-09-29 15:47:55 -0500 (Fri, 29 Sep 2023) $
# $Author: vondreele $
# $Revision: 5663 $
# $URL: https://subversion.xray.aps.anl.gov/pyGSAS/trunk/GSASIIexprGUI.py $
# $Id: GSASIIexprGUI.py 5663 2023-09-29 20:47:55Z vondreele $
########### SVN repository information ###################
'''Routines for users to input Python expressions used within
GSAS-II computations follow.
'''
from __future__ import division, print_function
import re
import platform
import copy
import wx
import wx.lib.scrolledpanel as wxscroll
import numpy as np
import GSASIIpath
GSASIIpath.SetVersionNumber("$Revision: 5663 $")
import GSASIIctrlGUI as G2G
import GSASIIobj as G2obj
import GSASIImath as G2mth
import GSASIIfiles as G2fil
# Define a short name for convenience
WACV = wx.ALIGN_CENTER_VERTICAL
[docs]
def IndexParmDict(parmDict,wildcard):
'''Separate the parameters in parmDict into list of keys by parameter
type.
:param dict parmDict: a dict with GSAS-II parameters
:param bool wildcard: True if wildcard versions of parameters should
be generated and added to the lists
:returns: a dict of lists where key 1 is a list of phase parameters,
2 is histogram/phase parms, 3 is histogram parms and 4 are global parameters
'''
prex = re.compile('[0-9]+::.*')
hrex = re.compile(':[0-9]+:.*')
parmLists = {}
for i in (1,2,3,4):
parmLists[i] = []
for i in sorted(parmDict.keys()):
if i.startswith("::") or i.find(':') == -1: # globals
parmLists[4].append(i)
elif prex.match(i):
parmLists[1].append(i)
elif hrex.match(i):
parmLists[3].append(i)
else:
parmLists[2].append(i)
if wildcard:
for i in (1,2,3,4):
parmLists[i] += G2obj.GenWildCard(parmLists[i]) # generate wildcard versions
for i in (1,2,3,4):
parmLists[i].sort()
return parmLists
#==========================================================================
defaultExpressions = None
[docs]
def LoadDefaultExpressions():
'''Read a configuration file with default expressions from all files named
DefaultExpressions.txt found in the path. Duplicates are removed and
expressions are sorted alphabetically
'''
global defaultExpressions
if defaultExpressions is not None: return # run this routine only once
defaultExpressions = sorted(list(set(GSASIIpath.LoadConfigFile('DefaultExpressions.txt'))))
#==========================================================================
[docs]
class ExpressionDialog(wx.Dialog):
'''A wx.Dialog that allows a user to input an arbitrary expression
to be evaluated and possibly minimized.
To do this, the user assigns a new (free) or existing
GSAS-II parameter to each parameter label used in the expression.
The free parameters can optionally be designated to be refined.
For example, is an expression is used such as::
'A*np.exp(-B/C)'
then A, B and C can each be assigned as Free parameter with a user-selected
value or to any existing GSAS-II variable in the parameter dictionary.
As the expression is entered it is checked for validity.
After the :class:`ExpressionDialog` object is created, use :meth:`Show` to
run it and obtain a :class:`GSASIIobj.ExpressionObj` object with the user
input.
:param wx.Frame parent: The parent of the Dialog. Can be None,
but better is to provide the name of the Frame where the dialog
is called.
:param dict parmDict: a dict with defined parameters and their values. Each value
may be a list with parameter values and a refine flag or may just contain
the parameter value (non-float/int values in dict are ignored)
:param exprObj: a :class:`GSASIIobj.ExpressionObj` object with an expression and
label assignments or None (default)
:param str wintitle: String placed on title bar of dialog;
defaults to "Expression Editor"
:param str header: String placed at top of dialog to tell the user
what they will do here; default is "Enter restraint expression here"
:param bool fit: determines if the expression will be used in fitting (default=True).
If set to False, refinement flags are not shown
and Free parameters are not offered as an assignment option.
:param str VarLabel: an optional variable label to include before the expression
input. Ignored if None (default)
:param list depVarDict: a dict of choices for the dependent variable to be
fitted to the expression and their values. Ignored if None (default).
:param list ExtraButton: a list with two terms that define [0]: the label
for an extra button and [1] the callback routine to be used when the
button is pressed. The button will only be enabled when the OK button can be
used (meaning the equation/expression is valid). The default is None, meaning this
will not be used.
:param list usedVars: defines a list of previously used variable names. These names
will not be reused as defaults for new free variables.
(The default is an empty list).
:param bool wildCard: If True (default), histogram names are converted to wildcard
values, as is appropriate for the sequential refinement table
'''
def __init__(self, parent, parmDict, exprObj=None,
header='Enter restraint expression here',
wintitle='Expression Editor',
fit=True,VarLabel=None,depVarDict=None,
ExtraButton=None,usedVars=[],
wildCard=True):
self.fit = fit
self.depVarDict = depVarDict
'dict for dependent variables'
self.parmDict = {}
'''A copy of the G2 parameter dict (parmDict) except only numerical
values are included and only the value (not the vary flag, if present)
is included.
'''
self.exprVarLst = []
'''A list containing the variables utilized in the current expression.
Placed into a :class:`GSASIIobj.ExpressionObj` object when the dialog is closed
with "OK", saving any changes.
'''
self.varSelect = {}
'''A dict that shows the variable type for each label
found in the expression.
* If the value is None or is not defined, the value is not assigned.
* If the value is 0, then the varible is "free" -- a new refineable
parameter.
* Values above 1 determine what variables will be shown
when the option is selected.
'''
self.varName = {}
'Name assigned to each variable'
self.varValue = {}
'Value for a variable (Free parameters only)'
self.varRefflag = {}
'Refinement flag for a variable (Free parameters only)'
self.expr = ''
'Expression as a text string'
self.dependentVar = None
'name for dependent variable selection, when depVarDict is specified'
self.usedVars = usedVars
'variable names that have been used and should not be reused by default'
self.wildCard = wildCard
defSize = (620,340) # seems like a good size
'Starting size for dialog'
# process dictionary of values and create an index
for key in parmDict:
try: # deal with values that are in lists
val = parmDict[key][0]
except KeyError:
continue # there were dicts in parmDict (should be gone now)
except (TypeError,IndexError):
val = parmDict[key]
if '2' in platform.python_version_tuple()[0]:
basestr = basestring
else:
basestr = str
if isinstance(val, basestr): continue
try:
self.parmDict[key] = float(val)
except:
pass
# separate the variables by type
self.parmLists = IndexParmDict(self.parmDict,self.fit)
self.timer = wx.Timer()
self.timer.Bind(wx.EVT_TIMER,self.OnValidate)
LoadDefaultExpressions()
style=wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER
wx.Dialog.__init__(self, parent, wx.ID_ANY, wintitle, style=style, size=defSize)
self.mainsizer = wx.BoxSizer(wx.VERTICAL)
label = wx.StaticText(self, wx.ID_ANY, header)
self.mainsizer.Add(label, 0, wx.ALIGN_CENTRE|wx.ALL, 5)
self.exsizer = wx.BoxSizer(wx.HORIZONTAL)
if VarLabel:
label = wx.StaticText(self, wx.ID_ANY, VarLabel + ' = ')
self.exsizer.Add(label, 0, wx.ALL|wx.EXPAND, 0)
elif depVarDict:
self.depParmLists = IndexParmDict(self.depVarDict,False)
choices = ['','Phase','Hist./Phase','Hist.','Global']
for i in range(1,len(choices)): # remove empty menus from choice list
if not len(self.depParmLists[i]): choices[i] = ''
self.depChoices = [i for i in range(len(choices)) if choices[i]]
choice = wx.Choice(
self, wx.ID_ANY,
choices = [choices[i] for i in self.depChoices]
)
choice.SetSelection(wx.NOT_FOUND)
choice.Bind(wx.EVT_CHOICE,self.OnDepChoice)
self.exsizer.Add(choice, 0, wx.ALL|wx.EXPAND, 0)
self.exsizer.Add((5,5))
self.depLabel = wx.StaticText(self, wx.ID_ANY, ' ')
self.exsizer.Add(self.depLabel, 0, wx.ALL|wx.EXPAND, 0)
label = wx.StaticText(self, wx.ID_ANY, ' = ')
self.exsizer.Add(label, 0, wx.ALL|wx.EXPAND, 0)
#self.exCtrl = wx.TextCtrl(self, wx.ID_ANY, size=(150,-1),style=wx.TE_PROCESS_ENTER)
self.exCtrl = wx.ComboBox(self, wx.ID_ANY, "", (90, 50), (160, -1),
defaultExpressions,wx.CB_DROPDOWN| wx.TE_PROCESS_ENTER)
self.exCtrl.Bind(wx.EVT_CHAR, self.OnChar)
self.exCtrl.Bind(wx.EVT_COMBOBOX, self.OnValidate)
self.exCtrl.Bind(wx.EVT_TEXT_ENTER, self.OnValidate)
self.exsizer.Add(self.exCtrl, 1, wx.ALL|wx.EXPAND, 0)
#self.mainsizer.Add(self.exCtrl, 0, wx.ALL|wx.EXPAND, 5)
self.mainsizer.Add(self.exsizer, 0, wx.ALL|wx.EXPAND, 5)
self.mainsizer.Add((-1,5),0,wx.EXPAND,1)
evalSizer = wx.BoxSizer(wx.HORIZONTAL)
self.mainsizer.Add(evalSizer,0,wx.ALL|wx.EXPAND,0)
btn = wx.Button(self, wx.ID_ANY,"Validate")
btn.Bind(wx.EVT_BUTTON,self.OnValidate)
evalSizer.Add(btn,0,wx.LEFT|wx.RIGHT,5)
self.result = wx.StaticText(self, wx.ID_ANY, '')
evalSizer.Add(self.result, 0, wx.ALIGN_CENTRE|wx.ALL, 5)
self.varSizer = wx.BoxSizer(wx.HORIZONTAL)
self.mainsizer.Add(self.varSizer,1,wx.ALL|wx.EXPAND,1)
self.mainsizer.Add((-1,5),0,wx.EXPAND,1)
bSizer = wx.BoxSizer(wx.HORIZONTAL)
btnsizer = wx.StdDialogButtonSizer()
if ExtraButton:
self.ExtraBtn = wx.Button(self, wx.ID_ANY, ExtraButton[0])
self.ExtraBtn.Bind(wx.EVT_BUTTON,self.OnExtra)
self.ExtraCallBack = ExtraButton[1]
self.ExtraBtn.Disable()
bSizer.Add(self.ExtraBtn, 0, wx.ALL|WACV, 2)
else:
self.ExtraBtn = None
bSizer.Add((1,1), 1, wx.ALL|wx.EXPAND, 0)
self.OKbtn = wx.Button(self, wx.ID_OK)
self.OKbtn.Bind(wx.EVT_BUTTON,lambda event: self.EndModal(wx.ID_OK))
self.OKbtn.SetDefault()
self.OKbtn.Disable()
btnsizer.AddButton(self.OKbtn)
btn = wx.Button(self, wx.ID_CANCEL)
btn.Bind(wx.EVT_BUTTON,lambda event: self.EndModal(wx.ID_CANCEL))
btnsizer.AddButton(btn)
btnsizer.Realize()
bSizer.Add(btnsizer, 0, WACV|wx.ALL, 5)
self.mainsizer.Add(bSizer, 0, wx.ALL|wx.EXPAND, 5)
self.SetSizer(self.mainsizer)
self.CenterOnParent()
if exprObj:
self.expr = exprObj.EditExpression(
self.exprVarLst,
self.varSelect,
self.varName,
self.varValue,
self.varRefflag,
)
# set the initial value for the dependent value
if self.depVarDict:
var = exprObj.GetDepVar()
if var in self.depVarDict:
self.depLabel.SetLabel(var)
self.dependentVar = var
self.exCtrl.SetValue(self.expr)
self.OnValidate(None)
self.SetMinSize(defSize)
#self.errbox.SetAutoLayout(1)
#self.errbox.SetupScrolling()
#self.varbox.SetAutoLayout(1)
#self.varbox.SetupScrolling()
#self.mainsizer.Fit(self)
def OnExtra(self,event):
exprObj = G2obj.ExpressionObj()
exprObj.LoadExpression(
self.expr,
self.exprVarLst,
self.varSelect,
self.varName,
self.varValue,
self.varRefflag,
)
if self.depVarDict:
exprObj.SetDepVar(self.dependentVar)
self.ExtraCallBack(exprObj)
# put results back into displayed dialog
resDict = dict(exprObj.GetVariedVarVal())
for v,var in self.varName.items():
varname = "::" + var.lstrip(':').replace(' ','_').replace(':',';')
val = resDict.get(varname)
if val:
self.varValue[v] = val
wx.CallLater(100,self.Repaint,exprObj)
[docs]
def Show(self,mode=True):
'''Call to use the dialog after it is created.
:returns: None (On Cancel) or a new :class:`~GSASIIobj.ExpressionObj`
'''
self.Layout()
#self.mainsizer.Fit(self)
self.SendSizeEvent() # force repaint
if self.ShowModal() == wx.ID_OK:
# store the edit results in the object and return it
exprObj = G2obj.ExpressionObj()
exprObj.LoadExpression(
self.expr,
self.exprVarLst,
self.varSelect,
self.varName,
self.varValue,
self.varRefflag,
)
if self.depVarDict:
exprObj.SetDepVar(self.dependentVar)
return exprObj
else:
return None
[docs]
def setEvalResult(self,msg):
'Show a string in the expression result area'
self.result.SetLabel(msg)
[docs]
def RestartTimer(self):
'''Cancels any running timer and starts a new one.
The timer causes a check of syntax after 2 seconds unless there is further input.
Disables the OK button until a validity check is complete.
'''
if self.timer.IsRunning():
self.timer.Stop()
self.timer.Start(2000,oneShot=True)
[docs]
def OnChar(self,event):
'''Called as each character is entered. Cancels any running timer
and starts a new one. The timer causes a check of syntax after 2 seconds
without input.
Disables the OK button until a validity check is complete.
'''
self.RestartTimer()
self.OKbtn.Disable()
if self.ExtraBtn: self.ExtraBtn.Disable()
event.Skip()
return
[docs]
def CheckVars(self):
'''Check that appropriate variables are defined for each
symbol used in :data:`self.expr`
:returns: a text error message or None if all needed input is present
'''
invalid = 0
for v in self.exprVarLst:
if self.varSelect.get(v) is None:
invalid += 1
if invalid==1:
return '(a variable is not assigned)'
elif invalid:
return '('+str(invalid)+' variables are not assigned)'
msg = ''
for v in self.exprVarLst:
varname = self.varName.get(v)
if not varname:
invalid += 1
if msg: msg += "; "
msg += 'No variable for '+str(v)
elif self.varSelect.get(v,0) > 0:
if varname in self.parmDict:
pass
elif '*' in varname:
l = G2obj.LookupWildCard(varname,list(self.parmDict.keys()))
if len(l) == 0:
invalid += 1
if msg: msg += "; "
msg += 'No variables match '+str(varname)
elif varname not in self.parmDict:
invalid += 1
if msg: msg += "; "
msg += 'No variables match '+str(varname)
else:
# value assignment: this check is likely unneeded
val = self.varValue.get(v)
try:
float(val)
except (ValueError,TypeError):
invalid += 1
if msg: msg += "; "
if val is None:
msg += 'No value for '+str(v)
else:
msg += 'Value '+str(val)+' invalid for '+str(v)
if invalid:
return '('+msg+')'
return
[docs]
def OnDepChoice(self,event):
'''Respond to a selection of a variable type for a label in
an expression
'''
if event: event.Skip()
sel = self.depChoices[event.GetEventObject().GetSelection()]
var = self.SelectG2var(sel,'Dependent variable',self.depParmLists[sel])
if var is None:
self.dependentVar = None
self.OnValidate(None)
event.GetEventObject().SetSelection(wx.NOT_FOUND)
return
self.dependentVar = var
self.depLabel.SetLabel(var)
self.OnValidate(None)
self.Layout()
[docs]
def GetDepVar(self):
'''Returns the name of the dependent variable, when depVarDict is used.
'''
return self.dependentVar
[docs]
def OnChoice(self,event):
'''Respond to a selection of a variable type for a label in
an expression
'''
if event: event.Skip()
v = event.GetEventObject().label
sel = self.AllowedChoices[event.GetEventObject().GetSelection()]
if sel == 0:
sv = G2obj.MakeUniqueLabel(v,self.usedVars)
self.varSelect[v] = sel
self.varName[v] = sv
self.varValue[v] = self.varValue.get(v,0.0)
else:
var = self.SelectG2var(sel,v,self.parmLists[sel])
if var is None:
self.OnValidate(None)
return
self.varSelect[v] = sel
self.varName[v] = var
self.OnValidate(None)
[docs]
def SelectG2var(self,sel,var,parmList):
'''Offer a selection of a GSAS-II variable.
:param int sel: Determines the type of variable to be selected.
where 1 is used for Phase variables, and 2 for Histogram/Phase vars,
3 for Histogram vars and 4 for Global vars.
:returns: a variable name or None (if Cancel is pressed)
'''
def wildHist(var):
'''Replace a histogram number with a wild card
'''
slst = var.split(':')
if len(slst) > 2 and slst[1]:
slst[1] = '*'
return ':'.join(slst)
return var
if not parmList:
return None
l2 = l1 = 1
if self.wildCard:
wildList = [wildHist(i) for i in parmList]
else:
wildList = parmList
for i in wildList:
l1 = max(l1,len(i))
loc,desc = G2obj.VarDescr(i)
l2 = max(l2,len(loc))
fmt = u"{:"+str(l1)+"s} {:"+str(l2)+"s} {:s}"
varListlbl = [fmt.format(i,*G2obj.VarDescr(i)) for i in wildList]
dlg = G2G.G2SingleChoiceDialog(
self,'Select GSAS-II parameter for variable "'+str(var)+'":',
'Select parameter',
varListlbl,monoFont=True)
dlg.SetSize((625,250))
dlg.CenterOnParent()
var = None
if dlg.ShowModal() == wx.ID_OK:
i = dlg.GetSelection()
var = wildList[i]
dlg.Destroy()
return var
[docs]
def showError(self,msg1,msg2='',msg3=''):
'''Show an error message of 1 to 3 sections. The second
section is shown in an equally-spaced font.
:param str msg1: msg1 is shown in a the standard font
:param str msg2: msg2 is shown in a equally-spaced (wx.MODERN) font
:param str msg3: msg3 is shown in a the standard font
'''
self.varSizer.Clear(True)
self.OKbtn.Disable()
if self.ExtraBtn: self.ExtraBtn.Disable()
self.varSizer.Clear(True)
self.errbox = wxscroll.ScrolledPanel(self,style=wx.HSCROLL)
self.errbox.SetMinSize((200,130))
self.varSizer.Add(self.errbox,1,wx.ALL|wx.EXPAND,1)
Siz = wx.BoxSizer(wx.VERTICAL)
errMsg1 = wx.StaticText(self.errbox, wx.ID_ANY,"")
Siz.Add(errMsg1, 0, wx.LEFT|wx.EXPAND, 5)
errMsg2 = wx.StaticText(self.errbox, wx.ID_ANY,"\n\n")
font1 = wx.Font(errMsg2.GetFont().GetPointSize(),
wx.MODERN, wx.NORMAL, wx.NORMAL, False)
errMsg2.SetFont(font1)
Siz.Add(errMsg2, 0, wx.LEFT|wx.EXPAND, 5)
errMsg3 = wx.StaticText(self.errbox, wx.ID_ANY,"")
Siz.Add(errMsg3, 0, wx.LEFT|wx.EXPAND, 5)
self.errbox.SetSizer(Siz,True)
Siz.Fit(self.errbox)
errMsg1.SetLabel(msg1)
errMsg2.SetLabel(" "+msg2)
errMsg2.Wrap(-1)
errMsg3.SetLabel(msg3)
self.Layout()
[docs]
def OnValidate(self,event):
'''Respond to a press of the Validate button or when a variable
is associated with a label (in :meth:`OnChoice`)
'''
if event: event.Skip()
self.setEvalResult('(expression cannot be evaluated)')
self.timer.Stop()
self.expr = self.exCtrl.GetValue().strip()
if not self.expr:
wx.CallAfter(self.showError,
"Invalid Expression:",""," (an expression must be entered)")
return
exprObj = G2obj.ExpressionObj()
ret = exprObj.ParseExpression(self.expr)
if not ret:
wx.CallAfter(self.showError,*exprObj.lastError)
return
self.exprVarLst,pkgdict = ret
wx.CallLater(100,self.Repaint,exprObj)
[docs]
def Repaint(self,exprObj):
'Redisplay the variables and continue the validation'
# create widgets to associate vars with labels and/or show messages
self.varSizer.Clear(True)
self.errbox = wxscroll.ScrolledPanel(self,style=wx.HSCROLL)
self.errbox.SetMinSize((100,130))
self.varSizer.Add(self.errbox,0,wx.ALL|wx.EXPAND,1)
self.varbox = wxscroll.ScrolledPanel(self,style=wx.HSCROLL)
self.varSizer.Add(self.varbox,1,wx.ALL|wx.EXPAND,1)
Siz = wx.BoxSizer(wx.VERTICAL)
Siz.Add(
wx.StaticText(self.varbox,wx.ID_ANY,
'Assignment of variables to labels:'),
0,wx.EXPAND,0)
GridSiz = wx.FlexGridSizer(0,5,2,2)
GridSiz.Add(
wx.StaticText(self.varbox,wx.ID_ANY,'label',style=wx.CENTER),
0,wx.ALIGN_CENTER)
lbls = ('varib. type\nselection','variable\nname','value')
choices = ['Free','Phase','Hist./Phase','Hist.','Global']
if self.fit:
lbls += ('refine\nflag',)
else:
lbls += ('',)
choices[0] = ''
for i in range(1,len(choices)): # remove empty menus from choice list
if not len(self.parmLists[i]): choices[i] = ''
self.AllowedChoices = [i for i in range(len(choices)) if choices[i]]
for lbl in lbls:
w = wx.StaticText(self.varbox,wx.ID_ANY,lbl,style=wx.CENTER)
w.SetMinSize((80,-1))
GridSiz.Add(w,0,wx.ALIGN_CENTER)
# show input for each var in expression.
for v in self.exprVarLst:
# label
GridSiz.Add(wx.StaticText(self.varbox,wx.ID_ANY,v),0,wx.ALIGN_CENTER,0)
# assignment type
ch = wx.Choice(
self.varbox, wx.ID_ANY,
choices = [choices[i] for i in self.AllowedChoices]
)
GridSiz.Add(ch,0,wx.ALIGN_LEFT,0)
if v in self.varSelect and self.varSelect.get(v) in self.AllowedChoices:
i = self.AllowedChoices.index(self.varSelect[v])
ch.SetSelection(i)
else:
ch.SetSelection(wx.NOT_FOUND)
ch.label = v
ch.Bind(wx.EVT_CHOICE,self.OnChoice)
# var name/var assignment
if self.varSelect.get(v) is None:
GridSiz.Add((-1,-1),0,wx.EXPAND,0)
elif self.varSelect.get(v) == 0:
wid = G2G.ValidatedTxtCtrl(self.varbox,self.varName,v,size=(50,-1))
GridSiz.Add(wid,0,wx.EXPAND,0)
else:
wid = wx.StaticText(self.varbox,wx.ID_ANY,self.varName[v])
GridSiz.Add(wid,0,wx.ALIGN_LEFT,0)
# value
if self.varSelect.get(v) is None:
GridSiz.Add((-1,-1),0,wx.EXPAND,0)
elif self.varSelect.get(v) == 0:
wid = G2G.ValidatedTxtCtrl(self.varbox,self.varValue,v,size=(75,-1))
GridSiz.Add(wid,0,wx.EXPAND,0)
wid.Bind(wx.EVT_CHAR,self.OnChar)
else:
var = self.varName[v]
if var in self.parmDict:
val = self.parmDict[var]
s = G2fil.FormatSigFigs(val).rstrip('0')
elif '*' in var:
vs = G2obj.LookupWildCard(var,self.parmDict.keys())
s = '('+str(len(vs))+' values)'
else:
s = '?'
wid = wx.StaticText(self.varbox,wx.ID_ANY,s)
GridSiz.Add(wid,0,wx.ALIGN_LEFT,0)
# show a refine flag for Free Vars only
if self.varSelect.get(v) == 0 and self.fit:
self.varRefflag[v] = self.varRefflag.get(v,True)
wid = G2G.G2CheckBox(self.varbox,'',self.varRefflag,v)
GridSiz.Add(wid,0,wx.EXPAND,0)
else:
wid = (-1,-1)
GridSiz.Add(wid,0,wx.EXPAND,0)
Siz.Add(GridSiz)
self.varbox.SetSizer(Siz,True)
# evaluate the expression, displaying errors or the expression value
try:
msg = self.CheckVars()
if msg:
self.setEvalResult(msg)
return
exprObj.LoadExpression(
self.expr,
self.exprVarLst,
self.varSelect,
self.varName,
self.varValue,
self.varRefflag,
)
try:
calcobj = G2obj.ExpressionCalcObj(exprObj)
calcobj.SetupCalc(self.parmDict)
val = calcobj.EvalExpression()
except Exception as msg:
self.setEvalResult("Error in evaluation: "+str(msg))
return
if not np.isfinite(val):
self.setEvalResult("Expression value is infinite or out-of-bounds")
return
s = G2fil.FormatSigFigs(val).rstrip('0')
depVal = ""
if self.depVarDict:
if not self.dependentVar:
self.setEvalResult("A dependent variable must be selected.")
return
depVal = '; Variable "' + self.dependentVar + '" = ' + str(
self.depVarDict.get(self.dependentVar,'?')
)
self.setEvalResult("Expression evaluates to: "+str(s)+depVal+
" with first defined values")
self.OKbtn.Enable()
if self.ExtraBtn: self.ExtraBtn.Enable()
finally:
xwid,yhgt = Siz.Fit(self.varbox)
self.varbox.SetMinSize((xwid,130))
self.varbox.SetAutoLayout(1)
self.varbox.SetupScrolling()
self.varbox.Refresh()
self.Layout()
self.SendSizeEvent() # force repaint
#==========================================================================
[docs]
class BondDialog(wx.Dialog):
'''A wx.Dialog that allows a user to select a bond length to be evaluated.
What needs to be done here? Need phase info for atoms
0. Select phase
1. Select 1st atom from dialog
2. Find neighbors & select one from dialog
3. Set up distance equation & save it - has to look like result from Show in above ExpressionDialog
Use existing bond & esd calculate routines
'''
def __init__(self, parent, Phases, parmDict, exprObj=None,
header='Select a bond for table',
wintitle='Select bond',
VarLabel=None,depVarDict=None,
ExtraButton=None,usedVars=[]):
wx.Dialog.__init__(self,parent,wx.ID_ANY,wintitle,
pos=wx.DefaultPosition,style=wx.DEFAULT_DIALOG_STYLE)
self.panel = wx.Panel(self) #just a dummy - gets destroyed in Draw!
self.parent = parent
self.Phases = Phases
self.parmDict = parmDict
self.header = header
self.pName = list(Phases.keys())[0]
self.Oatom = ''
self.Tatom = ''
if 'DisAglCtls' not in self.Phases[self.pName]['General']:
self.OnSetRadii(None)
else:
self.Draw()
def OnSetRadii(self,event):
if 'DisAglCtls' in self.Phases[self.pName]['General']:
DisAglCtls = copy.deepcopy(self.Phases[self.pName]['General']['DisAglCtls'])
else:
DisAglCtls = {}
dlg = G2G.DisAglDialog(self.parent,DisAglCtls,self.Phases[self.pName]['General'],Reset=False)
if dlg.ShowModal() == wx.ID_OK:
self.Phases[self.pName]['General']['DisAglCtls'] = dlg.GetData()
dlg.Destroy()
wx.CallAfter(self.Draw)
[docs]
def Draw(self):
'paints the distance dialog window'
def OnPhase(event):
Obj = event.GetEventObject()
self.pName = Obj.GetValue()
self.Oatom = ''
if 'DisAglCtls' not in self.Phases[self.pName]['General']:
self.OnSetRadii(None)
else:
wx.CallAfter(self.Draw)
def OnOrigAtom(event):
Obj = event.GetEventObject()
self.Oatom = Obj.GetValue()
wx.CallAfter(self.Draw)
def OnTargAtom(event):
Obj = event.GetEventObject()
self.Tatom = Obj.GetValue()
wx.CallAfter(self.Draw)
self.panel.Destroy()
self.panel = wx.Panel(self)
mainSizer = wx.BoxSizer(wx.VERTICAL)
mainSizer.Add(wx.StaticText(self.panel,label=self.header))
pNames = list(self.Phases.keys())
phaseSizer = wx.BoxSizer(wx.HORIZONTAL)
phaseSizer.Add(wx.StaticText(self.panel,label=' Select phase: '),0,WACV)
phase = wx.ComboBox(self.panel,value=self.pName,choices=pNames,
style=wx.CB_READONLY|wx.CB_DROPDOWN)
phase.Bind(wx.EVT_COMBOBOX,OnPhase)
phaseSizer.Add(phase,0,WACV)
phaseSizer.Add((15,-1))
radii = wx.Button(self.panel,label='Set search radii')
radii.Bind(wx.EVT_BUTTON,self.OnSetRadii)
phaseSizer.Add(radii,0,WACV)
mainSizer.Add(phaseSizer)
Phase = self.Phases[self.pName]
cx,ct = Phase['General']['AtomPtrs'][:2]
Atoms = Phase['Atoms']
aNames = [atom[ct-1] for atom in Atoms]
atomSizer = wx.BoxSizer(wx.HORIZONTAL)
atomSizer.Add(wx.StaticText(self.panel,label=' Origin atom: '),0,WACV)
if not self.Oatom and len(aNames) > 0: # select an atom so distances get computed
self.Oatom = aNames[0]
origAtom = wx.ComboBox(self.panel,value=self.Oatom,choices=aNames,
style=wx.CB_READONLY|wx.CB_DROPDOWN)
origAtom.Bind(wx.EVT_COMBOBOX,OnOrigAtom)
atomSizer.Add(origAtom,0,WACV)
atomSizer.Add(wx.StaticText(self.panel,label=' distance to: '),0,WACV)
neigh = []
bNames = []
if self.Oatom:
neigh = G2mth.FindAllNeighbors(Phase,self.Oatom,aNames)
if neigh:
bNames = [item[0]+' d=%.3f'%(item[2]) for item in neigh[0]]
if bNames:
targAtom = wx.ComboBox(self.panel,value=self.Tatom,choices=['']+bNames,
style=wx.CB_READONLY|wx.CB_DROPDOWN)
targAtom.Bind(wx.EVT_COMBOBOX,OnTargAtom)
else:
targAtom = wx.StaticText(self.panel,label='(none in search range)')
atomSizer.Add(targAtom,0,WACV)
mainSizer.Add(atomSizer)
OkBtn = wx.Button(self.panel,-1,"Ok")
OkBtn.Bind(wx.EVT_BUTTON, self.OnOk)
OkBtn.Enable(bool(self.Tatom))
cancelBtn = wx.Button(self.panel,-1,"Cancel")
cancelBtn.Bind(wx.EVT_BUTTON, self.OnCancel)
btnSizer = wx.BoxSizer(wx.HORIZONTAL)
btnSizer.Add((20,20),1)
btnSizer.Add(OkBtn)
btnSizer.Add((20,20),1)
btnSizer.Add(cancelBtn)
btnSizer.Add((20,20),1)
mainSizer.Add(btnSizer,0,wx.EXPAND|wx.BOTTOM|wx.TOP, 10)
self.panel.SetSizer(mainSizer)
self.panel.Fit()
self.Fit()
self.CenterOnParent()
def GetSelection(self):
return self.pName,self.Oatom,self.Tatom
def OnOk(self,event):
parent = self.GetParent()
parent.Raise()
self.EndModal(wx.ID_OK)
def OnCancel(self,event):
parent = self.GetParent()
parent.Raise()
self.EndModal(wx.ID_CANCEL)
#==========================================================================
[docs]
class AngleDialog(wx.Dialog):
'''A wx.Dialog that allows a user to select a bond angle to be evaluated.
What needs to be done here? Need phase info for atom
0. Select phase
1. Select 1st atom from dialog
2. Find neighbors & select two from dialog
3. Set up angle equation & save it - has to look like result from Show in above ExpressionDialog
Use existing angle & esd calculate routines
'''
def __init__(self, parent, Phases, parmDict, exprObj=None,
header='Select an angle for table',
wintitle='Select angle',
VarLabel=None,depVarDict=None,
ExtraButton=None,usedVars=[]):
wx.Dialog.__init__(self,parent,wx.ID_ANY,wintitle,
pos=wx.DefaultPosition,style=wx.DEFAULT_DIALOG_STYLE)
self.panel = wx.Panel(self) #just a dummy - gets destroyed in Draw!
self.parent = parent
self.Phases = Phases
self.parmDict = parmDict
self.header = header
self.pName = list(Phases.keys())[0]
self.Oatom = ''
self.Tatoms = ''
if 'DisAglCtls' not in self.Phases[self.pName]['General']:
self.OnSetRadii(None)
else:
self.Draw()
def OnSetRadii(self,event):
if 'DisAglCtls' in self.Phases[self.pName]['General']:
DisAglCtls = copy.deepcopy(self.Phases[self.pName]['General']['DisAglCtls'])
else:
DisAglCtls = {}
dlg = G2G.DisAglDialog(self.parent,DisAglCtls,self.Phases[self.pName]['General'],Reset=False)
if dlg.ShowModal() == wx.ID_OK:
self.Phases[self.pName]['General']['DisAglCtls'] = dlg.GetData()
dlg.Destroy()
wx.CallAfter(self.Draw)
[docs]
def Draw(self):
'paints the angle dialog window'
def OnPhase(event):
Obj = event.GetEventObject()
self.pName = Obj.GetValue()
self.Oatom = ''
if 'DisAglCtls' not in self.Phases[self.pName]['General']:
self.OnSetRadii(None)
else:
wx.CallAfter(self.Draw)
def OnOrigAtom(event):
Obj = event.GetEventObject()
self.Oatom = Obj.GetValue()
wx.CallAfter(self.Draw)
def OnTargAtoms(event):
Obj = event.GetEventObject()
self.Tatoms = Obj.GetValue()
wx.CallAfter(self.Draw)
self.panel.Destroy()
self.panel = wx.Panel(self)
mainSizer = wx.BoxSizer(wx.VERTICAL)
mainSizer.Add(wx.StaticText(self.panel,label=self.header))
pNames = list(self.Phases.keys())
phaseSizer = wx.BoxSizer(wx.HORIZONTAL)
phaseSizer.Add(wx.StaticText(self.panel,label=' Select phase: '),0,WACV)
phase = wx.ComboBox(self.panel,value=self.pName,choices=pNames,
style=wx.CB_READONLY|wx.CB_DROPDOWN)
phase.Bind(wx.EVT_COMBOBOX,OnPhase)
phaseSizer.Add(phase,0,WACV)
phaseSizer.Add((15,-1))
radii = wx.Button(self.panel,label='Set search radii')
radii.Bind(wx.EVT_BUTTON,self.OnSetRadii)
phaseSizer.Add(radii,0,WACV)
mainSizer.Add(phaseSizer)
Phase = self.Phases[self.pName]
cx,ct = Phase['General']['AtomPtrs'][:2]
Atoms = Phase['Atoms']
aNames = [atom[ct-1] for atom in Atoms]
atomSizer = wx.BoxSizer(wx.HORIZONTAL)
atomSizer.Add(wx.StaticText(self.panel,label=' Origin atom (O in A-O-B): '),0,WACV)
if not self.Oatom and len(aNames) > 0: # select an atom so angles get computed
self.Oatom = aNames[0]
origAtom = wx.ComboBox(self.panel,value=self.Oatom,choices=aNames,
style=wx.CB_READONLY|wx.CB_DROPDOWN)
origAtom.Bind(wx.EVT_COMBOBOX,OnOrigAtom)
atomSizer.Add(origAtom,0,WACV)
mainSizer.Add(atomSizer)
atomSizer = wx.BoxSizer(wx.HORIZONTAL)
atomSizer.Add(wx.StaticText(self.panel,label=' A-O-B angle for A,B: '),0,WACV)
neigh = []
bNames = []
if self.Oatom:
neigh = G2mth.FindAllNeighbors(Phase,self.Oatom,aNames,searchType='Angle')[0]
if neigh:
for iA,aName in enumerate(neigh):
for cName in neigh[iA+1:]:
bNames.append('%s;%s'%(aName[0].replace(' ',''),cName[0].replace(' ','')))
if bNames:
targAtoms = wx.ComboBox(self.panel,value=self.Tatoms,choices=['']+bNames,
style=wx.CB_READONLY|wx.CB_DROPDOWN)
targAtoms.Bind(wx.EVT_COMBOBOX,OnTargAtoms)
atomSizer.Add(targAtoms,0,WACV)
if self.Tatoms:
aobj = G2obj.makeAngleObj(self.Phases[self.pName],self.Oatom,self.Tatoms)
calcobj = G2obj.ExpressionCalcObj(aobj)
calcobj.UpdateDict(self.parmDict)
atomSizer.Add(wx.StaticText(self.panel,
label=' = {:.2f} deg'.format(calcobj.EvalExpression())),
0,WACV)
else:
atomSizer.Add(wx.StaticText(self.panel,label='(none in search range)'))
mainSizer.Add(atomSizer)
OkBtn = wx.Button(self.panel,-1,"Ok")
OkBtn.Bind(wx.EVT_BUTTON, self.OnOk)
OkBtn.Enable(bool(self.Tatoms))
cancelBtn = wx.Button(self.panel,-1,"Cancel")
cancelBtn.Bind(wx.EVT_BUTTON, self.OnCancel)
btnSizer = wx.BoxSizer(wx.HORIZONTAL)
btnSizer.Add((20,20),1)
btnSizer.Add(OkBtn)
btnSizer.Add((20,20),1)
btnSizer.Add(cancelBtn)
btnSizer.Add((20,20),1)
mainSizer.Add(btnSizer,0,wx.EXPAND|wx.BOTTOM|wx.TOP, 10)
self.panel.SetSizer(mainSizer)
self.panel.Fit()
self.Fit()
self.CenterOnParent()
def GetSelection(self):
return self.pName,self.Oatom,self.Tatoms
def OnOk(self,event):
parent = self.GetParent()
parent.Raise()
self.EndModal(wx.ID_OK)
def OnCancel(self,event):
parent = self.GetParent()
parent.Raise()
self.EndModal(wx.ID_CANCEL)
if __name__ == "__main__":
app = wx.PySimpleApp() # create the App
frm = wx.Frame(None)
frm.Show()
PSvarDict = {'::a':1.0,'::b':1.1,'0::c':1.2,'::AlongVaraiableName':1000.}
#PSvars = PSvarDict.keys()
indepvarDict = {'Temperature':1.0,'Pressure':1.1,'Phase of Moon':1.2,'1:1:HAP':1.3}
dlg = ExpressionDialog(frm,indepvarDict,
header="Edit the PseudoVar expression",
fit=False,
depVarDict=PSvarDict,
#VarLabel="New PseudoVar",
)
newobj = dlg.Show(True)
dlg = ExpressionDialog(frm,indepvarDict,newobj,
header="Edit the PseudoVar expression",
fit=False,
depVarDict=PSvarDict,
#VarLabel="New PseudoVar",
)
newobj = dlg.Show(True)
print (dlg.GetDepVar())
dlg = ExpressionDialog(frm,PSvarDict,
header="Edit the PseudoVar expression",
fit=True)
newobj = dlg.Show(True)
print (dlg.GetDepVar())
#app.MainLoop()
if '2' in platform.python_version_tuple()[0]:
import cPickle
else:
import pickle as cPickle
def showEQ(calcobj):
print
print (calcobj.eObj.expression)
for v in sorted(calcobj.eObj.freeVars.keys()+calcobj.eObj.assgnVars.keys()):
print (" ",v,'=',calcobj.exprDict[v])
print (calcobj.EvalExpression())
print ("starting test")
obj = G2obj.ExpressionObj()
obj.expression = "A*np.exp(B)"
obj.assgnVars = {'B': '0::Afrac:*'}
obj.freeVars = {'A': [u'A', 0.5, True]}
obj.CheckVars()
parmDict2 = {'0::Afrac:0':1.0, '0::Afrac:1': 1.0}
calcobj = G2obj.ExpressionCalcObj(obj)
calcobj.SetupCalc(parmDict2)
showEQ(calcobj)
fp = open('/tmp/obj.pickle','w')
cPickle.dump(obj,fp)
fp.close()
obj.expression = "A*np.exp(-2/B)"
obj.assgnVars = {'A': '0::Afrac:0', 'B': '0::Afrac:1'}
obj.freeVars = {}
parmDict1 = {'0::Afrac:0':1.0, '0::Afrac:1': -2.0}
calcobj = G2obj.ExpressionCalcObj(obj)
calcobj.SetupCalc(parmDict1)
showEQ(calcobj)
fp = open('/tmp/obj.pickle','r')
obj = cPickle.load(fp)
fp.close()
parmDict2 = {'0::Afrac:0':0.0, '0::Afrac:1': 1.0}
calcobj = G2obj.ExpressionCalcObj(obj)
calcobj.SetupCalc(parmDict2)
showEQ(calcobj)
parmDict2 = {'0::Afrac:0':1.0, '0::Afrac:1': 1.0}
calcobj = G2obj.ExpressionCalcObj(obj)
calcobj.SetupCalc(parmDict2)
showEQ(calcobj)