Closed L2uke closed 5 months ago
Hello @L2uke ,
I will run the code tomorrow, but looking at the error stack I believe it is caused by matplotlib not being able to render the latex on the legend of some of its entries.
You can try 'legend=False' on the graphics command, or you can manually set the 'label' for each of the line...
I definitely need to implement a fix to this behavior!
Dear David, thank you so much for the reply!
line(hyper((1, 2, 3), [3, 4], c), (c,0,1), label="Hyper")
worked! Have a nice weekend. Luke
Dear Davide,
maybe this is related. If I want to plot a piecewise defined hyper-function I also get an error. (Please note, if I call that with the sympy-plot then it works. Code provide after the error-message). This time, however, adding label="abs" shows no effect. Do you have an idea?
from sympy import *
from spb import *
from sympy.abc import x
g2= graphics(
line(Piecewise((hyper((1.0331469998003, 9.67916472867169), (10.0,), x), x<0.6),
(7,True)),(x,0,1)), label="abs")
Gives the following error:
/opt/conda/lib/python3.11/site-packages/spb/series.py:259: UserWarning: The evaluation with NumPy/SciPy failed.
NameError: name 'hyper' is not defined
Trying to evaluate the expression with Sympy, but it might be a slow operation.
---------------------------------------------------------------------------
NameError Traceback (most recent call last)
File /opt/conda/lib/python3.11/site-packages/spb/series.py:253, in _uniform_eval(f1, f2, modules, force_real_eval, has_sum, *args)
251 try:
252 # attempt to use numpy/numexpr native vectorized operation
--> 253 return f1(*args)
254 except ValueError:
255 # attempt to use numpy/numexpr with numpy.vectorize
File <lambdifygenerated-1>:2, in _lambdifygenerated(x)
1 def _lambdifygenerated(x):
----> 2 return select([less(x, 0.6),True], [hyper((1.0331469998003, 9.67916472867169), (10.0,), x),7], default=nan)
NameError: name 'hyper' is not defined
During handling of the above exception, another exception occurred:
TypeError Traceback (most recent call last)
Cell In[2], line 1
----> 1 plot(Piecewise(
2 (hyper((1.0331469998003, 9.67916472867169), (10.0,), x), x<0.6),
3 (7,True)),(x,0,1))
File /opt/conda/lib/python3.11/site-packages/spb/plot_functions/functions_2d.py:345, in plot(*args, **kwargs)
343 _set_labels(lines, global_labels, global_rendering_kw)
344 gs = _create_generic_data_series(**kwargs)
--> 345 return graphics(*lines, gs, **kwargs)
File /opt/conda/lib/python3.11/site-packages/spb/graphics/graphics.py:226, in graphics(aspect, axis_center, is_polar, legend, show, size, title, xlabel, ylabel, zlabel, xlim, ylim, zlim, *args, **kwargs)
224 is_3D = any(s.is_3D for s in series)
225 Backend = kwargs.pop("backend", TWO_D_B if is_3D else THREE_D_B)
--> 226 return _instantiate_backend(
227 Backend, *series,
228 aspect=aspect, axis_center=axis_center,
229 is_polar=is_polar, legend=legend, show=show, size=size,
230 title=title, xlim=xlim, ylim=ylim, zlim=zlim, **kwargs)
File /opt/conda/lib/python3.11/site-packages/spb/utils.py:465, in _instantiate_backend(Backend, *series, **kwargs)
462 _validate_kwargs(p, **kwargs)
464 if kwargs.get("show", True):
--> 465 p.show()
466 return p
File /opt/conda/lib/python3.11/site-packages/spb/backends/matplotlib/matplotlib.py:635, in MatplotlibBackend.show(self, **kwargs)
626 def show(self, **kwargs):
627 """Display the current plot.
628
629 Parameters
(...)
633 Keyword arguments to be passed to plt.show().
634 """
--> 635 self.draw()
636 if _show:
637 self._fig.tight_layout()
File /opt/conda/lib/python3.11/site-packages/spb/backends/matplotlib/matplotlib.py:622, in MatplotlibBackend.draw(self)
618 # create the figure from scratch every time, otherwise if the plot was
619 # previously shown, it would not be possible to show it again. This
620 # behaviour is specific to Matplotlib
621 self._create_figure()
--> 622 self._process_renderers()
File /opt/conda/lib/python3.11/site-packages/spb/backends/matplotlib/matplotlib.py:340, in MatplotlibBackend._process_renderers(self)
338 for r, s in zip(self.renderers, self.series):
339 self._check_supported_series(r, s)
--> 340 r.draw()
341 xlims.extend(r._xlims)
342 ylims.extend(r._ylims)
File /opt/conda/lib/python3.11/site-packages/spb/backends/matplotlib/renderers/line2d.py:113, in Line2DRenderer.draw(self)
112 def draw(self):
--> 113 data = self.series.get_data()
115 # NOTE: matplotlib's is able to deal with axis limits when
116 # LineCollection is present, so no need to compute them here.
117 if not self.series.is_2Dline:
File /opt/conda/lib/python3.11/site-packages/spb/series.py:1161, in Line2DBaseSeries.get_data(self)
1140 """Return coordinates for plotting the line.
1141
1142 Returns
(...)
1158 corresponding interactive series).
1159 """
1160 np = import_module('numpy')
-> 1161 points = self._get_data_helper()
1163 if (
1164 isinstance(self, LineOver1DRangeSeries) and
1165 (self.detect_poles == "symbolic")
1166 ):
1167 poles = _detect_poles_symbolic_helper(
1168 self.expr.subs(self.params), *self.ranges[0])
File /opt/conda/lib/python3.11/site-packages/spb/series.py:1520, in LineOver1DRangeSeries._get_data_helper(self)
1516 """Returns coordinates that needs to be postprocessed.
1517 """
1518 np = import_module('numpy')
-> 1520 x, _re, _im = self._get_real_imag()
1522 if self._return is None:
1523 # The evaluation could produce complex numbers. Set real elements
1524 # to NaN where there are non-zero imaginary elements
1525 _re[np.invert(np.isclose(_im, np.zeros_like(_im)))] = np.nan
File /opt/conda/lib/python3.11/site-packages/spb/series.py:1513, in LineOver1DRangeSeries._get_real_imag(self)
1511 if self.adaptive:
1512 return self._adaptive_sampling()
-> 1513 return self._uniform_sampling()
File /opt/conda/lib/python3.11/site-packages/spb/series.py:1500, in LineOver1DRangeSeries._uniform_sampling(self)
1497 def _uniform_sampling(self):
1498 np = import_module('numpy')
-> 1500 x, result = self._evaluate()
1501 _re, _im = np.real(result), np.imag(result)
1502 _re = self._correct_shape(_re, x)
File /opt/conda/lib/python3.11/site-packages/spb/series.py:591, in BaseSeries._evaluate(self, cast_to_real)
589 results = []
590 for f in self._functions:
--> 591 r = _uniform_eval(*f, *args, modules=self.modules)
592 # the evaluation might produce an int/float. Need this correction.
593 r = self._correct_shape(np.array(r), discr[0])
File /opt/conda/lib/python3.11/site-packages/spb/series.py:260, in _uniform_eval(f1, f2, modules, force_real_eval, has_sum, *args)
257 except Exception as err:
258 # fall back to sympy
259 _msg(err)
--> 260 return _eval_with_sympy()
262 try:
263 # any other module attempts to use numpy.vectorize
264 return wrapper_func(f1, *args)
File /opt/conda/lib/python3.11/site-packages/spb/series.py:235, in _uniform_eval.<locals>._eval_with_sympy(err)
230 if f2 is None:
231 raise RuntimeError(
232 "Impossible to evaluate the provided numerical function "
233 "because there is no fall-back numerical function to "
234 "be evaluated with SymPy.")
--> 235 return wrapper_func(f2, *args)
File /opt/conda/lib/python3.11/site-packages/numpy/lib/function_base.py:2329, in vectorize.__call__(self, *args, **kwargs)
2326 vargs = [args[_i] for _i in inds]
2327 vargs.extend([kwargs[_n] for _n in names])
-> 2329 return self._vectorize_call(func=func, args=vargs)
File /opt/conda/lib/python3.11/site-packages/numpy/lib/function_base.py:2412, in vectorize._vectorize_call(self, func, args)
2409 # Convert args to object arrays first
2410 inputs = [asanyarray(a, dtype=object) for a in args]
-> 2412 outputs = ufunc(*inputs)
2414 if ufunc.nout == 1:
2415 res = asanyarray(outputs, dtype=otypes[0])
File /opt/conda/lib/python3.11/site-packages/spb/series.py:220, in _uniform_eval.<locals>.wrapper_func(func, *args)
218 def wrapper_func(func, *args):
219 try:
--> 220 return complex(func(*args))
221 except (ZeroDivisionError, OverflowError):
222 return complex(np.nan, np.nan)
File <lambdifygenerated-2>:2, in _lambdifygenerated(Dummy_36)
1 def _lambdifygenerated(Dummy_36):
----> 2 return ((hyper((1.0331469998003, 9.67916472867169), (10.0,), Dummy_36)) if (Dummy_36 < 0.6) else (7))
TypeError: '<' not supported between instances of 'complex' and 'float'
The same happened when I switch to
plot(Piecewise(
(hyper((1.0331469998003, 9.67916472867169), (10.0,), x), x<0.6),
(7,True)),(x,0,1))
Here is the code for the simply-plot which works
from sympy import *
from sympy.plotting import plot
from sympy.abc import x
plot(Piecewise(
(hyper((1.0331469998003, 9.67916472867169), (10.0,), x), x<0.6),
(7,True)),(x,0,1))
By default, spb
is going to evaluate the lambdified expression with complex numbers. In this case, the lambdified expression contains comparisons with <
: for example, the left hand side evaluates to a complex number, while the right hand side is a float. This comparison doesn't make mathematical sense, hence it raises the error.
In these cases we have to force the evaluation to use real numbers with force_real_eval=True
. Here is how:
from sympy import *
from spb import *
from sympy.abc import x
g2= graphics(
line(Piecewise((hyper((1.0331469998003, 9.67916472867169), (10.0,), x), x<0.6),
(7,True)),(x,0,1), force_real_eval=True, detect_poles=True), label="abs")
I'll save this example into my todo list. I think it's possible to make the module detect these cases and automatically switch to real evaluation... Thanks for pointing this out :D
New version is out, fixing both problems highlighted in this issue.
from sympy import *
from spb import *
c = Symbol('c')
graphics(
line(hyper((1, 2, 3), [3, 4], c), (c,0,1)),
line(c**4, (c,0,1)))
This will show a warning on the screen:
/home/davide/Documents/Development/sympy_plot_backends/spb/series.py:648: UserWarning: NumPy is unable to evaluate with complex numbers some of the functions included in this symbolic expression: [hyper]. Hence, the evaluation will use real numbers. If you believe the resulting plot is incorrect, change the evaluation module by setting the `modules` keyword argument.
/home/davide/Documents/Development/sympy_plot_backends/spb/series.py:259: UserWarning: The evaluation with NumPy/SciPy failed.
NameError: name 'hyper' is not defined
Trying to evaluate the expression with Sympy, but it might be a slow operation.
/home/davide/Documents/Development/sympy_plot_backends/spb/backends/matplotlib/matplotlib.py:647: UserWarning: The picture could not be shown. The following error was raised:
ValueError:
{{}_{3}F_{2}\left(\begin{matrix} 1, 2, 3 \\ 3, 4 \end{matrix}\middle| {c} \right)}
^
ParseSyntaxException: Expected '\\right', found '\' (at char 18), (line:1, col:19)
This is probably caused by Matplotlib's inability to render a legend entry. Hence, the legend has been turned off in order to visualize the plot. If you need a legend you have to manually provide labels for each symbolic expression.
Essentially, it says the legend can't be shown, and suggests to provide appropriate labels for each symbolic expression. Nonetheless, the plot will be visible:
The second example automatically detects the hyper function and switches to an evaluation strategy which uses real numbers.
from sympy import *
from spb import *
from sympy.abc import x
g2= graphics(
line(Piecewise((hyper((1.0331469998003, 9.67916472867169), (10.0,), x), x<0.6),
(7,True)),(x,0,1)), label="abs")
This is the warning to inform users about the evaluation strategy.
/home/davide/Documents/Development/sympy_plot_backends/spb/series.py:648: UserWarning: NumPy is unable to evaluate with complex numbers some of the functions included in this symbolic expression: [hyper]. Hence, the evaluation will use real numbers. If you believe the resulting plot is incorrect, change the evaluation module by setting the `modules` keyword argument.
And this is the plot:
I'm closing this issue.
Hello, I have tried to improve my figures by using spb and it doing an amazing job! Only recently the following problem occurred. While plotting multiple standard functions like cos(x) and sin(x) worked well, I'm running into a value error issue when plotting multiple hypergeometric functions or when combining them with other functions, e.g.
I get a couple of value errors (see below). Any help how to overcome this issue is very much appreciated.