Two different errors were obvious in the first parsing.
(found to be not valid for python numpy) As told several times, the standard implementations of fft
do not contain the scaling by the dimension, this is the responsibility of the user. Thus for a vector u
of n
components,
fft(ifft(uhat)) == n*uhat and ifft(fft(u))==n*u
If you want to use uhat = fft(u)
, then the reconstruction has to be u=ifft(uhat)/n
.
In the RK4 step, you have to decide on one place for the factor h
. Either (as example, the others analogously)
k2 = f(t+0.5*h, y+0.5*h*k1)
or
k2 = h*f(t+0.5*h, y+0.5*k1)
However, correcting these points only delays the blowup. That there is the possibility for dynamical blow-up is no wonder, it is to be expected from the cubic term. In general one can only expect "slow" exponential growth if all terms are linear or sub-linear.
To avoid "unphysical" singularities, one has to scale the step size inversely proportional to the Lipschitz constant. Since the Lipschitz constant here is of size u^2
, one has to dynamically adapt. I found that using 1000 steps in the interval [0,1], i.e., h=0.001
, proceeds without singularity. This still holds true for 10 000 steps on the interval [0,10].
Update The part of the original equation without the time derivative is self-adjoint, which means that the norm square of the function (integral over the square of the absolute value) is preserved in the exact solution. The general picture is thus a rotation. The problem now is that parts of the function might "rotate" with such a small radius or such a high velocity that a time step represents a large part of a rotation or even multiple rotations. This is hard to capture with numerical methods, which requires thus a reduction in the time step. The general name for this phenomenon is "stiff differential equation": Explicit Runge-Kutta methods are ill-suited for stiff problems.
Update2: Employing methods used before, one can solve the linear part in the decoupled frequency domain using
vhat = exp( 0.5j * kx**2 * t) * uhat
which allows for a stable solution with larger step sizes. As in the treatment of the KdV equation, the linear part i*u_t+0.5*u_xx=0
decouples under the DFT to
i*uhat_t-0.5*kx**2*uhat=0
and can thus be easily solved into the corresponding exponentials
exp( -0.5j * kx**2 * t).
The full equation is then tackled using variation of constants by setting
uhat = exp( -0.5j * kx**2 * t)*vhat.
This lifts some of the burden of the stiffness for the larger components of kx
, but still, the third power remains. So if the step size gets to large, the numerical solution explodes in very few steps.
Working code below
import numpy as np
import math
from matplotlib import pyplot as plt
from matplotlib import animation
#----- Numerical integration of ODE via fixed-step classical Runge-Kutta -----
def RK4Stream(odefunc,TimeSpan,uhat0,nt):
h = float(TimeSpan[1]-TimeSpan[0])/nt
print h
w = uhat0
t = TimeSpan[0]
while True:
w = RK4Step(odefunc, t, w, h)
t = t+h
yield t,w
def RK4Step(odefunc, t,w,h):
k1 = odefunc(t,w)
k2 = odefunc(t+0.5*h, w+0.5*k1*h)
k3 = odefunc(t+0.5*h, w+0.5*k2*h)
k4 = odefunc(t+h, w+k3*h)
return w + (k1+2*k2+2*k3+k4)*(h/6.)
#----- Constructing the grid and kernel functions -----
L = 40
nx = 512
x = np.linspace(-L/2,L/2, nx+1)
x = x[:nx]
kx1 = np.linspace(0,nx/2-1,nx/2)
kx2 = np.linspace(1,nx/2, nx/2)
kx2 = -1*kx2[::-1]
kx = (2.* np.pi/L)*np.concatenate((kx1,kx2))
def uhat2vhat(t,uhat):
return np.exp( 0.5j * (kx**2) *t) * uhat
def vhat2uhat(t,vhat):
return np.exp(- 0.5j * (kx**2) *t) * vhat
#----- Define RHS -----
def uhatprime(t, uhat):
u = np.fft.ifft(uhat)
return - 0.5j * (kx**2) * uhat + 1j * np.fft.fft((abs(u)**2) * u)
def vhatprime(t, vhat):
u = np.fft.ifft(vhat2uhat(t,vhat))
return 1j * uhat2vhat(t, np.fft.fft((abs(u)**2) * u) )
#------ Initial Conditions -----
u0 = 1./np.cosh(x) #+ 1./np.cosh(x+0.4*L)+1./np.cosh(x-0.4*L) #symmetric or remove jump at wrap-around
uhat0 = np.fft.fft(u0)
#------ Solving for ODE -----
t0 = 0; tf = 10.0;
TimeSpan = [t0, tf]
# nt = 500 # limit case, barely stable, visible spurious bumps in phase
nt = 1000 # boring but stable. smaller step sizes give same picture
vhat0 = uhat2vhat(t0,uhat0)
fig = plt.figure()
ax1 = plt.subplot(211,ylim=(-0.1,2))
ax2 = plt.subplot(212,ylim=(-3.2,3.2))
line1, = ax1.plot(x,u0)
line2, = ax2.plot(x,u0*0)
vhatstream = RK4Stream(vhatprime,[t0,tf],vhat0,nt)
def animate(i):
t,vhat = vhatstream.next()
print t
u = np.fft.ifft(vhat2uhat(t,vhat))
line1.set_ydata(np.real(np.abs(u)))
line2.set_ydata(np.real(np.angle(u)))
return line1,line2
anim = animation.FuncAnimation(fig, animate, interval=15000/nt+10, blit=False)
plt.show()