Search code examples
pythonnumpymatplotlibmatplotlib-animation

Updating the equation of a line matplotlib animation


I'm trying to animate the Taylor series from 1 through 50 terms. The code I currently have allows me to specify a number of terms and plots it onto a graph (with another graph for comparison):

import math
import numpy as np

import matplotlib.pyplot as plt

def func_cos(x, n):
    cos_approx = 0
    for i in range(n):
        coef = (-1)**i
        num = x**(2*i)
        denom = math.factorial(2*i)
        cos_approx += ( coef ) * ( (num)/(denom) )
    
    return cos_approx

def func_sin(x, n):
    sin_approx = 0
    for i in range(n):
        coef = (-1)**i
        num = x**(2*i+1)
        denom = math.factorial(2*i+1)
        sin_approx += ( coef ) * ( (num)/(denom) )
    
    return sin_approx

i = 1j
e = math.e

x = np.linspace(0, 10, 1000)

fx = e**(i*x)

taylor = lambda j: np.array([func_cos(x_val,j) + i*func_sin(x_val,j) for x_val in x])

plt.plot(fx.real, fx.imag)
plt.plot(taylor(50).real, taylor(50).imag)

plt.show()

Is there a way to animate the graph so that the line changes equation every frame to that number of Taylor series terms?


Solution

  • Animation module

    One way is animation module. It is probably the best method in such a case, since you have a clear definition of what ith frame should look like.

    Here is your code, updated with some commented lines.

    import math
    import numpy as np
    import matplotlib.pyplot as plt
    
    # You need this module 
    import matplotlib.animation as anim
    
    
    def func_cos(x, n):
        cos_approx = 0
        for i in range(n):
            coef = (-1)**i
            num = x**(2*i)
            denom = math.factorial(2*i)
            cos_approx += ( coef ) * ( (num)/(denom) )
        
        return cos_approx
    
    def func_sin(x, n):
        sin_approx = 0
        for i in range(n):
            coef = (-1)**i
            num = x**(2*i+1)
            denom = math.factorial(2*i+1)
            sin_approx += ( coef ) * ( (num)/(denom) )
        
        return sin_approx
    
    i = 1j
    e = math.e
    
    x = np.linspace(0, 10, 1000)
    
    fx = e**(i*x)
    
    taylor = lambda j: np.array([func_cos(x_val,j) + i*func_sin(x_val,j) for x_val in x])
    
    plt.plot(fx.real, fx.imag)
    
    # You need to keep the "artist", that is the plotted line, to be able to update its data later
    # Note the `,`: plt.plot returns 2 things, the first being the one we want (pltdata)
    pltdata,=plt.plot(taylor(0).real, taylor(0).imag)
    
    # The animate function is in charge of updating the data
    # i is the frame number
    def animate(i):
        pltdata.set_data(taylor(i).real, taylor(i).imag)
        # Animate returns the list of the "artists" it has changed. So here, just [pltdata]
        return [pltdata]
    
    # Note the dummy "myanim" variable. Even if we don't use it afterward, it is necessary 
    # so that the garbage collector doesn't destry the animation
    # plt.gcf() is just the figure. So, if you have a fig=plt.figure() somewhere, that should be replaced by `fig`
    # I reduced the 50 values, because, well, 20 is more than enough (at this point, you even start adding
    # more numerical noise that accuracy. But well, that has nothing to do with animation)
    myanim = anim.FuncAnimation(plt.gcf(), animate, frames=20, interval=200, blit=True)
    plt.show()
    # or myanim.save('out.mp4') to create a animation in a file
    

    Plus (see last, commented, line), animation allows easy creation of cool animated gif (or mp4, but gif is needed to put in a [so] message), like this one enter image description here

    ion

    Another way is to make the plot interactive, and to update it (or even redraw everything from scratch each time, it is up to you), using your own timing strategy.

    plt.ion()
    
    for k in range(20):
        plt.gcf().clear()
        plt.xlim(-1.5, 1.5)
        plt.ylim(-1.5, 1.5)
        plt.plot(fx.real, fx.imag)
        plt.plot(taylor(k).real, taylor(k).imag)
        plt.draw()
        plt.pause(0.2)
    

    Or a mixture of both

    plt.ion()
    
    plt.plot(fx.real, fx.imag)
    plt.xlim(-1.5, 1.5)
    plt.ylim(-1.5, 1.5)
    pltdata,=plt.plot(taylor(0).real, taylor(0).imag)
    
    for k in range(20):
        pltdata.set_data(taylor(k).real, taylor(k).imag)
        plt.draw()
        plt.pause(0.2)
    

    Note that I wanted to focus only on animation part here. So no optimization specific to your case. For example, by computing taylor(k) each times, taylor(k) itself computing all terms, I am basically computing 30 times the constant coefficent, 29 times the x coefficient, 28 times the x² coefficient, ...

    That, obviously could be optimized.

    For example like this

    plt.ion()
    
    plt.plot(fx.real, fx.imag)
    plt.xlim(-1.5, 1.5)
    plt.ylim(-1.5, 1.5)
    pltdata,=plt.plot(xx, yy)
    
    fac=1.0
    for k in range(20):
        xx += (-1)**k * x**(2*k) / fac
        fac *= (2*k+1)
        yy += (-1)**k * x**(2*k+1) / fac
        fac *= (2*k+2)
        pltdata.set_data(xx,yy)
        plt.draw()
        plt.pause(0.2)