0

This is a more precise version of this question where the comments said my minimal reproducible code was too minimal, so with more information:

I want to evaluate an Expression without having to pass the symbols along separately. My current workaround is this:

from sympy import Symbol, lambdify


def evaluate_expr(expr):
    lambdified = lambdify(tuple(expr.free_symbols), expr)
    return lambdified(*[i.value for i in expr.free_symbols])


class symbol_x(Symbol):
    def __new__(cls, symbol, value):
        obj = Symbol.__new__(cls, symbol)
        obj.value = value
        return obj

x = symbol_x('x', 2)
y = symbol_x('y', 3)

value = evaluate_expr(x / y)
print(value)

This works. My problem is that expr.free_symbols is a set (which doesn't maintain order), so casting it to a tuple might create unexpected bugs. What would be the correct way of evaluating this expression?

Nathan
  • 3,558
  • 1
  • 18
  • 38
  • 1
    The fact that you have attached a numerical value to each of your symbols is a crucial piece of information that was missing from your previous question. There isn't any benefit in using `lambdify` if you use it this way (only calling the function once). It would be better to just use `subs` or `evalf` with the `subs` argument. – Oscar Benjamin Aug 02 '22 at 10:20
  • @OscarBenjamin the problem is that the value of x and y might change later down the road... But thanks, I'll have a look at it. If you post it as an answer I'll accept it. – Nathan Aug 02 '22 at 10:45
  • Defining `args=tuple(expr.free_symbols)` and using `args` in both lines of `evaluate` will (doubly) ensure the order is the same. – hpaulj Aug 02 '22 at 14:12
  • If the value of `x` and `y` might change then I still don't think that the question as posed above really represents the problem you are trying to solve. I still don't understand what you are trying to do: in the example above you could just use `x=2` and `y=3` and not bother with the strange symbol that has a number attached (the point of symbols is that they are supposed to represent *unknown* values). – Oscar Benjamin Aug 02 '22 at 15:07

1 Answers1

1

You need to sort the free symbols according to some reproducible logic, like sorting by name:

def evaluate_expr(expr):
    fs = sorted(expr.free_symbols, key=lambda t: t.name)
    lambdified = lambdify(fs, expr)
    return lambdified(*[i.value for i in fs])

Edit for explanation:

The problem is that expr.free_symbols returns a set. lambdify requires a list. In Python, the conversion from a set to a list is non-deterministic. For example, say you have a set {a, b}. When you convert it to a list you can either have [a, b] or [b, a].

To fix this behavior, we can use sorted to sort the free symbols alphabetically.

Davide_sd
  • 10,578
  • 3
  • 18
  • 30
  • I'm sorry, but I don't understand how sorting the symbols to a logic I understand will effect `sympy`s functionality. Could you elaborate on how this prevents bugs from occurring? – Nathan Aug 02 '22 at 08:01
  • added explanation on the answer. – Davide_sd Aug 02 '22 at 08:46
  • I must be overlooking something. I understand making it sorted ensures it's deterministic, but how does it being deterministic prevent bugs compared to the non sorted list input? – Nathan Aug 02 '22 at 09:30
  • 1
    In your original code, you used `tuple(expr.free_symbols)` inside `lambdify` and then you executed your function with `lambdified(*[i.value for i in expr.free_symbols])`. The problem is that in the last command, `expr.free_symbols` might have a different order in comparison to `tuple(expr.free_symbols)` (remember, non-deterministic conversion). So, if your function has the form `lambdified(x, y)`, with the last command it is possible to execute it with `lambdified(y, x)`, which will create the wrong result. With sorted arguments, that problem goes away! No bugs. – Davide_sd Aug 02 '22 at 10:36
  • ahhh so I could do `args = tuple(expr.free_symbols)` and pass `args` both times. The order doesn't matter as long as it's the same. – Nathan Aug 03 '22 at 08:50