在Python中解析包含子公式的方程式

0 投票
1 回答
1423 浏览
提问于 2025-04-17 22:25

我正在尝试用编译器的方法在Python中开发一个方程解析器。遇到的主要问题是,我可能没有所有的变量,因此需要寻找子公式。下面的例子说明得很清楚;)

我有四个已知值的变量:vx、vy、vz和c:

list_know_var = ['vx', 'vy', 'vz', 'c']

我想计算马赫数(M),它的定义是

equation = 'M = V / c'

我已经知道变量c,但不知道V。不过,我知道速度V可以通过vx、vy和vz计算出来,这个计算结果存储在一个字典里,里面还有其他公式(这里只展示了一个子公式)

know_equations = {'V': '(vx ** 2 + vy ** 2 + vz ** 2) ** 0.5'}

因此,我需要做的是解析这个方程,检查我是否有所有的变量。如果没有,我就要查看know_equations字典,看看是否有定义的方程,并且这个过程会递归进行,直到我的方程完全定义好。

目前为止,我已经能够使用这里提供的答案来解析我的方程,并判断我是否知道所有的变量。问题是,我还没有找到方法来用know_equation中的表达式替换未知变量(这里是V):

parsed_equation = compiler.parse(equation)
list_var = re.findall("Name\(\'(\w*)\'\)", str(parsed_equation.getChildNodes()[0]))

list_unknow_var = list(set(list_var) - set(list_know_var))
for var in list_unknow_var:
    if var in know_equations:
        # replace var in equation by its expression given in know_equations
        # and repeate until all the variables are known or raise Error
        pass

提前感谢你的帮助,非常感激!

Adrien

1 个回答

1

我来随便说说这个问题。

compiler.parse这个函数会返回一个叫做compiler.ast.Module的东西,它里面包含了一个抽象语法树。你可以用getChildNodes这个方法来遍历这个树。通过递归地检查每个节点的leftright属性,你可以找到compiler.ast.Name的实例,并把它们替换成你想要的表达式。

举个例子:

import compiler

def recursive_parse(node,substitutions):

    # look for left hand side of equation and test
    # if it is a variable name

    if hasattr(node.left,"name"):
        if node.left.name in substitutions.keys():
            node.left = substitutions[node.left.name]
    else:

        # if not, go deeper
        recursive_parse(node.left,substitutions)

    # look for right hand side of equation and test
    # if it is a variable name

    if hasattr(node.right,"name"):
        if node.right.name in substitutions.keys():
            node.right = substitutions[node.right.name]
    else:

        # if not, go deeper
        recursive_parse(node.right,substitutions)

def main(input):        

    substitutions = {
        "r":"sqrt(x**2+y**2)"
    }    

    # each of the substitutions needs to be compiled/parsed
    for key,value in substitutions.items():

        # this is a quick ugly way of getting the data of interest
        # really this should be done in a programatically cleaner manner 
        substitutions[key] = compiler.parse(substitutions[key]).getChildNodes()[0].getChildNodes()[0].getChildNodes()[0]

    # compile the input expression.
    expression = compiler.parse(input)

    print "Input:        ",expression

    # traverse the selected input, here we only pass the branch of interest. 
    # again, as with above, this done quick and dirty.
    recursive_parse(expression.getChildNodes()[0].getChildNodes()[0].getChildNodes()[1],substitutions)

    print "Substituted:  ",expression

if __name__ == "__main__":
    input = "t = r*p"
    main(input)

我承认我只在几个例子上测试过这个,但我觉得这个方法的基础是可以的,可以处理很多不同的输入。

运行这个代码后,我得到了以下输出:

Input:         Module(None, Stmt([Assign([AssName('t', 'OP_ASSIGN')], Mul((Name('r'), Name('p'))))]))
Substituted:   Module(None, Stmt([Assign([AssName('t', 'OP_ASSIGN')], Mul((CallFunc(Name('sqrt'), [Add((Power((Name('x'), Const(2))), Power((Name('y'), Const(2)))))], None, None), Name('p'))))]))

编辑:

在Python 3.0中,compiler模块已经不再推荐使用,所以更好(也更干净)的解决方案是使用ast模块:

import ast
from math import sqrt

# same a previous recursion function but with looking for 'id' not 'name' attribute
def recursive_parse(node,substitutions):
    if hasattr(node.left,"id"):
        if node.left.id in substitutions.keys():
            node.left = substitutions[node.left.id]
    else:
        recursive_parse(node.left,substitutions)

    if hasattr(node.right,"id"):
        if node.right.id in substitutions.keys():
            node.right = substitutions[node.right.id]
    else:
        recursive_parse(node.right,substitutions)

def main(input):

    substitutions = {
        "r":"sqrt(x**2+y**2)"
        }

    for key,value in substitutions.items():
        substitutions[key] = ast.parse(substitutions[key], mode='eval').body

    # As this is an assignment operation, mode must be set to exec
    module = ast.parse(input, mode='exec')

    print "Input:        ",ast.dump(module)

    recursive_parse(module.body[0].value,substitutions)

    print "Substituted:  ",ast.dump(module)

    # give some values for the equation
    x = 3
    y = 2
    p = 1
    code = compile(module,filename='<string>',mode='exec')
    exec(code)

    print input
    print "t =",t


if __name__ == "__main__":
    input = "t = r*p"
    main(input)

这个方法会编译表达式并在本地环境中执行。输出应该是:

Input:         Module(body=[Assign(targets=[Name(id='t', ctx=Store())], value=BinOp(left=Name(id='r', ctx=Load()), op=Mult(), right=Name(id='p', ctx=Load())))])
Substituted:   Module(body=[Assign(targets=[Name(id='t', ctx=Store())], value=BinOp(left=Call(func=Name(id='sqrt', ctx=Load()), args=[BinOp(left=BinOp(left=Name(id='x', ctx=Load()), op=Pow(), right=Num(n=2)), op=Add(), right=BinOp(left=Name(id='y', ctx=Load()), op=Pow(), right=Num(n=2)))], keywords=[], starargs=None, kwargs=None), op=Mult(), right=Name(id='p', ctx=Load())))])
t = r*p
t = 3.60555127546

撰写回答