Search code examples
pythonscipy

Solve IVP wave equation - Unwanted reflection


I am a student in physics, and out of curiosity I wanted to create a code that makes an animation of a wave propagating in space, eventually reflecting on the boundaries of that space.

import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation
from scipy.integrate import solve_ivp
from scipy.fftpack import diff as psdiff

#%% Functions

def wave_eq(t, y, c, L, dx):
    """
    Wave equation

    Parameters
    ----------
    y : list
        Initial conditions.
    t : numpy.ndarray
        Time array.
    c : float
        Phase velocity.
    L : float
        Length of the system.

    Returns
    -------
    dydt : numpy.ndarray

    """
    N = len(y)//2
    ux      = np.gradient(y[:N], dx)
    uxx     = np.gradient(ux, dx)
    dudt    = y[N:]
    
    du_tdt = c**2*uxx      # wave eq
    
    dydt = np.append(dudt, du_tdt)
    return dydt

#%% Solve

## Space, discretized grid
L   = 50
dx  = 0.01
x   = np.arange(0, L, dx)

## Time
T   = 20
dt  = 0.1
t   = np.arange(0, T, dt)

## Parameters
c = 4   # phase velocity
A = 1   # amplitude

## Initial conditions
u0 = A*np.exp(-((x-L/2)/2)**2)
# u0 = 2*np.cosh(x)**(-1)
u_t0 = np.zeros(len(x))
y0 = np.append(u0, u_t0)

## Solve
print("Calculating...")
sol = solve_ivp(wave_eq, t_span=[0, T], t_eval=t, y0=y0, args=(c, L, dx))
N   = len(sol.y)//2
y   = sol.y[:N]

#%% Wave reflection

dc = int(c*dt/dx)    # at each iteration, the wave advances c*dt meters

## Reflection at the right hand side
refl_r = np.zeros((len(y), len(y.T)))

for i in range(len(y.T)):
    if i > 0:
        refl_r.T[i] += np.roll(refl_r.T[i-1], -dc)
        refl_r.T[i][-dc:] -= y.T[i-1][:-dc-1:-1]
        
## Reflection at the left hand side
refl_l = np.zeros((len(y), len(y.T)))

for i in range(len(y.T)):
    if i > 0:
        refl_l.T[i] += np.roll(refl_l.T[i-1], dc)
        refl_l.T[i][:dc] -= y.T[i-1][dc-1::-1]


# y += refl_r + refl_l

#%% Animation

plt.close('all')

fig, ax = plt.subplots()
line1, = ax.plot(x, y.T[0])
# line2, = ax.plot(x, refl_r.T[0])
# line3, = ax.plot(x, refl_l.T[0])
ax.set_ylim([-A, A])

def animate(i):
    line1.set_ydata(y.T[i])  # update the data.
    # line2.set_ydata(refl_r.T[i])
    # line3.set_ydata(refl_l.T[i])
    return line1, #line2, line3,

ani = animation.FuncAnimation(fig, animate, frames=int(T/dt), interval=50, blit=True, repeat=False)

print("Movie")

plt.show()

#%% Save animation - Use one of the following methods to save the animation

# ani.save("movie.gif", fps=20)

# writer = animation.FFMpegWriter(fps=15, metadata=dict(artist='Me'), bitrate=1800)
# ani.save("movie.gif", writer=writer)

# FFwriter = animation.FFMpegWriter()
# ani.save('animation.mp4', writer = FFwriter, fps=10)

# ani.save('movie.mp4', writer='ffmpeg')

Here is the entire code.

In the part "Solve", I define the different constants, variables, inital conditions, etc. Then I get the the output y = sol.y[:N] which is the solution of the equation that I wish to display as an animation.

I have also added a part called "Wave reflection" which simulates de reflection of the wave at 0m and at 50m. Then in the part "Animation" I get the animation. It has worked pretty well in my first try:

Gaussian wave is generated in the middle and is reflected on the boundaries. The time was set to T=10s

In my second attempt I increased the time to see what would happen. The second reflection did not work as I expected (sorry I couldn't include the gif file because it was too large). It looked like something was interfering with it and there would not be the usual pi phase shift, but instead no phase shift at all.

I investigated the problem and I had the idea to make an animation of the solution without taking into account the reflections. In this third attempt, I was surprised to find out that a "reflection" would occur - that is, I would see the waves coming back as if they were reflected but with a great delay. This delay is exactly the time it takes for the first reflected waves to travel accross the whole space and hit the boundaries - so the two overlapped in my second attempt.

Gaussian wave is generated in the middle, without taking reflection into account. However, after some time, we can see the waves coming back as if they were reflected by an object outside the space that I defined. Here the time was set to T=20s

Do you know why this happens? Does the scipy solve_ivp function work in a way that I am not aware of, that could explain this phenomenon? I would like to solve this problem so that I can create an animation of sinusoidal wave which would be generated on the left hand side of the space and would be reflected on both boundaries, then showcasing the appearance of a standing wave over time.


Solution

  • Jared is absolutely right: you don't have to post-process your results to add wave reflection: you simply have to make sure that you have the correct side boundary condition in the first place.

    Since u = 0 at x=0 and x=L for all time, you simply have to set du/dt=0 at these points; i.e. add the line

    dudt[0] = 0;    dudt[N-1] = 0
    

    immediately after setting dudt[] as an array.

    Your code in full (minus lots of commented-out bits) below. The waves reflect beautifully and immediately, with the desired phase change.

    Other boundary conditions, e.g. dudx=0 at boundaries are also possible.

    import numpy as np
    import matplotlib.pyplot as plt
    import matplotlib.animation as animation
    from scipy.integrate import solve_ivp
    from scipy.fftpack import diff as psdiff
    
    def wave_eq(t, y, c, L, dx):
        N = len(y)//2
        ux      = np.gradient(y[:N], dx)
        uxx     = np.gradient(ux, dx)
        dudt    = y[N:]
        dudt[0] = 0;    dudt[N-1] = 0           ### BOUNDARY CONDITIONS ###
    
        du_tdt = c**2*uxx      # wave eq
        
        dydt = np.append(dudt, du_tdt)
        return dydt
    
    ## Space, discretized grid
    L   = 50
    dx  = 0.01
    x   = np.arange(0, L, dx)
    
    ## Time
    T   = 20
    dt  = 0.1
    t   = np.arange(0, T, dt)
    
    ## Parameters
    c = 4   # phase velocity
    A = 1   # amplitude
    
    ## Initial conditions
    u0 = A*np.exp(-((x-L/2)/2)**2)
    u_t0 = np.zeros(len(x))
    y0 = np.append(u0, u_t0)
    
    ## Solve
    print("Calculating...")
    sol = solve_ivp(wave_eq, t_span=[0, T], t_eval=t, y0=y0, args=(c, L, dx))
    N   = len(sol.y)//2
    y   = sol.y[:N]
    
    
    #%% Animation
    plt.close('all')
    
    fig, ax = plt.subplots()
    line1, = ax.plot(x, y.T[0])
    ax.set_ylim([-A, A])
    
    def animate(i):
        line1.set_ydata(y.T[i])  # update the data.
        return line1, #line2, line3,
    
    ani = animation.FuncAnimation(fig, animate, frames=int(T/dt), interval=50, blit=True, repeat=False)
    print("Movie")
    plt.show()