Chapter 3
Learning by Examples

This chapter is for those, like us, who don’t like manuals. A number of simple examples cover a good deal of the capacity of FreeFem++ and are self-explanatory. For the modelling part this chapter continues at Chapter 9 where some PDEes of physics, engineering and finance are studied in greater depth.

3.1 Membranes

Summary Here we shall learn how to solve a Dirichlet and/or mixed Dirichlet Neumann problem for the Laplace operator with application to the equilibrium of a membrane under load. We shall also check the accuracy of the method and interface with other graphics packages.

An elastic membrane Ω is attached to a planar rigid support Γ, and a force f(x)dx is exerted on each surface element dx = dx1dx2. The vertical membrane displacement, φ(x), is obtained by solving Laplace’s equation:

- Δφ = f  in  Ω.
As the membrane is fixed to its planar support, one has:
φ|Γ = 0.
If the support wasn’t planar but at an elevation z(x1,x2) then the boundary conditions would be of non-homogeneous Dirichlet type.
φ| =  z.
  Γ

If a part Γ2 of the membrane border Γ is not fixed to the support but is left hanging, then due to the membrane’s rigidity the angle with the normal vector n is zero; thus the boundary conditions are

φ|  = z,  ∂-φ|  = 0
  Γ1       ∂n Γ2
where Γ1 = Γ -Γ2; recall that ∂φ
∂n = φ n. Let us recall also that the Laplace operator Δ is defined by:
       2      2
      ∂-φ-  ∂-φ-
Δ φ = ∂x 2+  ∂x2.
         1     2
With such ”mixed boundary conditions” the problem has a unique solution (see (1987), Dautray-Lions (1988), Strang (1986) and Raviart-Thomas (1983)); the easiest proof is to notice that φ is the state of least energy, i.e.
                                ∫
                                    1    2
E (ϕ ) = mφ-iz∈nV E(v), w ith  E (v) =   (2|∇v| - fv)
                                  Ω
and where V is the subspace of the Sobolev space H1(Ω) of functions which have zero trace on Γ1. Recall that (x ∈ ℝd, d = 2 here)
  1           2              2   d
H  (Ω ) = {u ∈ L (Ω ) : ∇u ∈ (L  (Ω ))}
Calculus of variation shows that the minimum must satisfy, what is known as the weak form of the PDE or its variational formulation (also known here as the theorem of virtual work)
∫             ∫
   ∇ φ ⋅ ∇w =    fw   ∀w ∈ V
  Ω            Ω
Next an integration by parts (Green’s formula) will show that this is equivalent to the PDE when second derivatives exist.

WARNING Unlike freefem+ which had both weak and strong forms, FreeFem++ implements only weak formulations. It is not possible to go further in using this software if you don’t know the weak form (i.e. variational formulation) of your problem: either you read a book, or ask help form a colleague or drop the matter. Now if you want to solve a system of PDE like A(u,v) = 0, B(u,v) = 0 don’t close this manual, because in weak form it is

∫
   (A (u,v)w 1 + B (u, v)w 2) = 0 ∀w 1,w 2...
 Ω

Example Let an ellipse have the length of the semimajor axis a = 2, and unitary the semiminor axis Let the surface force be f = 1. Programming this case with FreeFem++ gives:

Example 3.1 (membrane.edp)


// file membrane.edp
real theta=4.⋆pi/3.;
real a=2.,b=1.;  // the length of the semimajor axis and semiminor axis
func z=x;
border Gamma1(t=0,theta)    { x = a ⋆ cos(t); y = b⋆sin(t); }
border Gamma2(t=theta,2⋆pi) { x = a ⋆ cos(t); y = b⋆sin(t); }
mesh Th=buildmesh(Gamma1(100)+Gamma2(50));
fespace Vh(Th,P2);  // P2 conforming triangular FEM
Vh phi,w, f=1;
solve Laplace(phi,w)=int2d(Th)(dx(phi)⋆dx(w) + dy(phi)⋆dy(w))
                - int2d(Th)(f⋆w) + on(Gamma1,phi=z);
plot(phi,wait=true, ps="membrane.eps");  // Plot phi
plot(Th,wait=true, ps="membraneTh.eps");  // Plot Th
savemesh(Th,"Th.msh");


PIC    PIC
PIC

Figure 3.1: Mesh and level lines of the membrane deformation. Below: the same in 3D drawn by gnuplot from a file generated by FreeFem++ .


A triangulation is built by the keyword buildmesh. This keyword calls a triangulation subroutine based on the Delaunay test, which first triangulates with only the boundary points, then adds internal points by subdividing the edges. How fine the triangulation becomes is controlled by the size of the closest boundary edges.

The PDE is then discretized using the triangular second order finite element method on the triangulation; as was briefly indicated in the previous chapter, a linear system is derived from the discrete formulation whose size is the number of vertices plus the number of mid-edges in the triangulation. The system is solved by a multi-frontal Gauss LU factorization implemented in the package UMFPACK. The keyword plot will display both Th and φ (remove Th if φ only is desired) and the qualifier fill=true replaces the default option (colored level lines) by a full color display. Results are on figure 3.1.


plot(phi,wait=true,fill=true);  // Plot phi with full color display

Next we would like to check the results!
One simple way is to adjust the parameters so as to know the solutions. For instance on the unit circle a=1 , φe = sin(x2 + y2 -1) solves the problem when

z = 0, f = -4 (cos(x2 + y2 - 1) - (x2 + y2) sin(x2 + y2 - 1))

except that on Γ2 nφ = 2 instead of zero. So we will consider a non-homogeneous Neumann condition and solve

∫            ∫        ∫

 Ω (∇ φ ⋅ ∇w =  Ω fw +  Γ 2w   ∀w  ∈ V
                        2
We will do that with two triangulations, compute the L2 error:
    ∫
ϵ =    |φ - φe|2
      Ω

and print the error in both cases as well as the log of their ratio an indication of the rate of convergence.

Example 3.2 (membranerror.edp)


// file membranerror.edp
verbosity =0;  // to remove all default output
real theta=4.⋆pi/3.;
real a=1.,b=1.;  // the length of the semimajor axis and semiminor axis
border Gamma1(t=0,theta)    { x = a ⋆ cos(t); y = b⋆sin(t); }
border Gamma2(t=theta,2⋆pi) { x = a ⋆ cos(t); y = b⋆sin(t); }
func f=-4⋆(cos(x^2+y^2-1) -(x^2+y^2)⋆sin(x^2+y^2-1));
func phiexact=sin(x^2+y^2-1);
real[int] L2error(2);   // an array two values
for(int n=0;n<2;n++)
{
  mesh Th=buildmesh(Gamma1(20⋆(n+1))+Gamma2(10⋆(n+1)));
  fespace Vh(Th,P2);
   Vh phi,w;
  solve laplace(phi,w)=int2d(Th)(dx(phi)⋆dx(w) + dy(phi)⋆dy(w))
    - int2d(Th)(f⋆w) - int1d(Th,Gamma2)(2⋆w)+ on(Gamma1,phi=0);
  plot(Th,phi,wait=true,ps="membrane.eps");  // Plot Th and phi
  L2error[n]= sqrt(int2d(Th)((phi-phiexact)^2));
}
for(int n=0;n<2;n++)
 cout << " L2error " << n << " = "<<  L2error[n] <<endl;
cout <<" convergence rate = "<< log(L2error[0]/L2error[1])/log(2.)  <<endl;

the output is


 L2error 0 = 0.00462991
 L2error 1 = 0.00117128
 convergence rate = 1.9829
times: compile 0.02s, execution 6.94s

We find a rate of 1.93591, which is not close enough to the 3 predicted by the theory. The Geometry is always a polygon so we lose one order due to the geometry approximation in O(h2)

Now if you are not satisfied with the .eps plot generated by FreeFem++ and you want to use other graphic facilities, then you must store the solution in a file very much like in C++. It will be useless if you don’t save the triangulation as well, consequently you must do


     {
       ofstream ff("phi.txt");
       ff << phi[];
      }
    savemesh(Th,"Th.msh");

For the triangulation the name is important: it is the extension that determines the format.
Still that may not take you where you want. Here is an interface with gnuplot to produce the right part of figure 3.2.


// to build a gnuplot data file
{ ofstream ff("graph.txt");
   for (int i=0;i<Th.nt;i++)
   { for (int j=0; j <3; j++)
       ff<<Th[i][j].x  << "    "<< Th[i][j].y<< "  "<<phi[][Vh(i,j)]<<endl;
    ff<<Th[i][0].x  << "    "<< Th[i][0].y<< "  "<<phi[][Vh(i,0)]<<"\n\n\n"
   }
}

We use the finite element numbering, where Wh(i,j) is the global index of jTh degrees of freedom of triangle number i.

Then open gnuplot and do


        set palette rgbformulae 30,31,32
        splot "graph.txt" w l pal

This works with P2 and P1, but not with P1nc because the 3 first degrees of freedom of P2 or P2 are on vertices and not with P1nc.

3.2 Heat Exchanger

Summary Here we shall learn more about geometry input and triangulation files, as well as read and write operations.

The problem Let {Ci}1,2, be 2 thermal conductors within an enclosure C0. The first one is held at a constant temperature u1 the other one has a given thermal conductivity κ2 5 times larger than the one of C0. We assume that the border of enclosure C0 is held at temperature 20C and that we have waited long enough for thermal equilibrium.

In order to know u(x) at any point x of the domain Ω, we must solve

∇ ⋅ (κ∇u ) = 0 in Ω,  u|Γ = g
where Ω is the interior of C0 minus the conductors C1 and Γ is the boundary of Ω, that is C0 C1 Here g is any function of x equal to ui on Ci. The second equation is a reduced form for:
u = ui on  Ci,  i = 0,1.
The variational formulation for this problem is in the subspace H01(Ω) H1(Ω) of functions which have zero traces on Γ.
                 ∫
u - g ∈ H 10(Ω) :    ∇u∇v =  0 ∀v ∈ H 10(Ω)
                  Ω

Let us assume that C0 is a circle of radius 5 centered at the origin, Ci are rectangles, C1 being at the constant temperature u1 = 60C.

Example 3.3 (heatex.edp)


// file heatex.edp
int C1=99, C2=98;  // could be anything such that 0 and C1 C2
border C0(t=0,2⋆pi){x=5⋆cos(t); y=5⋆sin(t);}
border C11(t=0,1){ x=1+t;  y=3;      label=C1;}
border C12(t=0,1){ x=2;    y=3-6⋆t;  label=C1;}
border C13(t=0,1){ x=2-t;  y=-3;     label=C1;}
border C14(t=0,1){ x=1;    y=-3+6⋆t; label=C1;}
border C21(t=0,1){ x=-2+t; y=3;      label=C2;}
border C22(t=0,1){ x=-1;   y=3-6⋆t;  label=C2;}
border C23(t=0,1){ x=-1-t; y=-3;     label=C2;}
border C24(t=0,1){ x=-2;   y=-3+6⋆t; label=C2;}
plot(    C0(50)             // to see the border of the domain
        + C11(5)+C12(20)+C13(5)+C14(20)
        + C21(-5)+C22(-20)+C23(-5)+C24(-20),
        wait=true, ps="heatexb.eps");
mesh Th=buildmesh(    C0(50)
                    + C11(5)+C12(20)+C13(5)+C14(20)
                    + C21(-5)+C22(-20)+C23(-5)+C24(-20));
plot(Th,wait=1);
fespace Vh(Th,P1); Vh u,v;
Vh kappa=1+2⋆(x<-1)⋆(x>-2)⋆(y<3)⋆(y>-3);
solve a(u,v)= int2d(Th)(kappa⋆(dx(u)⋆dx(v)+dy(u)⋆dy(v)))
                +on(C0,u=20)+on(C1,u=60);
plot(u,wait=true, value=true, fill=true, ps="heatex.eps");

Note the following:


PIC    PIC

Figure 3.2: The heat exchanger


Exercise Use the symmetry of the problem with respect to the axes; triangulate only one half of the domain, and set Dirichlet conditions on the vertical axis, and Neumann conditions on the horizontal axis.

Writing and reading triangulation files Suppose that at the end of the previous program we added the line


savemesh(Th,"condensor.msh");

and then later on we write a similar program but we wish to read the mesh from that file. Then this is how the condenser should be computed:


mesh Sh=readmesh("condensor.msh");
fespace Wh(Sh,P1); Wh us,vs;
solve b(us,vs)= int2d(Sh)(dx(us)⋆dx(vs)+dy(us)⋆dy(vs))
                +on(1,us=0)+on(99,us=1)+on(98,us=-1);
plot(us);

Note that the names of the boundaries are lost but either their internal number (in the case of C0) or their label number (for C1 and C2) are kept.

3.3 Acoustics

Summary Here we go to grip with ill posed problems and eigenvalue problems

Pressure variations in air at rest are governed by the wave equation:

 2
∂-u-- c2Δu = 0.
∂t2

When the solution wave is monochromatic (and that depend on the boundary and initial conditions), u is of the form u(x,t) = Re(v(x)eikt) where v is a solution of Helmholtz’s equation:

k2v + c2Δv  = 0 in Ω,
∂v
---|Γ = g.                                        (3.1)
∂n
where g is the source. Note the “+” sign in front of the Laplace operator and that k > 0 is real. This sign may make the problem ill posed for some values of c
k, a phenomenon called “resonance”.
At resonance there are non-zero solutions even when g = 0. So the following program may or may not work:

Example 3.4 (sound.edp)


// file sound.edp
real kc2=1;
func g=y⋆(1-y);
border a0(t=0,1) { x= 5; y= 1+2⋆t ;}
border a1(t=0,1) { x=5-2⋆t; y= 3 ;}
border a2(t=0,1) { x= 3-2⋆t; y=3-2⋆t ;}
border a3(t=0,1) { x= 1-t; y= 1 ;}
border a4(t=0,1) { x= 0; y= 1-t ;}
border a5(t=0,1) { x= t; y= 0  ;}
border a6(t=0,1) { x= 1+4⋆t; y= t ;}
mesh Th=buildmesh( a0(20) + a1(20) + a2(20)
        + a3(20) + a4(20) + a5(20) + a6(20));
fespace Vh(Th,P1);
Vh u,v;
solve sound(u,v)=int2d(Th)(u⋆v ⋆ kc2 - dx(u)⋆dx(v) - dy(u)⋆dy(v))
                 - int1d(Th,a4)(g⋆v);
plot(u, wait=1, ps="sound.eps");

Results are on Figure 3.3. But when kc2 is an eigenvalue of the problem, then the solution is not unique: if ue 0 is an eigen state, then for any given solution u + ue is another a solution. To find all the ue one can do the following


real sigma = 20;   // value of the shift
// OP = A - sigma B ; // the shifted matrix
varf  op(u1,u2)= int2d(Th)(  dx(u1)⋆dx(u2) + dy(u1)⋆dy(u2) - sigma⋆ u1⋆u2 );
varf b([u1],[u2]) = int2d(Th)( u1⋆u2 ) ; // no Boundary condition see note 9.1
matrix OP= op(Vh,Vh,solver=Crout,factorize=1);
matrix B= b(Vh,Vh,solver=CG,eps=1e-20);
int nev=2;   // number of requested eigenvalues near sigma
real[int] ev(nev);  // to store the nev eigenvalue
Vh[int] eV(nev);    // to store the nev eigenvector
int k=EigenValue(OP,B,sym=true,sigma=sigma,value=ev,vector=eV,
                   tol=1e-10,maxit=0,ncv=0);
cout<<ev(0)<<" 2 eigen values "<<ev(1)<<endl;
v=eV[0];
plot(v,wait=1,ps="eigen.eps");


PIC    PIC

Figure 3.3: Left:Amplitude of an acoustic signal coming from the left vertical wall. Right: first eigen state (λ = (k∕c)2 = 19.4256) close to 20 of eigenvalue problem :-Δφ = λφ and ∂φ
∂n = 0 on Γ


3.4 Thermal Conduction

Summary Here we shall learn how to deal with a time dependent parabolic problem. We shall also show how to treat an axisymmetric problem and show also how to deal with a nonlinear problem.

How air cools a plate We seek the temperature distribution in a plate (0,Lx) ×(0,Ly) ×(0,Lz) of rectangular cross section Ω = (0,6) ×(0,1); the plate is surrounded by air at temperature ue and initially at temperature u = u0 + x
Lu1. In the plane perpendicular to the plate at z = Lz∕2, the temperature varies little with the coordinate z; as a first approximation the problem is 2D.

We must solve the temperature equation in Ω in a time interval (0,T).

∂tu - ∇ ⋅ (κ∇u ) = 0 in Ω × (0,T ),
u(x,y,0) = u0 + xu1
κ∂u-+ α(u - u ) = 0 on Γ × (0,T ).                    (3.2)
 ∂n          e
Here the diffusion κ will take two values, one below the middle horizontal line and ten times less above, so as to simulate a thermostat. The term α(u -ue) accounts for the loss of temperature by convection in air. Mathematically this boundary condition is of Fourier (or Robin, or mixed) type.
The variational formulation is in L2(0,T; H1(Ω)); in loose terms and after applying an implicit Euler finite difference approximation in time; we shall seek un(x,y) satisfying for all w ∈ H1(Ω):
∫                          ∫
   un---un-1        n            n
  (    δt   w + κ∇u ∇w ) +    α(u - uue)w = 0
 Ω                          Γ


func u0 =10+90⋆x/6;
func k = 1.8⋆(y<0.5)+0.2;
real ue = 25, alpha=0.25, T=5, dt=0.1 ;
mesh Th=square(30,5,[6⋆x,y]);
fespace Vh(Th,P1);
Vh u=u0,v,uold;
problem thermic(u,v)= int2d(Th)(u⋆v/dt + k⋆(dx(u) ⋆ dx(v) + dy(u) ⋆ dy(v)))
                + int1d(Th,1,3)(alpha⋆u⋆v)
                - int1d(Th,1,3)(alpha⋆ue⋆v)
                - int2d(Th)(uold⋆v/dt) + on(2,4,u=u0);
ofstream ff("thermic.dat");
for(real t=0;t<T;t+=dt){
    uold=u;     // uold un-1 = un u
    thermic;   // here solve the thermic problem
    ff<<u(3,0.5)<<endl;
    plot(u);
}

Notice that we must separate by hand the bilinear part from the linear one.

Notice also that the way we store the temperature at point (3,0.5) for all times in file thermic.dat. Should a one dimensional plot be required, the same procedure can be used. For instance to print x↦→∂u
∂y(x,0.9) one would do


for(int i=0;i<20;i++) cout<<dy(u)(6.0⋆i/20.0,0.9)<<endl;

Results are shown on Figure 3.4.


PIC    PIC

Figure 3.4: Temperature at T=4.9. Right: decay of temperature versus time at x=3, y=0.5


3.4.1 Axisymmetry: 3D Rod with circular section

Let us now deal with a cylindrical rod instead of a flat plate. For simplicity we take κ = 1. In cylindrical coordinates, the Laplace operator becomes (r is the distance to the axis, z is the distance along the axis, θ polar angle in a fixed plane perpendicular to the axis):

Δu  = 1∂r(r∂ru) +-1∂ 2θθu + ∂2zz.
      r          r2

Symmetry implies that we loose the dependence with respect to θ; so the domain Ω is again a rectangle ]0,R[×]0,|[ . We take the convention of numbering of the edges as in square() (1 for the bottom horizontal ...); the problem is now:

r∂ u - ∂ (r∂ u) - ∂ (r∂u ) = 0 in Ω,
  t    r   r   z z   z
u(t = 0) = u0 + L-(u1 - u)
                z                ∂u
u|Γ4 = u0,  u|Γ2 = u1, α (u - ue) +---|Γ1∪Γ3 = 0.               (3.3)
                                 ∂n
Note that the PDE has been multiplied by r.

After discretization in time with an implicit scheme, with time steps dt, in the FreeFem++ syntax r becomes x and z becomes y and the problem is:


problem thermaxi(u,v)=int2d(Th)((u⋆v/dt + dx(u)⋆dx(v) + dy(u)⋆dy(v))⋆x)
                + int1d(Th,3)(alpha⋆x⋆u⋆v) - int1d(Th,3)(alpha⋆x⋆ue⋆v)
                - int2d(Th)(uold⋆v⋆x/dt) + on(2,4,u=u0);

Notice that the bilinear form degenerates at x = 0. Still one can prove existence and uniqueness for u and because of this degeneracy no boundary conditions need to be imposed on Γ1.

3.4.2 A Nonlinear Problem : Radiation

Heat loss through radiation is a loss proportional to the absolute temperature to the fourth power (Stefan’s Law). This adds to the loss by convection and gives the following boundary condition:

κ∂u-+ α(u - u ) + c[(u + 273)4 - (u + 273)4] = 0
 ∂n          e                  e

The problem is nonlinear, and must be solved iteratively. If m denotes the iteration index, a semi-linearization of the radiation condition gives

∂um+1-     m+1          m+1       m              m       2            2
 ∂n   + α(u   - ue) + c(u   - ue)(u  + ue + 54 6)((u + 273) + (ue + 2 73) ) = 0,
because we have the identity a4 -b4 = (a -b)(a + b)(a2 + b2). The iterative process will work with v = u -ue.


...
fespace Vh(Th,P1);   // finite element space
real rad=1e-8, uek=ue+273;  // def of the physical constants
Vh vold,w,v=u0-ue,b;
problem thermradia(v,w)
    = int2d(Th)(v⋆w/dt + k⋆(dx(v) ⋆ dx(w) + dy(v) ⋆ dy(w)))
                + int1d(Th,1,3)(b⋆v⋆w)
                - int2d(Th)(vold⋆w/dt) + on(2,4,v=u0-ue);
for(real t=0;t<T;t+=dt){
    vold=v;
    for(int m=0;m<5;m++){
       b= alpha + rad ⋆ (v + 2⋆uek) ⋆ ((v+uek)^2 + uek^2);
       thermradia;
    }
}
vold=v+ue; plot(vold);

3.5 Irrotational Fan Blade Flow and Thermal effects

Summary Here we will learn how to deal with a multi-physics system of PDEs on a Complex geometry, with multiple meshes within one problem. We also learn how to manipulate the region indicator and see how smooth is the projection operator from one mesh to another.

Incompressible flow Without viscosity and vorticity incompressible flows have a velocity given by:

    (    )
    |||∂∂ψx2 |||
u = |(- ∂ψ-|),   where ψ is solution of   Δψ = 0
      ∂x1
This equation expresses both incompressibility (∇⋅u = 0) and absence of vortex (∇×u = 0).

As the fluid slips along the walls, normal velocity is zero, which means that ψ satisfies:

ψ constan t on the w alls.
One can also prescribe the normal velocity at an artificial boundary, and this translates into non constant Dirichlet data for ψ.

Airfoil Let us consider a wing profile S in a uniform flow. Infinity will be represented by a large circle C where the flow is assumed to be of uniform velocity; one way to model this problem is to write

Δ ψ = 0 in Ω,     ψ |S = 0,  ψ |C = u∞y,                    (3.4)
where Ω = C S

The NACA0012 Airfoil An equation for the upper surface of a NACA0012 (this is a classical wing profile in aerodynamics) is:

           √ --
y = 0.1773 5 x - 0.075 597x - 0.21 2836x2 + 0.17 363x3 - 0.062 54x4.

Example 3.5 (potential.edp)


// file potential.edp
real S=99;
border C(t=0,2⋆pi) {  x=5⋆cos(t);  y=5⋆sin(t);}
border Splus(t=0,1){  x = t; y = 0.17735⋆sqrt(t)-0.075597⋆t
        - 0.212836⋆(t^2)+0.17363⋆(t^3)-0.06254⋆(t^4); label=S;}
border Sminus(t=1,0){  x =t; y= -(0.17735⋆sqrt(t)-0.075597⋆t
        -0.212836⋆(t^2)+0.17363⋆(t^3)-0.06254⋆(t^4)); label=S;}
mesh Th= buildmesh(C(50)+Splus(70)+Sminus(70));
fespace Vh(Th,P2); Vh psi,w;
solve potential(psi,w)=int2d(Th)(dx(psi)⋆dx(w)+dy(psi)⋆dy(w))+
  on(C,psi = y) + on(S,psi=0);
plot(psi,wait=1);

A zoom of the streamlines are shown on Figure 3.5.


PIC PIC

Figure 3.5: Zoom around the NACA0012 airfoil showing the streamlines (curve ψ = constant). To obtain such a plot use the interactive graphic command: “+” and p. Right: temperature distribution at time T=25 (now the maximum is at 90 instead of 120). Note that an incidence angle has been added here (see Chapter 9).


3.5.1 Heat Convection around the airfoil

Now let us assume that the airfoil is hot and that air is there to cool it. Much like in the previous section the heat equation for the temperature u is

                                         ∂v
∂tv - ∇ ⋅ (κ∇v ) + u ⋅ ∇v = 0, v(t = 0) = v0,-|C = 0
                                         ∂n
But now the domain is outside AND inside S and κ takes a different value in air and in steel. Furthermore there is convection of heat by the flow, hence the term u⋅∇v above. Consider the following, to be plugged at the end of the previous program:


...
border D(t=0,2){x=1+t;y=0;}  // added to have a fine mesh at trail
mesh Sh = buildmesh(C(25)+Splus(-90)+Sminus(-90)+D(200));
fespace Wh(Sh,P1); Wh v,vv;
int steel=Sh(0.5,0).region, air=Sh(-1,0).region;
fespace W0(Sh,P0);
W0 k=0.01⋆(region==air)+0.1⋆(region==steel);
W0 u1=dy(psi)⋆(region==air), u2=-dx(psi)⋆(region==air);
Wh vold = 120⋆(region==steel);
real dt=0.05, nbT=50;
int i;
problem thermic(v,vv,init=i,solver=LU)= int2d(Sh)(v⋆vv/dt
                + k⋆(dx(v) ⋆ dx(vv) + dy(v) ⋆ dy(vv))
                + 10⋆(u1⋆dx(v)+u2⋆dy(v))⋆vv)- int2d(Sh)(vold⋆vv/dt);
for(i=0;i<nbT;i++){
    v=vold; thermic;
 plot(v);
}

Notice here

3.6 Pure Convection : The Rotating Hill

Summary Here we will present two methods for upwinding for the simplest convection problem. We will learn about Characteristics-Galerkin and Discontinuous-Galerkin Finite Element Methods.

Let Ω be the unit disk centered at 0; consider the rotation vector field

u = [u1,u2],    u 1 = y, u 2 = - x.
Pure convection by u is
                                        0
∂tc + u.∇c = 0 in  Ω × (0,T)  c(t = 0) = c in  Ω.
The exact solution c(xt,t) at time t en point xt is given by
c(x,t) = c0(x,0)
   t
where xt is the particle path in the flow starting at point x at time 0. So xt are solutions of
                                      d(t ↦→ xt)
x_t = u(xt),  ,  xt=0 = x,  where  x_t = ---------
                                         dt

The ODE are reversible and we want the solution at point x at time t ( not at point xt) the initial point is x-t, and we have

c(x,t) = c0(x-t,0)
The game consists in solving the equation until T = 2π, that is for a full revolution and to compare the final solution with the initial one; they should be equal.

Solution by a Characteristics-Galerkin Method In FreeFem++ there is an operator called convect([u1,u2],dt,c) which compute c X with X is the convect field defined by X(x) = xdt and where xτ is particule path in the steady state velocity field u = [u1,u2] starting at point x at time τ = 0, so xτ is solution of the following ODE:

x_τ = u(xτ), x τ=0 = x.

When u is piecewise constant; this is possible because xτ is then a polygonal curve which can be computed exactly and the solution exists always when u is divergence free; convect returns c(xdf ) = C X.

Example 3.6 (convects.edp)


// file convects.edp
border C(t=0, 2⋆pi) { x=cos(t);  y=sin(t); };
mesh Th = buildmesh(C(100));
fespace Uh(Th,P1);
Uh cold, c = exp(-10⋆((x-0.3)^2 +(y-0.3)^2));
real dt = 0.17,t=0;
Uh u1 = y, u2 = -x;
for (int m=0; m<2⋆pi/dt ; m++) {
    t += dt;     cold=c;
    c=convect([u1,u2],-dt,cold);
    plot(c,cmm=" t="+t + ", min=" + c[].min + ", max=" +  c[].max);
}

The method is very powerful but has two limitations: a/ it is not conservative, b/ it may diverge in rare cases when |u| is too small due to quadrature error.

Solution by Discontinuous-Galerkin FEM Discontinuous Galerkin methods take advantage of the discontinuities of c at the edges to build upwinding. There are may formulations possible. We shall implement here the so-called dual-P1DC formulation (see Ern[11]):

∫                       ∫                        ∫
    cn+1---cn                         1-
   (   δt   + u ⋅ ∇c )w +   (α|n ⋅ u| - 2n ⋅ u)[c]w = -|n ⋅ u|cw ∀w
 Ω                        E                        EΓ

where E is the set of inner edges and EΓ- is the set of boundary edges where un < 0 (in our case there is no such edges). Finally [c] is the jump of c across an edge with the convention that c+ refers to the value on the right of the oriented edge.

Example 3.7 (convects_end.edp)


// file convects.edp
...
fespace Vh(Th,P1dc);
Vh w, ccold, v1 = y, v2 = -x, cc = exp(-10⋆((x-0.3)^2 +(y-0.3)^2));
real u, al=0.5;  dt = 0.05;
macro n(N.x⋆v1+N.y⋆v2)  // problem Adual(cc,w) =
int2d(Th)((cc/dt+(v1⋆dx(cc)+v2⋆dy(cc)))⋆w)
  + intalledges(Th)((1-nTonEdge)⋆w⋆(al⋆abs(n)-n/2)⋆jump(cc))
// - int1d(Th,C)((n<0)⋆abs(n)⋆cc⋆w) // unused because cc=0 on Ω-
  - int2d(Th)(ccold⋆w/dt);
for ( t=0; t< 2⋆pi ; t+=dt)
{
  ccold=cc; Adual;
  plot(cc,fill=1,cmm="t="+t + ", min=" + cc[].min + ", max=" +  cc[].max);
};
real [int] viso=[-0.2,-0.1,0,0.1,0.2,0.3,0.4,0.5,0.6,0.7,0.8,0.9,1,1.1];
plot(c,wait=1,fill=1,ps="convectCG.eps",viso=viso);
plot(c,wait=1,fill=1,ps="convectDG.eps",viso=viso);

Notice the new keywords, intalledges to integrate on all edges of all triangles

                    ∑  ∫
inta lledg es(Th) ≡
                    T∈Th  ∂T
(3.5)

(so all internal edges are see two times ), nTonEdge which is one if the triangle has a boundary edge and zero otherwise, jump to implement [c]. Results of both methods are shown on Figure 3.6 with identical levels for the level line; this is done with the plot-modifier viso.

Notice also the macro where the parameter u is not used (but the syntax needs one) and which ends with a //; it simply replaces the name n by (N.x⋆v1+N.y⋆v2). As easily guessed N.x,N.y is the normal to the edge.


PIC PIC

Figure 3.6: The rotated hill after one revolution, left with Characteristics-Galerkin, on the right with Discontinuous P1 Galerkin FEM.


Now if you think that DG is too slow try this


// the same DG very much faster
varf aadual(cc,w) = int2d(Th)((cc/dt+(v1⋆dx(cc)+v2⋆dy(cc)))⋆w)
        + intalledges(Th)((1-nTonEdge)⋆w⋆(al⋆abs(n)-n/2)⋆jump(cc));
varf bbdual(ccold,w) =  - int2d(Th)(ccold⋆w/dt);
matrix  AA= aadual(Vh,Vh);
matrix BB = bbdual(Vh,Vh);
set (AA,init=t,solver=sparsesolver);
Vh rhs=0;
for ( t=0; t< 2⋆pi ; t+=dt)
{
  ccold=cc;
  rhs[] = BB⋆ ccold[];
  cc[] = AA^-1⋆rhs[];
  plot(cc,fill=0,cmm="t="+t + ", min=" + cc[].min + ", max=" +  cc[].max);
};

Notice the new keyword set to specify a solver in this framework; the modifier init is used to tel the solver that the matrix has not changed (init=true), and the name parameter are the same that in problem definition (see. 6.7) .

Finite Volume Methods can also be handled with FreeFem++ but it requires programming. For instance the P0 -P1 Finite Volume Method of Dervieux et al associates to each P0 function c1 a P0 function c0 with constant value around each vertex qi equal to c1(qi) on the cell σ i made by all the medians of all triangles having qi as vertex. Then upwinding is done by taking left or right values at the median:

∫                  ∫
    1- 1n+1   1n            -
  σ δt(c    - c  ) +  ∂σu ⋅ nc = 0  ∀i
   i                   i

It can be programmed as


load "mat_dervieux";   // external module in C++ must be loaded
border a(t=0, 2⋆pi){ x = cos(t); y = sin(t);  }
mesh th = buildmesh(a(100));
fespace Vh(th,P1);
Vh vh,vold,u1 = y, u2 = -x;
Vh v = exp(-10⋆((x-0.3)^2 +(y-0.3)^2)), vWall=0, rhs =0;
real dt = 0.025;
// qf1pTlump means mass lumping is used
problem  FVM(v,vh) = int2d(th,qft=qf1pTlump)(v⋆vh/dt)
                  - int2d(th,qft=qf1pTlump)(vold⋆vh/dt)
      + int1d(th,a)(((u1⋆N.x+u2⋆N.y)<0)⋆(u1⋆N.x+u2⋆N.y)⋆vWall⋆vh)
+ rhs[] ;
matrix A;
MatUpWind0(A,th,vold,[u1,u2]);
for ( int t=0; t< 2⋆pi ; t+=dt){
  vold=v;
  rhs[] = A ⋆ vold[] ; FVM;
  plot(v,wait=0);
};

the mass lumping parameter forces a quadrature formula with Gauss points at the vertices so as to make the mass matrix diagonal; the linear system solved by a conjugate gradient method for instance will then converge in one or two iterations.

The right hand side rhs is computed by an external C++ function MatUpWind0(...) which is programmed as


// computes matrix a on a triangle for the Dervieux FVM
int   fvmP1P0(double q[3][2],  // the 3 vertices of a triangle T
              double u[2],    // convection velocity on T
              double c[3],    // the P1 function on T
              double a[3][3], // output matrix
              double where[3] )  // where>0 means we're on the boundary
{
  for(int i=0;i<3;i++) for(int j=0;j<3;j++) a[i][j]=0;
    for(int i=0;i<3;i++){
        int ip = (i+1)%3, ipp =(ip+1)%3;
        double unL =-((q[ip][1]+q[i][1]-2⋆q[ipp][1])⋆u[0]
                    -(q[ip][0]+q[i][0]-2⋆q[ipp][0])⋆u[1])/6;
        if(unL>0) { a[i][i] += unL; a[ip][i]-=unL;}
            else{ a[i][ip] += unL; a[ip][ip]-=unL;}
        if(where[i]&&where[ip]){         // this is a boundary edge
            unL=((q[ip][1]-q[i][1])⋆u[0] -(q[ip][0]-q[i][0])⋆u[1])/2;
            if(unL>0) { a[i][i]+=unL; a[ip][ip]+=unL;}
        }
    }
  return 1;
}

It must be inserted into a larger .cpp file, shown in Appendix A, which is the load module linked to FreeFem++ .

3.7 A Projection Algorithm for the Navier-Stokes equations

Summary Fluid flows require good algorithms and good triangultions. We show here an example of a complex algorithm and or first example of mesh adaptation.

An incompressible viscous fluid satisfies:

∂tu + u ⋅ ∇u + ∇p - νΔu = 0, ∇ ⋅ u = 0  in Ω × ]0,T [,
u|t=0 = u0, u |Γ = uΓ .
A possible algorithm, proposed by Chorin, is
1-  m+1   m   m       m      m
δt[u   - u  oX  ] + ∇p - νΔu   = 0,  u|Γ = uΓ ,
- Δpm +1 = - ∇ ⋅ umoXm, ∂npm +1 = 0,
where uoX(x) = u(x -u(x)δt) since tu+ u⋅∇u is approximated by the method of characteristics, as in the previous section.

An improvement over Chorin’s algorithm, given by Rannacher, is to compute a correction, q, to the pressure (the overline denotes the mean over Ω)
-Δq = ∇ ⋅ u - ∇-⋅ u

and define

um+1 = ~u + ∇q δt, pm +1 = pm - q - pm---q

where ũ is the (um+1,vm+1) of Chorin’s algorithm.

The backward facing step The geometry is that of a channel with a backward facing step so that the inflow section is smaller than the outflow section. This geometry produces a fluid recirculation zone that must be captured correctly.

This can only be done if the triangulation is sufficiently fine, or well adapted to the flow.

Example 3.8 (NSprojection.edp)


// file NSprojection.edp
border a0(t=1,0){ x=0;      y=t;           label=1;}
border a1(t=0,1){ x=2⋆t;    y=0;           label=2;}
border a2(t=0,1){ x=2;      y=-t/2;        label=2;}
border a3(t=0,1){ x=2+18⋆t^1.2;  y=-0.5;   label=2;}
border a4(t=0,1){ x=20;     y=-0.5+1.5⋆t;  label=3;}
border a5(t=1,0){ x=20⋆t;   y=1;           label=4;}
int n=1;
mesh Th= buildmesh(a0(3⋆n)+a1(20⋆n)+a2(10⋆n)+a3(150⋆n)+a4(5⋆n)+a5(100⋆n));
plot(Th);
fespace Vh(Th,P1);
real nu = 0.0025, dt = 0.2;  // Reynolds=400
Vh w,u = 4⋆y⋆(1-y)⋆(y>0)⋆(x<2), v =0, p = 0, q=0;
real area= int2d(Th)(1.);
for(int n=0;n<100;n++){
  Vh uold = u,  vold = v, pold=p;
  Vh f=convect([u,v],-dt,uold),  g=convect([u,v],-dt,vold);
  solve pb4u(u,w,init=n,solver=LU)
        =int2d(Th)(u⋆w/dt +nu⋆(dx(u)⋆dx(w)+dy(u)⋆dy(w)))
        -int2d(Th)((f/dt-dx(p))⋆w)
        + on(1,u = 4⋆y⋆(1-y)) + on(2,4,u = 0)+ on(3,u=f);
  plot(u);
  solve pb4v(v,w,init=n,solver=LU)
        = int2d(Th)(v⋆w/dt +nu⋆(dx(v)⋆dx(w)+dy(v)⋆dy(w)))
        -int2d(Th)((g/dt-dy(p))⋆w)
        +on(1,2,3,4,v = 0);
 real meandiv = int2d(Th)(dx(u)+dy(v))/area;
 solve pb4p(q,w,init=n,solver=LU)= int2d(Th)(dx(q)⋆dx(w)+dy(q)⋆dy(w))
    - int2d(Th)((dx(u)+ dy(v)-meandiv)⋆w/dt)+ on(3,q=0);
 real meanpq = int2d(Th)(pold - q)/area;
 if(n==50){
    Th = adaptmesh(Th,u,v,q); plot(Th, wait=true);
 }
 p = pold-q-meanpq;
 u = u + dx(q)⋆dt;
 v = v + dy(q)⋆dt;
}


PIC
PIC
PIC

Figure 3.7: Rannacher’s projection algorithm: result on an adapted mesh (top) showing the pressure (middle) and the horizontal velocity u at Reynolds 400.


We show in figure 3.7 the numerical results obtained for a Reynolds number of 400 where mesh adaptation is done after 50 iterations on the first mesh.

3.8 The System of elasticity

Elasticity Solid objects deform under the action of applied forces: a point in the solid, originally at (x,y,z) will come to (X,Y,Z) after some time; the vector u = (u1,u2,u3) = (X -x,Y -y,Z -z) is called the displacement. When the displacement is small and the solid is elastic, Hooke’s law gives a relationship between the stress tensor σ(u) = (σij(u)) and the strain tensor ϵ(u) = ϵij(u)

σij(u ) = λδij∇.u + 2 μϵij(u),
where the Kronecker symbol δij = 1 if i = j, 0 otherwise, with
        1-∂ui-  ∂uj-
ϵij(u) = 2(∂x  + ∂x  ),
             j     i
and where λ,μ are two constants that describe the mechanical properties of the solid, and are themselves related to the better known constants E, Young’s modulus, and ν, Poisson’s ratio:
μ = ---E----,  λ = -----E-ν------.
    2(1 + ν )      (1 + ν)(1 - 2ν)

Lamé’s system Let us consider a beam with axis Oz and with perpendicular section Ω. The components along x and y of the strain u(x) in a section Ω subject to forces f perpendicular to the axis are governed by

- μΔu - (μ + λ )∇ (∇.u) = f in Ω,
where λ,μ are the Lamé coefficients introduced above.

Remark, we do not used this equation because the associated variationnal form does not give the right boundary condition, we simply use

- div(σ ) = f inΩ
where the corresponding variationnal form is:
∫                ∫
   σ(u) : ϵ(v )dx -   vf dx = 0;
 Ω                 Ω
where : denote the tensor scalar product, i.e. a : b = i,jaijbij.

So the variationnal form can be written as :

∫                            ∫

 Ω λ∇.u∇.v + 2μϵ(u) : ϵ(v )dx -  Ωvf dx = 0;

Example Consider elastic plate with the undeformed rectangle shape [0,20] ×[-1,1]. The body force is the gravity force f and the boundary force g is zero on lower, upper and right sides. The left vertical sides of the beam is fixed. The boundary conditions are

σ.n  =  g = 0 o n Γ ,Γ ,Γ ,
                   1  4  3
  u  =  0  on Γ 2
Here u = (u,v) has two components.

The above two equations are strongly coupled by their mixed derivatives, and thus any iterative solution on each of the components is risky. One should rather use FreeFem++ ’s system approach and write:

Example 3.9 (lame.edp)


// file lame.edp
mesh Th=square(10,10,[20⋆x,2⋆y-1]);
fespace Vh(Th,P2);
Vh u,v,uu,vv;
real sqrt2=sqrt(2.);
macro epsilon(u1,u2)  [dx(u1),dy(u2),(dy(u1)+dx(u2))/sqrt2]  // EOM
// the sqrt2 is because we want: epsilon(u1,u2)'⋆ epsilon(v1,v2) == ϵ(u) : ϵ(v)
macro div(u,v) ( dx(u)+dy(v) )  // EOM
real E = 21e5, nu = 0.28, mu= E/(2⋆(1+nu));
real lambda = E⋆nu/((1+nu)⋆(1-2⋆nu)), f = -1;  //
solve lame([u,v],[uu,vv])= int2d(Th)(
        lambda⋆div(u,v)⋆div(uu,vv)
        +2.⋆mu⋆( epsilon(u,v)'⋆epsilon(uu,vv) ) )
        - int2d(Th)(f⋆vv)
        + on(4,u=0,v=0);
real coef=100;
plot([u,v],wait=1,ps="lamevect.eps",coef=coef);
mesh th1 = movemesh(Th, [x+u⋆coef, y+v⋆coef]);
plot(th1,wait=1,ps="lamedeform.eps");
real dxmin  = u[].min;
real dymin  = v[].min;
cout << " - dep.  max   x = "<< dxmin<< " y=" << dymin << endl;
cout << "   dep.  (20,0)  = " << u(20,0) << " " << v(20,0) << endl;

The numerical results are shown on figure 3.8 and the output is:


 -- square mesh : nb vertices  =121 ,  nb triangles = 200 ,  nb boundary edges 40
 -- Solve :           min -0.00174137  max 0.00174105
          min -0.0263154  max 1.47016e-29
 - dep.  max   x = -0.00174137 y=-0.0263154
   dep.  (20,0)  = -1.8096e-07 -0.0263154
times: compile 0.010219s, execution 1.5827s


PIC
PIC

Figure 3.8: Solution of Lamé’s equations for elasticity for a 2D beam deflected by its own weight and clamped by its left vertical side; result are shown with a amplification factor equal to 100. Remark: the size of the arrow is automatically bound, but the color gives the real length


3.9 The System of Stokes for Fluids

In the case of a flow invariant with respect to the third coordinate (two-dimensional flow), flows at low Reynolds number (for instance micro-organisms) satisfy,

 Δu + ∇p  = 0
∇ ⋅ u = 0
where u = (u1,u2) is the fluid velocity and p its pressure.
The driven cavity is a standard test. It is a box full of liquid with its lid moving horizontally at speed one. The pressure and the velocity must be discretized in compatible fintie element spaces for the LBB conditions to be satisfied:
 su p(u,∇p-) ≥ β|u| ∀u ∈ Uh
p∈Ph   |p|


// file stokes.edp
int n=3;
mesh Th=square(10⋆n,10⋆n);
fespace Uh(Th,P1b); Uh u,v,uu,vv;
fespace Ph(Th,P1);  Ph p,pp;
solve stokes([u,v,p],[uu,vv,pp]) =
    int2d(Th)(dx(u)⋆dx(uu)+dy(u)⋆dy(uu) + dx(v)⋆dx(vv)+ dy(v)⋆dy(vv)
            + dx(p)⋆uu + dy(p)⋆vv + pp⋆(dx(u)+dy(v)))
            + on(1,2,4,u=0,v=0) + on(3,u=1,v=0);
plot([u,v],p,wait=1);


PIC

Figure 3.9: Solution of Stokes’ equations for the driven cavity problem, showing the velocity field and the pressure level lines.


Results are shown on figure 3.9

3.10 A Large Fluid Problem

A friend of one of us in Auroville-India was building a ramp to access an air conditioned room. As I was visiting the construction site he told me that he expected to cool air escaping by the door to the room to slide down the ramp and refrigerate the feet of the coming visitors. I told him ”no way” and decided to check numerically. The results are on the front page of this book.
The fluid velocity and pressure are solution of the Navier-Stokes equations with varying density function of the temperature.
The geometry is trapezoidal with prescribed inflow made of cool air at the bottom and warm air above and so are the initial conditions; there is free outflow, slip velocity at the top (artificial) boundary and no-slip at the bottom. However the Navier-Stokes cum temperature equations have a RANS k -ϵ model and a Boussinesq approximation for the buoyancy. This comes to

∂tθ + u∇ θ - ∇ ⋅ (κmT ∇θ) = 0
∂ u + u ∇u - ∇ ⋅ (μ ∇u ) + ∇p + e(θ - θ )e , ∇ ⋅ u = 0
 t      2        T                 0  2
μT = cμk-,  κT = κμT
        ϵ                   μ
∂tk + u ∇k + ϵ - ∇ ⋅ (μT∇k ) =-T|∇u + ∇uT|2
               2            2
∂tϵ + u∇ ϵ + c2ϵ-- cϵ ∇ ⋅ (μT ∇ϵ) = c1k|∇u + ∇uT |2 = 0           (3.6)
              k   cμ             2
We use a time discretization which preserves positivity and uses the method of characteristics (Xm(x) x -um(x)δt)
1-(θm+1 - θm ∘ Xm) - ∇ ⋅ (κm ∇θm+1) = 0
δt                      T
1-  m+1    m   m         m   m+1      m+1     m+1             m+1
δt(u   - u  ∘ X  ) - ∇ ⋅ (μT∇u  ) + ∇p   + e(θ   - θ0)e2, ∇ ⋅ u   = 0
1-  m+1   m    m     m+1ϵm-       m   m+1   μmT   m     mT 2
δt(k   - k  ∘ X ) + k   km - ∇ ⋅ (μT∇k   ) = 2 |∇u   + ∇u   |
1   m+1   m    m       m+1ϵm   cϵ   m   m+1    c1 m   m      mT2
--(ϵ   - ϵ  ∘ X ) + c2ϵ    -m-- --∇_(μT ∇ϵ   ) = --k |∇u  + ∇u   |
δt           2            k    cμ              2
 m+1     km+1-   m+1     m+1
μT   = cμϵm+1 , κT   = κμT                                               (3.7)
In variational form and with appropriated boundary conditions the problem is:


real L=6;
border aa(t=0,1){x=t; y=0 ;}
border bb(t=0,14){x=1+t; y= - 0.1⋆t ;}
border cc(t=-1.4,L){x=15; y=t ;}
border dd(t=15,0){x= t ; y = L;}
border ee(t=L,0.5){ x=0; y=t ;}
border ff(t=0.5,0){ x=0; y=t ;}
int n=8;
mesh Th=buildmesh(aa(n)+bb(9⋆n) + cc(4⋆n) + dd(10⋆n)+ee(6⋆n) + ff(n));
real s0=clock();
fespace Vh2(Th,P1b);  // velocity space
fespace Vh(Th,P1);  // pressure space
fespace V0h(Th,P0);  // for gradients
Vh2 u2,v2,up1=0,up2=0;
Vh2 u1,v1;
Vh  u1x=0,u1y,u2x,u2y, vv;
real reylnods=500;
// cout << " Enter the reynolds number :"; cin >> reylnods;
assert(reylnods>1 && reylnods < 100000);
up1=0;
up2=0;
func g=(x)⋆(1-x)⋆4;   // inflow
Vh p=0,q, temp1,temp=35, k=0.001,k1,ep=0.0001,ep1;
V0h muT=1,prodk,prode, kappa=0.25e-4, stress;
real alpha=0, eee=9.81/303, c1m = 1.3/0.09 ;
real  nu=1, numu=nu/sqrt( 0.09), nuep=pow(nu,1.5)/4.1;
int i=0,iter=0;
real dt=0;
problem TEMPER(temp,q) =  // temperature equation
    int2d(Th)(
             alpha⋆temp⋆q + kappa ⋆ ( dx(temp)⋆dx(q) + dy(temp)⋆dy(q) ))
// + int1d(Th,aa,bb)(temp⋆q⋆ 0.1)
  + int2d(Th) ( -alpha⋆convect([up1,up2],-dt,temp1)⋆q )
   + on(ff,temp=25)
  + on(aa,bb,temp=35) ;
problem kine(k,q)=   // get the kinetic turbulent energy
    int2d(Th)(
             (ep1/k1+alpha)⋆k⋆q + muT ⋆ ( dx(k)⋆dx(q) + dy(k)⋆dy(q) ))
// + int1d(Th,aa,bb)(temp⋆q⋆0.1)
  + int2d(Th) ( prodk⋆q-alpha⋆convect([up1,up2],-dt,k1)⋆q )
   + on(ff,k=0.0001)  + on(aa,bb,k=numu⋆stress) ;
 problem viscturb(ep,q)=  // get the rate of turbulent viscous energy
    int2d(Th)(
             (1.92⋆ep1/k1+alpha)⋆ep⋆q + c1m⋆muT ⋆ ( dx(ep)⋆dx(q) + dy(ep)⋆dy(q) ))
// + int1d(Th,aa,bb)(temp⋆q⋆0.1)
  + int2d(Th) ( prode⋆q-alpha⋆convect([up1,up2],-dt,ep1)⋆q )
   + on(ff,ep= 0.0001) + on(aa,bb,ep=nuep⋆pow(stress,1.5)) ;
 solve NS ([u1,u2,p],[v1,v2,q]) =  // Navier-Stokes k-epsilon and Boussinesq
    int2d(Th)(
             alpha⋆( u1⋆v1 + u2⋆v2)
            + muT ⋆ (dx(u1)⋆dx(v1)+dy(u1)⋆dy(v1)+dx(u2)⋆dx(v2)+dy(u2)⋆dy(v2))
  // ( 2⋆dx(u1)⋆dx(v1) + 2⋆dy(u2)⋆dy(v2)+(dy(u1)+dx(u2))⋆(dy(v1)+dx(v2)))
            + p⋆q⋆(0.000001)
            - p⋆dx(v1) - p⋆dy(v2)
            - dx(u1)⋆q - dy(u2)⋆q
           )
  + int1d(Th,aa,bb,dd)(u1⋆v1⋆ 0.1)
  + int2d(Th) (eee⋆(temp-35)⋆v1 -alpha⋆convect([up1,up2],-dt,up1)⋆v1
                             -alpha⋆convect([up1,up2],-dt,up2)⋆v2 )
   + on(ff,u1=3,u2=0)
  + on(ee,u1=0,u2=0)
  + on(aa,dd,u2=0)
  + on(bb,u2= -up1⋆N.x/N.y)
  + on(cc,u2=0) ;
 plot(coef=0.2,cmm=" [u1,u2] et p  ",p,[u1,u2],ps="StokesP2P1.eps",value=1,wait=1);
{
  real[int] xx(21),yy(21),pp(21);
  for (int i=0;i<21;i++)
   {
     yy[i]=i/20.;
     xx[i]=u1(0.5,i/20.);
     pp[i]=p(i/20.,0.999);
    }
      cout << " " << yy << endl;
// plot([xx,yy],wait=1,cmm="u1 x=0.5 cup");
// plot([yy,pp],wait=1,cmm="pressure y=0.999 cup");
}
dt = 0.05;
int nbiter = 3;
real coefdt = 0.25^(1./nbiter);
real coefcut = 0.25^(1./nbiter) , cut=0.01;
real tol=0.5,coeftol = 0.5^(1./nbiter);
nu=1./reylnods;
for (iter=1;iter<=nbiter;iter++)
{
 cout << " dt = " << dt << " ------------------------ " << endl;
  alpha=1/dt;
  for (i=0;i<=500;i++)
   {
     up1=u1;
     up2=u2;
     temp1=max(temp,25);
     temp1=min(temp1,35);
     k1=k; ep1=ep;
     muT=0.09⋆k⋆k/ep;
      NS; plot([u1,u2],wait=1);  // Solves Navier-Stokes
     prode =0.126⋆k⋆(pow(2⋆dx(u1),2)+pow(2⋆dy(u2),2)+2⋆pow(dx(u2)+dy(u1),2))/2;
     prodk= prode⋆k/ep⋆0.09/0.126;
     kappa=muT/0.41;
     stress=abs(dy(u1));
     kine; plot(k,wait=1);
     viscturb; plot(ep,wait=1);
     TEMPER;  // solves temperature equation
     if ( !(i % 5)){
         plot(temp,value=1,fill=true,ps="temp_"+iter+"_"+i+".ps");
         plot(coef=0.2,cmm=" [u1,u2] et p  ",p,[u1,u2],ps="plotNS_"+iter+"_"+i+".ps");
     }
     cout << "CPU " << clock()-s0 << "s " << endl;
   }
  if (iter>= nbiter) break;
   Th=adaptmesh(Th,[dx(u1),dy(u1),dx(u1),dy(u2)],splitpbedge=1,
               abserror=0,cutoff=cut,err=tol, inquire=0,ratio=1.5,hmin=1./1000);
 plot(Th,ps="ThNS.eps");
  dt = dt⋆coefdt;
  tol = tol ⋆coeftol;
  cut = cut ⋆coefcut;
}
cout << "CPU " <<clock()-s0 << "s " << endl;

3.11 An Example with Complex Numbers

In a microwave oven heat comes from molecular excitation by an electromagnetic field. For a plane monochromatic wave, amplitude is given by Helmholtz’s equation:

βv + Δv = 0.
We consider a rectangular oven where the wave is emitted by part of the upper wall. So the boundary of the domain is made up of a part Γ1 where v = 0 and of another part Γ2 = [c,d] where for instance v = sin(πy-c c-d).

Within an object to be cooked, denoted by B, the heat source is proportional to v2. At equilibrium, one has

       2
-Δ θ = v IB, θΓ = 0
where IB is 1 in the object and 0 elsewhere.

PIC PIC PIC

Figure 3.10: A microwave oven: real (left) and imaginary (middle) parts of wave and temperature (right).


Results are shown on figure 3.10

In the program below β = 1(1 -I∕2) in the air and 2(1 -I∕2) in the object (i = √ ---
  -1):

Example 3.10 (muwave.edp)


// file muwave.edp
real a=20, b=20, c=15, d=8, e=2, l=12, f=2, g=2;
border a0(t=0,1) {x=a⋆t; y=0;label=1;}
border a1(t=1,2) {x=a; y= b⋆(t-1);label=1;}
border a2(t=2,3) { x=a⋆(3-t);y=b;label=1;}
border a3(t=3,4){x=0;y=b-(b-c)⋆(t-3);label=1;}
border a4(t=4,5){x=0;y=c-(c-d)⋆(t-4);label=2;}
border a5(t=5,6){ x=0; y= d⋆(6-t);label=1;}
border b0(t=0,1) {x=a-f+e⋆(t-1);y=g; label=3;}
border b1(t=1,4) {x=a-f; y=g+l⋆(t-1)/3; label=3;}
border b2(t=4,5) {x=a-f-e⋆(t-4); y=l+g; label=3;}
border b3(t=5,8) {x=a-e-f; y= l+g-l⋆(t-5)/3; label=3;}
int n=2;
mesh Th = buildmesh(a0(10⋆n)+a1(10⋆n)+a2(10⋆n)+a3(10⋆n)
        +a4(10⋆n)+a5(10⋆n)+b0(5⋆n)+b1(10⋆n)+b2(5⋆n)+b3(10⋆n));
plot(Th,wait=1);
fespace Vh(Th,P1);
real meat =  Th(a-f-e/2,g+l/2).region, air= Th(0.01,0.01).region;
Vh R=(region-air)/(meat-air);
Vh<complex> v,w;
solve muwave(v,w) = int2d(Th)(v⋆w⋆(1+R)
                -(dx(v)⋆dx(w)+dy(v)⋆dy(w))⋆(1-0.5i))
   + on(1,v=0) + on(2, v=sin(pi⋆(y-c)/(c-d)));
Vh vr=real(v), vi=imag(v);
plot(vr,wait=1,ps="rmuonde.ps", fill=true);
plot(vi,wait=1,ps="imuonde.ps", fill=true);
fespace Uh(Th,P1); Uh u,uu, ff=1e5⋆(vr^2 + vi^2)⋆R;
solve temperature(u,uu)= int2d(Th)(dx(u)⋆ dx(uu)+ dy(u)⋆ dy(uu))
     - int2d(Th)(ff⋆uu) + on(1,2,u=0);
plot(u,wait=1,ps="tempmuonde.ps", fill=true);

3.12 Optimal Control

Thanks to the function BFGS it is possible to solve complex nonlinear optimization problem within FreeFem++. For example consider the following inverse problem

         ∫
                   2
mb,ci,dn∈RJ =   (u - ud)  : - ∇(κ(b,c,d) ⋅ ∇u) = 0, u|Γ = uΓ
          E

where the desired state ud, the boundary data uΓ and the observation set E Ω are all given. Furthermore let us assume that

κ(x) = 1 + bI (x) + cI (x) + dI (x) ∀x ∈ Ω
            B       C        D

where B,C,D are separated subsets of Ω.

To solve this problem by the quasi-Newton BFGS method we need the derivatives of J with respect to b,c,d. We self explanatory notations, if δb,δc,δd are variations of b,c,d we have

      ∫
δJ ≈ 2   (u - ud )δu, - ∇ (κ ⋅ ∇δu ) ≈ ∇ (δκ ⋅ ∇u ) δu|Γ = 0
        E

Obviously Jb is equal to δJ when δb = 1,δc = 0,δd = 0, and so on for Jc and Jd.

All this is implemented in the following program


// file optimcontrol.edp
border aa(t=0, 2⋆pi) {    x = 5⋆cos(t);    y = 5⋆sin(t);  };
border bb(t=0, 2⋆pi) {    x = cos(t);    y = sin(t);  };
border cc(t=0, 2⋆pi) {    x = -3+cos(t);    y = sin(t);  };
border dd(t=0, 2⋆pi) {    x = cos(t);    y = -3+sin(t);  };
mesh th = buildmesh(aa(70)+bb(35)+cc(35)+dd(35));
fespace Vh(th,P1);
Vh Ib=((x^2+y^2)<1.0001),
   Ic=(((x+3)^2+ y^2)<1.0001),
   Id=((x^2+(y+3)^2)<1.0001),
   Ie=(((x-1)^2+ y^2)<=4),
   ud,u,uh,du;
real[int] z(3);
problem A(u,uh) =int2d(th)((1+z[0]⋆Ib+z[1]⋆Ic+z[2]⋆Id)⋆(dx(u)⋆dx(uh)
                    +dy(u)⋆dy(uh))) + on(aa,u=x^3-y^3);
z[0]=2; z[1]=3; z[2]=4;
A; ud=u;
ofstream f("J.txt");
func real J(real[int] & Z)
{
    for (int i=0;i<z.n;i++)z[i]=Z[i];
    A; real s= int2d(th)(Ie⋆(u-ud)^2);
    f<<s<<"   "; return s;
}
real[int] dz(3), dJdz(3);
problem B(du,uh)
  =int2d(th)((1+z[0]⋆Ib+z[1]⋆Ic+z[2]⋆Id)⋆(dx(du)⋆dx(uh)+dy(du)⋆dy(uh)))
  +int2d(th)((dz[0]⋆Ib+dz[1]⋆Ic+dz[2]⋆Id)⋆(dx(u)⋆dx(uh)+dy(u)⋆dy(uh)))
  +on(aa,du=0);
func real[int] DJ(real[int] &Z)
    {
      for(int i=0;i<z.n;i++)
        { for(int j=0;j<dz.n;j++) dz[j]=0;
          dz[i]=1; B;
          dJdz[i]= 2⋆int2d(th)(Ie⋆(u-ud)⋆du);
      }
     return dJdz;
 }
 real[int] Z(3);
 for(int j=0;j<z.n;j++) Z[j]=1;
 BFGS(J,DJ,Z,eps=1.e-6,nbiter=15,nbiterline=20);
 cout << "BFGS: J(z) = " << J(Z) <<  endl;
 for(int j=0;j<z.n;j++) cout<<z[j]<<endl;
 plot(ud,value=1,ps="u.eps");

In this example the sets B,C,D,E are circles of boundaries bb,cc,dd,ee are the domain Ω is the circle of boundary aa. The desired state ud is the solution of the PDE for b = 2,c = 3,d = 4. The unknowns are packed into array z. Notice that it is necessary to recopy Z into z because one is a local variable while the other one is global. The program found b = 2.00125,c = 3.00109,d = 4.00551. Figure 3.11 shows u at convergence and the successive function evaluations of J.


PIC PIC

Figure 3.11: On the left the level lines of u. On the right the successive evaluations of J by BFGS (5 values above 500 have been removed for readability)


Note that an adjoint state could have been used. Define p by

- ∇ ⋅ (κ∇p ) = 2IE(u - ud), p|Γ = 0

Consequently

       ∫

δJ = -  Ω(∇ ⋅ (κ∇p ))δu
  ∫                 ∫
=    (κ∇p  ⋅ ∇δu) = -  (δκ∇p  ⋅ ∇u)                     (3.8)
   Ω                 Ω
Then the derivatives are found by setting δb = 1,δc = δd = 0 and so on:
      ∫                  ∫                 ∫
J′b = -   ∇p ⋅ ∇u, J′c = -    ∇p ⋅ ∇u, J′d = -   ∇p  ⋅ ∇u
        B                 C                  D

Remark As BFGS stores an M ×M matrix where M is the number of unknowns, it is dangerously expensive to use this method when the unknown x is a Finite Element Function. One should use another optimizer such as the NonLinear Conjugate Gradient NLCG (also a key word of FreeFem++). See the file algo.edp in the examples folder.

3.13 A Flow with Shocks

Compressible Euler equations should be discretized with Finite Volumes or FEM with flux upwinding scheme but these are not implemented in FreeFem++ . Nevertheless acceptable results can be obtained with the method of characteristiscs provided that the mean values  ˉ
f = 1
2(f+ + f-) are used at shocks in the scheme.

∂tρ + ˉu∇ ρ + ˉρ∇ ⋅ u = 0
       ρu-
ˉρ(∂tu + ---∇u + ∇p = 0
        ˉρ
∂tp + ˉu∇p  + (γ - 1)ˉp∇ ⋅ u = 0                        (3.9)
One possibility is to couple u,p and then update ρ, i.e.
-----1-----(pm+1 - pm ∘ Xm ) + ∇ ⋅ um+1 = 0
(γ - 1 )δtˉpm
ˉρm- m +1    m    m      m+1
δt (u    - u  ∘X~ ) + ∇p    = 0
 m+1    m    m      ˉρm      m+1   m    m
ρ   =  ρ ∘ X  + --------m (p    - p  ∘ X )                 (3 .10)
                (γ - 1)ˉp
A numerical result is given on Figure 3.12 and the FreeFem++ script is


PIC

Figure 3.12: Pressure for a Euler flow around a disk at Mach 2 computed by (3.10)



verbosity=1;
int anew=1;
real x0=0.5,y0=0, rr=0.2;
border ccc(t=0,2){x=2-t;y=1;};
border ddd(t=0,1){x=0;y=1-t;};
border aaa1(t=0,x0-rr){x=t;y=0;};
border cercle(t=pi,0){ x=x0+rr⋆cos(t);y=y0+rr⋆sin(t);}
border aaa2(t=x0+rr,2){x=t;y=0;};
border bbb(t=0,1){x=2;y=t;};
int m=5; mesh Th;
if(anew) Th = buildmesh (ccc(5⋆m) +ddd(3⋆m) + aaa1(2⋆m) + cercle(5⋆m)
              + aaa2(5⋆m) + bbb(2⋆m) );
      else Th = readmesh("Th_circle.mesh"); plot(Th,wait=0);
real dt=0.01, u0=2, err0=0.00625, pena=2;
fespace Wh(Th,P1);
fespace Vh(Th,P1);
Wh u,v,u1,v1,uh,vh;
Vh r,rh,r1;
macro dn(u) (N.x⋆dx(u)+N.y⋆dy(u) )  // def the normal derivative
if(anew){ u1= u0; v1= 0; r1 = 1;}
else {
    ifstream g("u.txt");g>>u1[];
    ifstream gg("v.txt");gg>>v1[];
    ifstream ggg("r.txt");ggg>>r1[];
    plot(u1,ps="eta.eps", value=1,wait=1);
    err0=err0/10; dt = dt/10;
}
problem  eul(u,v,r,uh,vh,rh)
   = int2d(Th)(  (u⋆uh+v⋆vh+r⋆rh)/dt
                  + ((dx(r)⋆uh+ dy(r)⋆vh) - (dx(rh)⋆u + dy(rh)⋆v))
               )
 + int2d(Th)(-(rh⋆convect([u1,v1],-dt,r1) + uh⋆convect([u1,v1],-dt,u1)
                + vh⋆convect([u1,v1],-dt,v1))/dt)
  +int1d(Th,6)(rh⋆u)    // +int1d(Th,1)(rh⋆v)
 + on(2,r=0) + on(2,u=u0) + on(2,v=0);
int j=80;
for(int k=0;k<3;k++)
{
    if(k==20){ err0=err0/10; dt = dt/10; j=5;}
    for(int i=0;i<j;i++){
       eul; u1=u; v1=v; r1=abs(r);
        cout<<"k="<<k<<"  E="<<int2d(Th)(u^2+v^2+r)<<endl;
        plot(r,wait=0,value=1);
}
Th = adaptmesh (Th,r, nbvx=40000,err=err0,
      abserror=1,nbjacoby=2, omega=1.8,ratio=1.8, nbsmooth=3,
      splitpbedge=1, maxsubdiv=5,rescaling=1) ;
 plot(Th,wait=0);
 u=u;v=v;r=r;
savemesh(Th,"Th_circle.mesh");
ofstream f("u.txt");f<<u[];
ofstream ff("v.txt");ff<<v[];
ofstream fff("r.txt");fff<<r[];
r1 = sqrt(u⋆u+v⋆v);
plot(r1,ps="mach.eps", value=1);
r1=r;
}

3.14 Classification of the equations

Summary It is usually not easy to determine the type of a system. Yet the approximations and algorithms suited to the problem depend on its type:

When the system changes type, then expect difficulties (like shock discontinuities)!

Elliptic, parabolic and hyperbolic equations A partial differential equation (PDE) is a relation between a function of several variables and its derivatives.

                           2          m
F (φ (x ), ∂φ-(x),⋅⋅⋅, ∂φ-(x),∂-φ(x),⋅⋅⋅,∂-φ-(x)) = 0  ∀x ∈ Ω ⊂ Rd.
        ∂x1        ∂xd    ∂x21        ∂xmd
The range of x over which the equation is taken, here Ω, is called the domain of the PDE. The highest derivation index, here m, is called the order. If F and φ are vector valued functions, then the PDE is actually a system of PDEs.
Unless indicated otherwise, here by convention one PDE corresponds to one scalar valued F and φ. If F is linear with respect to its arguments, then the PDE is said to be linear.
The general form of a second order, linear scalar PDE is 2φ __ ∂xi∂xj and A : B means i,j=1da ijbij.
αφ + a ⋅ ∇φ + B : ∇ (∇φ) = f in   Ω ⊂ Rd,
where f(x)(x) ∈ R,a(x) ∈ Rd,B(x) ∈ Rd×d are the PDE coefficients. If the coefficients are independent of x, the PDE is said to have constant coefficients.

To a PDE we associate a quadratic form, by replacing φ by 1, ∂φ∕∂xi by zi and 2φ∕∂xi∂xj by zizj, where z is a vector in Rd :

α + a ⋅ z + zTBz = f.
If it is the equation of an ellipse (ellipsoid if d 2), the PDE is said to be elliptic; if it is the equation of a parabola or a hyperbola, the PDE is said to be parabolic or hyperbolic. If A 0, the degree is no longer 2 but 1, and for reasons that will appear more clearly later, the PDE is still said to be hyperbolic.

These concepts can be generalized to systems, by studying whether or not the polynomial system P(z) associated with the PDE system has branches at infinity (ellipsoids have no branches at infinity, paraboloids have one, and hyperboloids have several).
If the PDE is not linear, it is said to be non linear. Those are said to be locally elliptic, parabolic, or hyperbolic according to the type of the linearized equation.
For example, for the non linear equation
∂2φ-  ∂φ-∂2φ-
∂t2 - ∂x ∂x 2 = 1,
we have d = 2,x1 = t,x2 = x and its linearized form is:
∂2u   ∂u ∂2φ   ∂φ ∂2u
∂t2-- ∂x-∂x2-- ∂x-∂x2-= 0,
which for the unknown u is locally elliptic if ∂φ ∂x < 0 and locally hyperbolic if ∂φ ∂x > 0.

Examples Laplace’s equation is elliptic:

     ∂ 2φ    ∂2φ        ∂2φ
Δφ ≡ ---2+  --2-+ ⋅⋅⋅ +--2-= f,  ∀x ∈ Ω ⊂ Rd.
     ∂x 1   ∂x2        ∂xd
The heat equation is parabolic in Q = Ω×]0,T[Rd+1 :
∂φ-                       d
∂t - μΔφ = f   ∀x ∈ Ω ⊂ R  ,  ∀t ∈]0,T[.
If μ > 0, the wave equation is hyperbolic:
∂2φ
∂t2-- μΔφ = f    in   Q.
The convection diffusion equation is parabolic if μ 0 and hyperbolic otherwise:
∂φ-+ a∇φ - μΔ φ = f.
∂t
The biharmonic equation is elliptic:
Δ(Δφ ) = f  in   Ω.

Boundary conditions A relation between a function and its derivatives is not sufficient to define the function. Additional information on the boundary Γ = Ω of Ω, or on part of Γ is necessary.
Such information is called a boundary condition. For example,

φ(x) given, ∀x ∈ Γ ,
is called a Dirichlet boundary condition. The Neumann condition is
∂φ-
∂n(x) given on  Γ  (or n ⋅ B ∇φ,given on Γ for a general second order PDE )
where n is the normal at x ∈ Γ directed towards the exterior of Ω (by definition ∂φ ∂n = φ n).

Another classical condition, called a Robin (or Fourier) condition is written as:

           ∂φ-
φ (x) + β(x)∂n(x) given on Γ .
Finding a set of boundary conditions that defines a unique φ is a difficult art.
In general, an elliptic equation is well posed (i.e. φ is unique) with one Dirichlet, Neumann or Robin conditions on the whole boundary.
Thus, Laplace’s equations is well posed with a Dirichlet or Neumann condition but also with
                ∂-φ
φ given on Γ 1, ∂n  given on Γ 2, Γ 1 ∪ Γ 2 = Γ , Γ_1 ∩ Γ_ 2 = ∅.
Parabolic and hyperbolic equations rarely require boundary conditions on all of Γ×]0,T[. For instance, the heat equation is well posed with
φ given at t = 0 and D irichlet or N eum ann or m ix ed con ditions o n ∂Ω.
Here t is time so the first condition is called an initial condition. The whole set of conditions are also called Cauchy conditions.
The wave equation is well posed with
φ and ∂φ-given at t = 0 and Dirich let o r Ne uman n or m ixed conditio n�
      ∂t