Is it possible to plot implicit equations?
Asked Answered
O

6

39

I would like to plot implicit equations (of the form f(x, y)=g(x, y) eg. X^y=y^x) in Matplotlib. Is this possible?

Orpington answered 20/3, 2010 at 20:0 Comment(1)
Drawing conics in Matplotlib, Sage MathDudgeon
J
36

I don't believe there's very good support for this, but you could try something like

import matplotlib.pyplot
from numpy import arange
from numpy import meshgrid

delta = 0.025
xrange = arange(-5.0, 20.0, delta)
yrange = arange(-5.0, 20.0, delta)
X, Y = meshgrid(xrange,yrange)

# F is one side of the equation, G is the other
F = Y**X
G = X**Y

matplotlib.pyplot.contour(X, Y, (F - G), [0])
matplotlib.pyplot.show()

See the API docs for contour: if the fourth argument is a sequence then it specifies which contour lines to plot. But the plot will only be as good as the resolution of your ranges, and there are certain features it may never get right, often at self-intersection points.

Judaism answered 20/3, 2010 at 20:16 Comment(1)
This is a good solution. My solution is a more manual way of getting at the same piece of information using the same underlying concept: setting up the implicit equation as f(x, y) such that f(x, y) = 0 is equivalent to the original implicit equation and isolating its zero contour.You
T
36

Since you've tagged this question with sympy, I will give such an example.

From the documentation: http://docs.sympy.org/latest/modules/plotting.html.

from sympy import var, plot_implicit
var('x y')
plot_implicit(x*y**3 - y*x**3)
Thereat answered 22/3, 2010 at 12:10 Comment(1)
It looks like the new syntax is plot_implicit(Eq(x**5 + y**5, 1)), and the new doc link is here.Pulque
Y
9

matplotlib does not plot equations; it plots serieses of points. You can use a tool like scipy​.optimize to numerically calculate y points from x values (or vice versa) of implicit equations numerically or any number of other tools as appropriate.


For example, here is an example where I plot the implicit equation x ** 2 + x * y + y ** 2 = 10 in a certain region.

from functools import partial

import numpy
import scipy.optimize
import matplotlib.pyplot as pp

def z(x, y):
    return x ** 2 + x * y + y ** 2 - 10

x_window = 0, 5
y_window = 0, 5

xs = []
ys = []
for x in numpy.linspace(*x_window, num=200):
    try:
        # A more efficient technique would use the last-found-y-value as a 
        # starting point
        y = scipy.optimize.brentq(partial(z, x), *y_window)
    except ValueError:
        # Should we not be able to find a solution in this window.
        pass
    else:
        xs.append(x)
        ys.append(y)

pp.plot(xs, ys)
pp.xlim(*x_window)
pp.ylim(*y_window)
pp.show()
You answered 20/3, 2010 at 20:17 Comment(2)
I don't think this is a smart approach. If a variable changes suddenly in x, this approach will render a long straight line between the two neighbouring points, possible spanning the entire figure height. Something trivial like f(x, y) = x and g(x, y) = a will fail altogether. (The exact failure mode will depend on whether one of your interpolation points happens to hit the line or not.)Heelpost
This is a good approach -- which allows you to find the location of the zeros more accurately and efficiently than the coarse discretizations of some of the other approaches.Biliary
S
6

There is an implicit equation (and inequality) plotter in sympy. It is created as a part of GSoC and it produces the plots as matplotlib figure instances.

Docs at http://docs.sympy.org/latest/modules/plotting.html#sympy.plotting.plot_implicit.plot_implicit

Since sympy version 0.7.2 it is available as:

>>> from sympy.plotting import plot_implicit
>>> p = plot_implicit(x < sin(x)) # also creates a window with the plot
>>> the_matplotlib_axes_instance = p._backend._ax
Sioux answered 25/6, 2012 at 21:42 Comment(4)
It seems it's released now. :)Hydrocortisone
Where is your the_matplotlib_axes_instance coming from?Dying
p is the plot you created. p._backend._ax would be axes instance and if you wish you can reference it in a new variable and use it for whatever you would use any matplotlib axes instance.Sioux
Can you update this answer to remove references to future versions (now old) and I will delete my out-of-date answer. Thanks.Thereat
M
3

Edit: If you plot a hyperbola using plt.plot() then you will get the undesired branching effect. plt.scatter() in its place should still work. Then there is no need to reverse the order of negative or positive values, but if you wanted to use this code for some reason (instead of using contour plot from scipy) it will work anyways with plt.scatter()

An implicit function in two dimensions in general can be written as:

f(x,y)=0

Since we cannot write this as f(x) = y, then we cannot compute y from an easily programmable set of discrete x. It is possible, however, to see how close a point generated from a grid is from the true function.

So make a grid of x and y to a custom point density and see how close each point is to satisfying the equation.

In other words, if we can't get f(x,y) =0, perhaps we can get close to 0. Instead of looking for f(x,y) =0 look for f(x,y) > -\epsilon and f(x,y) < \epsilon.

\epsilon is your tolerance and if this condition fits within your tolerance of 0 and tuning the grid appropriately you can get your function plotted.

The code below does just that for a circle of radius 1 (f(x,y)= x^2 + y^2 -1 = 0). I used the symbol dr for \epsilon.

Also, to make sure the plt.plot function connects the lines in the correct order, I use a reversed version of the x values for the negative y values. That way, the evaluation of f(x,y) is done in a clockwise loop so that the nearest values are one after another. Without this, lines from opposite sides of the function would connect and it would appear slightly filled in.

import numpy as np
import matplotlib.pyplot as plt
r = 1 #arbitrary radius to set up the span of points
points = 250 
dr = r/points #epsilon window 

x=list(np.linspace(-5*r,5*r,5*points+1)) #setting up the x,y grid
y=x

xreversed = reversed(x) #reversing the array

x_0=[] #placeholder arrays
y_0=[]

for i in x:
    for j in y:
        if i**2 + j**2 -1 < dr and i**2+j**2 -1 > -dr  and j >= 0: #positive values of y
            x_0.append(i)
            y_0.append(j)
for i in xreversed:            
    for j in y:
        if i**2+j**2 -1 < dr and i**2+j**2 -1 > -dr  and j < 0: #negative values of y, using x reversed
            x_0.append(i)
            y_0.append(j)

plt.plot(x_0,y_0)
plt.show()

Magruder answered 20/1, 2020 at 2:49 Comment(0)
O
1

Many thanks Steve, Mike, Alex. I have gone along with Steve's solution (please see code below). My only remaining issue is that the contour plot appears behind my gridlines, as opposed to a regular plot, which I can force to the front with zorder. Any more halp greatly appreciated.

Cheers, Geddes

import matplotlib.pyplot as plt 
from matplotlib.ticker import MultipleLocator, FormatStrFormatter
import numpy as np 

fig = plt.figure(1) 
ax = fig.add_subplot(111) 

# set up axis 
ax.spines['left'].set_position('zero') 
ax.spines['right'].set_color('none') 
ax.spines['bottom'].set_position('zero') 
ax.spines['top'].set_color('none') 
ax.xaxis.set_ticks_position('bottom') 
ax.yaxis.set_ticks_position('left') 

# setup x and y ranges and precision
x = np.arange(-0.5,5.5,0.01) 
y = np.arange(-0.5,5.5,0.01)

# draw a curve 
line, = ax.plot(x, x**2,zorder=100) 

# draw a contour
X,Y=np.meshgrid(x,y)
F=X**Y
G=Y**X
ax.contour(X,Y,(F-G),[0],zorder=100)

#set bounds 
ax.set_xbound(-1,7)
ax.set_ybound(-1,7) 

#produce gridlines of different colors/widths
ax.xaxis.set_minor_locator(MultipleLocator(0.2)) 
ax.yaxis.set_minor_locator(MultipleLocator(0.2)) 
ax.xaxis.grid(True,'minor',linestyle='-')
ax.yaxis.grid(True,'minor',linestyle='-') 

minor_grid_lines = [tick.gridline for tick in ax.xaxis.get_minor_ticks()] 
for idx,loc in enumerate(ax.xaxis.get_minorticklocs()): 
    if loc % 2.0 == 0:
        minor_grid_lines[idx].set_color('0.3')
        minor_grid_lines[idx].set_linewidth(2)
    elif loc % 1.0 == 0:
        minor_grid_lines[idx].set_c('0.5')
        minor_grid_lines[idx].set_linewidth(1)
    else:
        minor_grid_lines[idx].set_c('0.7')
        minor_grid_lines[idx].set_linewidth(1)

minor_grid_lines = [tick.gridline for tick in ax.yaxis.get_minor_ticks()] 
for idx,loc in enumerate(ax.yaxis.get_minorticklocs()): 
    if loc % 2.0 == 0:
        minor_grid_lines[idx].set_color('0.3')
        minor_grid_lines[idx].set_linewidth(2)
    elif loc % 1.0 == 0:
        minor_grid_lines[idx].set_c('0.5')
        minor_grid_lines[idx].set_linewidth(1)
    else:
        minor_grid_lines[idx].set_c('0.7')
        minor_grid_lines[idx].set_linewidth(1)

plt.show()
Orpington answered 21/3, 2010 at 12:4 Comment(1)
@Geddes, it looks like support for contour respecting zorder has only recently been added to the matplotlib source. From their SVN trunk: matplotlib.svn.sourceforge.net/viewvc/…Talishatalisman

© 2022 - 2024 — McMap. All rights reserved.