Search code examples
pythonplotsympyimplicit

python (sympy) implicit function: get values instead of plot?


I am new to sympy but I already get a nice output when I plot the implicit function (actually the formula for Cassini's ovals) using sympy:

from sympy import plot_implicit, symbols, Eq, solve
x, y = symbols('x y')
k=2.7
a=3
eq = Eq((x**2 + y**2)**2-2*a**2*(x**2-y**2), k**4-a**4)
plot_implicit(eq)

Now is it actually possible to somehow get the x and y values corresponding to the plot? or alternatively solve the implicit equation without plotting at all?

thanks! :-)


Solution

  • This is an answer addressing your

    is it actually possible to somehow get the x and y values corresponding to the plot?

    and I say "addressing" because it's not possible to get the x and y values used to draw the curves — because the curves are not drawn using a sequenc of 2D points… more on this later,


    TL;DR

    pli = plot_implicit(...)
    series = pli[0]
    data, action = series.get_points()
    data = np.array([(x_int.mid, y_int.mid) for x_int, y_int in data])
    

    Let's start with your code

    from sympy import plot_implicit, symbols, Eq, solve
    x, y = symbols('x y')
    k=2.7
    a=3
    eq = Eq((x**2 + y**2)**2-2*a**2*(x**2-y**2), k**4-a**4)
    

    and plot it, with a twist: we save the Plot object and print it

    pli = plot_implicit(eq)
    print(pli)
    

    to get

    Plot object containing:
    [0]: Implicit equation: Eq(-18*x**2 + 18*y**2 + (x**2 + y**2)**2, -27.8559000000000) for x over (-5.0, 5.0) and y over (-5.0, 5.0)
    

    We are interested in this object indexed by 0,

    ob = pli[0]
    print(dir(ob))
    

    that gives (ellipsis are mine)

    ['__class__', …, get_points, …, 'var_y']
    

    The name get_points sounds full of promise, doesn't it?

     print(ob.get_points())
    

    that gives (edited for clarity and with a big cut)

    ([
      [interval(-3.759774, -3.750008), interval(-0.791016, -0.781250)],
      [interval(-3.876961, -3.867195), interval(-0.634768, -0.625003)],
      [interval(-3.837898, -3.828133), interval(-0.693361, -0.683596)],
      [interval(-3.847664, -3.837898), interval(-0.673830, -0.664065)],
      ...
      [interval(3.837895, 3.847661), interval(0.664064, 0.673830)],
      [interval(3.828130, 3.837895), interval(0.683596, 0.693362)],
      [interval(3.867192, 3.876958), interval(0.625001, 0.634766)],
      [interval(3.750005, 3.759770), interval(0.781255, 0.791021)]
      ], 'fill')
    

    What is this? the documentation of plot_implicit has

    plot_implicit, by default, uses interval arithmetic to plot functions.

    Following the source code of plot_implicit.py and plot,py one realizes that, in this case, the actual plotting (speaking of the matpolotlib backend) is just a line of code

     self.ax.fill(x, y, facecolor=s.line_color, edgecolor='None')
    

    where x and y are constructed from the list of intervals, as returned from .get_points(), as follows

    x, y = [], []
    for intervals in interval_list:
        intervalx = intervals[0]
        intervaly = intervals[1]
        x.extend([intervalx.start, intervalx.start,
                      intervalx.end, intervalx.end, None])
        y.extend([intervaly.start, intervaly.end,
                      intervaly.end, intervaly.start, None])
    

    so that for each couple of intervals matplotlib is directed to draw a filled rectangle, small enough that the eye sees a continuous line (note the use of None to have disjoint rectangles).

    We can conclude that the list of couples of intervals

    l_xy_intervals = ((pli[0]).get_points())[0]
    

    represents rectangular areas where the implicit expression you are plotting is "true enough"