Nonlinear problems

1. A model problem

We consider the following nonlinear Poisson equation:

(k(u)u)=f

k(u) makes the equation nonlinear if is not constant in u. In order to verify the solution procedure, we choose the domain, k(u),f, and the boundary conditions such that we have a simple, exact solution u. Denote Ω the unit hypercube [0,1]d in d dimensions, k(u)=(1+u)m,f=0,u=0 for x0=0,u=1 for x0=1, and u/n=0 at all other boundaries xi=0 and xi=1,i=1,,d1. The coordinates are now represented by the symbols x0,,xd1 . The exact solution is then

u(x0,,xd)=((2m+11)x0+1)1/(m+1)1

The variational formulation of our model problem reads: find uV such that

F(u;v)=0vV0

where

F(u;v)=Ωk(u)uvdx

and

V0={vH1(Ω):v=0 on x0=0 and x0=1}V={vH1(Ω):v=0 on x0=0 and v=1 on x0=1}

The discrete problem arises as usual by restricting V and V0 to a pair of discrete spaces. The problem reads: find uV such that

F(u;v)=0vV0

with u=Nj=1Ujϕj where U=(Uj)Tj=1,...,N are the components of u in the basis (ϕj)j=1,...,N of V. We have a system of nonlinear algebraic equations to solve.

2. Picard strategy

The Picard strategy is also known as the method of successive substitutions.

Picard iteration is an easy way of handling nonlinear PDEs: it uses a known, previous solution in the nonlinear terms so that these terms become linear in the unknown u.

For our particular problem, we use a known, previous solution in the coefficient k(u). Given a solution un from iteration n, we seek a new (hopefully improved) solution un+1 in iteration n+1 such that un+1 solves the linear problem

(k(un)un+1)=0,n=0,1,

The iterations require an initial guess u0. The hope is that unu as n, and that un+1 is sufficiently close to the exact solution u of the discrete problem after just a few iterations.

We can formulate a variational problem for un+1. Equivalently, we can approximate k(u) by k(un) to obtain the same linear variational problem. In both cases, the problem consists of seeking un+1V such that

˜F(un+1;v)=0vV0,n=0,1,

with

˜F(un+1;v)=Ωk(un)un+1v

since this is a linear problem in the unknown un+1, we can equivalently use the formulation

a(un+1,v)=(v)

with

a(u,v)=Ωk(un)uv(v)=0

The iterations can be stopped when ϵun+1un<ε] , where ε is small, say 105, or when the number of iterations exceed some critical limit. The latter case will detect divergence of the method or unacceptable slow convergence.

In the solution algorithm we only need to store un and un+1.

3. Newton strategy at the algebraic level

After having discretized our nonlinear PDE problem, we may use Newton’s method to solve the system of nonlinear algebraic equations. From the continuous variational problem, the discrete version results in a system of equations for the unknown parameters U1,,UN (by inserting u=Nj=1Ujϕj and v=ˆϕi)

Fi(U1,,UN)Nj=1Ω(k(N=1Uϕ)ϕjUj)ˆϕidx=0,i=1,,N

Newton’s method for the system Fi(U1,,Uj)=0,i=1,,N can be formulated as

Nj=1UjFi(Un1,,UnN)δUj=Fi(Un1,,UnN),i=1,,NUk+1j=Unj+ωδUj,j=1,,N

where ω[0,1] is a relaxation parameter, and n is an iteration index. An initial guess u0 must be provided to start the algorithm. The original Newton method has ω=1, but in problems where it is difficult to obtain convergence, so-called under-relaxation with ω<1 may help.

We need to compute the Jacobian matrix Fi/Uj and the right-hand side vector Fi. Our present problem has Fi given by [F_i]. The derivative Fi/Uj becomes

Ω[k(N=1Unϕ)ϕj(Nj=1Unjϕj)ˆϕi+k(N=1Unϕ)ϕjˆϕi]

The following results were used to obtain [der_incr_2]

uUj=UjNj=1Ujϕj=ϕj,Uju=ϕj,Ujk(u)=k(u)ϕj

We can reformulate the Jacobian matrix in [der] by introducing the short notation un=Nj=1Unjϕj

FiUj=Ω[k(un)ϕjunˆϕi+k(un)ϕjˆϕi]

We need to formulate a corresponding variational problem. Looking at the linear system of equations in Newton’s method,

Nj=1FiUjδUj=Fi,i=1,,N

we can introduce v as a general test function replacing ˆϕi, and we can identify the unknown δu= Nj=1δUjϕj. From the linear system we can now go "backwards" to construct the corresponding discrete weak form

Ω[k(un)δuunv+k(un)δuv]=Ωk(un)unvdx

Equation [discr_form] fits the standard form a(δu,v)=(v) with

a(δu,v)=Ω[k(un)δuunv+k(un)δuv]dx(v)=Ωk(un)unv
the important feature in Newton’s method that the previous solution un replaces u in the formulas when computing the matrix Fi/Uj and vector Fi for the linear system in each Newton iteration.

3.1. Implementation of Newton method

We have implemented in Feel++ the model problem.

First we need to define the coefficients. We set the reaction terms to 0 in the configuration file

Configure of the reaction term in the .cfg file
reaction=0:x:y
ini

the configuration is read in the code like this

C++ code to set the reaction terms
auto reaction = expr( option(_name="reaction").as<std::string>(), "u", idv(u), "reaction" );
auto reaction_diff = diff( option(_name="reaction").as<std::string>(), "u", "u", idv(u), "reaction_diff"  );
cpp

Then we set the diffusion terms

Configure of the reaction terms in the .cfg file
diffusion=(1+u)^m:x:y:m:u
ini

the configuration is read in the code like this

Feel++ code to set the diffusion terms
auto diffusion = expr( option(_name="diffusion").as<std::string>(), "u", idv(u), "diffusion" );
auto diffusion_diff = diff( option(_name="diffusion").as<std::string>(), "u", "u", idv(u), "diffusion_diff" );
cpp

We then define the jacobian of the functional

Feel++ code to define the jacobian
auto Jacobian = [=](const vector_ptrtype& X, sparse_matrix_ptrtype& J)
    {
        if (!J) J = backend()->newMatrix( _test=Vh, _trial=Vh );
        auto a = form2( _test=Vh, _trial=Vh, _matrix=J );
        a = integrate( _range=elements( mesh ), _expr=(diffusion*gradt( u )+diffusion_diff*idt(u)*gradv(u))*trans( grad( v ) ) );
        a += integrate( _range=elements( mesh ), _expr=reaction_diff*idt( u )*id( v ) );
        a += integrate( _range=boundaryfaces( mesh ),
                        _expr=
                        ( - trans( id( v ) )*( (diffusion*gradt( u )+diffusion_diff*idt(u)*gradv(u))*N() )
                          + trans( idt( u ) )*( (diffusion+diffusion_diff*idv(u))*grad( v )*N() )
                          + penalbc*trans( idt( u ) )*id( v )/hFace() ) );
    };
cpp

and the associated residual

Feel++ code to define the residual
    auto Residual = [=](const vector_ptrtype& X, vector_ptrtype& R)
        {
            auto u = Vh->element();
            u = *X;
            auto r = form1( _test=Vh, _vector=R );
            r = integrate( _range=elements( mesh ), _expr=diffusion*gradv( u )*trans( grad( v ) ) );
            r +=  integrate( _range=elements( mesh ),  _expr=reaction*id( v ) );
            r +=  integrate( _range=boundaryfaces( mesh ),
                             _expr=
                             ( - diffusion*trans( id( v ) )*( diffusion*gradv( u )*N() )
                               + diffusion*trans( idv( u ) )*( diffusion*grad( v )*N() )
                               + penalbc*trans( idv( u ) )*id( v )/hFace() ) );
        };
    u.zero();
    backend()->nlSolver()->residual = Residual;
    backend()->nlSolver()->jacobian = Jacobian;
    backend()->nlSolve( _solution=u );
    auto e = exporter( _mesh=mesh );
    e->add( "u", u );
    e->save();
}
cpp
we used Nitsche’s method to define the Dirichlet boundary conditions in a weak way.

Finally we solve the nonlinear problem:

Feel++ code to solve the non-linear problem
u.zero();
backend()->nlSolver()->residual = Residual;
backend()->nlSolver()->jacobian = Jacobian;
backend()->nlSolve( _solution=u );
cpp

The full code is available here.