Repository: sympy/sympy
Pull request: Added Lambdify with latex symbols test in test_codeprinter.py
<!-- Your title above should be a short description of what
was changed. Do not include the issue number in the title. -->

#### References to other Issues or PRs
<!-- If this pull request fixes an issue, write "Fixes #NNNN" in that exact
format, e.g. "Fixes #1234" (see
https://tinyurl.com/auto-closing for more information). Also, please
write a comment on that issue linking back to this pull request once it is
open. -->

Fixes #23374
#### Brief description of what is fixed or changed

This PR adds the `test_lambdify_LaTeX_symbols_issue_23374 `test in `test_codeprinter.py `to verify the handling of LaTeX-style symbols such as x_{1} being correctly converted to x_1 during the lambdify process. @oscargus and @oscarbenjamin.

- Remove curly braces from subscripted variables. x_{1} -> x_1 
- Added test for _print_Symbol in test_codeprinter.py

#### Release Notes

<!-- Write the release notes for this release below between the BEGIN and END
statements. The basic format is a bulleted list with the name of the subpackage
and the release note for this PR. For example:

* solvers
  * Added a new solver for logarithmic equations.

* functions
  * Fixed a bug with log of integers. Formerly, `log(-x)` incorrectly gave `-log(x)`.

* physics.units
  * Corrected a semantical error in the conversion between volt and statvolt which
    reported the volt as being larger than the statvolt.

or if no release note(s) should be included use:

NO ENTRY

See https://github.com/sympy/sympy/wiki/Writing-Release-Notes for more
information on how to write release notes. The bot will check your release
notes automatically to see if they are formatted correctly. -->

<!-- BEGIN RELEASE NOTES -->

* printing

    *  Fixed handling of LaTeX-style symbols in test_lambdify_LaTeX_symbols_issue_23374  by accounting for possible  substitution with  Dummy symbols during lambdify.


<!-- END RELEASE NOTES -->


Problem statement:
Lambdify with latex symbols
I have a complicated expression that needs to be evaluated. If the evaluation fails with numpy, than it switches to sympy. My symbols contains latex syntax.

This is the simplest reproducible example I could come up with:

```python
from sympy import *
import inspect
x1, x2 = symbols("x_{1} x_2")

f1 = lambdify([x1, x2], cos(x1**2 + x2**2))
print(inspect.getsource(f1))

# def _lambdifygenerated(x_1, x_2):
#     return cos(x_2**2 + x_1**2) 
```
So far so good. Note that `x1` was processed to `x_1`. Now, let's switch to SymPy:

```python
f2 = lambdify([x1, x2], cos(x1**2 + x2**2), modules="sympy")
print(inspect.getsource(f2))
```

```text
Traceback (most recent call last):

  File "/home/davide/Documents/Development/envs/sympy_plot/lib/python3.9/site-packages/IPython/core/interactiveshell.py", line 3457, in run_code
    exec(code_obj, self.user_global_ns, self.user_ns)

  File "/tmp/ipykernel_82217/2465285261.py", line 8, in <module>
    f2 = lambdify([x1, x2], cos(x1**2 + x2**2), modules="sympy")

  File "/home/davide/Documents/Development/envs/sympy_plot/lib/python3.9/site-packages/sympy/utilities/lambdify.py", line 900, in lambdify
    c = compile(funcstr, filename, 'exec')

  File "<lambdifygenerated-12>", line 2
    return cos(x_2**2 + x_{1}**2)
                          ^
SyntaxError: invalid syntax
```

Note that no processing is applied to `x1`, so curl brackets end up in the expression to be executed.

Hints from issue discussion:
This should probably be fixed in the SymPy-printer (and all printers...) similar to https://github.com/sympy/sympy/pull/20806
Probably the best place is to change here:
https://github.com/sympy/sympy/blob/68c37df362e8585d72fc7ef490013bb8eff16e3e/sympy/printing/codeprinter.py#L397-L408

In that way, all code printers would benefit from it (under the assumption that {} cannot be part of a variable name in any supported language).
This also means that at least the `_print_Symbol` method added in  https://github.com/sympy/sympy/pull/20806 can be removed (actually that method is exactly what the above should be replaced with). Possibly this holds for other printers as well.
is this issue still open?

Sort of. However, there is a PR solving it that is not yet merged. So if
you want to work on something there are probably better issues.

> actually that method is exactly what the above should be replaced with

replacing the function actually resolves error. 
So is this a valid solution for this issue or are there any other conditions remaining to be checked upon ? 
Related to this issue https://github.com/sympy/sympy/issues/12463. Ideally this should be handled automatically by the dummify flag. 
After having a look at all related PRs and issues (i.e. pertaining to dummify and lamdify), I have came to the conclusion that we should let `dummify=False` by default, but if the variable name is invalid (`.isidentifier()==False`) then `dummfiy=True` should be overridden.
Am I correct till here ?

Another altogether different thought of mine is that `dummify` option should not be there because we use dummify only if we _have to_. If there is no need for dummification then why would any user want to dummify its variables.
So should it be removed ?


There might be situations where dummification is needed but we don't correctly identify it. So I would keep the option. 
I think maybe I found the problem, while creating the lambda function there are two things `args` and `expr`.
So in case of `args` , they are getting passed through the `_print_Symbol` of `PythonCodePrinter` in pycode.py
https://github.com/sympy/sympy/blob/0c4cc831ab88b6ba39685540f74d9551544d06e5/sympy/printing/pycode.py#L564-L577
so the `x_{1}` changes to `x_1` and therefore the `_is_safe_ident` is passed in
https://github.com/sympy/sympy/blob/0c4cc831ab88b6ba39685540f74d9551544d06e5/sympy/utilities/lambdify.py#L1189-L1190
and the control doesn't enter `if` block.
However the `expr` (when `modules=sympy`) doesn't gets passed through `_print_Symbol` of `PythonCodePrinter` in pycode.py and as a result it remains `x_{1}` which then raises error on compiling.

On removing the code that removes "{}" from variables, the `_is_safe_ident` check is failed and as a result that argument is dummified.
```diff
 def _print_Symbol(self, expr): 
  
     name = super()._print_Symbol(expr) 
  
     if name in self.reserved_words: 
         if self._settings['error_on_reserved']: 
             msg = ('This expression includes the symbol "{}" which is a ' 
                    'reserved keyword in this language.') 
             raise ValueError(msg.format(name)) 
         return name + self._settings['reserved_word_suffix'] 
-     elif '{' in name:   # Remove curly braces from subscripted variables 
-         return name.replace('{', '').replace('}', '') 
     else: 
         return name 
```

So after the changes, this will be the output of above example
```python
>>> from sympy import *
>>> import inspect
>>> x1, x2 = symbols("x_1 x_{2}")
>>> f1 = lambdify([x1, x2], cos(x1**2 + x2**2))
>>> print(inspect.getsource(f1))
def _lambdifygenerated(x_1, Dummy_22):
    return cos(Dummy_22**2 + x_1**2)

>>> f2 = lambdify([x1, x2], cos(x1**2 + x2**2), modules="sympy")
>>> print(inspect.getsource(f2))
def _lambdifygenerated(x_1, Dummy_23):
    return cos(Dummy_23**2 + x_1**2)
```
which is the desired the output ? Any thoughts ?

Commit message: Added test for _print_Symbol in test_codeprinter.py

Patch to review:
diff --git a/sympy/printing/codeprinter.py b/sympy/printing/codeprinter.py
index 765f7f01f4c7..15a054609274 100644
--- a/sympy/printing/codeprinter.py
+++ b/sympy/printing/codeprinter.py
@@ -418,7 +418,6 @@ def _print_Variable(self, expr):
         return self._print(expr.symbol)
 
     def _print_Symbol(self, expr):
-
         name = super()._print_Symbol(expr)
 
         if name in self.reserved_words:
@@ -428,7 +427,8 @@ def _print_Symbol(self, expr):
                 raise ValueError(msg.format(name))
             return name + self._settings['reserved_word_suffix']
         else:
-            return name
+            # Remove curly braces from subscripted variables. x_{1} -> x_1
+            return name.replace('{', '').replace('}', '')
 
     def _can_print(self, name):
         """ Check if function ``name`` is either a known function or has its own
diff --git a/sympy/printing/tests/test_codeprinter.py b/sympy/printing/tests/test_codeprinter.py
index 2d89a27dab37..bb4efd95a2f4 100644
--- a/sympy/printing/tests/test_codeprinter.py
+++ b/sympy/printing/tests/test_codeprinter.py
@@ -1,3 +1,4 @@
+from sympy import lambdify
 from sympy.printing.codeprinter import CodePrinter, PrintMethodNotImplementedError
 from sympy.core import symbols
 from sympy.core.symbol import Dummy
@@ -36,6 +37,28 @@ def test_print_Symbol():
     p.reserved_words.update(['if'])
     assert p._print(y) == 'if_He_Man'
 
+
+def test_lambdify_LaTeX_symbols_issue_23374():
+    # Create symbols with Latex style names
+    x1, x2 = symbols("x_{1} x_2")
+
+    # Set up the printer
+    p = setup_test_printer()
+
+    # Print symbols with Latex style names to check if they are converted properly
+    assert p._print(x1) == 'x_1'
+    assert p._print(x2) == 'x_2'
+
+    # Lambdify the function
+    from sympy import cos
+    f1 = lambdify([x1, x2], cos(x1 ** 2 + x2 ** 2))
+
+    # Check if the generated Python function is correct (no curly braces in variable names)
+    import inspect
+    generated_code = inspect.getsource(f1)
+    assert 'x_1' in generated_code or 'Dummy' in generated_code
+
+
 def test_issue_15791():
     class CrashingCodePrinter(CodePrinter):
         def emptyPrinter(self, obj):


Code review comments:
diff_hunk: @@ -428,7 +427,8 @@ def _print_Symbol(self, expr):
                 raise ValueError(msg.format(name))
             return name + self._settings['reserved_word_suffix']
         else:
-            return name
+            # Remove curly braces from subscripted variables. x_{1} -> x_1
+            return name.replace('{', '').replace('}', '')
path: sympy/printing/codeprinter.py
text: @user1:
Why do you even need this if the code already works fine in the master for dummyfying.
```
x1, x2 = symbols("x_{1} x_2")
f1 = lambdify([x1, x2], cos(x1 ** 2 + x2 ** 2))
f1(1, 2)
```

I also don't think that printer should have the string replace logic unless necessary.

@author:
Thank you for your feedback @user1 I will restore to the previous code and update the test.