This paper by Tupper details a method for graphing two-dimensional implicit equations and inequalities. This package gives an implementation of the paper's basic algorithms to allow the
julia user to naturally represent and easily render graphs of implicit functions and equations.
The basic idea is to express a equation in $x$ and $y$ variables in terms of a function of two variables as a predicate. The
Plots is used to plot these predicates.
using Plots, ImplicitEquations a,b = -1,2 f(x,y) = y^4 - x^4 + a*y^2 + b*x^2 r = (f ⩵ 0) # \Equal[tab] plot(r)
f ⩵ 0 expression above creates a
Predicate that is graphed by
Predicates are generated using the function
Gt. The infix unicode operators
\leqq[tab]) may also be used.
## trident of Newton c,d,e,h = 1,1,1,1 f(x,y) = x*y g(x,y) = c*x^3 + d*x^2 + e*x + h plot(Eq(f,g)) ## aka f ⩵ g (using Unicode\Equal<tab>)
Inequalities can be graphed as well
f(x,y) = x - y plot(f ≪ 0) # \ll[tab]
This example is from Tupper's paper:
f(x,y) = (y-5)* cos(4sqrt((x-4)^2 +y^2)) g(x,y) = x * sin(2*sqrt(x^2 + y^2)) plot(Ge(f, g), xlims=(-10, 10), ylims=(-10, 10))
This graph illustrates the algorithm employed to graph
f ⩵ 0 where
f(x,y) = y - sqrt(x):
The basic algorithm is to initially break up the graphing region into square regions. (This uses the number of pixels, which are specified by
These regions are checked for the predicate.
If definitely not, the region is labeled "white;"
if definitely yes, the region is labeled "black;"
else the square region is subdivided into 4 smaller regions and the
above is repeated until subdivision would be below the pixel level. At which point, the remaining "1-by-1" pixels are checked for possible solutions, for example for equalities where continuity is known a random sample of points is investigated with the intermediate value theorem. A region may be labeled "black" or "red" if the predicate is still ambiguous.
The graph plots each "black" region as a "pixel". The "red" regions may optionally be colored, if a named color is passed through the keyword
For example, the Devil's curve is a bit different with red coloring:
a,b = -1,2 f(x,y) = y^4 - x^4 + a*y^2 + b*x^2 r = (f ⩵ 0) plot(r, red=:red) # show undecided regions in red
plot function accepts the usual keywords of
Plots and also:
the number of pixels is a power of 2 and specified by
plot(pred, N=4, M=5). The default is
N=8 or 256 x 256.
the colors red and black can be adjusted with the keywords
This example, the Batman equation, Uses a few new things: the
screen function is used to restrict ranges and logical operators to combine predicates.
f0(x,y) = ((x/7)^2 + (y/3)^2 - 1) * screen(abs(x)>3) * screen(y > -3*sqrt(33)/7) f1(x,y) = ( abs(x/2)-(3 * sqrt(33)-7) * x^2/112 -3 +sqrt(1-(abs((abs(x)-2))-1)^2)-y) f2(x,y) = y - (9 - 8*abs(x)) * screen((abs(x)>= 3/4) & (abs(x) <= 1) ) f3(x,y) = y - (3*abs(x) + 3/4) * I_((1/2 < abs(x)) & (abs(x) < 3/4)) # alternate name for screen f4(x,y) = y - 2.25 * I_(abs(x) <= 1/2) f5(x,y) = (6 * sqrt(10)/7 + (1.5-.5 * abs(x)) - 6 * sqrt(10)/14 * sqrt(4-(abs(x)-1)^2) -y) * screen(abs(x) >= 1) r = (f0 ⩵ 0) | (f1 ⩵ 0) | (f2 ⩵ 0) | (f3 ⩵ 0) | (f4 ⩵ 0) | (f5 ⩵ 0) plot(r, xlims=(-7, 7), ylims=(-4, 4), red=:black)
The above example illustrates a few things:
predicates can be joined logically with
! for negation.
screen function can be used to restrict values according to some predicate call.
the logical comparisons such as
(abs(x) >= 3/4) & (abs(x) <= 1) within
screen are not typical in that one can't write
3/4 <= abs(x) <= 1, a convenient
Julian syntax. This is due to the fact that the "
xs" being evaluated are not numbers, rather intervals via
ValidatedNumerics. For intervals, values may be true, false or "maybe" so a different interpretation of the logical operators is given that doesn't lend itself to the more convenient notation.
rendering can be slow. There are two reasons: images that require a lot of checking, such as the inequality above, are slow just because more regions must be analyzed. As well, some operations are slow, such as division, as adjustments for discontinuities are slow. (And by slow, it can mean really slow. The difference between rendering
csc(1-x^2)*cot(2-y^2) can be 10 times.)
If a function $f:C \rightarrow C$ is passed through the
map argument of
plot, each rectangle to render is mapped by the function $f$ prior to drawing. This allows for viewing of conformal maps. This example is one of several:
f(x,y) = x plot(f ≧ 1/2, map=z -> 1/z)
The region that is mapped above is not the half plane $x >= 1/2$, but truncated by $|y| < 5$ due to the default values of
ylims. Hence we don't see the full circle.
As well, the pieces plotted are polygonal approximations to the correct image. Consequently, gaps can appear.
A common calculus problem is to find the tangent line using implicit differentiation. We can plot the predicate to create the implicit graph, then add a layer with
f(x,y) = x^2 + y^2 plot(f ⩵ 2*3^2) ## now add tangent at (3,3) a,b = 3,3 dydx(a,b) = -b/a # implicit differentiate to get dy/dx =-y/x tl(x) = b + dydx(a,b)*(x-a) plot!(tl, linewidth=3, -5, 5)
Many such plots are simply a single level of a contour plot. Contour plots can be drawn with the
Plots package too. A simple contour plot will be faster than this package.
SymPy package exposes SymPy's
plot_implicit feature that will implicitly plot a symbolic expression in 2 variables including inequalities. The algorithm there also follows Tupper and uses interval arithmetic, as possible.
Check out these graphs to see which can be done
branch cut tracking and interval sets are employed by Tupper, these could be added. This would allow some other functions such as
± to be defined.
Tupper sketches out how to be more rigorous with computing whether a region is black or white.
increase speed (could color 1-pixel regions better if so, perhaps; division checks; type stability).