%% Finite Element code for solving 2D full periodic problem using Linear Traingular elements
% % The code is applying full-periodic boundary conditions

clear
close all
clc
%% Model parameters

Node_coordiante = xlsread('2D_CrossBedded_SandStone.xlsx','Node','A1:B34343');  % Reading nodes (x,y) co-ordinates
Connectivity = xlsread('2D_CrossBedded_SandStone.xlsx','Connectivity','A1:C67718');  %   % connectivity of nodes where the array shows element IDs
k = xlsread('2D_CrossBedded_SandStone.xlsx','Connectivity','D1:D67718');    % hydraulic conductivity   
G = [-1 -3];    % pressure gradient field
X_coordiante = Node_coordiante(:,1) ; Y_coordiante = Node_coordiante(:,2) ;   % (x,y) co-ordiantes of nodes
%% Extracting nodes located located at left, right, bottom and top boundaries and sorting them such that each node faces its corresponding

minX = min(X_coordiante);     % minimum x coordiante
maxX = max(X_coordiante);     % maximum x coordiante
minY = min(Y_coordiante);     % minimum y coordiante
maxY = max(Y_coordiante);     % maximum y coordiante
lx = maxX - minX;   % length of the domain in x-direction
ly = maxY - minY;   % length of the domain in y-direction
% Find node IDs at the left boundary
leftBoundaryNodes = find(X_coordiante == minX);
% Find nodes at the right boundary
rightBoundaryNodes = find(X_coordiante == maxX);
% Find node IDs at the bottom boundary
bottomBoundaryNodes = find(Y_coordiante == minY);
% Find nodes at the top boundary
topBoundaryNodes = find(Y_coordiante == maxY);
% Sort Boundary Nodes at left and right boundaries based on y-coordinates
[~, leftBoundarySortIndex] = sort(Node_coordiante(leftBoundaryNodes, 2));
leftBoundaryNodes = leftBoundaryNodes(leftBoundarySortIndex);
[~, rightBoundarySortIndex] = sort(Node_coordiante(rightBoundaryNodes, 2));
rightBoundaryNodes = rightBoundaryNodes(rightBoundarySortIndex);
% Sort Boundary Nodes at bottom and top boundaries based on x-coordinates
[~, bottomBoundarySortIndex] = sort(Node_coordiante(bottomBoundaryNodes, 1));
bottomBoundaryNodes = bottomBoundaryNodes(bottomBoundarySortIndex);
[~, topBoundarySortIndex] = sort(Node_coordiante(topBoundaryNodes, 1));
topBoundaryNodes = topBoundaryNodes(topBoundarySortIndex);

% Check if the lengths of leftBoundaryNodes and rightBoundaryNodes are the same
if length(leftBoundaryNodes) ~= length(rightBoundaryNodes)
    error('Lengths of leftBoundaryNodes and rightBoundaryNodes must be the same for periodicity.');
end

% Check if the y-coordinates of corresponding nodes are the same
if max(abs( Y_coordiante(leftBoundaryNodes) - Y_coordiante(rightBoundaryNodes))) ~= 0
    error('Nodes at Left and Right boundaries do not match')
end

% Check if the lengths of bottomBoundaryNodes and topBoundaryNodes are the same
if length(bottomBoundaryNodes) ~= length(topBoundaryNodes)
    error('Lengths of bottomBoundaryNodes and topBoundaryNodes must be the same for periodicity.');
end

% Check if the x-coordinates of corresponding nodes are the same
if max(abs( X_coordiante(bottomBoundaryNodes) - X_coordiante(topBoundaryNodes))) ~= 0
    error('Nodes at Bottom and Top boundaries do not match')
end
%% Construcuting conductance matrix

No_elements = length(k);    % number of elements
No_nodes = size(Node_coordiante, 1);    % number of nodes
Global_Conductnace = sparse(No_nodes, No_nodes);
RHS = zeros(No_nodes , 1);
% loop over elements and estimate weighted residuals
for j = 1:No_elements
    % Get the nodes and coordinates of the current element
    elementNodes = Connectivity(j, :);
    x = X_coordiante(elementNodes);
    y = Y_coordiante(elementNodes);
    
    % Local conductance matrix
    Local_Conductnace = k(j) *  ElementConductnace(x,y); 
    
    % Assembling the local contributions into the global matrices
    Global_Conductnace(elementNodes, elementNodes) = Global_Conductnace(elementNodes, elementNodes) + Local_Conductnace;
end

%% Apply periodic boundary conditions

Global_Conductnace_Periodic = Global_Conductnace;    % Global_Conductnace_Periodic is the modified matrix that coantains the periodicity
RHS_Periodic = RHS;   % Modified RHS that accounts for periodicity
% Apply periodic boundary conditions by accumulating terms at left and right boundaries
for i = 1:length(leftBoundaryNodes)
    leftNodeID = leftBoundaryNodes(i);
    rightNodeID = rightBoundaryNodes(i);

    % Modifying target node
    Global_Conductnace_Periodic(leftNodeID, leftNodeID) = Global_Conductnace_Periodic(leftNodeID, leftNodeID) + Global_Conductnace(rightNodeID, rightNodeID);
    RHS_Periodic(leftNodeID) = RHS_Periodic(leftNodeID) + RHS(rightNodeID);
    Global_Conductnace_Periodic(rightNodeID, rightNodeID) = Global_Conductnace_Periodic(rightNodeID, rightNodeID) + Global_Conductnace(leftNodeID, leftNodeID);
    RHS_Periodic(rightNodeID) = RHS_Periodic(rightNodeID) + RHS(leftNodeID);
    % Modifying nodes at lower side on boundary
    if i>1
        lowerLeftNodeID = leftBoundaryNodes(i-1);        % node at left boundary connected to target node and located at lower side
        lowerRightNodeID = rightBoundaryNodes(i-1);        % node at right boundary connected to target node and located at lower side
        Global_Conductnace_Periodic(leftNodeID, lowerLeftNodeID) = Global_Conductnace_Periodic(leftNodeID, lowerLeftNodeID) + Global_Conductnace(rightNodeID, lowerRightNodeID);
        Global_Conductnace_Periodic(rightNodeID, lowerRightNodeID) = Global_Conductnace_Periodic(rightNodeID, lowerRightNodeID) + Global_Conductnace(leftNodeID, lowerLeftNodeID);
    end
    % Modifying nodes at higher side on boundary
    if i<length(leftBoundaryNodes)
        higherLeftNodeID = leftBoundaryNodes(i+1);        % node at left boundary connected to target node and located at higher side
        higherRightNodeID = rightBoundaryNodes(i+1);        % node at right boundary connected to target node and located at higher side
        Global_Conductnace_Periodic(leftNodeID, higherLeftNodeID) = Global_Conductnace_Periodic(leftNodeID, higherLeftNodeID) + Global_Conductnace(rightNodeID, higherRightNodeID);
        Global_Conductnace_Periodic(rightNodeID, higherRightNodeID) = Global_Conductnace_Periodic(rightNodeID, higherRightNodeID) + Global_Conductnace(leftNodeID, higherLeftNodeID);
    end

    % Modifying non-boundary nodes at left target node
    rightNodeEq = find(Global_Conductnace(rightNodeID,:) ~= 0);   % nodes connected to a node at right boundary
    rightNonBoundaryNodeID = ismember(rightNodeEq,rightBoundaryNodes) == 0;
    rightNonBoundaryNodeID = rightNodeEq(rightNonBoundaryNodeID);     % non boundary nodes connected to target node
    Global_Conductnace_Periodic(leftNodeID,rightNonBoundaryNodeID) = Global_Conductnace(rightNodeID,rightNonBoundaryNodeID);
    RHS_Periodic(leftNodeID) = RHS_Periodic(leftNodeID) + G(1) * lx * sum(Global_Conductnace(rightNodeID,rightNonBoundaryNodeID));

    % Modifying non-boundary nodes at right target node
    leftNodeEq = find(Global_Conductnace(leftNodeID,:) ~= 0);   % nodes connected to a node at left boundary
    leftNonBoundaryNodeID = ismember(leftNodeEq,leftBoundaryNodes) == 0;
    leftNonBoundaryNodeID = leftNodeEq(leftNonBoundaryNodeID);     % non boundary nodes connected to target node
    Global_Conductnace_Periodic(rightNodeID,leftNonBoundaryNodeID) = Global_Conductnace(leftNodeID,leftNonBoundaryNodeID);
    RHS_Periodic(rightNodeID) = RHS_Periodic(rightNodeID) - G(1) * lx * sum(Global_Conductnace(leftNodeID,leftNonBoundaryNodeID));
    
    % Modifying corner nodes
    if i==1
        % Modifying lower left node
        rightUpperNodeID = rightBoundaryNodes(end);
        rightUpperNodeEq = find(Global_Conductnace(rightUpperNodeID,:) ~= 0);   % nodes connected to a node at upper right boundary node
        rightUpperNodeEq(rightUpperNodeEq == rightUpperNodeID) = [];      % removing target node as it's piled up directly into correponding node
        Global_Conductnace_Periodic(leftNodeID, leftNodeID) = Global_Conductnace_Periodic(leftNodeID, leftNodeID) + Global_Conductnace(rightUpperNodeID,rightUpperNodeID);
        Global_Conductnace_Periodic(leftNodeID, rightUpperNodeEq) = Global_Conductnace(rightUpperNodeID,rightUpperNodeEq);
        RHS_Periodic(leftNodeID) = RHS_Periodic(leftNodeID) + RHS(rightUpperNodeID) + sum(Global_Conductnace(rightUpperNodeID,rightUpperNodeEq)) * sum(G .* (Node_coordiante(rightUpperNodeID,:) - Node_coordiante(leftNodeID,:)));
        % Modifying lower right node
        leftUpperNodeID = leftBoundaryNodes(end);
        leftUpperNodeEq = find(Global_Conductnace(leftUpperNodeID,:) ~= 0);   % nodes connected to a node at upper right boundary node
        leftUpperNodeEq(leftUpperNodeEq == leftUpperNodeID) = [];      % removing target node as it's piled up directly into correponding node
        Global_Conductnace_Periodic(rightNodeID, rightNodeID) = Global_Conductnace_Periodic(rightNodeID, rightNodeID) + Global_Conductnace(leftUpperNodeID,leftUpperNodeID);
        Global_Conductnace_Periodic(rightNodeID, leftUpperNodeEq) = Global_Conductnace(leftUpperNodeID,leftUpperNodeEq);
        RHS_Periodic(rightNodeID) = RHS_Periodic(rightNodeID) + RHS(leftUpperNodeID) + sum(Global_Conductnace(leftUpperNodeID,leftUpperNodeEq)) * sum(G .* (Node_coordiante(leftUpperNodeID,:) - Node_coordiante(rightNodeID,:)));
    end
    
    if i == length(leftBoundaryNodes)
        % Modifying upper left node
        rightLowerNodeID = rightBoundaryNodes(1);
        rightLowerNodeEq = find(Global_Conductnace(rightLowerNodeID,:) ~= 0);   % nodes connected to a node at upper right boundary node
        rightLowerNodeEq(rightLowerNodeEq == rightLowerNodeID) = [];      % removing target node as it's piled up directly into correponding node
        Global_Conductnace_Periodic(leftNodeID, leftNodeID) = Global_Conductnace_Periodic(leftNodeID, leftNodeID) + Global_Conductnace(rightLowerNodeID,rightLowerNodeID);
        Global_Conductnace_Periodic(leftNodeID, rightLowerNodeEq) = Global_Conductnace(rightLowerNodeID,rightLowerNodeEq);
        RHS_Periodic(leftNodeID) = RHS_Periodic(leftNodeID) + RHS(rightLowerNodeID) + sum(Global_Conductnace(rightLowerNodeID,rightLowerNodeEq)) * sum(G .* (Node_coordiante(rightLowerNodeID,:) - Node_coordiante(leftNodeID,:)));
        % Modifying upper right node
        leftLowerNodeID = leftBoundaryNodes(1);
        leftLowerNodeEq = find(Global_Conductnace(leftLowerNodeID,:) ~= 0);   % nodes connected to a node at upper right boundary node
        leftLowerNodeEq(leftLowerNodeEq == leftLowerNodeID) = [];      % removing target node as it's piled up directly into correponding node
        Global_Conductnace_Periodic(rightNodeID, rightNodeID) = Global_Conductnace_Periodic(rightNodeID, rightNodeID) + Global_Conductnace(leftLowerNodeID,leftLowerNodeID);
        Global_Conductnace_Periodic(rightNodeID, leftLowerNodeEq) = Global_Conductnace(leftLowerNodeID,leftLowerNodeEq);
        RHS_Periodic(rightNodeID) = RHS_Periodic(rightNodeID) + RHS(leftLowerNodeID) + sum(Global_Conductnace(leftLowerNodeID,leftLowerNodeEq)) * sum(G .* (Node_coordiante(leftLowerNodeID,:) - Node_coordiante(rightNodeID,:)));        
    end
end

% Apply periodic boundary conditions by accumulating terms at bottom and top boundaries
for j = 1:length(bottomBoundaryNodes)
    bottomNodeID = bottomBoundaryNodes(j);
    topNodeID = topBoundaryNodes(j);

    % Modifying target node
    Global_Conductnace_Periodic(bottomNodeID, bottomNodeID) = Global_Conductnace_Periodic(bottomNodeID, bottomNodeID) + Global_Conductnace(topNodeID, topNodeID);
    RHS_Periodic(bottomNodeID) = RHS_Periodic(bottomNodeID) + RHS(topNodeID);
    Global_Conductnace_Periodic(topNodeID, topNodeID) = Global_Conductnace_Periodic(topNodeID, topNodeID) + Global_Conductnace(bottomNodeID, bottomNodeID);
    RHS_Periodic(topNodeID) = RHS_Periodic(topNodeID) + RHS(bottomNodeID);
    % Modifying nodes at lower side on boundary
    if j>1
        lowerBottomNodeID = bottomBoundaryNodes(j-1);        % node at bottom boundary connected to target node and located at lower side
        lowerTopNodeID = topBoundaryNodes(j-1);        % node at top boundary connected to target node and located at lower side
        Global_Conductnace_Periodic(bottomNodeID, lowerBottomNodeID) = Global_Conductnace_Periodic(bottomNodeID, lowerBottomNodeID) + Global_Conductnace(topNodeID, lowerTopNodeID);
        Global_Conductnace_Periodic(topNodeID, lowerTopNodeID) = Global_Conductnace_Periodic(topNodeID, lowerTopNodeID) + Global_Conductnace(bottomNodeID, lowerBottomNodeID);
    end
    % Modifying nodes at higher side on boundary
    if j<length(bottomBoundaryNodes)
        higherBottomNodeID = bottomBoundaryNodes(j+1);        % node at left boundary connected to target node and located at higher side
        higherTopNodeID = topBoundaryNodes(j+1);        % node at right boundary connected to target node and located at higher side
        Global_Conductnace_Periodic(bottomNodeID, higherBottomNodeID) = Global_Conductnace_Periodic(bottomNodeID, higherBottomNodeID) + Global_Conductnace(topNodeID, higherTopNodeID);
        Global_Conductnace_Periodic(topNodeID, higherTopNodeID) = Global_Conductnace_Periodic(topNodeID, higherTopNodeID) + Global_Conductnace(bottomNodeID, higherBottomNodeID);
    end

    % Modifying non-boundary nodes at bottom target node
    topNodeEq = find(Global_Conductnace(topNodeID,:) ~= 0);   % nodes connected to a node at top boundary
    topNonBoundaryNodeID = ismember(topNodeEq,topBoundaryNodes) == 0;
    topNonBoundaryNodeID = topNodeEq(topNonBoundaryNodeID);     % non boundary nodes connected to target node
    Global_Conductnace_Periodic(bottomNodeID,topNonBoundaryNodeID) = Global_Conductnace(topNodeID,topNonBoundaryNodeID);
    RHS_Periodic(bottomNodeID) = RHS_Periodic(bottomNodeID) + G(2) * ly * sum(Global_Conductnace(topNodeID,topNonBoundaryNodeID));

    % Modifying non-boundary nodes at right target node
    bottomNodeEq = find(Global_Conductnace(bottomNodeID,:) ~= 0);   % nodes connected to a node at left boundary
    bottomNonBoundaryNodeID = ismember(bottomNodeEq,bottomBoundaryNodes) == 0;
    bottomNonBoundaryNodeID = bottomNodeEq(bottomNonBoundaryNodeID);     % non boundary nodes connected to target node
    Global_Conductnace_Periodic(topNodeID,bottomNonBoundaryNodeID) = Global_Conductnace(bottomNodeID,bottomNonBoundaryNodeID);
    RHS_Periodic(topNodeID) = RHS_Periodic(topNodeID) - G(2) * ly * sum(Global_Conductnace(bottomNodeID,bottomNonBoundaryNodeID)); 
end

% specifying a pressure value to avoid singularity
new_row = zeros(1,No_nodes);  new_row(fix(No_nodes/2)) = 1;
Global_Conductnace_Periodic = [Global_Conductnace_Periodic;new_row];
RHS_Periodic = [RHS_Periodic ; 1];
%% Solving the linear system and Plotting

p = Global_Conductnace_Periodic\RHS_Periodic;    % pressure at nodes  
% plotting 
patch('Vertices', Node_coordiante, 'Faces', Connectivity, 'FaceVertexCData', p, 'FaceColor', 'interp');
xlabel('x (cm)');
ylabel('y (cm)');
grid on;
box on
colorbar
%%
function Conductance = ElementConductnace(x, y)
    % Element conductance matrix for a linear triangle
    % Customize this function for your specific element type and material properties
    % For a linear triangular element with constant material properties:
    area = 0.5 * abs((x(2) - x(1)) * (y(3) - y(1)) - (x(3) - x(1)) * (y(2) - y(1)));
    B = [y(2) - y(3), y(3) - y(1), y(1) - y(2); x(3) - x(2), x(1) - x(3), x(2) - x(1)] / (2 * area);
    Conductance = area * (B' * B);
end