Eldritch Cheese
Eldritch Cheese

Reputation: 1237

Substitute Function call with sympy

I want to receive input from a user, parse it, then perform some substitutions on the resulting expression. I know that I can use sympy.parsing.sympy_parser.parse_expr to parse arbitrary input from the user. However, I am having trouble substituting in function definitions. Is it possible to make subsitutions in this manner, and if so, how would I do so?

The overall goal is to allow a user to provide a function of x, which is then used to fit data. parse_expr gets me 95% of the way there, but I would like to provide some convenient expansions, such as shown below.

import sympy
from sympy.parsing.sympy_parser import parse_expr

x,height,mean,sigma = sympy.symbols('x height mean sigma')
gaus = height*sympy.exp(-((x-mean)/sigma)**2 / 2)

expr = parse_expr('gaus(100, 5, 0.2) + 5')

print expr.subs('gaus',gaus)                                  # prints 'gaus(100, 5, 0.2) + 5'
print expr.subs(sympy.Symbol('gaus'),gaus)                    # prints 'gaus(100, 5, 0.2) + 5'
print expr.subs(sympy.Symbol('gaus')(height,mean,sigma),gaus) # prints 'gaus(100, 5, 0.2) + 5'

# Desired output: '100 * exp(-((x-5)/0.2)**2 / 2) + 5'

This is done using python 2.7.9, sympy 0.7.5.

Upvotes: 3

Views: 3003

Answers (2)

asmeurer
asmeurer

Reputation: 91480

You can use the replace method. For instance

gaus = Function("gaus") # gaus is parsed as a Function
expr.replace(gaus, Lambda((height, mean, sigma), height*sympy.exp(-((x-mean)/sigma)**2 / 2)))

replace also has other options, such as pattern matching.

Upvotes: 4

Eldritch Cheese
Eldritch Cheese

Reputation: 1237

After some experimentation, while I did not find a built-in solution, it was not difficult to build one that satisfies simple cases. I am not a sympy expert, and so there may be edge cases that I haven't considered.

import sympy
from sympy.core.function import AppliedUndef

def func_sub_single(expr, func_def, func_body):
    """
    Given an expression and a function definition,
    find/expand an instance of that function.

    Ex:
        linear, m, x, b = sympy.symbols('linear m x b')
        func_sub_single(linear(2, 1), linear(m, b), m*x+b) # returns 2*x+1
    """
    # Find the expression to be replaced, return if not there
    for unknown_func in expr.atoms(AppliedUndef):
        if unknown_func.func == func_def.func:
            replacing_func = unknown_func
            break
    else:
        return expr

    # Map of argument name to argument passed in
    arg_sub = {from_arg:to_arg for from_arg,to_arg in
               zip(func_def.args, replacing_func.args)}

    # The function body, now with the arguments included
    func_body_subst = func_body.subs(arg_sub)

    # Finally, replace the function call in the original expression.
    return expr.subs(replacing_func, func_body_subst)


def func_sub(expr, func_def, func_body):
    """
    Given an expression and a function definition,
    find/expand all instances of that function.

    Ex:
        linear, m, x, b = sympy.symbols('linear m x b')
        func_sub(linear(linear(2,1), linear(3,4)),
                 linear(m, b), m*x+b)               # returns x*(2*x+1) + 3*x + 4
    """
    if any(func_def.func==body_func.func for body_func in func_body.atoms(AppliedUndef)):
        raise ValueError('Function may not be recursively defined')

    while True:
        prev = expr
        expr = func_sub_single(expr, func_def, func_body)
        if prev == expr:
            return expr

Upvotes: 1

Related Questions