Nonlinear problems
1. A model problem
We consider the following nonlinear Poisson equation:
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,…,d−1. The coordinates are now represented by the symbols x0,…,xd−1 . The exact solution is then
The variational formulation of our model problem reads: find u∈V such that
where
and
The discrete problem arises as usual by restricting V and V0 to a pair of discrete spaces. The problem reads: find u∈V such that
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
The iterations require an initial guess u0. The hope is that un→u 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+1∈V such that
with
since this is a linear problem in the unknown un+1, we can equivalently use the formulation
with
The iterations can be stopped when ϵ≡‖un+1−un‖<ε] , where ε is small, say 10−5, 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)
Newton’s method for the system Fi(U1,…,Uj)=0,i=1,…,N can be formulated as
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
The following results were used to obtain [der_incr_2]
We can reformulate the Jacobian matrix in [der] by introducing the short notation un=∑Nj=1Unjϕj
We need to formulate a corresponding variational problem. Looking at the linear system of equations in Newton’s method,
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
Equation [discr_form] fits the standard form a(δu,v)=ℓ(v) with
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
reaction=0:x:y
the configuration is read in the code like this
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" );
Then we set the diffusion terms
diffusion=(1+u)^m:x:y:m:u
the configuration is read in the code like this
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" );
We then define the jacobian of the functional
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() ) );
};
and the associated 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();
}
we used Nitsche’s method to define the Dirichlet boundary conditions in a weak way. |
Finally we solve the nonlinear problem:
u.zero();
backend()->nlSolver()->residual = Residual;
backend()->nlSolver()->jacobian = Jacobian;
backend()->nlSolve( _solution=u );
The full code is available here.