Math 503 computing assignment 1
FEM and FDM solution for u''(x)+q u(x)=f

by Nasser M Abbasi
Project for Mathematics 503, Summer 2007. California State University, Fullerton, CA.

(a) Purpose and design of project

In this project we are asked to implement the Finite Element Methods (FEM) and Finite difference method (FDM) to find the solution u(x) for the following differential equation, and also compare both methods.

-u''(x)+q u(x) = f           where            u(0)=0,u(1)=0

The 2 methods are compared for speed of convergence to the exact solution. The exact analytical solution is known for this simple differential equation:

Clear[u,x,f,q];
sol=First@DSolve[{-u''[x]+q u[x]==f,u[0]==0,u[1]==0},u[x],x]

sol=(u[x]/.%)/.{q->4,f->4}

Plot[sol,{x,0,1},ImageSize->300,FrameLabel->{"x","u(x)","Exact solution"},Frame->True]

The numerical solution for u(x) at each grid point is compared to the true solution, then the maximum error using each method is found. Also the RMSerror is calculated. 

A brief overview of the FEM and FDM scheme used is now discussed.

FEM method

For the FEM, the variational approach is used (as contrasted by the Garlkin method). In the variational method, we seek to find a solution y(x) to a functional J(y(x)) defined by an integral such that this solution y(x) minimizes this functional. This solution will be the solution to the differential equation itself. However, we do not use nor try to find the differential equation at all in this method. We work directly on the first variation equation J'(y;ϕ)=0 itself by finding y(x) such that J'(y;ϕ)=0 for all the (Subscript[ϕ, i])  permissible directions. In the Garlkin method, we are given the differential equation, and then we substitute equation (2) below into the differential equation itself. 

In this problem the ϕ functions are also the basis for the vector space in which the solution y(x) defined in. In other words, these (Subscript[ϕ, i]) represent a basis for the V space of y(x) and we seek a solution

u(x)=(∑_(i=1)^N)(Subscript[c, i])(Subscript[ϕ, i])(x)

Which satisfies J'(y;(Subscript[ϕ, i]))=0 for all the basis functions (Subscript[ϕ, i]). (Subscript[ϕ, i]) is defined by

(Subscript[ϕ, i])(x)=ψ((x/h)i)

Where i is the shape function number i=1,2,3..n and ψ(x)=1-x for 0<x<1, and ψ(x) = 0 for x>1 and ψ(x)=ψ(-x). The relationship between (Subscript[ϕ, i])(x) and ψ(x) is illustrated in this diagram

In this problem the functional we want to minimize is given

J(u)=(∫_0^1)(((u'(x)))^2)+q ((u(x))^2)-2f u(x)dx

Where q>0 and f are given constants. In the first part of this project we found that J(u) achieves a minimum iff J'(y;ϕ)=0 where J' (y; ϕ) is given by

J' (y;ϕ)=(∫_0^1)u'(x)ϕ'(x)+q  u(x)ϕ(x)-f ϕ(x)dx

We start the FEM by setting up the equations J' (y; (Subscript[ϕ, i])) =0, therefore, we will have N number of equations, where N is the number of the shape functions ϕ. Hence for each (j^th) shape function (Subscript[ϕ, j]) we would have

J' (y;(Subscript[ϕ, j]))=0
(∫_0^1)((∑_(i=1)^N)(Subscript[c, i])(Subscript[ϕ', i])(x)  )(Subscript[ϕ, j])'(x)+q ( (∑_(i=1)^N)(Subscript[c, i])(Subscript[ϕ, i])(x))  (Subscript[ϕ, j])(x)-f (Subscript[ϕ, j])(x)dx=0          j=1..N

The above will generate N algebraic equations which we will then solve for the (Subscript[c, i]) coefficients. Once the (Subscript[c, i]) is found then we can determine the FEM solution u(x) from (2) above at any x.

It is important to agree on the numbering scheme of nodes, elements, and shape functions. The following diagram illustrates the numbering used.

This below plots the shape functions for illustration.

L=20.; nPoints=10; nElements=nPoints-1; q=4; f=4;

nShapeFunctions=nPoints; 
h=(L/nElements);

Plot[Table[ϕ[i,x,h],{i,1,nShapeFunctions}],{x,0,L},PlotRange->All,AxesOrigin->{0,0},PlotLabel->"Shape functions ϕ(x), h="<>ToString[N[h]]<>", L="<>ToString[L]<>", N="<>ToString[nPoints],ImageSize->300]

When generating each equation J' (y;(Subscript[ϕ, j]))=0 for each basis (Subscript[ϕ, j]) we take advantage of the domain of influence of each specific (Subscript[ϕ, j]), we see from above that (Subscript[ϕ, j]) is nonzero only over the range (Subscript[x, j-1])..(Subscript[x, j+1]). The program calls a function to generate one equation for each basis. This function performs the integration shown in equation (4) but will only do the integration over (Subscript[x, j-1])..(Subscript[x, j+1]) since  (Subscript[ϕ, j])=0 elsewhere.

Once the N equations are computed, We solve Ax=b to find x where x here is the vector which contain (Subscript[c, j]) coefficients as shown in equation (2) above. The matrix A will be tridiagonal in this problem since for each basis (Subscript[ϕ, j]) (other than the first and the last basis) we see that it overlaps with 2 other basis (Subscript[ϕ, j-1]) and (Subscript[ϕ, j+1]) hence this causes an equation with 3 unknowns to be generated ((Subscript[c, j-1]),(Subscript[c, j]),(Subscript[c, j+1])). This below is an small function will shows the equations for N=8 and shows the A matrix to make this point more clear.

L=20.; nPoints=8; nElements=nPoints-1;h=(L/nElements);nShapeFunctions=nPoints;  q=4; f=4; leftBC=0; rightBC=0;
coeffShapeFunctions=Array["c",nShapeFunctions];
grid=N[Range[0,L,h]];
(FEMeqs=Table[makeEquation[i,h,coeffShapeFunctions,q,f,grid,nPoints]==0,{i,1,nShapeFunctions}])//TableForm

Which in Ax = b format, the above becomes

{b,A}=CoefficientArrays[FEMeqs,coeffShapeFunctions];
A[[1,2;;-1]]=0;
A[[-1,1;;-2]]=0;
b[[1]]=A[[1,1]]*leftBC;
b[[-1]]=A[[-1,-1]]*rightBC;
For[i=2,i≤nPoints-1,i++,
{
b[[i]]=b[[i]]-A[[i,1]]*leftBC-A[[i,-1]]*rightBC;
A[[i,1]]=0;
A[[i,-1]]=0;
}
];
MatrixForm[A]

The above shows that A is diagonally dominant, and tridiagonal. Hence a tridiagonal solver is used since it is much faster than Gaussian elimination in this case. In FEM, the generate A matrix will always contain diagonal bands as the above and these matrices are sparse in nature.

FDM

In the FDM method, the central difference method is used to approximate u''(x). Hence in this method we already know the differential equation and we work directly on the differential equation, while in the FDM method above, we do not know necessarily what the differential equation is and work directly on the first variational term.  
The central difference scheme is given by

(Subscript[u, i])''=(((Subscript[u, i+1])-2(Subscript[u, i])+(Subscript[u, i-1]))/(h^2))

Where (Subscript[u, i]) means u at grid point (Subscript[x, i]). Now we substitute the above equation directly into the differential equation -u'' (x) + q u(x) = f   and obtain

-((((Subscript[u, i+1])-2(Subscript[u, i])+(Subscript[u, i-1]))/(h^2)))+q (Subscript[u, i])=f 
(Subscript[u, i-1])+(2+(qh^2))(Subscript[u, i])-(Subscript[u, i+1])=f (h^2)

Hence for each point (we start from the first internal grid point i=1 and not from the boundary point i=0) and hence for each such internal point, we see that we have 3 unknown. Hence the general Ax=b equations will also be tridiagonal. This is illustrate by this short example below. Notice that 2 equations are inserted manually into the set of the generated equations since (Subscript[u, 0])=0 and (Subscript[u, n])=0, since these are boundary conditions given.

L=1.; nPoints=8; nElements=nPoints-1; q=4; f=4;
init[ nPoints,q,f,L,0,0];
u=Array["u",nPoints];
FDMeqs=Table[-(((u[[i+1]]-2u[[i]]+u[[i-1]])/(h^2)))+q u[[i]]==f,{i,2,nPoints-1}];
eq=u[[1]]==leftBC; FDMeqs=Append[FDMeqs,eq];
eq=u[[nPoints]]==rightBC; FDMeqs=Append[FDMeqs,eq];
FDMeqs//TableForm

This shows the A matrix from the above set of equations. We see it is a tridiagonal, The last 2 rows are for the 2 equations for the boundary conditions

{b,A}=CoefficientArrays[FDMeqs,u];
A//MatrixForm

(b) Summary of numerical results

The error in approximation between numerical and exact solution for u(x) as a function of increasing number of points is generated for up to n=200 and the complete table is shown in the appendix at the end of the report. Both max error and RMS error is calculated for each n. Below is partial listing of the table for number of points n=4..21

q=4;f=4;L=1;leftBC=0;rightBC=0; nElements=20;
p=analysis[nElements,q,f,L,leftBC,rightBC];
Grid[p,Frame->All]

The numerical results in the table is also plotted. The following 2 plots compare both methods accuracy for max error and rms error as function of increasing n for n up to 200. The full table is in the appendix.

ListLinePlot[   {p[[2;;10,2]],p[[2;;10,3]]},
PlotRange->All,
PlotStyle->{Red,Blue},
AxesOrigin->{0,0},
FrameLabel->{"number of points","RMS error"},
PlotLabel->"Comparing RMS error between FEM and Central difference",
Frame->True,
PlotLegends->{"FEM","FDM"}]

ListLinePlot[   {p[[2;;10,4]],p[[2;;10,5]]},
PlotRange->All,
PlotStyle->{Red,Blue},
AxesOrigin->{0,0},
FrameLabel->{"number of points","RMS error"},
PlotLabel->"Comparing Max error between FEM and Central difference",
Frame->True,
PlotLegends->{"FEM","FDM"}]

(c) Discussion of numerical results

The FDM is more accurate than the FEM for low N. Two error measurements were used: RMS error and Max error. For low N, FDM is more accurate than FEM, even more so when looking at the RMS error as seen by the plot above.

From the plots above we observe that as N increases both methods become as accurate as each others in approximating the exact solution. This happens for both max error and for RMS error.

The following diagram below shows this more clearly, where both FEM and FDM solution are plotted for n=2,3,4,5,6,7,8. We see that FDM for low N is more accurate, but FEM catches up very quickly, and about N=8 FEM max error was the same as FDM error for up to 6 decimal places. The following was done for L=10 instead of L=1 to make the plots a little more  interesting.

analysis2[]:=Module[{},
L=10; nPoints=10; q=40; f=40;
leftBC=10;rightBC=10;plotType=2;
Table[process[i,q,f,L,leftBC,rightBC,plotType],{i,3,8}]
 ]
p=analysis2[]; GraphicsGrid[ {{p[[1]],p[[2]]},{p[[3]],p[[4]]},{p[[5]],p[[6]]}},ImageSize->600]

FEM method is more complicated to implement than FDM. And from the above, we see that it does not give more accurate results than the simpler FDM method using central difference scheme. One might ask then why use FEM over FDM? Although this did not come up in this project, one can argue the following advantages of one method over the other:

Advantage of FEM over FDM

FEM solution u(x) can be used at point x in the domain and not just at the grid points (Subscript[x, i]) as with FDM

FEM can handle complicated boundary geometry, while FDM requires simple boundary geometry.

With FEM, one can add more elements in the vicinity where the solution changes rapidly to obtain more accuracy there. With FDM this is normally not done. Although one can also implement adaptive grid sizing as well in FDM, it is normally done in the time domain and not in the space domain.

Does not require knowing the differential equation to find the solution when working from the Variational approach as in this project. FDM requires knowing the differential equation.

Advantages of FDM over FEM

More accurate than FEM for low N. This is in particular when the exact solution is not too smooth. When the solution is not too smooth, more points are needed to make FEM as accurate as FDM.

Simpler to code and implement.

(d) Source code listing

Global parameters

gPlotTypeFEM=0;
gPlotTypeFDM=1;
gPlotTypeBoth=2;
gPlotTypeFEMdata=3;
gPlotTypeFDMdata=4;

Function name: makeEquation and its helpers
Input: the shape function number. Also access variable q,f,(Subscript[ϕ, i]),(Subscript[c, i]) to generate the weak form equation for the (i^th) shape function 
Purpose: generate the weak for equation for (i^th) shape function. To make it simpler, I have 2 special functions to handle the first and last shape elements since these are special conditions.  
Returns: The weak for equation for the (i^th) shape function

makeEquation[i_,h_,c_,q_,f_,grid_,nShapeFunctions_]:=Module[{g},
If[i==1,g=makeFirstEquation[h,c,q,f,grid],If[i==Length[c],g=makeLastEquation[h,c,q,f,grid,nShapeFunctions],g=makeInnerEquation[i,h,c,q,f,grid]]]
]
makeFirstEquation[h_,c_,q_,f_,grid_]:=Module[{},(∫_(grid[[1]])^(grid[[2]]))((c[[1]](((-1)/h))+c[[2]]((1/h)))(((-1)/h))+(q(c[[1]]Evaluate[ϕ[1,x,h]]+c[[2]]Evaluate[ϕ[2,x,h]])-f)  Evaluate[ϕ[1,x,h]])\[DifferentialD]x
]
makeLastEquation[h_,c_,q_,f_,grid_,nMax_]:=Module[{},(∫_(grid[[-2]])^(grid[[-1]]))((c[[-2]](((-1)/h))+c[[-1]]((1/h)))((1/h))+(q(c[[-2]]Evaluate[ϕ[nMax-1,x,h]]+c[[-1]]Evaluate[ϕ[nMax,x,h]])-f)Evaluate[ϕ[nMax,x,h]])\[DifferentialD]x
]
makeInnerEquation[i_,h_,c_,q_,f_,grid_]:=Module[{r1,r2},
(∫_(grid[[i-1]])^(grid[[i]]))((c[[i-1]](((-1)/h))+c[[i]]((1/h)))((1/h))+(q(c[[i-1]]ϕ[i-1,x,h]+c[[i]]ϕ[i,x,h])-f) ϕ[i,x,h])\[DifferentialD]x+(∫_(grid[[i]])^(grid[[i+1]]))((c[[i]](((-1)/h))+c[[i+1]]((1/h)))(((-1)/h))+(q(c[[i]]ϕ[i,x,h]+c[[i+1]]ϕ[i+1,x,h])-f )ϕ[i,x,h])\[DifferentialD]x
]

Function name: ϕ
Input: i, the shape function number. z, the distance along the domain to find  ϕ at
Purpose: generate the  ϕ function. 
Returns: the value of ϕ function at this z value.

ψ[x_]:=Module[{},If[x> 1,0,If[x≥ 0 ,1-x,ψ[-x]]]]

ϕ[i_,x_,h_]:=Module[{},
ψ[(x/h)-(i-1)]
]

Function name: yApprox
Input: x, distance along x direction. coeff, the (Subscript[c, i]) coefficient found for the FEM approximation from the equation y=(∑_(i=1)^n)c(i)ϕ[i,x] , h is the grid spacing, nPoints is number of points.
Purpose: calculate the solution from the FEM calculation after the (Subscript[c, i]) has been found. 
Returns: u(x) based on FEM approximation

yApprox[x_,coeff_,h_,nPoints_]:=Module[{i},
Sum[coeff[[i]] ϕ[i,x,h],{i,1,nPoints}]
]

Function name: getUCentralMethod
Input: access to number of points
Purpose: performs finite difference calculation using the central difference scheme for the second derivative.
Returns: data, which is a matrix of 2 columns. The first column is the x value, second column is the u value at this x.

getUCentralMethod[rightBC_,leftBC_,h_,q_,f_,nPoints_,grid_]:=Module[{data,i,eq,A,b,u,coeff,nRow,nCol,FDMeqs},
u=Array["c",nPoints-1];
(*The equation uses central difference method for the u''[x]. We just need to be carfull with the first last grid points, since these need boundary conditions on this*)

FDMeqs=Table[If[nPoints==3,-(((rightBC-2u[[i]]+leftBC)/(h^2)))+q u[[i]]==f,If[i==2,-(((u[[i+1]]-2u[[i]]+leftBC)/(h^2)))+q u[[i]]==f,If[i==nPoints-1,-(((rightBC-2u[[i]]+u[[i-1]])/(h^2)))+q u[[i]]==f,-(((u[[i+1]]-2u[[i]]+u[[i-1]])/(h^2)))+q u[[i]]==f]]],{i,2,nPoints-1}];

{b,A}=CoefficientArrays[FDMeqs,u[[2;;]]];
{nRow,nCol}=Dimensions[A];

(*Now solve Ax=b. Use triDiagonal for speed for large matrices*)
coeff=If[nRow<5,LinearSolve[A,b],triDiagonalSolve[A,b]];

data=Table[{grid[[i]],If[i==1,leftBC,If[i==nPoints,rightBC,coeff[[i-1]]]]},{i,1,nPoints}];
{data,FDMeqs,A,b}
];

Function name: getErrorsInApproximation
Input: access FEM matrix, grid points, FDM data.
Purpose: called to calculate the RMS error and Max error for the FEM and FDM methods.
Returns: 4 numbers. rmserrorFEM, maxErrorFEM, rmserrorDiff, maxErrorFDM

getErrorsInApproximation[plotType_,grid_,nPoints_,FEMcoeff_,xnumeric_,sol_,h_]:=Module[{k,i,rmserrorFEM=0,maxErrorFEM=0,rmserrorDiff=0,maxErrorFDM=0,femSolAtPoints,exactSolAtPoints},

exactSolAtPoints=Table[sol/.{z->grid[[i]]},{i,1,nPoints}];
If[plotType==gPlotTypeFEM||plotType==gPlotTypeBoth,
{
femSolAtPoints=Table[yApprox[grid[[i]],FEMcoeff,h,nPoints],{i,1,nPoints}];
rmserrorFEM=Sum[N[(exactSolAtPoints[[k]]-femSolAtPoints[[k]])^2],{k,1,nPoints}];
rmserrorFEM=Sqrt[rmserrorFEM]/nPoints;
maxErrorFEM=Max[Abs[exactSolAtPoints-femSolAtPoints]];
}
];

If[plotType==gPlotTypeFDM||plotType==gPlotTypeBoth,
{
rmserrorDiff=Sum[N[(exactSolAtPoints[[k]]-xnumeric[[k,2]])^2],{k,1,nPoints}];
rmserrorDiff=Sqrt[rmserrorDiff]/nPoints;
maxErrorFDM=Max[Abs[exactSolAtPoints-xnumeric[[All,2]]]];
}
];

N[{rmserrorFEM,maxErrorFEM,rmserrorDiff,maxErrorFDM}]
]

Function name: triDiagonalSolve
Input: A,d
Purpose: called to solve for x in Ax=d for use on tridiagonal matrices A. This only works correctly if A is diagonally dominant.
Returns: vector x, the solution from Ax=d

triDiagonalSolve[A_,d_]:=Module[{nRow,nCol,β,α,b,a,c,z,i,j,n,x},
{n,nCol}=Dimensions[A];
If[n ≠nCol,{Print["Matrix must be square. Matrix is"<>ToString[N[A]]]; Abort[]}];
β=Table[0,{n}];
c=Table[0,{n-1}];
z=Table[0,{n}];
α=β; b=α;x=z;
a=Diagonal[A];

For[{i=2;j=1},i≤n,{i++;j++},b[[i]]=A[[i,j]]];
For[{i=1;j=2},i<n,{i++;j++},c[[i]]=A[[i,j]]];

α[[1]]=a[[1]];
For[i=2,i≤n,i++,{β[[i]]=((b[[i]])/(α[[i-1]]));α[[i]]=a[[i]]-β[[i]]c[[i-1]]}];
z[[1]]=d[[1]];
For[i=2,i≤n,i++,z[[i]]=d[[i]]-β[[i]]z[[i-1]] ];

x[[n]]=((z[[n]])/(α[[n]]));
For[i=n-1,i≥ 1,i--,x[[i]]=(1/(α[[i]]))(z[[i]]-c[[i]]x[[i+1]]) ];
x
]

Function name: process
Input: access to all the input parameters from the GUI. See init[] function above.
Purpose: called by the GUI after the call to init[] is made. This function is the driver which calls all the other functions above. It find the FEM and FDM solutions and plot them.
Returns: Graphics plot for the GUI to display

process[nElements_,q_,f_,L_,leftBC_,rightBC_,plotType_]:=Module[
{i,A,b,k,p,p2,v,y,maxu,minu,nRow,nCol,nPoints,pExact,pFEM,h,grid,nShapeFunctions,coeffShapeFunctions,pFEMpoints,pFDMpoints,exactSolAtPoints,femSolAtPoints,maxFEM,minFEM,FEMeqs,sol,FEMcoeff,FDMeqs,FDMmatrix,xnumeric,FDMbMatrix,rmserrorFEM,maxErrorFEM,rmserrorDiff,maxErrorFDM,nRowFDM,nColFDM,g,statsFDM,statsFEM,stats,imageSize},

imageSize={300,250};
nPoints=nElements+1;
h=L/nElements; (*distance between 2 points*)
grid=N[Range[0,L,h]]; (*the GRID itself*)
nShapeFunctions=nPoints;
coeffShapeFunctions=Array["c",nShapeFunctions];

sol=y[z]/.First@DSolve[{-y''[z]+q y[z]==f,y[0]==leftBC,y[L]==rightBC},y[z],z];
exactSolAtPoints=Table[sol/.{z->grid[[i]]},{i,1,nPoints}];

(*Find max/min limits for plotting *)
maxu=Max[exactSolAtPoints];
maxu=maxu+0.3 Abs[maxu];

minu=Min[exactSolAtPoints];
minu=minu-0.3 Abs[minu];

(*Now based on plot type requested, do the needed calculations*)
If[plotType==gPlotTypeFEM||plotType==gPlotTypeBoth||plotType==gPlotTypeFEMdata,
{
(*Now generate the FEM set of equations from the weak form*)

FEMeqs=Table[makeEquation[i,h,coeffShapeFunctions,q,f,grid,nShapeFunctions]==0,{i,1,nShapeFunctions}];

{b,A}=CoefficientArrays[FEMeqs,coeffShapeFunctions];

(*Now we need to fix up the A matrix due to the initial conditions.*)
(*This below is a standard method I learned from my other courses, and it works, so I use it*)

A[[1,2;;-1]]=0;
A[[-1,1;;-2]]=0;
b[[1]]=A[[1,1]]*leftBC;
b[[-1]]=A[[-1,-1]]*rightBC;
For[i=2,i<nPoints,i++,
{
b[[i]]=b[[i]]-A[[i,1]]*leftBC-A[[i,-1]]*rightBC;
A[[i,1]]=0;
A[[i,-1]]=0;
}
];

(*Now solve Ax=b. *)
{nRow,nCol}=Dimensions[A];
FEMcoeff=LinearSolve[A,b];
femSolAtPoints=Table[yApprox[grid[[i]],FEMcoeff,h,nPoints],{i,1,nPoints}];
maxFEM=Max[femSolAtPoints];
If[ maxFEM>maxu,{maxu=maxFEM;maxu=maxu+0.1 Abs[maxu]}];

minFEM=Min[femSolAtPoints];
If[ minFEM<minu,{minu=minFEM;minu=minu-0.1 Abs[minu]}];

}
];

If[plotType==gPlotTypeFDM||plotType==gPlotTypeBoth||plotType==gPlotTypeFDMdata,
{
{xnumeric,FDMeqs,FDMmatrix,FDMbMatrix}=getUCentralMethod[rightBC,leftBC,h,q,f,nPoints,grid];
{nRowFDM,nColFDM}=Dimensions[FDMmatrix]
}
];

(*rest is just plotting stuff *)

g=0;
Which[
plotType==gPlotTypeFDMdata,g={Text[MatrixForm[N[FDMmatrix]],{0,0}],Text[MatrixForm[N[FDMbMatrix]],{0.4+0.03 nRowFDM,0}],
Text[TableForm[N[FDMeqs]],{0,.5}]},

plotType==gPlotTypeFEMdata,g={ Text[MatrixForm[N[A[[2;;-2,2;;-2]]]],{0,0}],Text[MatrixForm[N[b[[2;;-2]]]],{0.4+0.05 nRow,0}],
Text[TableForm[FEMeqs],{0,.5}]
},

True,
{
{rmserrorFEM,maxErrorFEM,rmserrorDiff,maxErrorFDM}=getErrorsInApproximation[plotType,grid,nPoints,FEMcoeff,xnumeric,sol,h];

statsFEM=""; statsFDM=""; stats="number elements="<>ToString[nElements]<>"  grid spacing="<>ToString[N[h]];
If[plotType==0||plotType==2,
statsFEM="[FEM error]     RMS="<>ToString[AccountingForm[rmserrorFEM]]<>"   Max error="<>ToString[AccountingForm[maxErrorFEM]]
];

If[plotType==gPlotTypeFDM||plotType==gPlotTypeBoth,
statsFDM="[FDM error]      RMS="<>ToString[AccountingForm[rmserrorDiff]]<>"   Max error="<>ToString[AccountingForm[maxErrorFDM]]
];

stats=stats<>"\n"<>statsFEM<>"\n"<>statsFDM;
pExact=Plot[sol/.z->x,{x,0,L},PlotRange->{{0,L},{maxu,minu}},ImageSize->imageSize,PlotStyle->Red,PlotLabel->stats];

If[plotType==gPlotTypeFEM||plotType==gPlotTypeBoth,
{
pFEM=Plot[yApprox[x,FEMcoeff,h,nPoints],{x,0,L},PlotRange->{{0,L},{maxu,minu}},AxesOrigin->{0,0},PlotStyle->Black,ImageSize->imageSize];
pFEMpoints=Table[ Graphics[{PointSize[0.02],Black,Point[{i h,yApprox[i h,FEMcoeff,h,nPoints]}]}],{i,0,nPoints-1}]; 
}
];

If[plotType==gPlotTypeFDM||plotType==gPlotTypeBoth,
{
p2=ListLinePlot[xnumeric,PlotRange->{{0,L},{maxu,minu}},AxesOrigin->{0,0},PlotStyle->{Blue,Dashed},ImageSize->imageSize];
pFDMpoints=Table[ Graphics[{PointSize[0.02],Blue,Point[{xnumeric[[i,1]],xnumeric[[i,2]]}]}],{i,1,nPoints}]; 
}
];

Which[plotType==gPlotTypeFEM,
g=Show[{pExact,pFEM,pFEMpoints}],

plotType==gPlotTypeFDM,
g=Show[{pExact,p2,pFDMpoints}],

plotType==gPlotTypeBoth,
g=Show[{pExact,pFEM,p2},pFDMpoints,pFEMpoints]
]
}
];

Graphics[ g,ImageSize->imageSize]
]

This is the Manipulate function. This is the GUI interface for the program. It display the GUI and called the Init and Process functions above to do the actual calculations

Manipulate[
process[numberOfElements,q,f,L,leftBC,rightBC,plotType],
{{q,4,"q"},1,100,1,Appearance->"Labeled"},
{{f,4,"f"},1,100,1,Appearance->"Labeled"},
{{L,1,"Length"},1,200,1,Appearance->"Labeled"},
{{leftBC,0,"Left boundary condition: u(0)"},-50,50,1,Appearance->"Labeled"},
{{rightBC,0,"Right boundary condition: u(L)"},-50,50,1,Appearance->"Labeled"},
{{plotType,0,"display type"},
{gPlotTypeFEM->"PLOT: Finite Elements",
gPlotTypeFDM->"PLOT: Central Difference",
gPlotTypeBoth->"PLOT: Both",
gPlotTypeFEMdata->"DATA: Finite element",
gPlotTypeFDMdata->"DATA: central difference"}
},
{{numberOfElements,2,"number of Elements"},2,50,1,Appearance->"Labeled"},
FrameLabel->"Solution to -y''+q y==f using FEM and divided difference methods",
SynchronousUpdating->True]

Appendix

This is an analysis function. Not part of the main program. It is used to generate a table that shows the error (RMS and Max) between FEM and FDM methods as n, the number of points, is increased.

analysis[maxN_,q_,f_,L_,leftBC_,rightBC_]:=Module[
{i,j,A,b,k,p,p2,v,y,maxu,minu,nRow,nCol,nPoints,pExact,pFEM,h,grid,nShapeFunctions,coeffShapeFunctions,pFEMpoints,pFDMpoints,exactSolAtPoints,femSolAtPoints,maxFEM,minFEM,startingAt,nEntries,sol,nElements,FEMeqs,xnumeric,FEMcoeff,rmserrorFEM,FDMeqs,FDMmatrix,FDMbMatrix,maxErrorFEM,rmserrorDiff,maxErrorFDM,nRowFDM,nColFDM},

startingAt=2;
nEntries=maxN-startingAt+1;
p=Table[{0,0,0,0,0},{nEntries+1}];
p[[1,1]]="number of elements"; p[[1,2]]="FEM rms error";p[[1,3]]="FDM rms error"; 
p[[1,4]]="FEM max error"; ;p[[1,5]]="FDM max error";

j=1;
For[nElements=startingAt,nElements≤maxN,nElements++,
{
nPoints=nElements+1;
h=L/nElements; (*distance between 2 points*)
grid=N[Range[0,L,h]]; (*the GRID itself*)
nShapeFunctions=nPoints;
coeffShapeFunctions=Array["c",nShapeFunctions];

sol=y[z]/.First@DSolve[{-y''[z]+q y[z]==f,y[0]==leftBC,y[L]==rightBC},y[z],z];
exactSolAtPoints=Table[sol/.{z->grid[[i]]},{i,1,nPoints}];

(*Find max/min limits for plotting *)
maxu=Max[exactSolAtPoints];
maxu=maxu+0.3 Abs[maxu];

minu=Min[exactSolAtPoints];
minu=minu-0.3 Abs[minu];

FEMeqs=Table[makeEquation[i,h,coeffShapeFunctions,q,f,grid,nShapeFunctions]==0,{i,1,nShapeFunctions}];

FEMeqs=Simplify[FEMeqs];

{b,A}=CoefficientArrays[FEMeqs,coeffShapeFunctions];

(*Now we need to fix up the A matrix due to the initial conditions.*)
(*This below is a standard method I learned from my other courses, and it works, so I use it*)

A[[1,2;;-1]]=0;
A[[-1,1;;-2]]=0;
b[[1]]=A[[1,1]]*leftBC;
b[[-1]]=A[[-1,-1]]*rightBC;
For[i=2,i<nPoints,i++,
{
b[[i]]=b[[i]]-A[[i,1]]*leftBC-A[[i,-1]]*rightBC;
A[[i,1]]=0;
A[[i,-1]]=0;
}
];

(*Now solve Ax=b. Use triDiagonal for speed for large matrices*)
{nRow,nCol}=Dimensions[A];
FEMcoeff=LinearSolve[A,b];

{xnumeric,FDMeqs,FDMmatrix,FDMbMatrix}=getUCentralMethod[rightBC,leftBC,h,q,f,nPoints,grid];
{nRowFDM,nColFDM}=Dimensions[FDMmatrix];

{rmserrorFEM,maxErrorFEM,rmserrorDiff,maxErrorFDM}=getErrorsInApproximation[2,grid,nPoints,FEMcoeff,xnumeric,sol,h];

j=j+1;
p[[j,1]]=nElements;
(*p[[j,2]]=AccountingForm[rmserrorFEM];
p[[j,3]]=AccountingForm[rmserrorDiff]; 
p[[j,4]]=AccountingForm[maxErrorFEM];
p[[j,5]]=AccountingForm[maxErrorFDM];*)

p[[j,2]]=rmserrorFEM;
p[[j,3]]=rmserrorDiff; 
p[[j,4]]=maxErrorFEM;
p[[j,5]]=maxErrorFDM;
}

];
p
]

L=10; nPoints=10; q=40; f=40;
leftBC=10;rightBC=10;plotType=2;
p=analysis[9,q,f,L,leftBC,rightBC];
Grid[p,Frame->All]