Let me illustrate the issue with an simplified example. Suppose we want to solve the following problem with finite difference method (FDM):
$$\frac{\partial u(t,x)}{\partial t}=\frac{\partial^2 u(t,x)}{\partial x^2}$$ $$u(0,x)=x(1-x),\ u(t,0)=0$$ $$t\in[0,1],\ x\in[0,1] $$
One boundary condition (b.c.) is missing, but let's ignore it for a while and discretize the equation, initial condition (i.c.) and b.c. with difference formula. Here we use 2nd order difference formula. Clearly if we only use
$$f' (x_i)\simeq \frac{f (x_{i}+h)-f (x_{i}-h)}{2 h}$$ $$f'' (x_i)\simeq\frac{f (x_{i}-h)-2 f (x_i)+f (x_{i}+h)}{h^2}$$
we cannot generate equation for $t=1,\ x=1$, so we need one-sided formula (If you're not familiar with one-sided formula, start from page 6 of this book ):
$$f' (x_n)\simeq \frac{f (x_{n}-2h)-4 f (x_{n}-h)+3 f (x_n)}{2 h}$$ $$f'' (x_n)\simeq \frac{-f (x_{n}-3h)+4 f (x_{n}-2h)-5f (x_{n}-h)+2 f (x_{n})}{h^2}$$
OK, now here comes the problem: we surprisingly find that, even if b.c. is not enough, we still obtain a closed algebraic equation system!
One may suspect the system will be kind of ill-posed e.g. "This system won't be solvable!" or "This system will have infinite many solutions!" etc., but once again, surprisingly, further check shows the system determines a unique solution and seems to be stable i.e. with smaller grid size or higher order difference formula, the solution doesn't have significant change.
How to explain this solution? If a hidden b.c. has been imposed, what's it?
At the very least, if the solution is really nothing more than numeric error, I'd like to know the exact cause of the error.
Here's the source code for solving the problem above in Mathematica. I've used pdetoae
for the generation of difference formula:
eq = D[u[t, x], t] == D[u[t, x], x, x];
ic = u[0, x] == x (1 - x);
bc = u[t, 0] == 0;
points = 25;
grid = tgrid = Array[# &, points, {0, 1}];
ptoafunc = pdetoae[u[t, x], {tgrid, grid}, 2];
ae = Rest /@ (ptoafunc[eq] // Rest);
aeic = ptoafunc[ic] // Rest;
aebc = ptoafunc[bc];
var = Outer[u, tgrid, grid] // Flatten;
{b, m} = CoefficientArrays[{ae, aeic, aebc} // Flatten, var];
solarray = Partition[LinearSolve[N@m, -b], points];
solfunc = ListInterpolation[solarray, {tgrid, grid}];
style = Style[#, 16] &;
Plot3D[solfunc[t, x], {t, 0, 1}, {x, 0, 1}, AxesLabel -> style /@ {"t", "x"}]
Plot[solfunc[t, #] & /@ grid // Evaluate, {t, 0, 1}]
The following is the norm of solution $\sqrt{\int_0^1 u(t,x)^2 \, dx}$:
norm[t_?NumericQ, order_: 2, func_: solfunc] :=
Power[NIntegrate[func[t, x]^order, {x, 0, 1},
Method -> {Automatic, "SymbolicProcessing" -> 0}], 1/order]
Plot[norm[t], {t, 0, 1}, AxesLabel -> {t, norm}, PlotRange -> All]
Remark
Notice that the value of i.c. is used at the corner $t=0,\ x=1$.
LinearSolve
is not a iterative solver but a symbolic solver for linear algebraic equation system.
Also, it's actually enough to reproduce the issue by discretizing in $x$ direction only, and the resulting ordinary differential equation (ODE) system can be solved symbolically. The following is the corresponding Mathematica code. I've used pdetoode
for the generation of ODE system:
eq = D[u[t, x], t] == D[u[t, x], x, x];
ic = u[0, x] == x (1 - x);
bc = u[t, 0] == 0;
points = 4;
grid = Array[# &, points, {0, 1}];
ptoofunc = pdetoode[u[t, x], t, grid, 2];
ode = ptoofunc[eq] // Rest;
odeic = ptoofunc[ic] // Rest;
odebc = ptoofunc[bc];
var = u /@ grid;
asol = DSolve[{ode, odeic, odebc}, var[t] // Through, t] // Simplify // First
$$ \begin{array}{l} u(0)(t)=0 \\ u\left(\frac{1}{3}\right)(t)=\frac{1}{18} \left(-18 t+e^{-18 t}+3\right) \\ u\left(\frac{2}{3}\right)(t)=\frac{2}{9}-2 t \\ u(1)(t)=-3 t-\frac{e^{-18 t}}{18}+\frac{1}{18} \\ \end{array} $$
style = Style[#, 16] &;
ParametricPlot3D[
Flatten@{t, #} & /@ ({grid, asol[[All, -1]]}\[Transpose]) // Evaluate, {t, 0, 1},
BoxRatios -> {1, 1, 0.4}, AxesLabel -> style /@ {"t", "x", "u"}]
I only used 4 grid points, or else the symbolic calculation will be too slow, but as you can see the result agrees well with the FDM solution.
For completeness, here are implementations in some other programming languages. Notice I've only discretized $x$ in the following.
Maple
points := 24: y[0](t):=0: h := 1/points:
eq := seq((D(y[i]))(t) = (y[i-1](t)-2*y[i](t)+y[i+1](t))/h^2, i = 1 .. points-1),
(D(y[points]))(t) =
(-y[points-3](t)+4*y[points-2](t)-5*y[points-1](t)+2*y[points](t))/h^2:
ic := seq(y[points*x](0) = -x^2+x, x = h .. 1, h):
var := [seq([t, y[i](t)], i = 1 .. points)]:
p := dsolve([eq, ic], numeric, range = 0 .. 1):
with(plots):
odeplot(p, var, size = [default, .618]);
Notice Maple can also calculate the symbolic solution of the sytem with the following line (remember to choose a small points
first):
dsolve([eq, ic]);
The solution is consistent with that of Mathematica.
Octave
The code isn't tested in, but should be compatible with MATLAB.
Notice $u(t,0)=0$ isn't plotted in the resulting graph.
points=24;
dx=1/points;
one=ones(points,1);
mat1=spdiags([one -2*one one],[-1 0 1],points-1,points);
mat2=zeros(1,points);
mat2(points-3:points)=[-1 4 -5 2];
mat=[mat1;mat2]/dx^2;
span=dx:dx:1;
[T,Y]=ode45(@(t,y) mat*y,[0,1],span.*(1-span));
plot(T,Y)
Python
from numpy import zeros,linspace
from scipy.integrate import odeint
points=24;h=1/points;
def func(y,t0):
dydt=zeros(points+1)
dydt[0]=0
for i in range(1,points):
dydt[i]=(y[i-1]-2*y[i]+y[i+1])/h**2
dydt[points]=(-y[points-3]+4*y[points-2]-5*y[points-1]+2*y[points])/h**2
return dydt
y0=[x*h-x*x*h*h for x in range(0,points+1)]
t=linspace(0,1,200)
sol=odeint(func,y0,t)
from matplotlib.pyplot import plot,show
plot(t,sol)
show()
As you can see, all these implementations lead to the same solution.
Background Information
OK, let me explain a bit about why I insist on finding the meaning of this strange difference scheme. Actually what I've reproduced in this question is the behavior of Mathematica function NDSolve
when insufficient b.c. is added to it for solving time dependent PDE. For more information, check this post. As I wrote there:
I know the best countermeasure is to add the missing boundary condition, I'm just curious. Anyway, if the output with insufficient boundary condition is completely meaningless, why doesn't
NDSolve
simply stop calculating and return the input?
So, perhaps we'll have to admit the solution is really meaningless in the end, but I think this should be the last thing to do.