Model Class

Abstract class to create and manage a Finite Element model. It provides methods and properties to define the mesh, boundary conditions, material properties, and other essential components of a FEM simulation. The class also includes methods for pre-processing, assembling global matrices, applying boundary conditions, and updating state variables.

Contents

Methods

Author

Danilo Cavalcanti

Version History

Version 1.00: Initial version (April 2023).

Class definition

classdef Model < handle

Public attributes

    properties (SetAccess = public, GetAccess = public)
        name                = 'mdl';         % Model name
        physics             = [];            % Physics of the problem
        NODE                = [];            % Nodes of the fem mesh
        ELEM                = [];            % Nodes connectivity
        DIRICHLET_TAG       = [];            % Matrix with tags indicating the Dirichlet BC
        DIRICHLET_VAL       = [];            % Matrix with values of the Dirichlet BC
        LOAD                = [];            % Matrix with the nodal Neumann BC
        INIT                = [];            % Matrix with the initial values of each dof
        t                   = 1.0;           % Thickness
        mat                 = [];            % Struct with material properties
        intOrder            = 2;             % Order of the numerical integration quadrature
        nnodes              = 1;             % Number of nodes
        nelem               = 1;             % Number of elements
        doffree             = [];            % Vector with the free dofs
        doffixed            = [];            % Vector with the fixed dofs
        ndof_nd             = 2;             % Number of dof per node
        ndof                = 1;             % Number of degrees of freedom
        ndoffree            = 0;             % Number of free degrees of freedom
        ndoffixed           = 0;             % Number of fixed degrees of freedom
        Dof                 = [];            % Vector with all regular dofs
        ID                  = [];            % Each line of the ID matrix contains the global numbers for the node DOFs
        U                   = [];            % Global displacement vector
        element             = [];            % Array with the element's objects
        nDofElemTot         = 0;             % Aux value used to sparse matrix assemblage
        sqrNDofElemTot      = 0;             % Aux value used to sparse matrix assemblage
        matID               = [];            % Vector with the material id of each element
        gravityOn           = false;         % Flag to consider the gravity forces
        massLumping         = false;         % Tag for applying a mass lumping process
        lumpStrategy        = 1;             % Id of the mass lumping strategy
        isAxisSymmetric     = false;         % Flag for axissymetric models
        enriched            = false;         % Flag to use embedded formulation
        discontinuitySet    = [];            % Array with the discontinuity objects
        condenseEnrDofs     = true;          % Flag to condense the enrichment dofs
        dofenr              = [];            % Vector with the enrichment dofs
        ndofenr             = 0;             % Number of enrichment dofs
        initializeMdl       = false;         % Flag to check if the model has been initialized
    end

Constructor method

    methods
        function this = Model()
            disp("*** Initializing model...")
        end
    end

Abstract methods

    methods(Abstract)

        % Set the material object
        setMaterial(this, varargin)

        % Initialize the elements objects
        initializeElements(this);

        % Configure the header to printed when printing results
        printResultsHeader();
    end

Public methods

    methods

        %------------------------------------------------------------------
        function setMesh(this,node,elem)
            % Set the mesh nodes coordinates and connectivity
            this.NODE = node;
            this.ELEM = elem;

            % Initialize basic variables
            this.initializeBasicVariables();

        end

        %------------------------------------------------------------------
        % Initializes the basic variables of the model
        function initializeBasicVariables(this)
            this.nnodes        = size(this.NODE,1);
            this.nelem         = size(this.ELEM,1);
            this.ndof          = this.ndof_nd * this.nnodes;
            this.DIRICHLET_TAG = zeros(this.nnodes,this.ndof_nd);
            this.DIRICHLET_VAL = zeros(this.nnodes,this.ndof_nd);
            this.LOAD          = zeros(this.nnodes,this.ndof_nd);
            this.INIT          = zeros(this.nnodes,this.ndof_nd);
        end

        %------------------------------------------------------------------
        % Check is the material is well defined or not
        function checkMaterialId(this)
            if isempty(this.matID)
                this.matID  = ones(this.nelem,1);
            end
        end

        %------------------------------------------------------------------
        % Creates and assembles the matrix containing all the degrees of
        % freedom
        function createNodeDofIdMtrx(this)
            % Initialize the ID matrix and the number of fixed dof
            this.ID = zeros(this.nnodes,this.ndof_nd);
            this.ndoffixed = 0;

            % Assemble the ID matrix
            for i = 1:this.nnodes
                for j = 1:this.ndof_nd
                    this.ID(i,j) = (i - 1) * this.ndof_nd + j;
                    if (this.DIRICHLET_TAG(i,j) == 1)
                        this.ndoffixed = this.ndoffixed + 1;
                    end
                end
            end

            % Vector with all the dofs
            this.Dof = 1:this.ndof;

            % Number of free dof
            this.ndoffree = this.ndof - this.ndoffixed;

            % Initialize the counters
            this.doffixed = zeros(this.ndoffixed,1);
            this.doffree  = zeros(this.ndoffree,1);

            % Update the ID matrix with the free dof numbered first
            countFree = 1;
            countFixed = 1;
            for i = 1:this.nnodes
                for j = 1:this.ndof_nd
                    if this.DIRICHLET_TAG(i,j) == 1
                        this.doffixed(countFixed) = this.ID(i,j);
                        countFixed = countFixed + 1;
                    else
                        this.doffree(countFree) = this.ID(i,j);
                        countFree = countFree + 1;
                    end
                end
            end

            % Add the enrichment dofs to the free dof vector
            for i = 1:this.ndofenr
                this.doffree(countFree) = this.dofenr(i);
                countFree = countFree + 1;
            end
        end

        %------------------------------------------------------------------
        % Prescribe a Dirichlet boundary condition at a node
        function setDirichletBCAtNode(this, nodeId, dofId, value)
            if (length(dofId) ~= length(value))
                disp('Error prescribing Dirichlet BC at a node');
                disp('length(dofId) ~= length(value)');
                error('Error in setDirichletBCAtNode');
            end
            for i = 1:length(dofId)
                if ( this.DIRICHLET_TAG(nodeId,dofId(i)) == 0)
                    this.DIRICHLET_TAG(nodeId,dofId(i)) = ~isnan(value(i));
                    this.DIRICHLET_VAL(nodeId,dofId(i)) = value(i);
                else
                    if ((this.DIRICHLET_VAL(nodeId,dofId(i)) ~= value(i)) && ~isnan(value(i)))
                        disp([' ** Warning: this node had a different prescribed value:',num2str(nodeId)])
                        this.DIRICHLET_VAL(nodeId,dofId(i)) = value(i);
                    end
                end
            end
        end

        %------------------------------------------------------------------
        % Prescribe a Dirichlet boundary condition at a node
        function setDirichletBCAtPoint(this, X, dofId, value)
            nodeId = this.closestNodeToPoint(X);
            this.setDirichletBCAtNode(nodeId,dofId,value);
        end

        %------------------------------------------------------------------
        % Prescribe a Dirichlet boundary condition at a border
        function setDirichletBCAtBorder(this, border, dofId, value)
            nodeIds = this.getNodesAtBorder(border);
            for i = 1:length(nodeIds)
                this.setDirichletBCAtNode(nodeIds(i),dofId,value);
            end
        end

        %------------------------------------------------------------------
        % Prescribe a Neumann boundary condition at a node
        function setNeumannBCAtNode(this, nodeId, dofId, value)
            this.LOAD(nodeId,dofId) = value;
        end

        %------------------------------------------------------------------
        % Prescribe a Dirichlet boundary condition at a point
        function setNeumannBCAtPoint(this, X, dofId, value)
            nodeId = this.closestNodeToPoint(X);
            this.LOAD(nodeId,dofId) = value;
        end

        %------------------------------------------------------------------
        % Prescribe a Neumann boundary condition at a node
        function setNeumannBCAtBorder(this, border, dofId, value)
            nodeIds = this.getNodesAtBorder(border);
            for i = 1:length(nodeIds)
                this.setNeumannBCAtNode(nodeIds(i),dofId,value);
            end
        end

        %------------------------------------------------------------------
        % Prescribe an initial boundary condition at the whole
        % domain
        function setInitialDofAtDomain(this, dofId, value)
            if (length(dofId) ~= length(value))
                disp('Error setting initial dof value at the domain');
                disp('length(dofId) ~= length(value)');
                error('Error in setInitialDofAtDomain');
            end
            this.INIT(:,dofId) = value;
        end

        %------------------------------------------------------------------
        % Prescribe an initial boundary condition at a node
        function setInitialDofAtNode(this, nodeId, dofId, value)
            if (length(dofId) ~= length(value))
                disp('Error setting initial dof value at the domain');
                disp('length(dofId) ~= length(value)');
                error('Error in setInitialDofAtNode');
            end
            this.INIT(nodeId,dofId) = value;
        end

        %------------------------------------------------------------------
        % Identify the nodes contained in any of the borders
        function nodeIds = getNodesAtBorder(this,border)
            % Get the nodes at the given border
            if strcmp(border,'left')
                nodeIds = find(abs(this.NODE(:,1)-min(this.NODE(:,1)))<1.0e-12);
            elseif strcmp(border,'right')
                nodeIds = find(abs(this.NODE(:,1)-max(this.NODE(:,1)))<1.0e-12);
            elseif strcmp(border,'top')
                nodeIds = find(abs(this.NODE(:,2)-max(this.NODE(:,2)))<1.0e-12);
            elseif strcmp(border,'bottom')
                nodeIds = find(abs(this.NODE(:,2)-min(this.NODE(:,2)))<1.0e-12);
            else
                disp('Warning: non-supported border.');
                disp('Available borders tag: ''left'',''right'', ''top'',''bottom''');
                nodeIds = [];
            end
        end

        %------------------------------------------------------------------
        % Find the closest node to a given point
        function nd = closestNodeToPoint(this,X)
            if size(X,1) == 2, X = X'; end
            d = vecnorm((this.NODE - X)');
            [~,id] = sort(d);
            nd = id(1);
        end

        %------------------------------------------------------------------
        % Perform all the necessary pre-computations to initialize the
        % model
        function preComputations(this)
            if(this.initializeMdl == false)
                disp("*** Pre-processing...");

                % Check and initialize the material ID vector
                this.checkMaterialId();

                % Create nodes DOF ids matrix
                this.createNodeDofIdMtrx();

                % Initialize elements
                this.initializeElements();

                % Assemble discontinuity segments to the elements
                this.assembleDiscontinuitySegments();

                % Compute auxiliar variables for assemblage of sparse matrices
                this.initializeSparseMtrxAssemblageVariables();

                % Initialize the displacement vector
                this.initializeDisplacementVct();

                % Update flag to indicate that the model has already been initialized
                this.initializeMdl = true;
            end
        end

        %------------------------------------------------------------------
        % Reset the model dof vector and state variables
        function resetModelState(this)
            if(this.initializeMdl == true)
                % Reset the displacement vector
                this.initializeDisplacementVct();
                % Reset the state variables
                for el = 1:this.nelem
                    this.element(el).type.resetIntegrationPts();
                end
            end
        end

        %------------------------------------------------------------------
        % Obtain all the element degrees of freedom
        function dof = getElementDofs(this,el,dofId)
            nnd_el = length(this.ELEM{el});
            dof = reshape(this.ID(this.ELEM{el},dofId)', 1, nnd_el*length(dofId));
        end

        %------------------------------------------------------------------
        % Initialize the vector containing all the displacement
        % values
        function initializeDisplacementVct(this)
            % Initialize the displacement vector
            this.U = zeros(this.ndof,1);

            % Set the initial values
            for i = 1:this.nnodes
                for j = 1:this.ndof_nd
                    this.U(this.ID(i,j)) = this.INIT(i,j);
                end
            end

            % Set the prescribed values
            for i = 1:this.nnodes
                for j = 1:this.ndof_nd
                    if (this.DIRICHLET_TAG(i,j) == 1.0)
                        this.U(this.ID(i,j)) = this.DIRICHLET_VAL(i,j);
                    end
                end
            end

            % Save initial dofs to the elements
            for el = 1 : this.nelem
                this.element(el).type.ue = this.U(this.element(el).type.gle);
            end
        end

        %------------------------------------------------------------------
        % Assembly discontinuity segments for enriched elements
        function assembleDiscontinuitySegments(this)
            if (this.enriched == false)
                return
            elseif isempty(this.discontinuitySet)
                return
            else
                nDiscontinuities = length(this.discontinuitySet);
                for i = 1:nDiscontinuities
                    % Loop through the segments of this discontinuity
                    k = 1;
                    for j = 1:size(this.discontinuitySet(i).Xlin,1)-1
                        el = this.discontinuitySet(i).elemID(j);
                        if (el > 0)
                            dseg = this.discontinuitySet(i).segment(k);
                            this.element(el).type.addDiscontinuitySegment(dseg);
                            k = k + 1;
                        end
                    end
                end
                this.updateEnrichedElementDofVector();
            end
        end

        %------------------------------------------------------------------
        % Add enrichment dofs to the elements dof vector
        function updateEnrichedElementDofVector(this)
            if (this.condenseEnrDofs)
                return
            else
                for el = 1:this.nelem
                    this.element(el).type.addEnrichmentToDofVector();
                end
            end
        end

        %------------------------------------------------------------------
        % Add contribution of nodal loads to reference load vector.
        function Fref = addNodalLoad(this,Fref)
            for i = 1:this.nnodes
                for j = 1:this.ndof_nd
                    Fref(this.ID(i,j)) = Fref(this.ID(i,j)) + this.LOAD(i,j);
                end
            end
        end

        %------------------------------------------------------------------
        % Obtain the characteristic length of the finite element
        function Lce = getElementsCharacteristicLength(this)
            Lce=zeros(this.nelem,1);
            for el = 1:this.nelem
                Lce(el) = this.getElementCharacteristicLength(el);
            end
        end

        %------------------------------------------------------------------
        % Get characteristic length of the elements
        function Lce = getElementCharacteristicLength(this,el)
            % Vertices of the element el coordinates
            vx = this.NODE(this.ELEM{el},1);
            vy = this.NODE(this.ELEM{el},2);

            % Number of vertices
            nv = length(this.ELEM{el});

            % Shifted vertices
            vxS = vx([2:nv 1]);
            vyS = vy([2:nv 1]);

            % Compute the area of the element (trapezoidal rule)
            temp = vx.*vyS - vy.*vxS;
            Ae   = 0.5*sum(temp);

            % Characteristic lenght (quadrilateral elements)
            Lce = sqrt(Ae);
            if (nv == 3)||(nv == 6)
                Lce = Lce * sqrt(2.0);
            end
        end

        %------------------------------------------------------------------
        % Compute the mean characteristic length of the elements associated
        % with each node
        function Lc = getNodeCharacteristicLength(this)
            Lce = this.getElementsCharacteristicLength();
            Lc = zeros(this.nnodes,1);
            for i = 1:this.nnodes
                % Get the elements associated with this node
                idElem = cellfun(@(elem) any(elem == i), this.ELEM);
                % Compute the mean characteristic lenght of these nodes
                Lc(i) = mean(Lce(idElem));
            end

        end

        %------------------------------------------------------------------
        % Initializes variables related to the assembly of sparse matrices
        % in the model.
        function initializeSparseMtrxAssemblageVariables(this)
            this.nDofElemTot = 0;
            this.sqrNDofElemTot = 0;
            for el = 1:this.nelem
                this.nDofElemTot = this.nDofElemTot + this.element(el).type.ngle;
                this.sqrNDofElemTot = this.sqrNDofElemTot + this.element(el).type.ngle*this.element(el).type.ngle;
            end
        end

        %------------------------------------------------------------------
        % Global system matrices
        function [K, C, Fi, Fe, dfidx] = globalMatrices(this,U)
            % Indices for the assembling the matrices and vector
            iDof = zeros(this.sqrNDofElemTot,1);
            jDof = zeros(this.sqrNDofElemTot,1);
            eDof = zeros(this.nDofElemTot,1);

            % Initialize the components of the matrices and vector
            K_ij      = zeros(this.sqrNDofElemTot,1);
            C_ij      = zeros(this.sqrNDofElemTot,1);
            dfidx_ij  = zeros(this.sqrNDofElemTot,1);
            fi_i      = zeros(this.nDofElemTot,1);
            fe_i      = zeros(this.nDofElemTot,1);

            % Initialize auxiliar variables
            counterK = 0;
            counterF = 0;

            % Compute and assemble element data
            for el = 1:this.nelem

                % Get the element object
                elementType = this.element(el).type;

                % Update the element displacement vector
                elementType.ue = U(elementType.gle);

                % Get the vector of the element dof
                gle_i  = elementType.gle;
                gle_j  = gle_i;
                nGlei  = length(gle_i);
                nGlej  = length(gle_j);
                nGleij = nGlei*nGlej;

                % Get the indices for assemblage
                iDofEl = repmat(gle_i',1,nGlej);
                jDofEl = repmat(gle_j,nGlei,1);
                iDof(counterK+1:counterK+nGleij) = iDofEl(:);
                jDof(counterK+1:counterK+nGleij) = jDofEl(:);
                eDof(counterF+1:counterF+nGlei)  = gle_i';

                % Get local matrices and vector
                [K_e,C_e,fi_e,fe_e,dfidx_e] = elementType.elementData();

                % Store in the global vectors
                K_ij(counterK+1:counterK+nGleij)     = K_e(:);
                C_ij(counterK+1:counterK+nGleij)     = C_e(:);
                dfidx_ij(counterK+1:counterK+nGleij) = dfidx_e(:);
                fi_i(counterF+1:counterF+nGlei)      = fi_e(:);
                fe_i(counterF+1:counterF+nGlei)      = fe_e(:);

                % Update auxiliar variables
                counterK = counterK + nGleij;
                counterF = counterF + nGlei;

            end

            % Assemble the matrices and vector
            K      = sparse(iDof,jDof,K_ij);
            C      = sparse(iDof,jDof,C_ij);
            dfidx  = sparse(iDof,jDof,dfidx_ij);
            Fi     = sparse(eDof,ones(this.nDofElemTot,1),fi_i);
            Fe     = sparse(eDof,ones(this.nDofElemTot,1),fe_i);

            % Add contribution of the nodal forces to the external force
            % vector
            Fe = this.addNodalLoad(Fe);

        end

        %------------------------------------------------------------------
        % Global system matrices
        function [A,b] = getLinearSystem(this,U,UOld,nonlinearScheme,dt)

            % Indices for the assembling the matrices and vector
            iDof = zeros(this.sqrNDofElemTot,1);
            jDof = zeros(this.sqrNDofElemTot,1);
            eDof = zeros(this.nDofElemTot,1);

            % Initialize the components of the matrices and vector
            A_ij  = zeros(this.sqrNDofElemTot,1);
            b_i  = zeros(this.nDofElemTot,1);

            % Initialize auxiliar variables
            counterK = 0;
            counterF = 0;

            % Compute and assemble element data
            for el = 1:this.nelem
                % Get the element object
                elementType = this.element(el).type;

                % Update the element displacement vector of each element
                elementType.DTime = dt;
                elementType.ue    = U(elementType.gle);
                elementType.ueOld = UOld(elementType.gle);

                % Get the vector of the element dof
                gle_i  = elementType.gle;
                gle_j  = gle_i;
                nGlei  = length(gle_i);
                nGlej  = length(gle_j);
                nGleij = nGlei*nGlej;

                % Get the indices for assemblage
                iDofEl = repmat(gle_i',1,nGlej);
                jDofEl = repmat(gle_j,nGlei,1);
                iDof(counterK+1:counterK+nGleij) = iDofEl(:);
                jDof(counterK+1:counterK+nGleij) = jDofEl(:);
                eDof(counterF+1:counterF+nGlei)  = gle_i';

                % Get local matrices and vector
                [A_e,b_e] = elementType.elementLinearSystem(nonlinearScheme);

                % Store in the global vectors
                A_ij(counterK+1:counterK+nGleij) = A_e(:);
                b_i(counterF+1:counterF+nGlei)   = b_e(:);

                % Update auxiliar variables
                counterK = counterK + nGleij;
                counterF = counterF + nGlei;

            end

            % Assemble the matrices and vector
            A = sparse(iDof,jDof,A_ij);
            b = sparse(eDof,ones(this.nDofElemTot,1),b_i);

            % Add contribution of the nodal forces
            Fe = sparse(this.ndof,1);
            Fe = this.addNodalLoad(Fe);
            b = nonlinearScheme.addNodalForces(b,Fe);

        end

        %------------------------------------------------------------------
        % Appply the Dirichlet boundary conditions
        function [Aff,bf] = applyDirichletBC(this, A, b, X, nlscheme)
            Aff = A(this.doffree,this.doffree);
            bf  = nlscheme.applyBCtoRHS(A, b, X, this.doffree,this.doffixed);
        end

        %------------------------------------------------------------------
        % Update the state variables from all integration points
        function updateStateVar(this)
            for el = 1:this.nelem
                this.element(el).type.updateStateVar();
            end
        end

        %------------------------------------------------------------------
        % Reorder the nodes to improve computational efficiency
        function resequenceNodes(this)
            % Get auxiliar variables
            nNode   = size(this.NODE,1);
            nElem   = size(this.ELEM,1);
            nNdElem = cellfun(@length,this.ELEM);
            % Size of the connectivity matrix
            nn = sum(nNdElem.^2);
            % Get connectivity matrix
            i=zeros(nn,1); j=zeros(nn,1); s=zeros(nn,1); index=0;
            for el = 1:nElem
              eNode=this.ELEM{el};
              ElemSet=index+1:index+nNdElem(el)^2;
              i(ElemSet) = kron(eNode,ones(nNdElem(el),1))';
              j(ElemSet) = kron(eNode,ones(1,nNdElem(el)))';
              s(ElemSet) = 1;
              index = index + nNdElem(el)^2;
            end
            K = sparse(i,j,s,nNode, nNode);
            % Apply a Symmetric reverse Cuthill-McKee permutation
            p = symrcm(K);
            cNode(p(1:nNode))=1:nNode;
            % Rebuild the nodes and elements matrices
            this.rebuildConnectivity(cNode);
        end

        %------------------------------------------------------------------
        % Updates the connectivity of nodes and elements
        function rebuildConnectivity(this,cNode)
            ELEM_Old = this.ELEM;
            [~,ix,jx] = unique(cNode);
            this.NODE = this.NODE(ix,:);
            for el=1:size(this.ELEM,1)
                for i = 1:length(this.ELEM{el})
                    this.ELEM{el}(i) = jx(ELEM_Old{el}(i));
                end
            end
        end

        %------------------------------------------------------------------
        % Adds pre-existing discontinuities to the model
        function addPreExistingDiscontinuities(this,dSet,additionalData)
            disp("*** Creating the discontinuity elements...");
            if nargin > 2
                this.addDiscontinuityData(additionalData);
            end
            % Check if the mesh is already set
            if (isempty(this.NODE) || isempty(this.ELEM))
                disp('Warning: empty mesh.');
                disp('Warning: the discontinuity set cannot be added.');
                return
            end
            % Create the discontinuity elements
            for i = 1:length(dSet)
                dSet(i).intersectMesh(this) ;
            end
            this.discontinuitySet = dSet;
            this.useEnrichedFormulation(true);
            this.initializeDiscontinuitySegments();
        end

        % -----------------------------------------------------------------
        % Adds some additional discontinuity data
        % To be implemented in the physics whenever required.
        function addDiscontinuityData(~,~)
        end

        %------------------------------------------------------------------
        % Initialize the discontinuity segments
        function initializeDiscontinuitySegments(this)
            nDiscontinuities = this.getNumberOfDiscontinuities();
            for i = 1:nDiscontinuities
                % Initialize common properties and dofs
                nDiscontinuitySeg = this.discontinuitySet(i).getNumberOfDiscontinuitySegments();
                for j = 1:nDiscontinuitySeg
                    this.discontinuitySet(i).segment(j).t = this.t;
                    if this.condenseEnrDofs == false
                        this.discontinuitySet(i).segment(j).initializeDofs(this.ndof);
                        this.ndof = this.ndof + this.discontinuitySet(i).segment(j).ndof;
                        this.dofenr = [this.dofenr; this.discontinuitySet(i).segment(j).dof];
                    end
                end
            end
            this.ndofenr = length(this.dofenr);
        end

        %------------------------------------------------------------------
        % Get the total number of discontinuities
        function n = getNumberOfDiscontinuities(this)
            n = length(this.discontinuitySet);
        end

        %------------------------------------------------------------------
        % Flag to use the enriched formulation
        function useEnrichedFormulation(this,flag)
            this.enriched = flag;
        end

        % -----------------------------------------------------------------
        % Print the nodal displacements
        function printResults(this)
            fprintf('\n******** NODAL RESULTS ********\n');
            this.printResultsHeader();
            for i = 1:this.nnodes
                fprintf("  %4d: \t",i);
                for j = 1:this.ndof_nd
                    fprintf("  %8.4e ",this.U(this.ID(i,j)))
                end
                fprintf("\n");
            end
        end

        %------------------------------------------------------------------
        % Evaluate a field at in a point inside an element
        % Assumes that the point is in the element domain
        function fieldValue = evaluateField(this, field, el, X)
            if strcmp(field,'Model')
                fieldValue = this.matID(el);
            elseif strcmp(field,'Pressure')
                fieldValue = this.element(el).type.pressureField(X);
            elseif strcmp(field,'Ux')
                u = this.element(el).type.displacementField(X);
                fieldValue = u(1);
            elseif strcmp(field,'Uy')
                u = this.element(el).type.displacementField(X);
                fieldValue = u(2);
            elseif strcmp(field,'E1')
                s = this.element(el).type.strainField(X);
                sp = this.element(el).type.principalStrain(s);
                fieldValue = sp(1);
            elseif strcmp(field,'PEMAG')
                fieldValue = this.element(el).type.plasticstrainMagnitude(X);
            elseif strcmp(field,'Sx')
                s = this.element(el).type.stressField(X);
                fieldValue = s(1);
            elseif strcmp(field,'Sy')
                s = this.element(el).type.stressField(X);
                fieldValue = s(2);
            elseif strcmp(field,'Sxy')
                s = this.element(el).type.stressField(X);
                fieldValue = s(4);
            elseif strcmp(field,'S1')
                s = this.element(el).type.stressField(X);
                sp = this.element(el).type.principalStress(s);
                fieldValue = sp(1);
            elseif strcmp(field,'S2')
                s = this.element(el).type.stressField(X);
                sp = this.element(el).type.principalStress(s);
                fieldValue = sp(2);
            elseif strcmp(field,'Sr')
                s = this.element(el).type.stressField(X);
                sp = this.element(el).type.stressCylindrical(s,X);
                fieldValue = sp(1);
            elseif strcmp(field,'LiquidPressure')
                fieldValue = this.element(el).type.pressureField(X);
            elseif strcmp(field,'CapillaryPressure')
                fieldValue = this.element(el).type.capillaryPressureField(X);
            elseif strcmp(field,'GasPressure')
                fieldValue = this.element(el).type.gasPressureField(X);
            elseif strcmp(field,'LiquidSaturation')
                fieldValue = this.element(el).type.liquidSaturationField(X);
            elseif strcmp(field,'GasSaturation')
                fieldValue = this.element(el).type.gasSaturationField(X);
            end
        end

        %------------------------------------------------------------------
        % Update the result nodes data of each element
        function updateResultVertexData(this,field)
            for el = 1:this.nelem
                this.element(el).type.ue = this.U(this.element(el).type.gle);
                vertexData = zeros(length(this.element(el).type.result.faces),1);
                for i = 1:length(this.element(el).type.result.faces)
                    X = this.element(el).type.result.vertices(i,:);
                    vertexData(i) = this.evaluateField(field, el, X);
                end
                this.element(el).type.result.setVertexData(vertexData);
            end
        end

        % -----------------------------------------------------------------
        % Plot given field over the mesh
        function plotField(this,field,range,ax)
            if nargin < 3, range = []; end
            if nargin < 4 || isempty(ax)
                figure;
                ax = gca;
            else
                axes(ax);
                cla(ax);
            end

            this.updateResultVertexData(field)
            FEMPlot(this).plotMesh(ax);
            if isempty(range)
                colorbar(ax);
            else
                clim(ax, range);
                c = colorbar(ax);
                c.Limits = range;
            end

        end

        % -----------------------------------------------------------------
        % Plot given field along a given segment
        function plotFieldAlongSegment(this,field, Xi, Xf, npts, axisPlot,ax)
            if nargin < 7 || isempty(ax)
                figure;         % Cria nova figura
                ax = gca;       % Usa o eixo atual
            else
                axes(ax);       % Define o eixo alvo
                cla(ax);        % Limpa o conteúdo
            end

            if nargin < 5
                npts     = 100;
                axisPlot = 'x';
            end
            if nargin < 6
                axisPlot = 'x';
            end
            FEMPlot(this).plotFieldAlongSegment(field, Xi, Xf, npts, axisPlot, ax);
        end

    end
end