# Solving a steady-state viscous Burger's equation with NDSolve

A steady-state viscous Burger's equation is given by $$u\,u'=\nu \,u'', \quad x\in (-1,1),$$ $$u(-1)=1+\delta,\quad u(1)=-1.$$ Here $$\nu>0$$ is the viscosity, $$\delta>0$$ is a small perturbation and $$u$$ is the solution. This ODE problem has a unique solution: $$u(x)=-A\,\text{tanh}\left(\frac{A}{2\nu}(x-z)\right),$$ where $$A>0$$ and $$z>0$$ are constants determined by the boundary conditions: $$A\,\text{tanh}\left(\frac{A}{2\nu}(1+z)\right)=1+\delta,\quad A\,\text{tanh}\left(\frac{A}{2\nu}(1-z)\right)=1.$$ The exact solution can be plotted in Mathematica:

Azex[nu_, delta_] :=
Quiet[{a, zz} /. Flatten@NSolve[{a*Tanh[a*(1 + zz)/(2*nu)] == 1 + delta,
a*Tanh[a*(1 - zz)/(2*nu)] == 1, a > 0, zz > 0}, {a, zz}, Reals]]

nu = 0.05;

{A, zex} = Azex[nu, 0.01];

Plot[-A*Tanh[A*(x - zex)/(2*nu)], {x, -1, 1}, PlotStyle -> Black,
PlotRange -> All, AxesLabel -> {"x", "u(x)"}, BaseStyle -> {Bold, FontSize -> 12},
PlotLabel -> "Solution with \[Nu]=0.05 and \[Delta]=0.01"]


I am interested in solving the equation numerically with NDSolve. The standard routine would be

nu = 0.05; delta = 0.01;
NDSolve[{u''[x] - (1/nu)*u[x]*u'[x] == 0, u[-1] == 1 + delta, u[1] == -1}, u[x], {x, -1, 1}]


However, this code gives rise to a warning of the form step size is effectively zero; singularity or stiff system suspected. I have tried with different methods but obtained no solution.

• Question 1: How can I solve the ODE {u''[x] - (1/nu)*u[x]*u'[x] == 0, u[-1] == 1 + delta, u[1] == -1}?

Even more complicated is to solve the following system of ODEs arising from a gPC-based stochastic Galerkin projection technique when $$\delta\sim\text{Uniform}(0,0.1)$$:

p = 10; P = p + 1;

basis = Expand[Orthogonalize[Z^Range[0, p], Integrate[#1 #2 *10, {Z, 0, 1/10}] &]];

region = {Z \[Distributed] UniformDistribution[{0, 1/10}]};

mat = ConstantArray[0, {P, P, P}];
Do[mat[[l, j, k]] = Expectation[basis[[k]]*basis[[j]]*basis[[l]], region],
{k, 1, P}, {j, 1, k}, {l, 1, j}];
Do[mat[[l, j, k]] = mat[[##]] & @@ Sort[{l, j, k}], {k, 1, P}, {j, 1, P}, {l, 1, P}];

cond1 = Table[Expectation[(1 + Z)*basis[[j]], region], {j, 1, P}];
cond2 = ConstantArray[0, P]; cond2[[1]] = -1;

Clear[coeff, x]
coeff[x_] = Table[w[i, x], {i, 1, P}];
side1 = Table[coeff''[x][[j]] - (1/nu)*
Sum[coeff[x][[k]]*coeff'[x][[l]]*mat[[k, l, j]], {k, 1, P}, {l, 1, P}], {j, 1, P}];
side1 = Join[side1, coeff[-1], coeff[1]];
side2 = Join[ConstantArray[0, P], cond1, cond2];
solution = NDSolve[side1 == side2, coeff[x], {x, -1, 1}];


It is not necessary to enter into mathematical details. The idea is that coeff[x] are coefficients of a stochastic expansion of $$u(x)$$ in terms of Legendre polynomials (which are orthogonal with respect to the density function of $$\delta$$): $$u(x)\approx\sum_{i=0}^p w_i(x)\text{basis}_i(\delta)$$. The equation side1 == side2 is a system of ODEs with a certain similarity to the steady-state Burger's equation.

• Question 2: How can I solve the ODE side1 == side2?

Remark: If someone is interested in the problem, it comes from the paper Supersensitivity due to uncertain boundary conditions (2004) by D. Xiu and G.E. Karniadakis, and the book Numerical Methods for Stochastic Computations: A Spectral Method Approach (2010) by D. Xiu (Chapter 1).

• Well, the 2 questions seem to be quite different, what's the relationship between them? – xzczd Nov 1 '19 at 7:57
• @xzczd There is a mathematical relation. When $\delta\sim\text{Uniform}(0,0.1)$, then the solution $u(x)$ is a stochastic process. According to the theory of generalized polynomial chaos (gPC) expansions, $$u(x)=\sum_{i=0}^\infty w_i(x)\text{basis}_i(\delta),$$ where $w_i(x)$ are deterministic functions, and $\{\text{basis}_i(\delta)\}_{i=0}^\infty$ is the family of Legendre polynomials of degree $i$, orthogonal with respect to the density $f_\delta$. The Galerkin method approximates the solution as $$u(x)\approx w(x)=\sum_{i=0}^p w_i(x)\text{basis}_i(\delta).$$ You put $w(x)$... – user68161 Nov 1 '19 at 8:19
• @xzczd ... in Burger's equation: $$w\,w'=\nu\, w''\Rightarrow \sum_{i,j=0} w_i(x)w_j'(x)\text{basis}_i(\delta)\text{basis}_j(\delta)=\nu \sum_{i=0}^p w_i''(x)\text{basis}_i(\delta).$$ Now multiply by $\text{basis}_k(\delta),$ $k=0,\ldots,p$, apply expectation and use orthonormality: $$\nu \,w_k''(x)=\sum_{i,j=0} w_i(x)w_j'(x)\text{mat}_{ijk}.$$ Similarly for the initial conditions $w(-1)=1+\delta$ and $w(1)=-1$. This is the system of ODE I wrote. I am just interested on the numerical solution in Mathematica. – user68161 Nov 1 '19 at 8:19
• Welcome to Mathematica.SE, user68161! I suggest the following: 1) Take the tour and check the faqs. 2) When you see good questions and answers, vote them up by clicking the gray triangles, because the credibility of the system is based on the reputation gained by users sharing their knowledge. Also, please remember to accept the answer, if any, that solves your problem, by clicking the checkmark sign! 3) As you receive help, try to give it too, by answering questions in your area of expertise. – Chris K Nov 1 '19 at 10:50
• Related Q&A focused on questions 1: mathematica.stackexchange.com/questions/208889/… – Michael E2 Nov 1 '19 at 14:57

# NDSolve-based Solution

We need to adjust the option of NDSolve a bit. For the first problem, if you're in v12, then you can use nonlinear FiniteElement:

ref = Plot[-A Tanh[A (x - zex)/(2 nu)], {x, -1, 1}, PlotStyle -> Black, PlotRange -> All];

test = NDSolveValue[{u''[x] - (1/nu) u[x] u'[x] == 0, u[-1] == 1 + delta, u[1] == -1},
u, {x, -1, 1}, Method -> FiniteElement]

Plot[test[x], {x, -1, 1}, PlotRange -> All,
PlotStyle -> {Orange, Dashed, Thickness[.01]}]~Show~ref


If you're before v12, then we need to adjust initial guess of Shooting method and choose a higher WorkingPrecision:

shoot[ic_]:={"Shooting", "StartingInitialConditions"->ic};

nu = 5/100; delta = 1/100;
test2 = NDSolveValue[{u''[x] - (1/nu)*u[x]*u'[x] == 0, u[-1] == 1 + delta, u[1] == -1},
u, {x, -1, 1}, Method -> shoot@{u[-1] == 1 + delta, u'[-1] == 0},
WorkingPrecision -> 32]

ListPlot[test2, PlotStyle -> {PointSize@Medium, Orange}]~Show~ref


Here I've plotted InterpolatingFunction with ListPlot, this undocumented syntax is mentioned in this post.

Though the second problem is more challenging, it can be solved in similar manner. Shooting method returns a solution after an hour:

solutionlist =
Head /@ NDSolveValue[side1 == side2, coeff[x], {x, -1, 1},
Method -> shoot@
Flatten@{side1[[-(p + P + 1);;-(P + 1)]]==side2[[-(p + P + 1);;-(P + 1)]] // Thread,
D[coeff[x], x] == 0 /. x -> -1 // Thread},
WorkingPrecision -> 32]; // AbsoluteTiming

(* {3614.74, Null} *)

ListLinePlot[#, PlotRange -> All] & /@ solutionlist


# FDM-based Solution

If speed is concerned for the second question, then turning to finite difference method (FDM) seems to be a good idea. Here I'll use pdetoae for the generation of difference equations.

First we slightly modify the definition of coeff to make it convenient for pdetoae:

coeff[x_] = Table[w[i][x], {i, 1, P}];
side1 = Table[
coeff''[x][[j]] -
Sum[coeff[x][[k]] coeff'[x][[l]] mat[[k, l, j]], {k, 1, P}, {l, 1, P}]/nu, {j, 1, P}];
side1lst = {side1, coeff[-1], coeff[1]};
side2lst = {ConstantArray[0, P], cond1, cond2};


Then we discretize the system:

domain = {-1, 1};
points = 100;
difforder = 2;
grid = Array[# &, points, domain];
(* Definition of pdetoae isn't included in this post,
ptoafunc = pdetoae[coeff[x], grid, difforder];

del = #[[2 ;; -2]] &;

ae = del /@ ptoafunc[side1lst[[1]] == side2lst[[1]] // Thread];

aebc = Flatten@side1lst[[2 ;;]] == Flatten@side2lst[[2 ;;]] // Thread;


A trivial initial guess seems to be enough, you can choose a better one if you like:

initialguess[var_, x_] := 0

sollst = FindRoot[{ae, aebc},
Flatten[#, 1] &@
Table[{var[x], initialguess[var, x]}, {var, w /@ Range@P}, {x, grid}],
MaxIterations -> 500][[All, -1]]; // AbsoluteTiming
(* {9.655, Null} *)

ListLinePlot[#, PlotRange -> All, DataRange -> domain] & /@ Partition[sollst, points]


The result looks the same as the one given by NDSolve so I'd like to omit it.

• (+1) have you thought about moving pdetoae into the wolfram resource function repository? – user21 Nov 2 '19 at 5:40
• @user21 I intend to, but every time I think about documenting, my stomach hurt 囧. – xzczd Nov 2 '19 at 7:26
• :-) welcome to my world..... – user21 Nov 4 '19 at 5:13

I show a solution based on the trapezoidal rule for first-order ODEs. The ODE $$uu'=\nu u''$$ is equivalent to $$(u,v)'=f(u,v)$$, where $$f(u,v)=(v,\frac{1}{\nu}uv)$$. If $$y=(u,v)$$, the trapezoidal FDM is $$y_{i+1}=y_i+\frac12 h(f(y_i)+f(y_{i+1}))$$. We use the mesh $$x_j=-1+jh$$, $$h=2/n$$, $$j=0,\ldots,n$$. The following Module returns $$\{(x_j,u_j)\}_{j=0}^n$$.

fdmODE[nu_, delta_, n_] := Module[{h, mesh, f, u, v, eqns, sv, froot, sol},
h = 2/n;
mesh = -1 + h*Range[0, n];
f[{u_, v_}] = {v, (1/nu)*u*v};
eqns = Flatten[Join[{u[0] == 1 + delta, u[n] == -1},
Table[Thread[{u[i], v[i]} == {u[i - 1], v[i - 1]} +
0.5*h*(f[{u[i - 1], v[i - 1]}] + f[{u[i], v[i]}])], {i, 1, n}]]];
sv = Flatten[Table[{{u[i], 0}, {v[i], 0}}, {i, 0, n}], 1]; (* initial guess root *)
froot = FindRoot[eqns, sv];
sol = Table[u[i], {i, 0, n}] /. froot;
];


To assess the code, we plot the exact solution and the numerical solution, for $$\nu=0.05$$ and $$\delta=0.01$$:

Azex[nu_, delta_] := Quiet[{a, zz} /.
Flatten@NSolve[{a*Tanh[a*(1 + zz)/(2*nu)] == 1 + delta,
a*Tanh[a*(1 - zz)/(2*nu)] == 1, a > 0, zz > 0}, {a, zz}, Reals]];

nu = 0.05; delta = 0.01;

{A, zex} = Azex[nu, delta];
Show[Plot[-A*Tanh[A*(x - zex)/(2*nu)], {x, -1, 1}, PlotStyle -> Black,
PlotRange -> All], ListLinePlot[fdmODE[nu, delta, 3000], PlotStyle -> {Blue, Dashed},
PlotRange -> All], AxesLabel -> {"x", "u(x)"}, PlotRange -> All,
BaseStyle -> {Bold, FontSize -> 12},
PlotLabel -> "Exact and numerical solution, \[Nu]=0.05 and \[Delta]=0.01"]


We consider the error $$e_n=h\sum_{i=1}^n |u(x_i)-u_i|$$. This is a Riemann sum corresponding to $$\int_{-1}^1 |u(x)-\tilde u_n(x)|dx$$, where $$\tilde u_n(x)$$ is an interpolation of $$\{(x_i,u_i)\}_{i=0}^n$$. As the following figure in log-log scale shows, $$e_n\propto n^{-2}$$:

delta = 0.01; {A, zex} = Azex[nu, delta];
rangen = {500, 1000, 2000, 4000, 8000, 16000, 32000};
error = Table[
h = 2/n;
mesh = -1 + h*Range[0, n];
exactSolMesh = -A*Tanh[A*(# - zex)/(2*nu)] & /@ mesh;
approxSolMesh = fdmODE[nu, delta, n][[All, 2]];
h*Total@Abs[exactSolMesh - approxSolMesh],
{n, rangen}
];
ListLogLogPlot[Thread[{rangen, error}], Joined -> True, Mesh -> All,
AxesLabel -> {"n", "\!$$\*SubscriptBox[\(e$$, $$n$$]\)"},
BaseStyle -> {Bold, FontSize -> 13}]


The system of ODEs for question 2 can also be solved in a similar manner:

p = 10; P = p + 1;

basis = Expand[Orthogonalize[Z^Range[0, p], Integrate[#1 #2 *10, {Z, 0, 1/10}] &]];

region = {Z \[Distributed] UniformDistribution[{0, 1/10}]};

mat = ConstantArray[0, {P, P, P}];
Do[mat[[l, j, k]] = Expectation[basis[[k]]*basis[[j]]*basis[[l]], region], {k, 1,
P}, {j, 1, k}, {l, 1, j}];
Do[mat[[l, j, k]] = mat[[##]] & @@ Sort[{l, j, k}], {k, 1, P}, {j, 1, P}, {l, 1, P}];

cond1 = Table[Expectation[(1 + Z)*basis[[j]], region], {j, 1, P}];
cond2 = ConstantArray[0, P]; cond2[[1]] = -1;

fdmODEGalerkin[nu_, n_, P_] := Module[{h, mesh, f, u, v, uu, vv, eqns, sv, froot, sol, coeffi, x},
h = 2/n;
mesh = -1 + h*Range[0, n];
f[{u_List, v_List}] := {v, (1/nu)*Table[Sum[
v[[j]]*u[[i]]*mat[[i, j, k]], {i, 1, P}, {j, 1, P}], {k, 1, P}]};
u = Table[uu[i, #], {i, 1, P}] &;
v = Table[vv[i, #], {i, 1, P}] &;
Flatten[Table[Thread[u[i] == u[i - 1] +
0.5*h*(f[{u[i - 1], v[i - 1]}][[1]] +
f[{u[i], v[i]}][[1]])], {i, 1, n}], 1]~Join~
v[i - 1] + 0.5*h*(f[{u[i - 1], v[i - 1]}][[2]] +
f[{u[i], v[i]}][[2]])], {i, 1, n}], 1];
sv = Flatten[Table[Thread[{#, 0} &@u[i]], {i, 0, n}], 1]~Join~
Flatten[Table[Thread[{#, 0} &@v[i]], {i, 0, n}], 1];
froot = FindRoot[eqns, sv];
sol = Table[u[i], {i, 0, n}] /. froot;

n = 300;

Remark: For question 1, I also tried with the classical Runge-Kutta method for the first-order ODE, but for $$n>1000$$ points it broke down. This is an issue of stiff equations. Only A-stable methods can solve numerically this type of ODEs. Explicit methods (in particular the classical Runge-Kutta scheme) are not A-stable. Only implicit methods are A-estable, whose order is at most 2. Hence, it seems that the trapezoidal method is optimal in this case. See Chapter 4 in A First Course in the Numerical Analysis of Differential Equations, by A. Iserles.