function [K,f] = ComputeSystem (X,T,Element,ProblemData)
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% This functions sets up the system to be solved for the steady convection,
% diffusion, reactino equation
%
% Input:
%  X --> nodal coordinates
%  T --> connectivities
%  Element --> data structure containing shape functions, derivatives,
%  gauss points, etc...
%  ProblemData --> data strucutre contaning diffusion and reaction
%  parameters, velocity, sourcer term, ...
%
% Output:
%  K --> left hand side of the linear system to be solved
%  f --> left hand side of the liner system
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

% Local variables 
method = ProblemData.method; % Method to solve the problem
s = ProblemData.s; % source term
ax = ProblemData.ConvVel(1); % convective velocity in x direction
ay = ProblemData.ConvVel(2);  % convective velocity in y direction
a = norm(ProblemData.ConvVel); % norm of the convective velocity
nu = ProblemData.nu; % diffusion coefficient
sigma = ProblemData.sigma; % reaction coefficient

% compute element size
Te = T(1,:); Xe = X(Te,:);  
hx = max(Xe(:,1)) - min(Xe(:,1)); % element size in x direction 
hy = max(Xe(:,2)) - min(Xe(:,2)); % element size in y direcion
h = (hx+hy)/2;  % average element size in the mesh
%h = sqrt(2/nx^2); % approximate element size

nen = Element.nen; % nodes per element
ngaus = length(Element.GaussPoints); % number of Gauss points
wgp = Element.GaussWeights; % Weights of Gauss points
N = Element.N; % Shape functions
Nxi = Element.Nxi; % Derivative w.r.t xi of the shape functions
Neta = Element.Neta; % Derivative w.r.t eta of the shape functions
Nxixi = Element.Nxixi; % Second derivative w.r.t xi
Netaeta = Element.Netaeta; % Second derivative w.r.t eta
numel = size(T,1); % total number of elements in the mesh
nnodes = size(X,1); % total number of nodes in the mesh

K = zeros(nnodes,nnodes); % initialize global system matrix
f = zeros(nnodes,1); % initialize global rhs forcing vector

%Peclet number and stabilization parameter depending on the method
if method == 1 % Galerkin
    disp('Galerkin formulation');
    tau = 0;
    % Peclet number
    Pe = a*h/(2*nu);
    disp(' ')
    disp(strcat('Peclet number: ',num2str(Pe)))
elseif method ==2 % SUPG
    disp ('SUPG formulation');
    % Peclet number
    Pe = a*h/(2*nu);
    disp(' ')
    disp(strcat('Peclet number: ',num2str(Pe)))
    % Stabilization parameter
    tau = h*(1 + 9/Pe^2)^(-1/2)/(2*a); % 4th order accurate formula
    disp(strcat('Recommended stabilization parameter = ',num2str(tau)));
elseif method ==3 %GLS
    disp ('GLS formulation');
    % Peclet number
    Pe = a*h/(2*nu);
    disp(' ')
    disp(strcat('Peclet number: ',num2str(Pe)))
    % Stabilization parameter
    tau = h*(1 + 9/Pe^2)^(-1/2)/(2*a); % 4th order accurate formula
    disp(strcat('Recommended stabilization parameter = ',num2str(tau)));
else
   error ('Unavailable method')
end

%Loop over elements
for ielem = 1:numel
    Te = T(ielem,:); % global number of the nodes in the current element
    Xe = X(Te,:); % nodal coordinates in the current element
    % Element matrices
    [Ke,fe] = EleMat(Xe,nen,ngaus,wgp,N,Nxi,Neta,Nxixi, Netaeta, method,tau,ax,ay,nu,sigma,s,h);
    % Assemble the element matrices
    K(Te,Te) = K(Te,Te) + Ke;
    f(Te) = f(Te) + fe;
    clear Ke; clear fe;
end
end

function [Ke,fe] = EleMat(Xe,nen,ngaus,wgp,N,Nxi,Neta,Nxixi, Netaeta, method,tau,ax,ay,nu,sigma,s, h)
%
Ke = zeros(nen,nen);
fe = zeros(nen,1);


% Loop on Gauss points (computation of integrals on the current element)
for igauss = 1:ngaus
    % Shape functions and derivatives at Gauss points
    N_igauss = N(igauss,:);
    Nxi_igauss = Nxi(igauss,:); 
    Neta_igauss = Neta(igauss,:);
    Nxixi_igauss = Nxixi(igauss,:);
    Netaeta_igauss = Netaeta(igauss,:);
    J = [Nxi_igauss*(Xe(:,1))	Nxi_igauss*(Xe(:,2))
         Neta_igauss*(Xe(:,1))	Neta_igauss*(Xe(:,2))]; % Jacobian
    dvolu = wgp(igauss)*det(J);
    res = J\[Nxi_igauss;Neta_igauss];
    Nx = res(1,:); % derivatives w.r.t x
    Ny = res(2,:); % derivatives w.r.t y
    Nxx = ((2/h)^2)*Nxixi_igauss;
    Nyy = ((2/h)^2)*Netaeta_igauss;
    
    % Elemental stiffness matrices
    if method == 1 % Galerkin
        aux = ax*Nx+ay*Ny;
        Ke = Ke + (N_igauss'*aux + nu*(Nx'*Nx+Ny'*Ny) + (sigma*N_igauss')*N_igauss)*dvolu; 
        f_igauss = SourceTerm(s); 
        fe = fe + N_igauss'*(f_igauss*dvolu); 
        
    elseif method == 2 % SUPG
        aux = ax*Nx+ay*Ny;
        Ke = Ke + (N_igauss'*aux + nu*(Nx'*Nx+Ny'*Ny) + (sigma*N_igauss')*N_igauss  ...
                + tau*(aux'*aux - aux'*nu*(Nxx + Nyy)+ aux'*sigma*N_igauss))*dvolu ;
        f_igauss = SourceTerm(s);
        fe = fe + (N_igauss + tau*(aux))'*(f_igauss*dvolu);
        
     elseif method == 3 % GLS
        aux = ax*Nx + ay*Ny;
        Ke = Ke + (N_igauss'*aux + nu*(Nx'*Nx+Ny'*Ny) + (sigma*N_igauss')*N_igauss ...
                + tau*((aux + sigma*N_igauss)'*(aux + sigma*N_igauss)))*dvolu ;
        f_igauss = SourceTerm(s); 
        fe = fe + (N_igauss + tau*(aux+sigma*N_igauss))'*(f_igauss*dvolu);
    end
end
end