0

I'm trying to do some math with sympy, but I don't seem to figure out how to support division:

from sympy import Symbol, lambdify


a = Symbol('a')
b = Symbol('b')
c = a * b
lambdified = lambdify(c.args, c)
value = lambdified(a=2, b=3)
print(value)


x = Symbol('x')
y = Symbol('y')
z = x / y
lambdified_2 = lambdify(z.args, z)
value_2 = lambdified_2(x=2, y=3)
print(value_2)

This returns:

    def _lambdifygenerated(x, 1/y):
                          ^
SyntaxError: invalid syntax

I tried to fix this by passing free_symbols instead, this works but raises the error:

x = Symbol('x')
y = Symbol('y')
z = x / y
lambdified_2 = lambdify(z.free_symbols, z)
value_2 = lambdified_2(x=2, y=3)
print(value_2)

returning

SymPyDeprecationWarning: 

Passing the function arguments to lambdify() as a set is deprecated. This
leads to unpredictable results since sets are unordered. Instead, use a list
or tuple for the function arguments.

Now, I could fix this by casting z.free_symbols to a tuple, but I'm sure there's a better way of doing this. How should I pass the arguments?


To be clear, I don't want to pass [x,y] in the args because I want to only have to pass the equation later on.

Nathan
  • 3,558
  • 1
  • 18
  • 38
  • Another option might be to parse the arguments recursively, but this seems like a sloppy solution as well. – Nathan Jul 31 '22 at 12:54
  • I don't understand why you don't want to pass `[x, y]` as the args. That's precisely what you should do in this situation. How else is lambdify supposed to know how many arguments the function should have and what order they should be in? – Oscar Benjamin Jul 31 '22 at 13:24
  • @OscarBenjamin I'd like to be able to pass just the equation, it seems weird to pass the equation and the arguments when the equation already includes the arguments. Just trying to reduce ways users can supply incorrect input. – Nathan Jul 31 '22 at 13:33
  • `c` is not an "equation". It's a `sympy` `Expr`. That `x/y` is actually `Mul(x,Pow(y,-1))`. What are the `args` of that nested expression? You won't get (simple) answers if you don't first understand the problem. – hpaulj Jul 31 '22 at 16:35
  • @hpaulj I'm sorry but I don't understand what you want me to change about my question? I think the `args` of that nested expression are `x, 1/y` as pointed out by the exception? Or is your point that my question is unclear because I call a `sympy Expr` an "equation" and this is confusing? – Nathan Jul 31 '22 at 17:08
  • @hpaulj as far as I understand, the problem is that `lambdify` requires a list of the input `Symbols`, and my question is how to generate this list automatically based on the `sympy Expr`. Right now I'm doing this based on casting `free_symbols` to a list, but I suspect this might cause unexpected bugs, so I wonder what the correct approach might be. – Nathan Jul 31 '22 at 17:10
  • 1
    The ordering of a `set` is not properly deterministic (that is why `lambdify` warns about it). Converting it to a `tuple` just means that you have a non-deterministically ordered `tuple`. You should probably at least sort the tuple somehow (SymPy has a function `ordered` for this). I still don't understand how you are going to be able to make use of the lambdified function though if you don't even know what symbols it contains and in what order you want the arguments to be. – Oscar Benjamin Aug 01 '22 at 11:33
  • @OscarBenjamin thanks, for the comment, I've done some testing and so far it seems to work as long as all symbols are unique. – Nathan Aug 01 '22 at 15:46
  • I think you might get a better answer to this question if you take a step back and explain what the problem is with just passing `[x, y]` and ask whether there is a better way to achieve what you are actually trying to do. – Oscar Benjamin Aug 01 '22 at 21:49
  • @OscarBenjamin I personally don't expect so, but I posted it here https://stackoverflow.com/questions/73203605/how-to-pass-args-to-sympy-lambdify – Nathan Aug 02 '22 at 07:32

1 Answers1

0

In the example, c and z aren't really functions. They are symbolic expressions. In a more elaborate example, c = a1 * a2 + a3, you'll note that c.args would be (a3, a1*a2), and c.func would be sympy.core.add.Add.

You can use c.free_symbols to extract the symbols on which c depends. To use this in lambdify, you can convert that set to a list.

from sympy import Symbol, lambdify

a1 = Symbol('a1')
a2 = Symbol('a2')
a3 = Symbol('a3')
c = a1 * a2 + a3
lambdified = lambdify(list(c.free_symbols), c)
value = lambdified(a1=2, a2=3, a3=17)
print(value)

x = Symbol('x')
y = Symbol('y')
z = x / y
lambdified_2 = lambdify(list(z.free_symbols), z)
value_2 = lambdified_2(x=2, y=3)
print(value_2)

Note that typing lambdified ? in an interactive environment will show the (numpy) source code of the generated function. That helps in clarifying problems. In this case it would show:

def _lambdifygenerated(a3, a2, a1):
    return a1*a2 + a3
JohanC
  • 71,591
  • 8
  • 33
  • 66
  • I'm sorry, but this doesn't really answer my question. I already explain I can cast my `z.free_symbols` to a list, but I worry this might cause bugs. My question is if this is the correct method of doing this. – Nathan Aug 01 '22 at 16:26
  • It's impossible to say what is "correct" given the information provided in the question. This solution will give you a callable lambdified function but the ordering of the arguments `a3, a2, a1` will be random from one Python process to another. If that doesn't bother you then converting `free_symbols` with `list` is fine. – Oscar Benjamin Aug 01 '22 at 21:47
  • To get a more consistent order, you could try `sorted(list(c.free_symbols), key=default_sort_key)` (from [Force SymPy to keep the order of terms](https://stackoverflow.com/a/36315097/12046409)) – JohanC Aug 01 '22 at 22:12