%% fem_50d applies the finite element method to Laplace's equation.
function fem_50d
% input  a prefix, e.g. A7_
% files read  A7_coord.dat   one x y pair per line, for nodes in order  1, 2, ...
%             A7_elem3.dat   three node numbers per line, triangles, any order
%                            function stima3 is applied to these cooddinates
%             A7_elem4.dat   four node numbers per line, quadralaterals
%                            function stima4 is applied to these coordinates
%             A7_dirich.dat  two node numbers per line, dirichlet boundary
%                            function u_d is applied to these coordinates
%             A7_neum.dat    two node numbers per line, neumann bounday
%                            function g is applied to these coordinates
%
%  Discussion:
%    FEM_50d is a set of MATLAB routines to apply the finite
%    element method to solving Laplace's equation in an arbitrary
%    region, using about 50 lines of MATLAB code.
%    
%    FEM_50d is partly a demonstration, to show how little it
%    takes to implement the finite element method (at least using
%    every possible MATLAB shortcut.)  The user supplies datafiles
%    that specify the geometry of the region and its arrangement
%    into triangular and quadrilateral elements, and the location
%    and type of the boundary conditions, which can be any mixture
%    of Neumann and dirichlet.
%    
%    The unknown state variable U(x,y) is assumed to satisfy
%    Laplace's equation:
%      -Uxx(x,y) - Uyy(x,y) = F(x,y) in Omega
%    with dirichlet boundary conditions
%      U(x,y) = U_D(x,y) on Gamma_D
%    and Neumann boundary conditions on the outward normal derivative:
%      Un(x,y) = G(x,y) on Gamma_N
%    If Gamma designates the boundary of the region Omega,
%    then we presume that
%      Gamma = Gamma_D + Gamma_N
%    but the user is free to determine which boundary conditions to
%    apply.  Note, however, that the problem will generally be singular
%    unless at least one dirichlet boundary condition is specified.
%    
%    The code uses piecewise linear basis functions for triangular elements,
%    and piecewise isoparametric bilinear basis functions for quadrilateral
%    elements.
%    
%    The user is required to supply a number of data files and MATLAB
%    functions that specify the location of nodes, the grouping of nodes
%    into elements, the location and value of boundary conditions, and 
%    the right hand side function in Laplace's equation.  Note that the
%    fact that the geometry is completely up to the user means that
%    just about any two dimensional region can be handled, with arbitrary
%    shape, including holes and islands.
%    
%  Modified:
%    29 March 2004
%    23 February 2008 JSS
%     3 March 2008 JSS
%  Reference:
%    Jochen Alberty, Carsten Carstensen, Stefan Funken,
%    Remarks Around 50 Lines of MATLAB:
%    Short Finite Element Implementation,
%    Numerical Algorithms,
%    Volume 20, pages 117-137, 1999.
%
  clear
  format compact
  prename = input('enter prefix to file names: ', 's')
  diary 'fem_50d.out'
  disp([prename 'fem_50d.out'])
  debug = input('enter debug level:0=none, 1=input and matrices, 2=assembly ')
%
%  Read the nodal coordinatesinate data file.
%  ordered list of  x  y  pairs, coordinates for nodes
%  nodes must be sequentially numbered 1, 2, 3, ...
  coordinates = load(strcat(prename,'coord.dat'));
  if debug>0
    disp([prename 'coord.dat ='])
	disp(coordinates)
  end % debug
%
%  Read the triangular element data file.
%  three integer node numbers per line
  elements3 = load(strcat(prename,'elem3.dat'));
  if debug>0
    disp([prename 'elem3.dat ='])
	disp(elements3)
  end % debug
%
%  Read the quadrilateral element data file.
%  four integer node numbers per line
  elements4 = load(strcat(prename,'elem4.dat'));
  if debug>0
    disp([prename 'elem4.dat ='])
	disp(elements4)
  end % debug
%
%  Read the dirichlet boundary edge data file.
%  two integer node numbers per line, function u_d sets values
%  there must be at least one pair
%  other boundary edges are set in  neumann
  dirichlet = load(strcat(prename,'dirich.dat'));
  if debug>0
    disp([prename 'dirich.dat ='])
	disp(dirichlet)
  end % debug
%
%  Read the Neumann boundary edge data file.
%  two integer node numbers per line, function g  sets values
  neumann = load(strcat(prename,'neum.dat'));
  if debug>0
    disp([prename 'neum.dat ='])
	disp(neumann)
  end % debug

  A = sparse ( size(coordinates,1), size(coordinates,1) );
  b = sparse ( size(coordinates,1), 1 );

%
%  Assembly from triangles.
%
  for j = 1 : size(elements3,1)
    VV = coordinates(elements3(j,:),:);
    MM = stima3(coordinates(elements3(j,:),:));
	if debug>1
	  disp(['MM = stima3(VV) at j=' num2str(j) ' nodes(' ...
	        num2str(elements3(j,1)) ',' num2str(elements3(j,2)) ',' ...
			num2str(elements3(j,3)) ')'])
	  disp('VV=')
	  disp(VV)
	  disp('MM=')
	  disp(MM)
	end % debug
    A(elements3(j,:),elements3(j,:)) = A(elements3(j,:),elements3(j,:)) + MM;
  end
  if debug>0
    disp('assembly from triangles A=')
    disp(A)
  end % debug
%
%  Assembly from quadralaterals.
%
  for j = 1 : size(elements4,1)
    VV = coordinates(elements3(j,:),:);
    MM = stima4(coordinates(elements4(j,:),:));
	if debug>1
	  disp(['MM = stima3(VV) at j=' num2str(j) ' nodes(' ...
	        num2str(elements4(j,1)) ',' num2str(elements4(j,2)) ',' ...
			num2str(elements4(j,3)) ',' num2str(elements4(j,4)) ')'])
	  disp('VV=')
	  disp(VV)
	  disp('MM=')
	  disp(MM)
	end % debug
    A(elements4(j,:),elements4(j,:)) = A(elements4(j,:),elements4(j,:)) + MM;
  end
  if debug>0
    disp('assembly from triangles and rectangles A=')
    disp(A)
  end % debug

%
%  Volume Forces from triangles.
%
  for j = 1 : size(elements3,1)
    b(elements3(j,:)) = b(elements3(j,:)) ...
      + det( [1,1,1; coordinates(elements3(j,:),:)'] ) * ...
      f(sum(coordinates(elements3(j,:),:))/3)/6;
  end
  if debug>0
    disp('forces from triangles b=')
    disp(b)
  end % debug
%
%  Volume Forces from quadralaterals.
%
  for j = 1 : size(elements4,1)
    b(elements4(j,:)) = b(elements4(j,:)) ...
      + det([1,1,1; coordinates(elements4(j,1:3),:)'] ) * ...
      f(sum(coordinates(elements4(j,:),:))/4)/4;
  end
  if debug>0
    disp('forces from triangles and rectangles b=')
    disp(b)
  end % debug

%
%  Neumann conditions.
%
  for j = 1 : size(neumann,1)
    GG = norm(coordinates(neumann(j,1),:) - coordinates(neumann(j,2),:)) * ...
         g(sum(coordinates(neumann(j,:),:))/2)/2;
    if debug>2
	  disp(['Neumann at j=' num2str(j)])
      disp(GG)
	end % debug
    b(neumann(j,:)) = b(neumann(j,:)) + GG
  end
  if debug>0
    disp('using g() add neumann conditions b=')
    disp(b)
  end % debug

%
%  Determine which nodes are associated with dirichlet conditions.
%  Assign the corresponding entries of U, and adjust the right hand side.
%
  u = sparse ( size(coordinates,1), 1 );
  BoundNodes = unique ( dirichlet );
  if debug>1
    disp('BoundNodes=')
    disp(BoundNodes)
  end % debug
  u(BoundNodes) = u_d ( coordinates(BoundNodes,:) );
  if debug>0
    disp('using u_d() add dirichlet conditions u=')
    disp(u)
  end % debug

  b = b - A * u;
  if debug>0
    disp('apply u to b, dirichlet conditions b=')
    disp(b)
  end % debug

%
%  Compute the solution by solving A * u = b 
%  for the, un bound, remaining unknown values of u.
%
  FreeNodes = setdiff ( 1:size(coordinates,1), BoundNodes );

  u(FreeNodes) = A(FreeNodes,FreeNodes) \ b(FreeNodes);
  if debug>0
    disp('solve for A * u = b  u=')
    disp(u)
  end % debug
  
%
%  Graphic representation.
%
  show ( elements3, elements4, coordinates, full(u), dirichlet );
%
%  check solution
%
  for i=1:size(coordinates,1)
    disp([num2str(i) '  x= ' num2str(coordinates(i,1)) '  y= '  num2str(coordinates(i,2)) ...
	      ' analytic solution= ' num2str(uana(coordinates(i,1),coordinates(i,2)))])
  end
  diary off
  return % logical end of function  fem_50d
  

%% uana computes boundary values and analytic solution for checking
%  Discussion:
%    This is generally unknown yet is used here for testing
%  Parameters:
%    Input, real pair  xx,yy are x,y coordinates
%    Output, value of solution at  x,y
%
  function ana = uana(xx, yy)
    ana= 1.0+(2.0/11.0)*xx*xx+(3.0/11.0)*yy*yy;
  end % uana


  %% f evaluates the right hand side of Laplace's equation.
  %  Discussion:
  %    This routine must be changed by the user to reflect a particular problem.
  %  Parameters:
  %    Input, real U(N,M), contains the M-dimensional coordinates of N points.
  %    Output, VALUE(N), contains the value of the right hand side of Laplace's
  %    equation at each of the points.
  function valuef = f ( uf )
    valuef = ones ( size ( uf, 1 ), 1 );
    valuef = valuef.*(-10/11.0);
  end % f


  %% g evaluates the outward normal values assigned at Neumann boundary conditions.
  %  Discussion:
  %    This routine must be changed by the user to reflect a particular problem.
  %  Parameters:
  %    Input, real U(N,M), contains the M-dimensional coordinates of N points.
  %    Output, VALUE(N), contains the value of outward normal at each point
  %    where a Neumann boundary condition is applied.
  function valueg = g ( ug )
    valueg = zeros ( size ( ug, 1 ), 1 );
  end % g


  %% u_d evaluates the dirichlet boundary conditions.
  %  Discussion:
  %    The user must supply the appropriate routine for a given problem
  %  Parameters:
  %    Input, real U(N,M), contains the M-dimensional coordinates of N points.
  %    Output, VALUE(N), contains the value of the dirichlet boundary
  %    condition at each point.
  function valued = u_d ( ud )
    valued = zeros ( size ( ud, 1 ), 1 );
    for kk=1:size(ud,1)
      % U(x,y) = 1 + 2/11 x^2 + 3/11 y^2 solution values on boundary
      valued(kk)=uana(ud(kk,1),ud(kk,2));
    end
  end % u_d


  %% STIMA3 determines the local stiffness matrix for a triangular element.
  %  Discussion:
  %
  %    Although this routine is intended for 2D usage, the same formulas
  %    work for tetrahedral elements in 3D.  The spatial dimension intended
  %    is determined implicitly, from the spatial dimension of the vertices.
  %  Parameters:
  %
  %    Input, real VERTICES(1:(D+1),1:D), contains the D-dimensional 
  %    coordinates of the vertices.
  %
  %    Output, real M(1:(D+1),1:(D+1)), the local stiffness matrix 
  %    for the element.
  function M = stima3 ( vertices )
    d = size ( vertices, 2 );
    D_eta = [ ones(1,d+1); vertices' ] \ [ zeros(1,d); eye(d) ];
    M = det ( [ ones(1,d+1); vertices' ] ) * D_eta * D_eta' / prod ( 1:d );
  end % stima3
  

  %% STIMA4 determines the local stiffness matrix for a quadrilateral element.
  %  Parameters:
  %    Input, real VERTICES(1:4,1:2), contains the coordinates of the vertices.
  %    Output, real M(1:4,1:4), the local stiffness matrix for the element.%
  function M = stima4 ( vertices )
    D_Phi = [ vertices(2,:) - vertices(1,:); vertices(4,:) - vertices(1,:) ]';
    B = inv ( D_Phi' * D_Phi );
    C1 =  [  2, -2; -2,  2 ] * B(1,1) ...
        + [  3,  0;  0, -3 ] * B(1,2) ...
        + [  2,  1;  1,  2 ] * B(2,2);
    C2 =  [ -1,  1;  1, -1 ] * B(1,1) ...
        + [ -3,  0;  0,  3 ] * B(1,2) ...
        + [ -1, -2; -2, -1 ] * B(2,2);
    M = det ( D_Phi ) * [ C1 C2; C2 C1 ] / 6;
  end % stima4


  %% SHOW displays the solution of the finite element computation.
  %  Parameters:
  %    Input, integer elements3(N3,3), the nodes that make up each triangle.
  %    Input, integer elements4(N4,4), the nodes that make up each quadrilateral.
  %    Input, real coordinates(N,1:2), the coordinates of each node.
  %    Input, real U(N), the finite element coefficients which represent the solution.
  %    There is one coefficient associated with each node.
  function show ( elements3, elements4, coordinates, us, dirichlet )
    figure(1)
    hold off
  %
  %  Display the information associated with triangular elements.
    trisurf ( elements3, coordinates(:,1), coordinates(:,2), us' );
  %
  %  Retain the previous image, and overlay the information associated
  %  with quadrilateral elements.
    hold on
    trisurf ( elements4, coordinates(:,1), coordinates(:,2), us' );
  %
  %  Define the initial viewing angle.
  %
    view ( -67.5, 30 );  
    title ( 'Solution to the Problem' )
    hold off
    figure(2)
    for kk=1:size(elements3,1)
      plot([coordinates(elements3(kk,1),1)   ...
            coordinates(elements3(kk,2),1)   ...
            coordinates(elements3(kk,3),1)   ...
            coordinates(elements3(kk,1),1)], ...
           [coordinates(elements3(kk,1),2)   ...
            coordinates(elements3(kk,2),2)   ...
            coordinates(elements3(kk,3),2)   ...
            coordinates(elements3(kk,1),2)],':r')
      hold on
    end
    for kk=1:size(dirichlet,1)
      plot([coordinates(dirichlet(kk,1),1)   ...
            coordinates(dirichlet(kk,2),1)], ...
           [coordinates(dirichlet(kk,1),2)   ...
            coordinates(dirichlet(kk,2),2)],'-b')
      hold on
    end
    title('triangularization')
    xlabel('X')
    ylabel('Y')
    axis tight
    axis square
    grid off
    hold on
    xb=coordinates(BoundNodes,1);
	yb=coordinates(BoundNodes,2);
	plot(xb,yb,'.b')
	hold on
    xb=coordinates(FreeNodes,1);
	yb=coordinates(FreeNodes,2);
	plot(xb,yb,'.r')
	hold off
  end % show
end % fem_50d
