This part of the tutorial assumes that the reader is familiar with the solution of linear problems (Part I). We will begin with explaining the basics of the Newton’s method for nonlinear PDE problems, and then illustrate it on examples with gradually increasing complexity. At the end of Part III the reader will be able to solve time-dependent nonlinear multiphysics PDE systems.
Consider a simple model problem of the form
(1)
Note that when using the Newton’s method, it is customary to have everything on the left-hand side. The corresponding discrete problem has the form
where
are the standard test functions and
Here
is the vector of unknown coefficients.
The nonlinear discrete problem can be written in the compact form
where
is the residual vector defined by
The Jacobi matrix
has the same sparsity structure as the
standard stiffness matrix that we know from linear problems. In fact, when the
problem is linear then the Jacobi matrix and the stiffness matrix are the same
thing. Using the chain rule of differentiation, we calculate that on the
position
, the Jacobi matrix has the value
To this end, note that
and
Using these relations, we obtain
Let’s assume that the Jacobi matrix has been assembled. The Newton’s method is written formally as
but a more practical formula to work with is
This is a system of linear algebraic equations that needs to be solved in every Newton’s
iteration. The Newton’s method will stop when
is sufficiently close
to the zero vector.
In the linear case we have
where
is a constant stiffness matrix and
a load vector.
The Newton’s method is now
Therefore, the Newton’s method will converge in one iteration.
Git reference: Tutorial example 15-newton-elliptic-1.
Let us solve the nonlinear model problem from the previous section,
One possible interpretation of this equation is stationary heat transfer where the thermal
conductivity
depends on the temperature
.
Our domain is a square
,
, and the nonlinearity
has the form
Recall that
must be entirely positive or entirely negative for the problem to be solvable, so it is safe
to restrict
to be an even nonnegative integer. Recall from the previous section that
and
In the code, this becomes
// Heat sources (can be a general function of 'x' and 'y').
template<typename Real>
Real heat_src(Real x, Real y)
{
return 1.0;
}
// Jacobian matrix.
template<typename Real, typename Scalar>
Scalar jac(int n, double *wt, Func<Scalar> *u_ext[], Func<Real> *u, Func<Real> *v, Geom<Real> *e, ExtData<Scalar> *ext)
{
Scalar result = 0;
Func<Scalar>* u_prev = ext->fn[0];
for (int i = 0; i < n; i++)
result += wt[i] * (dlam_du(u_prev->val[i]) * u->val[i] * (u_prev->dx[i] * v->dx[i] + u_prev->dy[i] * v->dy[i])
+ lam(u_prev->val[i]) * (u->dx[i] * v->dx[i] + u->dy[i] * v->dy[i]));
return result;
}
// Residual vector.
template<typename Real, typename Scalar>
Scalar res(int n, double *wt, Func<Scalar> *u_ext[], Func<Real> *v, Geom<Real> *e, ExtData<Scalar> *ext)
{
Scalar result = 0;
Func<Scalar>* u_prev = ext->fn[0];
for (int i = 0; i < n; i++)
result += wt[i] * (lam(u_prev->val[i]) * (u_prev->dx[i] * v->dx[i] + u_prev->dy[i] * v->dy[i])
- heat_src(e->x[i], e->y[i]) * v->val[i]);
return result;
}
Notice that the basis function
and the test function
are entering the weak forms via the parameters u and v, respectively (same as for linear
problems). The user does not have to
take care about their indices
and
, this is handled by Hermes outside the weak forms.
The code snippet above also shows how values and derivatives of the solution
can be accessed via
the ExtData structure, and the coordinates of the integration points via the Geom structure.
The contents of ExtData is user-defined and the Geom structure contains geometrical information
including the unit normal and tangential vectors to the boundary at the integration points
(also for curved boundaries). See the file
src/forms.h for more details.
The Newton’s method always has to start from an initial condition, and in this example this is
// Initial condition. It will be projected on the FE mesh
// to obtain initial coefficient vector for the Newton's method.
scalar init_cond(double x, double y, double& dx, double& dy)
{
dx = 0;
dy = 0;
return INIT_COND_CONST;
}
The weak forms are registered as usual, except that the previous solution u_prev is passed into the form as an extra argument:
// Previous solution for the Newton's iteration.
Solution u_prev;
// Initialize the weak formulation.
WeakForm wf;
wf.add_matrix_form(callback(jac), H2D_UNSYM, H2D_ANY, &u_prev);
wf.add_vector_form(callback(res), H2D_ANY, &u_prev);
Recall that by H2D_UNSYM we declare that the Jacobian bilinear form is not symmetric, and by H2D_ANY that the form should be used for elements with any material marker.
The NonlinSystem class is initialized in the same way as LinSystem:
// Initialize the linear system.
NonlinSystem nls(&wf, &space);
An important step in the Newton’s method that cannot be skipped is the projection
of the initial condition on the FE space. This is where the initial coefficient
vector
for the Newton’s iteration is created:
// Project the function init_cond() on the FE space
// to obtain initial coefficient vector for the Newton's method.
info("Projecting initial condition to obtain initial vector for the Newton'w method.");
nls.project_global(init_cond, &u_prev);
The method project_global() has an optional third argument which is the projection
norm. Its default value is H2D_DEFAULT_PROJ_NORM = 1 (
norm). Other
admissible values are 0 (
norm), 2 (
norm) and 3 (
norm) whose
use will be shown later. Later we’ll also see how to handle the projection for PDE systems.
The Newton’s iteration is done using the method solve_newton():
// Perform Newton's iteration.
info("Performing Newton's iteration.");
bool verbose = true; // Default is false.
if (!nls.solve_newton(&u_prev, NEWTON_TOL, NEWTON_MAX_ITER, verbose))
error("Newton's method did not converge.");
If the optional parameter “verbose” is set to “true”, convergence information is printed.
Note that arbitrary Filters can be passed as additional optional parameters. This will be shown in the tutorial example 19-timedep-flame. Results for this example are shown below.
Approximate solution
for
:

Approximate solution
for
:

Git reference: Tutorial example 16-newton-elliptic-2.
We will solve the nonlinear model problem from the previous section again,
but now with nonhomogeneous Dirichlet boundary conditions
and with a general initial guess init_guess(x,y).
The treatment of the Dirichlet boundary conditions in the code looks as follows:
// This function is used to define Dirichlet boundary conditions.
double dir_lift(double x, double y, double& dx, double& dy) {
dx = (y+10)/10.;
dy = (x+10)/10.;
return (x+10)*(y+10)/100.;
}
// Boundary condition types.
BCType bc_types(int marker)
{
return BC_ESSENTIAL;
}
// Essential (Dirichlet) boundary condition values.
scalar essential_bc_values(int ess_bdy_marker, double x, double y)
{
double dx, dy;
return dir_lift(x, y, dx, dy);
}
The initial condition has the form:
// Initial condition. It will be projected on the FE mesh
// to obtain initial coefficient vector for the Newton's method.
scalar init_cond(double x, double y, double& dx, double& dy)
{
// Using the Dirichlet lift elevated by two
double val = dir_lift(x, y, dx, dy) + 2;
return val;
}
The initial condition must be projected on the finite element space
in order to obtain the initial coefficient vector
for the Newton’s
iteration:
// Project the function init_cond() on the FE space
// to obtain initial coefficient vector for the Newton's method.
info("Projecting initial condition to obtain initial vector for the Newton'w method.");
nls.project_global(init_cond, &u_prev);
Recall that the vector
can be retrieved from the NonLinSystem
class using the method get_solution_vector().
The following figure shows the
-projection of the initial condition init_cond():

The Newton’s iteration is again performed using
// Perform Newton's iteration.
info("Performing Newton's iteration.");
bool verbose = true; // Default is false.
if (!nls.solve_newton(&u_prev, NEWTON_TOL, NEWTON_MAX_ITER, verbose))
error("Newton's method did not converge.");
The converged solution looks as follows:

Git reference: Tutorial example 17-newton-elliptic-adapt.
We will still keep the simple model problem
equipped with nonhomogeneous Dirichlet boundary conditions
but this time it will be solved using automatic adaptivity. As usual in Hermes, adaptivity will be guided by the difference between a coarse and fine mesh approximations. At the beginning, the initial condition is projected on the coarse mesh:
// Project the function init_cond() on the FE space
// to obtain initial coefficient vector for the Newton's method.
info("Projecting initial condition to obtain initial vector on coarse mesh.");
nls.project_global(init_cond, &u_prev);
Then we solve the nonlinear problem on the coarse mesh and store the coarse mesh solution:
// Newton's loop on the coarse mesh.
info("Solving on coarse mesh.");
bool verbose = true; // Default is false.
if (!nls.solve_newton(&u_prev, NEWTON_TOL_COARSE, NEWTON_MAX_ITER, verbose))
error("Newton's method did not converge.");
// Store the result in sln_coarse.
sln_coarse.copy(&u_prev);
Note that storing the solution u_prev in sln_coarse is equivalent to storing the
converged coefficient vector
, but the Solution can be passed into weak
forms.
Next a refinement selector is initialized:
// Initialize a refinement selector.
H1ProjBasedSelector selector(CAND_LIST, CONV_EXP, H2DRS_DEFAULT_ORDER);
Then the nonlinear problem on the fine mesh is initialized and the initial
coefficient vector
on the fine mesh is calculated:
// Initialize the fine mesh problem.
RefSystem rnls(&nls);
// Set initial condition for the Newton's method on the fine mesh.
if (as == 1) {
info("Projecting coarse mesh solution to obtain initial vector on new fine mesh.");
rnls.project_global(&sln_coarse, &u_prev);
}
else {
info("Projecting fine mesh solution to obtain initial vector on new fine mesh.");
rnls.project_global(&sln_fine, &u_prev);
}
Notice that we only use sln_coarse as the initial guess on the fine mesh in the first adaptivity step when we do not have any fine mesh solution yet, otherwise a projection of the last fine mesh solution is used.
Note that the procedure explained here is what we typically do and the reader does not have to follow it. It is possible to start the Newton’s method on the fine mesh using zero or any other initial condition.
Next we perform the Newton’s loop on the fine mesh and store the result in sln_fine:
// Newton's loop on the fine mesh.
info("Solving on fine mesh.");
if (!rnls.solve_newton(&u_prev, NEWTON_TOL_FINE, NEWTON_MAX_ITER, verbose))
error("Newton's method did not converge.");
// Store the fine mesh solution in sln_fine.
sln_fine.copy(&u_prev);
Now we have the solution pair to guide automatic adaptivity, and we can calculate the error estimate:
// Calculate element errors and total error estimate.
info("Calculating error.");
H1Adapt hp(&nls);
hp.set_solutions(&sln_coarse, &sln_fine);
err_est = hp.calc_error() * 100;
After adapting the mesh, we must not forget to calculate a new initial coefficient
vector
on the new coarse mesh. This can be done either by just projecting
the fine mesh solution onto the new coarse mesh, or by solving (in addition to that)
the nonlinear problem on the new coarse mesh:
// If err_est too large, adapt the mesh.
if (err_est < ERR_STOP) done = true;
else {
info("Adapting coarse mesh.");
done = hp.adapt(&selector, THRESHOLD, STRATEGY, MESH_REGULARITY);
if (nls.get_num_dofs() >= NDOF_STOP) {
done = true;
break;
}
// Project the fine mesh solution on the new coarse mesh.
if (SOLVE_ON_COARSE_MESH)
info("Projecting fine mesh solution to obtain initial vector on new coarse mesh.");
else
info("Projecting fine mesh solution on coarse mesh for error calculation.");
nls.project_global(&sln_fine, &u_prev);
if (SOLVE_ON_COARSE_MESH) {
// Newton's loop on the new coarse mesh.
info("Solving on coarse mesh.");
if (!nls.solve_newton(&u_prev, NEWTON_TOL_COARSE, NEWTON_MAX_ITER, verbose))
error("Newton's method did not converge.");
}
// Store the result in sln_coarse.
sln_coarse.copy(&u_prev);
In our experience, the Newton’s loop on the new coarse mesh can be skipped since this does not affect convergence and one saves some CPU time. This is illustrated in the following convergence comparison:
Convergence in the number of DOF (with and without Newton solve on the new coarse mesh):

Convergence in CPU time (with and without Newton solve on coarse mesh):

In the following we show the resulting meshes (corresponding to SOLVE_ON_COARSE_MESH = false). The solution itself is not shown since the reader knows it from the previous example.
Resulting coarse mesh.

Resulting fine mesh.

Git reference: Tutorial example 18-newton-timedep-heat.
We will employ the Newton’s method to solve a nonlinear parabolic PDE discretized in time by the implicit Euler method. To keep things simple, our model problem is a time-dependent version of the nonlinear equation used in the previous three sections,
We prescribe nonhomogeneous Dirichlet boundary conditions
and the same function is used to define the initial condition. The
problem will be solved in the square
and time interval
.
The weak formulation of the time-discretized problem reads
where the indices
and
indicate the previous and new time level, respectively. Hence in each
time step we need to solve a time-independent nonlinear problem, and this is something we learned
in the previous sections. The weak forms for the Newton’s method from the previous sections only
need to be enhanced with a simple term containing the time step
(called TAU):
// Jacobian matrix.
template<typename Real, typename Scalar>
Scalar jac(int n, double *wt, Func<Scalar> *u_ext[], Func<Real> *u, Func<Real> *v, Geom<Real> *e, ExtData<Scalar> *ext)
{
Scalar result = 0;
Func<Scalar>* u_prev_newton = ext->fn[0];
for (int i = 0; i < n; i++)
result += wt[i] * (u->val[i] * v->val[i] / TAU + dlam_du(u_prev_newton->val[i]) * u->val[i] *
(u_prev_newton->dx[i] * v->dx[i] + u_prev_newton->dy[i] * v->dy[i])
+ lam(u_prev_newton->val[i]) * (u->dx[i] * v->dx[i] + u->dy[i] * v->dy[i]));
return result;
}
Here the function u_prev_newton plays the role of u_prev from the previous sections - this is the
previous solution inside the Newton’s iteration. Note that the previous time level solution
that we call u_prev_time is not present in the Jacobian matrix. It is used in the residual only:
// Fesidual vector.
template<typename Real, typename Scalar>
Scalar res(int n, double *wt, Func<Scalar> *u_ext[], Func<Real> *v, Geom<Real> *e, ExtData<Scalar> *ext)
{
Scalar result = 0;
Func<Scalar>* u_prev_newton = ext->fn[0];
Func<Scalar>* u_prev_time = ext->fn[1];
for (int i = 0; i < n; i++)
result += wt[i] * ((u_prev_newton->val[i] - u_prev_time->val[i]) * v->val[i] / TAU +
lam(u_prev_newton->val[i]) * (u_prev_newton->dx[i] * v->dx[i] + u_prev_newton->dy[i] * v->dy[i])
- heat_src(e->x[i], e->y[i]) * v->val[i]);
return result;
}
Note that the function u_prev_newton evolves during the Newton’s iteration but the previous time level solution u_prev_time only is updated after the time step is finished. The weak forms are registered as usual:
// Initialize the weak formulation.
WeakForm wf;
wf.add_matrix_form(callback(jac), H2D_UNSYM, H2D_ANY, &u_prev_newton);
wf.add_vector_form(callback(res), H2D_ANY, Tuple<MeshFunction*>(&u_prev_newton, &u_prev_time));
The entire time-stepping loop (minus visualization) looks as follows:
// Time stepping loop:
double current_time = 0.0;
int t_step = 1;
do {
info("---- Time step %d, t = %g s.", t_step, current_time); t_step++;
// Newton's method.
info("Performing Newton's method.");
bool verbose = true; // Default is false.
if (!nls.solve_newton(&u_prev_newton, NEWTON_TOL, NEWTON_MAX_ITER, verbose))
error("Newton's method did not converge.");
// Update previous time level solution.
u_prev_time.copy(&u_prev_newton);
// Update time.
current_time += TAU;
} while (current_time < T_FINAL);
The stationary solution is not shown here since we already saw it in the previous sections.
Git reference: Tutorial example 19-newton-timedep-flame.
We will employ the Newton’s method to solve a nonlinear system of two parabolic equations describing a very simple flame propagation model (laminar flame, no fluid mechanics involved). The computational domain shown below contains in the middle a narrow portion (cooling rods) whose purpose is to slow down the chemical reaction:

The equations for the temperature
and species concentration
have the form
Boundary conditions are Dirichlet
and
on the inlet,
Newton
on the cooling rods,
and Neumann
,
elsewhere.
The objective of the computation is to obtain the reaction rate defined
by the Arrhenius law,
Here
is the gas expansion coefficient in a flow with nonconstant density,
the non-dimensional activation energy, and
the Lewis number (ratio of diffusivity of heat and diffusivity
of mass). Both
,
and
,
are dimensionless and so is the time
.
Time integration is performed using a second-order implicit BDF formula
that is obtained using a combination of the following two first-order methods:
and
Problem parameters are chosen as
// Problem constants
const double Le = 1.0;
const double alpha = 0.8;
const double beta = 10.0;
const double kappa = 0.1;
const double x1 = 9.0;
It is worth mentioning that the initial conditions for
and
,
// Initial conditions.
scalar temp_ic(double x, double y, scalar& dx, scalar& dy)
{ return (x <= x1) ? 1.0 : exp(x1 - x); }
scalar conc_ic(double x, double y, scalar& dx, scalar& dy)
{ return (x <= x1) ? 0.0 : 1.0 - exp(Le*(x1 - x)); }
are defined as exact functions:
// Set initial conditions.
t_prev_time_1.set_exact(&mesh, temp_ic); c_prev_time_1.set_exact(&mesh, conc_ic);
t_prev_time_2.set_exact(&mesh, temp_ic); c_prev_time_2.set_exact(&mesh, conc_ic);
t_prev_newton.set_exact(&mesh, temp_ic); c_prev_newton.set_exact(&mesh, conc_ic);
Here the pairs of solutions (t_prev_time_1, y_prev_time_1) and (t_prev_time_2, y_prev_time_2)
correspond to the two first-order time-stepping methods described above. and
(t_prev_newton, y_prev_newton) are used to store the previous step approximation
in the Newton’s method. The reaction rate
and its derivatives are handled
via Filters:
// Define filters for the reaction rate omega.
DXDYFilter omega(omega_fn, &t_prev_newton, &y_prev_newton);
DXDYFilter omega_dt(omega_dt_fn, &t_prev_newton, &y_prev_newton);
DXDYFilter omega_dy(omega_dy_fn, &t_prev_newton, &y_prev_newton);
Details on the functions omega_fn, omega_dt_fn, omega_dy_fn and the weak forms can be found in the file forms.cpp Here is how we register the weak forms,
// Initialize the weak formulation.
WeakForm wf(2);
wf.add_matrix_form(0, 0, callback(newton_bilinear_form_0_0), H2D_UNSYM, H2D_ANY, &omega_dt);
wf.add_matrix_form_surf(0, 0, callback(newton_bilinear_form_0_0_surf), 3);
wf.add_matrix_form(0, 1, callback(newton_bilinear_form_0_1), H2D_UNSYM, H2D_ANY, &omega_dc);
wf.add_matrix_form(1, 0, callback(newton_bilinear_form_1_0), H2D_UNSYM, H2D_ANY, &omega_dt);
wf.add_matrix_form(1, 1, callback(newton_bilinear_form_1_1), H2D_UNSYM, H2D_ANY, &omega_dc);
wf.add_vector_form(0, callback(newton_linear_form_0), H2D_ANY,
Tuple<MeshFunction*>(&t_prev_newton, &t_prev_time_1, &t_prev_time_2, &omega));
wf.add_vector_form_surf(0, callback(newton_linear_form_0_surf), 3, &t_prev_newton);
wf.add_vector_form(1, callback(newton_linear_form_1), H2D_ANY,
Tuple<MeshFunction*>(&c_prev_newton, &c_prev_time_1, &c_prev_time_2, &omega));
The nonlinear system is initialized as follows:
// Initialize the nonlinear system.
NonlinSystem nls(&wf, Tuple<Space*>(&tspace, &cspace));
The initial coefficient vector
for the Newton’s method is calculated
by projecting the initial conditions on the FE spaces:
// Project temp_ic() and conc_ic() onto the FE spaces to obtain initial
// coefficient vector for the Newton's method.
info("Projecting initial conditions to obtain initial vector for the Newton'w method.");
nls.project_global(Tuple<MeshFunction*>(&t_prev_newton, &c_prev_newton),
Tuple<Solution*>(&t_prev_newton, &c_prev_newton));
The time stepping loop looks as follows, notice the visualization of
through a DXDYFilter:
// Time stepping loop:
double current_time = 0.0; int ts = 1;
do {
info("---- Time step %d, t = %g s.", ts, current_time);
// Newton's method.
info("Performing Newton's iteration.");
bool verbose = true; // Default is false.
if (!nls.solve_newton(Tuple<Solution*>(&t_prev_newton, &c_prev_newton), NEWTON_TOL, NEWTON_MAX_ITER, verbose,
Tuple<MeshFunction*>(&omega, &omega_dt, &omega_dc))) error("Newton's method did not converge.");
// Visualization.
DXDYFilter omega_view(omega_fn, &t_prev_newton, &c_prev_newton);
rview.set_min_max_range(0.0,2.0);
char title[100];
sprintf(title, "Reaction rate, t = %g", current_time);
rview.set_title(title);
rview.show(&omega_view);
// Update current time.
current_time += TAU;
// Store two time levels of previous solutions.
t_prev_time_2.copy(&t_prev_time_1);
c_prev_time_2.copy(&c_prev_time_1);
t_prev_time_1.copy(&t_prev_newton);
c_prev_time_1.copy(&c_prev_newton);
ts++;
} while (current_time <= T_FINAL);
A few snapshots of the reaction rate
at various times are shown below:




Git reference: Tutorial example 21-newton-timedep-gp.
In this example we use the Newton’s method to solve the nonlinear complex-valued time-dependent Gross-Pitaevski equation. This equation describes the ground state of a quantum system of identical bosons using the Hartree–Fock approximation and the pseudopotential interaction model. For time-discretization one can use either the first-order implicit Euler method or the second-order Crank-Nicolson method.
The computational domain is the square
and boundary conditions are zero Dirichlet. The equation has the form
where
is the unknown solution (wave function),
the complex unit,
the Planck constant,
the mass of the boson,
the coupling constant (proportional to the scattering length of two interacting bosons) and
the frequency.
From the implementation point of view, the only detail worth mentioning is the use of the complex version of Hermes in the file CMakeLists.txt:
# use the complex version of the library:
set(HERMES ${HERMES_CPLX_BIN})
The weak forms can be found in the file forms.cpp:
// Residuum for the implicit Euler time discretization
template<typename Real, typename Scalar>
Scalar F_euler(int n, double *wt, Func<Scalar> *u_ext[], Func<Real> *v, Geom<Real> *e, ExtData<Scalar> *ext)
{
scalar ii = cplx(0.0, 1.0); // imaginary unit, ii^2 = -1
Scalar result = 0;
Func<Scalar>* psi_prev_newton = ext->fn[0];
Func<Scalar>* psi_prev_time = ext->fn[1];
for (int i = 0; i < n; i++)
result += wt[i] * (ii * H * (psi_prev_newton->val[i] - psi_prev_time->val[i]) * v->val[i] / TAU
- H*H/(2*M) * (psi_prev_newton->dx[i] * v->dx[i] + psi_prev_newton->dy[i] * v->dy[i])
- G * psi_prev_newton->val[i] * psi_prev_newton->val[i] * conj(psi_prev_newton->val[i]) * v->val[i]
- .5*M*OMEGA*OMEGA * (e->x[i] * e->x[i] + e->y[i] * e->y[i]) * psi_prev_newton->val[i] * v->val[i]);
return result;
}
// Jacobian for the implicit Euler time discretization
template<typename Real, typename Scalar>
Scalar J_euler(int n, double *wt, Func<Scalar> *u_ext[], Func<Real> *u, Func<Real> *v, Geom<Real> *e, ExtData<Scalar> *ext)
{
scalar ii = cplx(0.0, 1.0); // imaginary unit, ii^2 = -1
Scalar result = 0;
Func<Scalar>* psi_prev_newton = ext->fn[0];
for (int i = 0; i < n; i++)
result += wt[i] * (ii * H * u->val[i] * v->val[i] / TAU
- H*H/(2*M) * (u->dx[i] * v->dx[i] + u->dy[i] * v->dy[i])
- 2* G * u->val[i] * psi_prev_newton->val[i] * conj(psi_prev_newton->val[i]) * v->val[i]
- G * psi_prev_newton->val[i] * psi_prev_newton->val[i] * u->val[i] * v->val[i]
- .5*M*OMEGA*OMEGA * (e->x[i] * e->x[i] + e->y[i] * e->y[i]) * u->val[i] * v->val[i]);
return result;
}
// Residuum for the Crank-Nicolson method
template<typename Real, typename Scalar>
Scalar F_cranic(int n, double *wt, Func<Scalar> *u_ext[], Func<Real> *v, Geom<Real> *e, ExtData<Scalar> *ext)
{
scalar ii = cplx(0.0, 1.0); // imaginary unit, ii^2 = -1
Scalar result = 0;
Func<Scalar>* psi_prev_newton = ext->fn[0];
Func<Scalar>* psi_prev_time = ext->fn[1];
for (int i = 0; i < n; i++)
result += wt[i] * (ii * H * (psi_prev_newton->val[i] - psi_prev_time->val[i]) * v->val[i] / TAU
- 0.5*H*H/(2*M) * (psi_prev_newton->dx[i] * v->dx[i] + psi_prev_newton->dy[i] * v->dy[i])
- 0.5*H*H/(2*M) * (psi_prev_time->dx[i] * v->dx[i] + psi_prev_time->dy[i] * v->dy[i])
- 0.5*G * psi_prev_newton->val[i] * psi_prev_newton->val[i] * conj(psi_prev_newton->val[i]) * v->val[i]
- 0.5*G * psi_prev_time->val[i] * psi_prev_time->val[i] * conj(psi_prev_time->val[i]) * v->val[i]
- 0.5*0.5*M*OMEGA*OMEGA * (e->x[i] * e->x[i] + e->y[i] * e->y[i]) * (psi_prev_newton->val[i] + psi_prev_time->val[i]) * v->val[i]);
return result;
}
// Jacobian for the Crank-Nicolson method
template<typename Real, typename Scalar>
Scalar J_cranic(int n, double *wt, Func<Scalar> *u_ext[], Func<Real> *u, Func<Real> *v, Geom<Real> *e, ExtData<Scalar> *ext)
{
scalar ii = cplx(0.0, 1.0); // imaginary unit, ii^2 = -1
Scalar result = 0;
Func<Scalar>* psi_prev_newton = ext->fn[0];
for (int i = 0; i < n; i++)
result += wt[i] * (ii * H * u->val[i] * v->val[i] / TAU
- 0.5*H*H/(2*M) * (u->dx[i] * v->dx[i] + u->dy[i] * v->dy[i])
- 0.5*2* G * u->val[i] * psi_prev_newton->val[i] * conj(psi_prev_newton->val[i]) * v->val[i]
- 0.5*G * psi_prev_newton->val[i] * psi_prev_newton->val[i] * u->val[i] * v->val[i]
- 0.5*.5*M*OMEGA*OMEGA * (e->x[i] * e->x[i] + e->y[i] * e->y[i]) * u->val[i] * v->val[i]);
return result;
}
The way the weak forms are registered is standard:
// Initialize the weak formulation.
WeakForm wf;
if(TIME_DISCR == 1) {
wf.add_matrix_form(callback(jacobian_euler), H2D_UNSYM, H2D_ANY, &Psi_prev_newton);
wf.add_vector_form(callback(residual_euler), H2D_ANY, Tuple<MeshFunction*>(&Psi_prev_newton, &Psi_prev_time));
}
else {
wf.add_matrix_form(callback(jacobian_cranic), H2D_UNSYM, H2D_ANY, &Psi_prev_newton);
wf.add_vector_form(callback(residual_cranic), H2D_ANY, Tuple<MeshFunction*>(&Psi_prev_newton, &Psi_prev_time));
}
Also the time stepping loop and the call to the Newton’s method will not surprize a reader who made it this far in the tutorial:
// Time stepping loop:
int nstep = (int)(T_FINAL/TAU + 0.5);
for(int ts = 1; ts <= nstep; ts++)
{
info("---- Time step %d:", ts);
// Newton's method.
info("Performing Newton's method.");
bool verbose = true; // Default is false.
if (!nls.solve_newton(&Psi_prev_newton, NEWTON_TOL, NEWTON_MAX_ITER, verbose))
error("Newton's method did not converge.");
// Copy result of the Newton's iteration into Psi_prev_time.
Psi_prev_time.copy(&Psi_prev_newton);
}
Sample solution snapshots are shown below:
Snapshot 1:

Snapshot 2:

Snapshot 3:
