Python: 解析相互依赖的数学函数

Python: Parsing math functions that are dependent on each other

我有一个对象列表,每个对象都是一个数学函数,这些函数可能相互依赖,例如:

[
 Formula("G", ["y"], "1 - y"),
 Formula("V", ["x", "y"], "G(y) * x / 3"),
 Formula("U", ["x", "y"],"(G(y)**2) / (9 * V(x, y)) + V(x, y)")
]

其中第一个参数是函数名,第二个是使用的变量列表,第三个是字符串 - 函数的表达式。

有没有一种简单的方法来评估函数 U(x, y) 在给定点的值,例如在 [2, 3] 并递归调用 G(3)V(2, 3),得到最后的结果?

我曾尝试在 Sympy 中执行此操作,但无法在函数 V(x,y)[ 中调用例如函数 G(y) =25=]

也许是这样的?

>>> def Formula(*args):
...   return parse_expr('{%s(*%s): %s}' % args)
...
>>> f =[
...  Formula("G", ["y"], "1 - y"),
...  Formula("V", ["x", "y"], "G(y) * x / 3"),
...  Formula("U", ["x", "y"],"(G(y)**2) / (9 * V(x, y)) + V(x, y)")
... ]
>>> f
[{G(y): 1 - y}, {V(x, y): x*G(y)/3}, {U(x, y): G(y)**2/(9*V(x, y)) + V(x, y)}]

f 已按拓扑排序,因此返回替换

>>> from sympy import Dict
>>> e=Dict(f[-1])
>>> e=e.subs(f[-2])
>>> e=e.subs(f[-3])
>>> a,b=dict(e).popitem()
>>> U = Lambda(a.args,b)
>>> U(2,3)
-5/3

如果它没有排序,你可以使用 topological_sort,也许通过 repsort here 这样做:

>>> repsort(*[tuple(i.items()) for i in f])
[(U(x, y), G(y)**2/(9*V(x, y)) + V(x, y)), (V(x, y), x*G(y)/3), (G(y), 1 - y)]
>>> s = _
>>> expr = s[0][0]
>>> for i in s:
...   expr = expr.subs(*i)
...
>>> expr
x*(1 - y)/3 + (1 - y)/(3*x)
>>> U = Lambda(tuple(ordered(expr.free_symbols)), _)
>>> U(2,3)
-5/3

感谢您的所有建议。在我给出的示例中,Formula 是按拓扑顺序排列的,但实际上并不总是这样。

我可以使用@Stef 的解决方案,但我必须格式化公式表达式,然后 eval()

x,y = sympy.symbols('x y'); G = 1-y; V = G * x / 3; U = G**2 / (9*V+V)

然后@OscarBenjamin 建议使用 sympy 的 parse_expr,效果很好,直到我意识到公式并不总是按拓扑顺序给出。所以我发现,尝试按拓扑顺序排列然后解析它会花费太多执行时间。

最终,我决定制作自己的解析器,它看起来像这样(测试 类 和变量):

import re
import copy

class Formula():
    function_name = ""
    variables = []
    expression = ""
    __expr__ = ""
    other_func_calls = 0
    def __init__(self, function_name:str, variables:list, fun:str) -> None:
        self.function_name = function_name
        self.variables = variables
        self.expression = fun
        other_func = []
        for i in fun:
            if ord(i) in range(ord("A"), ord("Z") + 1):
                self.other_func_calls += 1
                other_func.append(i)
        self.__expr__ = re.sub('[A-Z]?\((\w|, )*\)','_TBR_', fun) # _TBR_ is being replaced later 
        self.other_func = other_func # list of other functions in chronological order
 
class Pyramid():
    name:str
    functions:dict[str:Formula]
 
    def __init__(self, name:str, funs:dict[str:Formula]) -> None:
        self.name = name
        self.functions = funs

    def get_result(self, fun:str, values:dict[str:int]):

        if (self.functions[fun].other_func_calls == 0): # Function does not call other functions 
            return eval(self.functions[fun].expression, values)

        other_funcs = copy.deepcopy(self.functions[fun].other_func)
        s = self.functions[fun].__expr__

        for i in range(len(other_funcs)):
            other_funcs[i] = self.get_result(other_funcs[i], values)
            s = re.sub("_TBR_", f"({str(other_funcs[i])})", s, count=1)
            
        return eval(s, values)
 

a = {
    "V": Formula("V", ["x", "y"], "G(y) * x / 3"),
    "U": Formula("U", ["x", "y"], "G(y)**2 / (9 * V(x, y)) + V(x, y)"),
    "G": Formula("G", ["y"], "1 - y")
}
 
p = Pyramid("Testing", a)
print(p.get_result("U", {"x":2,"y":3}))