gboffi
gboffi

Reputation: 25083

clean up plot of tan(x)

I want to visualize the roots of tan(xi) = tanh(xi), xi>0 and my plot

plot(tan(pi*xi), tanh(pi*xi), (xi, 0, 4), ylim=(-1, 2))

comes out like this

sympy plot

where one sees the actual roots, xi_i \approx pi*(n+1/4), n=1, ... but also fake roots at pi*(n+1/2), the reason why being sympy plotting algorithm that draws a vertical line between plus and minus infinity.

I tried to avoid the adaptive sampling and using a low sampling rate to no avail. Other programs, eg gnuplot, give me a more reasonable plot, at least in view of my concerns, that is...

gnuplot

Eventually my question is, is it possible to avoid those vertical lines in sympy's plot() function?

Upvotes: 2

Views: 578

Answers (2)

mins
mins

Reputation: 7524

When a curve has singularities, it can plotted by segments, excluding the singularities. A tiny interval around the each singularity is built with math.nextafter(a,b) which returns a plus the smallest possible increment for a float, in direction of b.

from sympy import init_printing, symbols, plot
from sympy import singularities, Interval, tan, pi
from math import nextafter

init_printing()
x = symbols('x')

# The function to plot
y = tan(pi*x)

# Split x range at x singularities
min_x = next_x = 0
max_x = 4 # 4*pi
segments = []
undefs = singularities(y, x, domain=Interval(min_x, max_x))
for u in undefs:
    # Add a subrange up to singularity for singularities within x range
    if (u >= min_x) and (u <= max_x):
        segments.append((x, next_x, nextafter(u, u-1)))
        next_x = nextafter(u, u+1)
# Add last segment
if u <= max_x: segments.append((x, next_x, max_x))

# Plot all segments
plots = plot(*[(y, segment) for segment in segments], ylim=(-2,2), show=False)
plots.aspect_ratio = (1,1)
plots.show()

enter image description here

Of course you can use the same color for each curve segment.

Upvotes: 0

user3717023
user3717023

Reputation:

Sympy uses matplotlib as a backend for plotting; the root cause is that matplotlib connects the dots even around a singularity. If one plots with numpy, the direct access to y-values being plotted allows one to replace overly large numbers with nan or infinity. If staying within sympy, such tight control over numerics does not appear to be available. The best I could do is to split the range into a list of smaller ranges that do not include singularities, using the knowledge of the specific function tan(pi*x):

import math
from sympy import *
xi = symbols('xi')
xmin = 0
xmax = 4
ranges = [(xi, n-0.499, n+0.499) for n in range(math.ceil(xmin+0.5), math.floor(xmax+0.5))]
ranges.insert(0, (xi, 0, 0.499))
ranges.append((xi, math.floor(xmax+0.5) - 0.499, xmax))
plot((tanh(pi*xi), (xi, xmin, xmax)), *[(tan(pi*xi), ran) for ran in ranges], ylim=(-1, 2))

Output:

tangent plot

Upvotes: 2

Related Questions