Repository: sympy/sympy
Pull request: Text backend plotting
Fixes #26481.  For some reason, the bounds of the backend renderer became complex at some point.  I just check if they are complex and then convert them to real if the complex part is equal to 0.

This solution is kinda a hack and it might be good to investigate why the endpoints are becoming complex at some point, but this fixes the problem I was having.

Also, might still be a good idea to add in some message telling user that installing matplotlib would probably be a good idea. 

<!-- 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. -->


#### Brief description of what is fixed or changed


#### Other comments


#### 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 -->
* plotting
  * Fixed an issue which caused plotting using backend='text' to error.
<!-- END RELEASE NOTES -->


Problem statement:
Plotting with Sympy's inbuilt TextBackend doesn't seem to be working
```from sympy import symbols
from sympy.plotting import plot
x = symbols('x')
plot(x, (x, 0, 5))

Traceback (most recent call last):
  File "c:\Users\sammy\gsoc\sympy\test.py", line 4, in <module>
    plot(x, (x, 0, 5))
  File "c:\Users\sammy\gsoc\sympy\sympy\plotting\plot.py", line 408, in plot
    plots.show()
  File "c:\Users\sammy\gsoc\sympy\sympy\plotting\backends\textbackend\text.py", line 21, in show
    textplot(ser.expr, ser.start, ser.end)
  File "c:\Users\sammy\gsoc\sympy\sympy\plotting\textplot.py", line 161, in textplot
    for line in textplot_str(expr, a, b, W, H):
  File "c:\Users\sammy\gsoc\sympy\sympy\plotting\textplot.py", line 53, in textplot_str
    a = float(a)
        ^^^^^^^^
TypeError: float() argument must be a string or a real number, not 'complex'
```

Note: I think you will only be able to reproduce this if you don't have matplotlib installed, or else it will just use matplotlib.

Hints from issue discussion:
You can force the backend to be text with `plot(..., backend='text')`.
@SamLubelsky did you come across this as a result of running the tests/doctests in some way?

I recently came across this as well with `pytest --doctest-only sympy/plotting` when matplotlib was not installed. I have fixed that in https://github.com/sympy/sympy/pull/26451 to at least skip the test when matplotlib is not installed but if this gets fixed then the test should not be skipped.
I came across it while I was trying to fix some of the issues shown in #19996  that should be fixed due to changing from exprimental_lambdify to lambdify when I didn't have matplotlib installed in my environment.

@moorepants This doesn't work because the problem seems to be with the text backend, so forcing it to use the text backend doesn't change anything.  Using backend='matplotlib' on the other hand does work as long as matplotlib is installed, cause the matplotlib plotting seems to be working.

If this issue can't be fixed(I have no idea how it would be), I feel like users who don't have matplotlib installed should be given some better error telling them something like:

`Since matplotlib is not installed, Sympy's graphing backend will be used.  Installing matplotlib is recommended.`

When I was trying to debug why it wasn't graphing properly it took me a while to figure out it was because matplotlib wasn't installed because Sympy currently gives no indication of that.


> This doesn't work because the problem seems to be with the text backend, so forcing it to use the text backend doesn't change anything. 

I wasn't trying to imply that it fixed anything. I just mentioned it so you can reproduce the error without uninstalling matplotlib.
Oh sorry, I see what you mean.

Commit message: added regression tests

Patch to review:
diff --git a/sympy/plotting/tests/test_plot.py b/sympy/plotting/tests/test_plot.py
index dc6950de2224..b62ec113bfeb 100644
--- a/sympy/plotting/tests/test_plot.py
+++ b/sympy/plotting/tests/test_plot.py
@@ -60,6 +60,13 @@ def save(self):
     def close(self):
         pass
 
+def test_basic_plotting_backend():
+    x = Symbol('x')
+    try:
+        plot(x, (x, 0, 3), backend='text')
+        plot(x**2 + 1, (x, 0, 3), backend='text')
+    except TypeError as exc:
+        assert False, f"{exc}"
 
 @pytest.mark.parametrize("adaptive", [True, False])
 def test_plot_and_save_1(adaptive):
diff --git a/sympy/plotting/textplot.py b/sympy/plotting/textplot.py
index 2ba83ad0fbfb..5f1f2b639d6c 100644
--- a/sympy/plotting/textplot.py
+++ b/sympy/plotting/textplot.py
@@ -50,6 +50,12 @@ def textplot_str(expr, a, b, W=55, H=21):
             .format(free))
     x = free.pop() if free else Dummy()
     f = lambdify([x], expr)
+    if isinstance(a, complex):
+        if a.imag == 0:
+            a = a.real
+    if isinstance(b, complex):
+        if b.imag == 0:
+            b = b.real
     a = float(a)
     b = float(b)
 


Code review comments:
diff_hunk: @@ -60,6 +60,13 @@ def save(self):
     def close(self):
         pass
 
+def test_basic_plotting_backend():
+    x = Symbol('x')
+    try:
+        plot(x, (x, 0, 3), backend='text')
+        plot(x**2 + 1, (x, 0, 3), backend='text')
+    except TypeError as exc:
+        assert False, f"{exc}"
path: sympy/plotting/tests/test_plot.py
text: @user1:
I don't think that you should catch errors here.
It is often pointless for test code to catch errors because the pytest or the test framework does actually handles the thrown errors. If you catch the errors, it can just obscure the error messages or stack trace.

```suggestion
    plot(x, (x, 0, 3), backend='text')
    plot(x**2 + 1, (x, 0, 3), backend='text')
```