评估用户输入的模型作为 python 函数
Evaluate a model entered by user as python function
我必须在 python 3.4 中编写一个需要以下内容的任务:
- 程序要求用户以字符串形式输入由多个子模型组成的数据模型,如下所示:
# the program ask :
please input the submodel1
# user will input:
A = a*x + b*y*exp(3*c/d*x)
# then the program ask :
-> please input the submodel2
# user will input:
B = e*x + f*y*exp(3*h/d*x)
# then the program ask :
-> please input the main model:
# user will input:
Y = A*x - exp(B**2/y)
然后程序采用这些模型(字符串)并对它们执行一些操作,例如将主模型曲线拟合到现有数据并绘制显示参数值的结果。
这里的想法是让用户可以在运行时自由选择模型,而无需将其作为应用程序中的函数进行编程。
我的问题在于将字符串转换或解释为具有 returns 值的 python 函数。我探索了像 eval() 函数这样的解决方案,但我正在寻找类似于 MatLab
中的解决方案
func = sym('string')
func = matlabfunction(func)
它创建了一个你可以轻松处理的匿名函数
希望我说清楚了,如果需要进一步说明,请告诉我,
该解决方案应与 python 3.4 兼容
提前致谢
您可以使用解析自:
https://pypi.python.org/pypi/parse
并训练你的解析器识别数学表达式,
看这个例子(python27):
http://www.nerdparadise.com/tech/python/parsemath/
# A really simple expression evaluator supporting the
# four basic math functions, parentheses, and variables.
class Parser:
def __init__(self, string, vars={}):
self.string = string
self.index = 0
self.vars = {
'pi' : 3.141592653589793,
'e' : 2.718281828459045
}
for var in vars.keys():
if self.vars.get(var) != None:
raise Exception("Cannot redefine the value of " + var)
self.vars[var] = vars[var]
def getValue(self):
value = self.parseExpression()
self.skipWhitespace()
if self.hasNext():
raise Exception(
"Unexpected character found: '" +
self.peek() +
"' at index " +
str(self.index))
return value
def peek(self):
return self.string[self.index:self.index + 1]
def hasNext(self):
return self.index < len(self.string)
def skipWhitespace(self):
while self.hasNext():
if self.peek() in ' \t\n\r':
self.index += 1
else:
return
def parseExpression(self):
return self.parseAddition()
def parseAddition(self):
values = [self.parseMultiplication()]
while True:
self.skipWhitespace()
char = self.peek()
if char == '+':
self.index += 1
values.append(self.parseMultiplication())
elif char == '-':
self.index += 1
values.append(-1 * self.parseMultiplication())
else:
break
return sum(values)
def parseMultiplication(self):
values = [self.parseParenthesis()]
while True:
self.skipWhitespace()
char = self.peek()
if char == '*':
self.index += 1
values.append(self.parseParenthesis())
elif char == '/':
div_index = self.index
self.index += 1
denominator = self.parseParenthesis()
if denominator == 0:
raise Exception(
"Division by 0 kills baby whales (occured at index " +
str(div_index) +
")")
values.append(1.0 / denominator)
else:
break
value = 1.0
for factor in values:
value *= factor
return value
def parseParenthesis(self):
self.skipWhitespace()
char = self.peek()
if char == '(':
self.index += 1
value = self.parseExpression()
self.skipWhitespace()
if self.peek() != ')':
raise Exception(
"No closing parenthesis found at character "
+ str(self.index))
self.index += 1
return value
else:
return self.parseNegative()
def parseNegative(self):
self.skipWhitespace()
char = self.peek()
if char == '-':
self.index += 1
return -1 * self.parseParenthesis()
else:
return self.parseValue()
def parseValue(self):
self.skipWhitespace()
char = self.peek()
if char in '0123456789.':
return self.parseNumber()
else:
return self.parseVariable()
def parseVariable(self):
self.skipWhitespace()
var = ''
while self.hasNext():
char = self.peek()
if char.lower() in '_abcdefghijklmnopqrstuvwxyz0123456789':
var += char
self.index += 1
else:
break
value = self.vars.get(var, None)
if value == None:
raise Exception(
"Unrecognized variable: '" +
var +
"'")
return float(value)
def parseNumber(self):
self.skipWhitespace()
strValue = ''
decimal_found = False
char = ''
while self.hasNext():
char = self.peek()
if char == '.':
if decimal_found:
raise Exception(
"Found an extra period in a number at character " +
str(self.index) +
". Are you European?")
decimal_found = True
strValue += '.'
elif char in '0123456789':
strValue += char
else:
break
self.index += 1
if len(strValue) == 0:
if char == '':
raise Exception("Unexpected end found")
else:
raise Exception(
"I was expecting to find a number at character " +
str(self.index) +
" but instead I found a '" +
char +
"'. What's up with that?")
return float(strValue)
def evaluate(expression, vars={}):
try:
p = Parser(expression, vars)
value = p.getValue()
except Exception as (ex):
msg = ex.message
raise Exception(msg)
# Return an integer type if the answer is an integer
if int(value) == value:
return int(value)
# If Python made some silly precision error
# like x.99999999999996, just return x + 1 as an integer
epsilon = 0.0000000001
if int(value + epsilon) != int(value):
return int(value + epsilon)
elif int(value - epsilon) != int(value):
return int(value)
return value
print evaluate("1 + 2 * 3")
print evaluate("(1 + 2) * 3")
print evaluate("-(1 + 2) * 3")
print evaluate("(1-2)/3.0 + 0.0000")
print evaluate("1 + pi / 4")
print evaluate("(a + b) / c", { 'a':1, 'b':2, 'c':3 })
print evaluate("(x + e * 10) / 10", { 'x' : 3 })
print evaluate("1.0 / 3 * 6")
print evaluate("(1 - 1 + -1) * pi")
print evaluate("pi * e")
我通过创建一个小型解析器找到了解决我的问题的方法,或者说是一个基于 python 库 PLY 的非常小的 dsl,请参阅此网页了解详细信息:
http://www.dabeaz.com/ply/ply.html#ply_nn1
和这个项目:
https://github.com/maldoinc/mamba
lmfit (http://lmfit.github.io/lmfit-py/) 有一个您可能会觉得有用的 ExpressionModel:
import numpy as np
import matplotlib.pyplot as plt
from lmfit.models import ExpressionModel
# make fake data
x = np.linspace(-10, 10, 201)
amp, cen, wid = 3.4, 1.8, 0.5
y = amp * np.exp(-(x-cen)**2 / (2*wid**2)) / (np.sqrt(2*np.pi)*wid)
y += np.random.normal(size=len(x), scale=0.01)
# create model from expression
model= ExpressionModel("amp * exp(-(x-cen)**2 /(2*wid**2))/(sqrt(2*pi)*wid)")
# fit data to that model
result = model.fit(y, x=x, amp=5, cen=5, wid=1)
# show results
print(result.fit_report())
plt.plot(x, y, 'bo')
plt.plot(x, result.init_fit, 'k--')
plt.plot(x, result.best_fit, 'r-')
plt.show()
这使用了 asteval (http://newville.github.io/asteval),因此支持相当丰富的 Python 语法子集以及 numpy 常量和 ufunc,包括 sqrt
、exp
、sin
、pi
,依此类推。
我必须在 python 3.4 中编写一个需要以下内容的任务: - 程序要求用户以字符串形式输入由多个子模型组成的数据模型,如下所示:
# the program ask :
please input the submodel1
# user will input:
A = a*x + b*y*exp(3*c/d*x)
# then the program ask :
-> please input the submodel2
# user will input:
B = e*x + f*y*exp(3*h/d*x)
# then the program ask :
-> please input the main model:
# user will input:
Y = A*x - exp(B**2/y)
然后程序采用这些模型(字符串)并对它们执行一些操作,例如将主模型曲线拟合到现有数据并绘制显示参数值的结果。 这里的想法是让用户可以在运行时自由选择模型,而无需将其作为应用程序中的函数进行编程。 我的问题在于将字符串转换或解释为具有 returns 值的 python 函数。我探索了像 eval() 函数这样的解决方案,但我正在寻找类似于 MatLab
中的解决方案func = sym('string')
func = matlabfunction(func)
它创建了一个你可以轻松处理的匿名函数 希望我说清楚了,如果需要进一步说明,请告诉我, 该解决方案应与 python 3.4 兼容 提前致谢
您可以使用解析自:
https://pypi.python.org/pypi/parse
并训练你的解析器识别数学表达式,
看这个例子(python27):
http://www.nerdparadise.com/tech/python/parsemath/
# A really simple expression evaluator supporting the
# four basic math functions, parentheses, and variables.
class Parser:
def __init__(self, string, vars={}):
self.string = string
self.index = 0
self.vars = {
'pi' : 3.141592653589793,
'e' : 2.718281828459045
}
for var in vars.keys():
if self.vars.get(var) != None:
raise Exception("Cannot redefine the value of " + var)
self.vars[var] = vars[var]
def getValue(self):
value = self.parseExpression()
self.skipWhitespace()
if self.hasNext():
raise Exception(
"Unexpected character found: '" +
self.peek() +
"' at index " +
str(self.index))
return value
def peek(self):
return self.string[self.index:self.index + 1]
def hasNext(self):
return self.index < len(self.string)
def skipWhitespace(self):
while self.hasNext():
if self.peek() in ' \t\n\r':
self.index += 1
else:
return
def parseExpression(self):
return self.parseAddition()
def parseAddition(self):
values = [self.parseMultiplication()]
while True:
self.skipWhitespace()
char = self.peek()
if char == '+':
self.index += 1
values.append(self.parseMultiplication())
elif char == '-':
self.index += 1
values.append(-1 * self.parseMultiplication())
else:
break
return sum(values)
def parseMultiplication(self):
values = [self.parseParenthesis()]
while True:
self.skipWhitespace()
char = self.peek()
if char == '*':
self.index += 1
values.append(self.parseParenthesis())
elif char == '/':
div_index = self.index
self.index += 1
denominator = self.parseParenthesis()
if denominator == 0:
raise Exception(
"Division by 0 kills baby whales (occured at index " +
str(div_index) +
")")
values.append(1.0 / denominator)
else:
break
value = 1.0
for factor in values:
value *= factor
return value
def parseParenthesis(self):
self.skipWhitespace()
char = self.peek()
if char == '(':
self.index += 1
value = self.parseExpression()
self.skipWhitespace()
if self.peek() != ')':
raise Exception(
"No closing parenthesis found at character "
+ str(self.index))
self.index += 1
return value
else:
return self.parseNegative()
def parseNegative(self):
self.skipWhitespace()
char = self.peek()
if char == '-':
self.index += 1
return -1 * self.parseParenthesis()
else:
return self.parseValue()
def parseValue(self):
self.skipWhitespace()
char = self.peek()
if char in '0123456789.':
return self.parseNumber()
else:
return self.parseVariable()
def parseVariable(self):
self.skipWhitespace()
var = ''
while self.hasNext():
char = self.peek()
if char.lower() in '_abcdefghijklmnopqrstuvwxyz0123456789':
var += char
self.index += 1
else:
break
value = self.vars.get(var, None)
if value == None:
raise Exception(
"Unrecognized variable: '" +
var +
"'")
return float(value)
def parseNumber(self):
self.skipWhitespace()
strValue = ''
decimal_found = False
char = ''
while self.hasNext():
char = self.peek()
if char == '.':
if decimal_found:
raise Exception(
"Found an extra period in a number at character " +
str(self.index) +
". Are you European?")
decimal_found = True
strValue += '.'
elif char in '0123456789':
strValue += char
else:
break
self.index += 1
if len(strValue) == 0:
if char == '':
raise Exception("Unexpected end found")
else:
raise Exception(
"I was expecting to find a number at character " +
str(self.index) +
" but instead I found a '" +
char +
"'. What's up with that?")
return float(strValue)
def evaluate(expression, vars={}):
try:
p = Parser(expression, vars)
value = p.getValue()
except Exception as (ex):
msg = ex.message
raise Exception(msg)
# Return an integer type if the answer is an integer
if int(value) == value:
return int(value)
# If Python made some silly precision error
# like x.99999999999996, just return x + 1 as an integer
epsilon = 0.0000000001
if int(value + epsilon) != int(value):
return int(value + epsilon)
elif int(value - epsilon) != int(value):
return int(value)
return value
print evaluate("1 + 2 * 3")
print evaluate("(1 + 2) * 3")
print evaluate("-(1 + 2) * 3")
print evaluate("(1-2)/3.0 + 0.0000")
print evaluate("1 + pi / 4")
print evaluate("(a + b) / c", { 'a':1, 'b':2, 'c':3 })
print evaluate("(x + e * 10) / 10", { 'x' : 3 })
print evaluate("1.0 / 3 * 6")
print evaluate("(1 - 1 + -1) * pi")
print evaluate("pi * e")
我通过创建一个小型解析器找到了解决我的问题的方法,或者说是一个基于 python 库 PLY 的非常小的 dsl,请参阅此网页了解详细信息: http://www.dabeaz.com/ply/ply.html#ply_nn1 和这个项目: https://github.com/maldoinc/mamba
lmfit (http://lmfit.github.io/lmfit-py/) 有一个您可能会觉得有用的 ExpressionModel:
import numpy as np
import matplotlib.pyplot as plt
from lmfit.models import ExpressionModel
# make fake data
x = np.linspace(-10, 10, 201)
amp, cen, wid = 3.4, 1.8, 0.5
y = amp * np.exp(-(x-cen)**2 / (2*wid**2)) / (np.sqrt(2*np.pi)*wid)
y += np.random.normal(size=len(x), scale=0.01)
# create model from expression
model= ExpressionModel("amp * exp(-(x-cen)**2 /(2*wid**2))/(sqrt(2*pi)*wid)")
# fit data to that model
result = model.fit(y, x=x, amp=5, cen=5, wid=1)
# show results
print(result.fit_report())
plt.plot(x, y, 'bo')
plt.plot(x, result.init_fit, 'k--')
plt.plot(x, result.best_fit, 'r-')
plt.show()
这使用了 asteval (http://newville.github.io/asteval),因此支持相当丰富的 Python 语法子集以及 numpy 常量和 ufunc,包括 sqrt
、exp
、sin
、pi
,依此类推。