Home > OS >  Python: Parsing math functions that are dependent on each other
Python: Parsing math functions that are dependent on each other

Time:02-22

I have a list of objects, each object is a mathematical function, and those functions may be dependent on each other, for example:

[
 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)")
]

Where first argument is function name, second one is list of used variables, and third one is string - the function's expression.

Is there a simple way to evaluate value of function U(x, y) at a given point, for example, at [2, 3] and recursively call G(3) and V(2, 3), and get the final result?

I have tried to do this in Sympy, but couldn't call for example function G(y) in function V(x,y)

CodePudding user response:

Maybe something like this?

>>> 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 is already topologically sorted so back substitute

>>> 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

CodePudding user response:

Thanks for all your suggestions. In example I have given, the Formulas are in topological order, but in practice it will not always be so.

I could use @Stef's solution, but I would have to format the formula expressions and then eval() it

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

Then @OscarBenjamin suggested to use sympy's parse_expr which worked just fine, until I realized that formulas will not always be given in topological order. So I found out, that trying to put it in topological order and then parse it, would take too much execution time.

Eventually, I decided to make my own parser, which looks something like this (test classes and variables):

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}))
  • Related