Search code examples
pythonsympysubstitutionevaluation

In sympy, evaluating an expression using .subs() when that expression has been defined in another function


If I define a symbolic expression inside a function, how do I evaluate that expression using .subs() outside that function. The following code demonstrates the issue:

import sympy as sp

def myFun():
    x = sp.symbols("x", positive = True)
    y = 3*x

    return y

expr = myFun()
print(expr.subs({x:10}))

Running the code above gives NameError: name 'x' is not defined.

How can I perform this evaluation? Two things I could think of is declaring x to be a global variable using global x inside myFun(). However, I want to avoid this method. Another way is to lambdify the expression in myFun() before returning the expression. But I would prefer not to use this method because in the real problem I am working with, I have multiple variables in my expression and would like to substitute only some of the variables at one stage, and the remaining variables at a later stage. Would you adopt one of the two approaches I mentioned or is it possible to evaluate the expression using .subs() using some other approach?


Solution

  • @cards answer is not going to work, the reason is that you have defined x to be positive, instead when calling print(expr.subs({'x':10})) the string 'x' will generate a generic symbol, without assumptions.

    You either create your symbols outside of the function, like this:

    import sympy as sp
    
    x = sp.symbols("x", positive = True)
    def myFun():
        y = 3*x
        return y
    
    expr = myFun()
    print(expr.subs({x:10}))
    # out: 30
    

    Or you can retrieve the symbols that make up a symbolic expression with the free_symbol attribute, like this:

    import sympy as sp
    
    def myFun():
        x = sp.symbols("x", positive = True)
        y = 3*x
        return y
    
    expr = myFun()
    x = expr.free_symbols.pop()
    print(expr.subs({x:10}))
    # out: 30
    

    EDIT (to accommodate comment):

    I was just wondering but what if the expression had three variables, say 5*y + 3*x + 7*z? I tried the code you provided. The line expr.free_symbols.pop() only gives one of the variables - it gives x. Is there a way to use free_symbols to get all three variables?

    free_symbols returns a set with all variables. If the expression is expr = 5*y + 3*x + 7*z, then expr.free_symbols returns {x, y, z}.

    pop() is a method of Python's set: it returns the first element of a set.

    To get all the variables of your expression you could try: x, y, z = expr.free_symbols, or x, y, z = list(expr.free_symbols). However, this creates the following problem: execute print(x, y, z) and you'll get something similar to: y z x. In other words, the symbols returned by expr.free_symbols are unordered. This is the default Python behavior.

    Can we get ordered symbols? Yes, we can sort them alphabetically with : x, y, z = sorted(expr.free_symbols, key=str). If we now execute print(x, y, z) we will get x y z.