Repository: sympy/sympy

Issue:
`lambdify(..., cse=True)` fails if the function argments are functions that need to be transformed into dummies
It seems that if I have an iterable of undefined functions and use that iterable as an argument for lambdify and use `cse=True` the automatic dummification of the functions does not get translated into the cse'd replacements. I have haven't made a simpler example yet, but wanted to log this now regardless.

Full example pulled from https://moorepants.github.io/learn-multibody-dynamics/holonomic-eom.html

```
q1, q2, q3 = me.dynamicsymbols('q1, q2, q3')
u1, u2, u3 = me.dynamicsymbols('u1, u2, u3')
la, lb, lc, ln = sm.symbols('l_a, l_b, l_c, l_n')
m, g = sm.symbols('m, g')
t = me.dynamicsymbols._t

p = sm.Matrix([la, lb, lc, ln, m, g])

q = sm.Matrix([q1])
qr = sm.Matrix([q2, q3])
qN = q.col_join(qr)

u = sm.Matrix([u1])
ur = sm.Matrix([u2, u3])
uN = u.col_join(ur)

...

In [16]: qN, u, p
Out[16]: 
⎛            ⎡lₐ ⎤⎞
⎜            ⎢   ⎥⎟
⎜            ⎢l_b⎥⎟
⎜⎡q₁⎤        ⎢   ⎥⎟
⎜⎢  ⎥        ⎢l_c⎥⎟
⎜⎢q₂⎥, [u₁], ⎢   ⎥⎟
⎜⎢  ⎥        ⎢lₙ ⎥⎟
⎜⎣q₃⎦        ⎢   ⎥⎟
⎜            ⎢ m ⎥⎟
⎜            ⎢   ⎥⎟
⎝            ⎣ g ⎦⎠

In [17]: me.find_dynamicsymbols(Md) | me.find_dynamicsymbols(gd)
Out[17]: {q₁, q₂, q₃, u₁}

In [18]: Md.shape, gd.shape
Out[18]: ((1, 1), (1, 1))

In [19]: eval_d = sm.lambdify((qN, u, p), (Md, gd), cse=True)

In [20]: eval_d??
```

```
Signature: eval_d(_Dummy_114, _Dummy_115, _Dummy_116)
Docstring:
Created with lambdify. Signature:

func(arg_0, arg_1, arg_2)

Expression:

(Matrix([[-2*l_a**2*m - 2*l_a*l_b*m*(1 + (-l_a*sin(q1(t)) -...

Source code:

def _lambdifygenerated(_Dummy_114, _Dummy_115, _Dummy_116):
    [_Dummy_112, _Dummy_111, _Dummy_110] = _Dummy_114
    [_Dummy_113] = _Dummy_115
    [l_a, l_b, l_c, l_n, m, g] = _Dummy_116
    x0 = l_a**2
    x1 = 2*m
    x2 = q1(t)
    x3 = q2(t)
    x4 = x2 + x3
    x5 = l_b*sin(x4)
    x6 = q3(t)
    x7 = x4 + x6
    x8 = sin(x7)
    x9 = l_c*x8
    x10 = x5 + x9
    x11 = x10**(-1.0)
    x12 = sin(x2)
    x13 = l_a*x12
    x14 = l_b**(-1.0)
    x15 = sin(x6)
    x16 = x15**(-1.0)
    x17 = sin(x3)
    x18 = l_b*x17
    x19 = x3 + x6
    x20 = l_c*sin(x19) + x18
    x21 = l_a*x14*x16*x20
    x22 = x21*x8
    x23 = x11*(-x10 - x13 - x22) + 1
    x24 = l_b**2
    x25 = m*x24
    x26 = cos(x3)
    x27 = l_b*x26
    x28 = l_a*x23
    x29 = cos(x2)
    x30 = l_a*x29
    x31 = u1(t)
    x32 = x31**2
    x33 = -x13*x31 - x22*x31 - x31*x5 - x31*x9
    x34 = x11*x33
    x35 = x31 + x34
    x36 = l_b*x35*cos(x4)
    x37 = l_c**(-1.0)
    x38 = x21*x31
    x39 = x37*x38
    x40 = (x35 + x39)*cos(x7)
    x41 = l_c*x40
    x42 = x11*(-l_a*x14*x16*x31*x8*(l_c*(x34 + x39)*cos(x19) + x27*x34) + x0*x20**2*x32*x37*x8*cos(x6)/(x15**3*x24) - x30*x32 - x31*x36 - x31*x41 - x38*x40) + x33*(-x36 - x41)/x10**2
    return (array([[-x0*x1 - x1*x27*x28 - x23**2*x25]]), array([[-g*l_b*m*x23*(-x12*x17 + x26*x29) - g*x1*x30 + l_a*(l_b*m*x17*x35**2 - m*x27*x42) - m*x18*x28*x32 - x23*x25*x42]]))
```

You can see that these lines:

```
    x2 = q1(t)
    x3 = q2(t)
    x6 = q3(t)
    x31 = u1(t)
```

should be:

```
    x2 = _Dummy_112
    x3 = _Dummy_111
    x6 = _Dummy_110
    x31 = _Dummy_113
```

It may be that `cse=True` does not work with `dummify=True`.

If I don't use the iterables, it also fails:

```
In [22]: eval_d = sm.lambdify((q1, q2, q3, u1, p), (Md, gd), cse=True)

In [23]: eval_d??

Signature: eval_d(_Dummy_120, _Dummy_119, _Dummy_118, _Dummy_117, _Dummy_121)
Docstring:
Created with lambdify. Signature:

func(q1, q2, q3, u1, arg_4)

Expression:

(Matrix([[-2*l_a**2*m - 2*l_a*l_b*m*(1 + (-l_a*sin(q1(t)) -...

Source code:

def _lambdifygenerated(_Dummy_120, _Dummy_119, _Dummy_118, _Dummy_117, _Dummy_121):
    [l_a, l_b, l_c, l_n, m, g] = _Dummy_121
    x0 = l_a**2
    x1 = 2*m
    x2 = q1(t)
    x3 = q2(t)
    x4 = x2 + x3
    x5 = l_b*sin(x4)
    x6 = q3(t)
```

Discussion:

Patch:
diff --git a/sympy/core/symbol.py b/sympy/core/symbol.py
index 2aca8bab27..f4a555c13f 100644
--- a/sympy/core/symbol.py
+++ b/sympy/core/symbol.py
@@ -791,7 +791,7 @@ def literal(s):
         return tuple(result)
     else:
         for name in names:
-            result.append(symbols(name, cls=cls, **args))
+            result.append(symbols(name, **args))
 
         return type(names)(result)
 
diff --git a/sympy/utilities/lambdify.py b/sympy/utilities/lambdify.py
index d7402d87b5..5a07ca86f2 100644
--- a/sympy/utilities/lambdify.py
+++ b/sympy/utilities/lambdify.py
@@ -1119,7 +1119,21 @@ def doprint(self, funcname, args, expr, *, cses=()):
         if not iterable(args):
             args = [args]
 
-        argstrs, expr = self._preprocess(args, expr)
+        if cses:
+            subvars, subexprs = zip(*cses)
+            try:
+                exprs = expr + list(subexprs)
+            except TypeError:
+                try:
+                    exprs = expr + tuple(subexprs)
+                except TypeError:
+                    expr = [expr]
+                    exprs = expr + list(subexprs)
+            argstrs, exprs = self._preprocess(args, exprs)
+            expr, subexprs = exprs[:len(expr)], exprs[len(expr):]
+            cses = zip(subvars, subexprs)
+        else:
+            argstrs, expr = self._preprocess(args, expr)
 
         # Generate argument unpacking and final argument list
         funcargs = []
@@ -1147,7 +1161,6 @@ def doprint(self, funcname, args, expr, *, cses=()):
 
         str_expr = _recursive_to_string(self._exprrepr, expr)
 
-
         if '\n' in str_expr:
             str_expr = '({})'.format(str_expr)
         funcbody.append('return {}'.format(str_expr))


Test patch:
diff --git a/sympy/core/tests/test_symbol.py b/sympy/core/tests/test_symbol.py
index 2b7bca85dc..19494c2083 100644
--- a/sympy/core/tests/test_symbol.py
+++ b/sympy/core/tests/test_symbol.py
@@ -1,4 +1,3 @@
-from sympy.core.function import Function, UndefinedFunction
 from sympy.core.numbers import (I, Rational, pi)
 from sympy.core.relational import (GreaterThan, LessThan, StrictGreaterThan, StrictLessThan)
 from sympy.core.symbol import (Dummy, Symbol, Wild, symbols)
@@ -295,7 +294,6 @@ def test_symbols():
     assert symbols('aa:d,x:z') == (aa, ab, ac, ad, x, y, z)
     assert symbols(('aa:d','x:z')) == ((aa, ab, ac, ad), (x, y, z))
 
-    assert type(symbols(('q:2', 'u:2'), cls=Function)[0][0]) == UndefinedFunction  # issue 23532
 
     # issue 6675
     def sym(s):
diff --git a/sympy/utilities/tests/test_lambdify.py b/sympy/utilities/tests/test_lambdify.py
index 780f86ebc8..c945b51fce 100644
--- a/sympy/utilities/tests/test_lambdify.py
+++ b/sympy/utilities/tests/test_lambdify.py
@@ -1576,3 +1576,12 @@ def assertAllClose(self, result, *, abstol=1e-15, reltol=1e-15):
 def test_deprecated_set():
     with warns_deprecated_sympy():
         lambdify({x, y}, x + y)
+
+def test_23536_lambdify_cse_dummy():
+
+    f = Function('x')(y)
+    g = Function('w')(y)
+    expr = z + (f**4 + g**5)*(f**3 + (g*f)**3)
+    expr = expr.expand()
+    eval_expr = lambdify(((f, g), z), expr, cse=True)
+    eval_expr((1.0, 2.0), 3.0)