From b22ee5a3a8ab01d062194a3cfb573166e8c6c1b1 Mon Sep 17 00:00:00 2001
From: Louis Denis <louis.denis@student.uliege.be>
Date: Fri, 13 May 2022 11:56:31 +0200
Subject: [PATCH] cleaning FEM code + comments

---
 srcs/FEM/complex_validation.geo        |   14 +-
 srcs/FEM/functionsFEM.cpp              | 1786 +++++++++++++-----------
 srcs/FEM/functionsFEM.hpp              |  283 +++-
 srcs/FEM/large_rotation_validation.geo |    2 +-
 srcs/FEM/mainFEM.cpp                   |   13 +-
 srcs/FEM/solverFEM.cpp                 |  474 ++-----
 6 files changed, 1399 insertions(+), 1173 deletions(-)

diff --git a/srcs/FEM/complex_validation.geo b/srcs/FEM/complex_validation.geo
index 2c7851a..c6497d7 100644
--- a/srcs/FEM/complex_validation.geo
+++ b/srcs/FEM/complex_validation.geo
@@ -5,12 +5,12 @@ Ly = 2;
 nx = 250;
 ny = 100;
 
-Point(1) = {0, 0, 0, 0.1}; //4th number is mesh density
-Point(2) = {Lx, 0, 0, 0.2};
-Point(3) = {Lx, Ly, 0, 0.2};
-Point(4) = {0, Ly, 0, 0.1};
-Point(5) = {Lx/2, 0, 0, 0.15}; 
-Point(6) = {Lx/2, Ly, 0, 0.15}; 
+Point(1) = {0, 0, 0, 0.05}; //4th number is mesh density
+Point(2) = {Lx, 0, 0, 0.1};
+Point(3) = {Lx, Ly, 0, 0.1};
+Point(4) = {0, Ly, 0, 0.05};
+Point(5) = {Lx/2, 0, 0, 0.075}; 
+Point(6) = {Lx/2, Ly, 0, 0.075}; 
 Line(1) = {1, 5};
 Line(2) = {2, 5};
 Line(3) = {3, 2};
@@ -56,4 +56,4 @@ SetNumber("Boundary Conditions/fixed_node/uy",2);
 
 Physical Curve("BEM_FEM_boundary", 10) = {1,2,3,4,5,6};
 
-SetNumber("Non_linear_solver", 1);
\ No newline at end of file
+SetNumber("Non_linear_solver", 0);
\ No newline at end of file
diff --git a/srcs/FEM/functionsFEM.cpp b/srcs/FEM/functionsFEM.cpp
index e6cb0bd..4f69cc0 100644
--- a/srcs/FEM/functionsFEM.cpp
+++ b/srcs/FEM/functionsFEM.cpp
@@ -1,25 +1,27 @@
 #ifdef _MSC_VER
-#include <gmsh.h_cwrap>       // gmsh main header
+#include <gmsh.h_cwrap>
 #else
-#include <gmsh.h>       // gmsh main header
+#include <gmsh.h>
 #endif
 
 #include "functionsFEM.hpp"
 
-#include <iostream>     // for std::cout
+#include <iostream> 
 #include <map>
-#include <sstream>  // for std::stringstream
-#include <Eigen/Dense> // Eigen library
-#include <Eigen/Sparse> // Eigen library for sparse matrices
+#include <sstream>
+#include <Eigen/Dense> 
+#include <Eigen/Sparse>
 #include <Eigen/SparseCholesky> // solving sparse linear systems
 #include <Eigen/Core>
 #include <cmath>
-#include <algorithm> // to sort and merge the FEM node vectors
+#include <algorithm> 
 #ifdef _OPENMP
 #include <omp.h>
 #endif
 #define PI M_PI
 
+// returns the boolean non_linear_solver parameter based on what is set in the .geo file,
+// add SetNumber("Non_linear_solver", 0); in .geo file to use linear solver.
 bool getNonLinearParameter()
 {
     std::vector<std::string> keys;
@@ -44,42 +46,190 @@ bool getNonLinearParameter()
     }
 }
 
-//Compute the matrix H
-Eigen::Matrix<double, 3, 3> computeHmatrix(const double E, const double nu)
+// fills the "FEM_nodeTags" vector with the nodes in the FEM domain, fills the "FEM_nodeCoordsMap" with the coordinates (x,y)
+// corresponding to each nodeTag, looking at entity tags "tags" of dimension "dim" corresponding to the FEM domain.
+void RetrieveFEMNodeTagsAndCoords(std::vector<std::size_t> & FEM_nodeTags, 
+                            std::map<int,std::pair<double,double>> & FEM_nodeCoordsMap, 
+                            int & dim, std::vector<int> & tags)
 {
-    Eigen::Matrix<double, 3, 3> H_matrix;
-    H_matrix(0,0) = 1.;
-    H_matrix(1,0) = nu;
-    H_matrix(2,0) = 0.;
-    H_matrix(0,1) = nu;
-    H_matrix(1,1) = 1.;
-    H_matrix(2,1) = 0.;
-    H_matrix(0,2) = 0.;
-    H_matrix(1,2) = 0.;
-    H_matrix(2,2) = (1-nu)/2;
-    H_matrix = E/(1-nu*nu)*H_matrix;
-    return H_matrix;
+    //loop over the entities of the FEM domain to retrieve all nodeTags by taking care not to take some nodes twice
+    //using sort and set_union methods + retrieving the node coordinates and storing them in a map
+    std::vector<double> FEM_nodecoord;
+    std::vector<double> FEM_nodeparametricCoord;
+    std::vector<std::vector<std::size_t>> tmp_nodeTags(tags.size()); // will store the nodeTags associated to one given entity of the domain
+    gmsh::model::mesh::getNodes(tmp_nodeTags[0], FEM_nodecoord, FEM_nodeparametricCoord, dim, tags[0], true); 
+    for(std::size_t j = 0; j < tmp_nodeTags[0].size(); j++)
+    {
+        FEM_nodeCoordsMap[tmp_nodeTags[0][j]] = std::pair<double, double>(FEM_nodecoord[3*j], FEM_nodecoord[3*j+1]);
+    }
+    std::sort(tmp_nodeTags[0].begin(), tmp_nodeTags[0].end()); // need to sort the vectors in order to merge them afterwards
+    for (std::size_t i = 1; i < tags.size(); i++)
+    { // in the case the domain contains multiple entities
+        gmsh::model::mesh::getNodes(tmp_nodeTags[i], FEM_nodecoord, FEM_nodeparametricCoord, dim, tags[i], true);
+        for(std::size_t j = 0; j < tmp_nodeTags[i].size(); j++)
+        {
+            FEM_nodeCoordsMap[tmp_nodeTags[i][j]] = std::pair<double, double>(FEM_nodecoord[3*j], FEM_nodecoord[3*j+1]);
+        }
+        std::sort(tmp_nodeTags[i].begin(), tmp_nodeTags[i].end());
+        std::set_union(tmp_nodeTags[i-1].begin(), tmp_nodeTags[i-1].end(),
+                       tmp_nodeTags[i].begin(), tmp_nodeTags[i].end(), std::back_inserter(FEM_nodeTags)); // works only for 2 domains -> see little trick just below
+        
+    }
+    if(tags.size() == 1){ // if the domain contains one single entity
+        FEM_nodeTags = tmp_nodeTags[0];
+    }
+    else{ // little trick for more than 2 domains
+        std::sort(FEM_nodeTags.begin(), FEM_nodeTags.end());
+        auto last = std::unique(FEM_nodeTags.begin(), FEM_nodeTags.end());
+        FEM_nodeTags.erase(last, FEM_nodeTags.end());
+    }
 }
 
-void computeBmatrix(const int &j, const int &numNodes, const std::vector<double> & basisFunctionsGradient,
-                      const std::vector<double> & determinants, 
-                      const Eigen::Matrix<double, 2, 2> & jacobinvtrans,
-                      Eigen::Matrix<double, Eigen::Dynamic, Eigen::Dynamic> & B_matrix)
+// fills the "FEM_elementTags" vector with all element tags of the elements of dimension "dim" (in practice: 2)
+// in the entities associated to the entity tags "tags" (corresponding to the FEM domain).
+// associates an initialized elementData structure to each element tag through the "FEM_kinematicsMap" map,
+// using the "FEM_nodeCoordsMap" map gathering the coordinates of each node in the FEM domain.
+int initKinematics(std::vector<std::size_t> & FEM_elementTags, std::map<int, elementData> & FEM_kinematicsMap, 
+                std::map<int,std::pair<double,double>> & FEM_nodeCoordsMap, int & dim, std::vector<int> & tags)
 {
-    for (int l = 0; l < numNodes; ++l){ // looping over the number of shape functions
-        B_matrix(0,2*l) = jacobinvtrans(0,0)*basisFunctionsGradient[3*l+numNodes*3*j]+jacobinvtrans(0,1)*basisFunctionsGradient[3*l+numNodes*3*j+1];
-        B_matrix(1,2*l) = 0.;
-        B_matrix(2,2*l) = jacobinvtrans(1,0)*basisFunctionsGradient[3*l+numNodes*3*j]+jacobinvtrans(1,1)*basisFunctionsGradient[3*l+numNodes*3*j+1];
-        B_matrix(0,2*l+1) = 0.;
-        B_matrix(1,2*l+1) = B_matrix(2,2*l);
-        B_matrix(2,2*l+1) = B_matrix(0,2*l);
+    std::vector<int> elementTypes;
+    std::vector<std::vector<std::size_t>> elementTags;
+    std::vector<std::vector<std::size_t>> elnodeTags;
+
+    std::string elementName;
+    int elDim, order, numNodes, numPrimaryNodes;
+    std::vector<double> localNodeCoord;
+    
+    int nbelems=0; // will store total number of elements in FEM domain
+    for(std::size_t i=0; i< tags.size(); ++i)
+    {
+        gmsh::model::mesh::getElements(elementTypes, elementTags, elnodeTags, dim, tags[i]);
+        for(std::size_t j=0; j< elementTags.size(); ++j)
+        {
+            nbelems+=elementTags[j].size();
+            // filling the FEM_elementTags vector
+            FEM_elementTags.insert(FEM_elementTags.end(), elementTags[j].begin(), elementTags[j].end());
+
+            // getting element properties, in particular: number of nodes
+            gmsh::model::mesh::getElementProperties(elementTypes[j],
+                                                        elementName, elDim, order,
+                                                        numNodes, localNodeCoord, 
+                                                        numPrimaryNodes);
+            for(std::size_t k = 0; k < elementTags[j].size(); k++)
+            {
+                elementData element;
+                std::vector<size_t> tmp_nodes(numNodes);
+                std::vector<double> tmp_x(numNodes);
+                std::vector<double> tmp_y(numNodes);
+                std::vector<double> tmp_u(numNodes);
+                std::vector<double> tmp_v(numNodes);
+                std::vector<double> tmp_ui(numNodes);
+                std::vector<double> tmp_vi(numNodes);
+                double tmp_xc = 0; 
+                double tmp_yc = 0;
+                for(int l = 0; l < numNodes; l++)
+                {
+                    tmp_nodes[l] = elnodeTags[j][numNodes*k + l];
+                    tmp_x[l] = FEM_nodeCoordsMap[tmp_nodes[l]].first;
+                    tmp_y[l] = FEM_nodeCoordsMap[tmp_nodes[l]].second;
+                    tmp_u[l] = 0; // initializing the displacement at zero
+                    tmp_v[l] = 0;
+                    tmp_ui[l] = 0;
+                    tmp_vi[l] = 0;
+                    tmp_xc += tmp_x[l];
+                    tmp_yc += tmp_y[l];
+                }
+                tmp_xc = tmp_xc / numNodes;
+                tmp_yc = tmp_yc / numNodes;
+
+                std::vector<double> tmp_xi(numNodes);
+                std::vector<double> tmp_yi(numNodes);
+                for(int l = 0; l < numNodes; l++)
+                {
+                    tmp_xi[l] = tmp_x[l] - tmp_xc;
+                    tmp_yi[l] = tmp_y[l] - tmp_yc;
+                }
+                element.nodes = tmp_nodes;
+                element.x = tmp_x;
+                element.y = tmp_y;
+                element.xc = tmp_xc;
+                element.yc = tmp_yc;
+                element.xi = tmp_xi;
+                element.yi = tmp_yi;
+                element.u = tmp_u;
+                element.v = tmp_v;
+                element.uc = 0;
+                element.vc = 0;
+                element.theta = 0;
+                element.ui = tmp_ui;
+                element.vi = tmp_vi;
+                element.pl = Eigen::Matrix<double, Eigen::Dynamic, 1>::Zero(2*numNodes,1);
+                element.A = Eigen::Matrix<double, Eigen::Dynamic, 1>(2*numNodes,1);
+                element.G = Eigen::Matrix<double, 1, Eigen::Dynamic>(1,2*numNodes);
+                element.E = Eigen::Matrix<double, Eigen::Dynamic, Eigen::Dynamic>::Zero(2*numNodes, 2*numNodes);
+
+                FEM_kinematicsMap[elementTags[j][k]] = element;
+                updateKinMatrices(& FEM_kinematicsMap[elementTags[j][k]], false);
+            }
+        }
+    }
+    return nbelems;
+}
+
+// updates the kinematics matrices of one element "element" based on its updated nodal positions and rotation angle.
+// if "computeFl" is set as true, also computes the local nodal forces vector "fl = Kl*pl", only OK if 
+// the local stiffness matrix Kl has already been initialized, using "fillElementalKandF".
+void updateKinMatrices(elementData* element, bool computeFl)
+{
+    // for the different formulas, please refer to the report
+    size_t numNodes = element->nodes.size();
+    // update A matrix
+    for(size_t i = 0; i < numNodes; i++)
+    {
+        element->A(2*i, 0) = -(element->vi[i]+element->yi[i]);
+        element->A(2*i + 1, 0) = element->ui[i]+element->xi[i];
+    }
+
+    // update G matrix
+    double denominator = 0;
+    for(size_t i = 0; i < numNodes; i++)
+    {
+        element->G(0, 2*i) = -element->yi[i];
+        element->G(0, 2*i + 1) = element->xi[i];
+        denominator += element->xi[i]*(element->ui[i]+element->xi[i]) + element->yi[i]*(element->vi[i]+element->yi[i]);
+    }
+    element->G *= 1 / denominator;
+
+    // update P matrix 
+    element->P = Eigen::Matrix<double, Eigen::Dynamic, Eigen::Dynamic>::Identity(2*numNodes,2*numNodes) - element->A*element->G;
+
+    // update E matrix
+    double tmp_cos = cos(element->theta);
+    double tmp_sin = sin(element->theta);
+    for(size_t i = 0; i < numNodes; i++)
+    {
+        element->E(2*i, 2*i) = tmp_cos;
+        element->E(2*i, 2*i + 1) = -tmp_sin;
+        element->E(2*i + 1, 2*i) = tmp_sin;
+        element->E(2*i + 1, 2*i + 1) = tmp_cos;
+    }
+
+    //update C matrix
+    element->C = element->P*element->E.transpose();
+
+    //update fl vector, only if Kl has been initialized
+    if(computeFl)
+    {
+        element->fl = element->Kl*element->pl;
     }
 }
 
-//Get physical properties of domain
-void getPhysicalProperties(const std::vector<std::string> & keys, double & E, double & nu, double & rho,
-                              double & bx, double & by)
+// gets physical properties of FEM_domain: Young's modulus "E", Poisson's ratio "nu",
+// density "rho", volumic force along x "bx", volumic force along y "by".
+void getPhysicalProperties(double & E, double & nu, double & rho, double & bx, double & by)
 {
+    std::vector<std::string> keys;
+    gmsh::onelab::getNames(keys, "(Volumic Forces|Materials).+");
     for (auto &key : keys)
     {
         // get corresponding value
@@ -113,6 +263,28 @@ void getPhysicalProperties(const std::vector<std::string> & keys, double & E, do
     }
 }
 
+// computes the Hooke's matrix in plane stress "H_matrix" based on the Young's modulus "E" and Poisson's ratio "nu".
+Eigen::Matrix<double, 3, 3> computeHmatrix(const double E, const double nu)
+{
+    Eigen::Matrix<double, 3, 3> H_matrix;
+    H_matrix(0,0) = 1.;
+    H_matrix(1,0) = nu;
+    H_matrix(2,0) = 0.;
+    H_matrix(0,1) = nu;
+    H_matrix(1,1) = 1.;
+    H_matrix(2,1) = 0.;
+    H_matrix(0,2) = 0.;
+    H_matrix(1,2) = 0.;
+    H_matrix(2,2) = (1-nu)/2;
+    H_matrix = E/(1-nu*nu)*H_matrix;
+    return H_matrix;
+}
+
+// looping over every element in the FEM_domain (dimension "dim" and entity tags "tags"), filling "full_f_vector",
+// with the local volumic forces computed based on the volumic force ("bx","by") and density "rho" with the help of
+// "nodeIndexMap" mapping each nodeTag to its global index.
+// filling the local stiffness matrices stored in the elementData structure contained in "FEM_kinematicsMap".
+// numerical integration performed using Gauss integration based on "integration_rule".
 void fillElementalKandF(const Eigen::Matrix<double, 3, 3> H_matrix, const double rho, const double bx, const double by,
               const std::string & integration_rule, 
               int & dim, std::vector<int> & tags, std::map<int, int>  & nodeIndexMap,
@@ -135,7 +307,7 @@ void fillElementalKandF(const Eigen::Matrix<double, 3, 3> H_matrix, const double
         gmsh::model::mesh::getElements(elementTypes, elementTags,
                                        nodeTags, dim, tags[k]);
         
-        // looping over element types, enlever les déclarations des boucles pr aller plus vite (cfr Boman)
+        // looping over element types
         for (std::size_t i = 0; i < elementTypes.size(); ++i)
         {
             // getting element properties
@@ -169,16 +341,16 @@ void fillElementalKandF(const Eigen::Matrix<double, 3, 3> H_matrix, const double
 
             /*---------computing the K matrix (and f vector, only volumic part for now)------*/
             // looping over the elements (computing elemental K matrices)
-            //double test1 = omp_get_wtime();
-            //Eigen::setNbThreads(1);
             #ifdef _MSC_VER
             // https://github.com/tesseract-ocr/tesseract/issues/3285
             // OpenMP 2.0 standard is enforced - we may use /openmp:llvm and keep size_t
             #pragma omp parallel for
-            for (size_t el = 0; el < elementTags[i].size(); el++){
+            for (size_t el = 0; el < elementTags[i].size(); el++)
+            {
             #else
             #pragma omp parallel for
-            for (std::size_t el = 0; el < elementTags[i].size(); el++){
+            for (std::size_t el = 0; el < elementTags[i].size(); el++)
+            {
             #endif
                 // elemental K matrix and f vector initialization
                 Eigen::Matrix<double, Eigen::Dynamic, Eigen::Dynamic> K_matrix(2*numNodes,2*numNodes);
@@ -186,12 +358,13 @@ void fillElementalKandF(const Eigen::Matrix<double, 3, 3> H_matrix, const double
                 K_matrix.setZero();
                 for (int j = 0; j < 2*numNodes; ++j)f_vector[j] = 0.;
 
-                // looping over the Gauss points (formula of slide 17)
-                for (std::size_t j = 0; j < weights.size(); ++j){
+                // looping over the Gauss points
+                for (std::size_t gaussIndex = 0; gaussIndex < weights.size(); ++gaussIndex)
+                {
                     // converting jacobian to Eigen format
                     Eigen::Matrix<double, 2, 2> jacob2D;
                     Eigen::Map<Eigen::Matrix<double, Eigen::Dynamic, Eigen::Dynamic> >
-                        jacob(&jacobians[9*j+9*weights.size()*el], 3, 3);
+                        jacob(&jacobians[9*gaussIndex+9*weights.size()*el], 3, 3);
                     // 3D to 2D (plane stress case)
                     jacob2D(0,0) = jacob(0,0);
                     jacob2D(1,0) = jacob(1,0);
@@ -207,27 +380,47 @@ void fillElementalKandF(const Eigen::Matrix<double, 3, 3> H_matrix, const double
                     // computing the B matrix (formula slide 19) and the f vector (also using gauss integration)
                     Eigen::Matrix<double, Eigen::Dynamic, Eigen::Dynamic> B_matrix(3,2*numNodes);
 
-
-                    computeBmatrix(j, numNodes, basisFunctionsGradient, determinants, jacobinvtrans, B_matrix);
+                    computeBmatrix(gaussIndex, numNodes, basisFunctionsGradient, determinants, jacobinvtrans, B_matrix);
                     
-                    for (int l = 0; l < numNodes; ++l){ // looping over the number of shape functions
-                        f_vector[2*l] = f_vector[2*l] + rho*basisFunctions[l+numNodes*j]*bx*determinants[j+weights.size()*el]*weights[j]; //pas oublier le dét !
-                        f_vector[2*l+1] = f_vector[2*l+1] + rho*basisFunctions[l+numNodes*j]*by*determinants[j+weights.size()*el]*weights[j]; // pas oublier le weights[j] non plus !
+                    for (int l = 0; l < numNodes; ++l)
+                    { // looping over the number of shape functions
+                        f_vector[2*l] = f_vector[2*l] + rho*basisFunctions[l+numNodes*gaussIndex]*bx*determinants[gaussIndex+weights.size()*el]*weights[gaussIndex];
+                        f_vector[2*l+1] = f_vector[2*l+1] + rho*basisFunctions[l+numNodes*gaussIndex]*by*determinants[gaussIndex+weights.size()*el]*weights[gaussIndex];
                     }
 
-                    K_matrix = K_matrix + B_matrix.transpose()*H_matrix*B_matrix*determinants[j+weights.size()*el]*weights[j];
+                    K_matrix = K_matrix + B_matrix.transpose()*H_matrix*B_matrix*determinants[gaussIndex+weights.size()*el]*weights[gaussIndex];
                 }
                 /*------------- ASSEMBLY PROCESS ------------*/
                 FEM_kinematicsMap[elementTags[i][el]].Kl = K_matrix;
-                assembleFlocal(i, el, nodeTags, numNodes, nodeIndexMap, full_f_vector, f_vector);
+                assembleFlocal(el, nodeTags[i], numNodes, nodeIndexMap, full_f_vector, f_vector);
             }
         }
     }
 }
 
-//Assembles the local "f vector" into the full f vector
-void assembleFlocal(const int &i, const int &el, 
-                          const std::vector<std::vector<std::size_t>> & nodeTags, 
+// computing the strain displacement "B_matrix" at the "gaussIndex"-th gaussian point of the element
+// consisting of "numNodes" nodes, based on the "basisFunctionsGradient", "determinants" and the inverse of the jacobian
+// matrix "jacobinvtrans".
+void computeBmatrix(const int &gaussIndex, const int &numNodes, const std::vector<double> & basisFunctionsGradient,
+                      const std::vector<double> & determinants, 
+                      const Eigen::Matrix<double, 2, 2> & jacobinvtrans,
+                      Eigen::Matrix<double, Eigen::Dynamic, Eigen::Dynamic> & B_matrix)
+{
+    for (int l = 0; l < numNodes; ++l){ // looping over the number of shape functions
+        B_matrix(0,2*l) = jacobinvtrans(0,0)*basisFunctionsGradient[3*l+numNodes*3*gaussIndex]+jacobinvtrans(0,1)*basisFunctionsGradient[3*l+numNodes*3*gaussIndex+1];
+        B_matrix(1,2*l) = 0.;
+        B_matrix(2,2*l) = jacobinvtrans(1,0)*basisFunctionsGradient[3*l+numNodes*3*gaussIndex]+jacobinvtrans(1,1)*basisFunctionsGradient[3*l+numNodes*3*gaussIndex+1];
+        B_matrix(0,2*l+1) = 0.;
+        B_matrix(1,2*l+1) = B_matrix(2,2*l);
+        B_matrix(2,2*l+1) = B_matrix(0,2*l);
+    }
+}
+
+// assembles the local "f_vector" into the full f vector, "elIndex" is the element index with respect to "nodeTags"
+// having "numNodes" nodes, "nodeTags" gathering all the nodeTags of the current element type in the current entity.
+// "nodeIndexMap" associates a global index to each nodetag.
+void assembleFlocal(const int &elIndex, 
+                          const std::vector<std::size_t> & nodeTags, 
                           const int &numNodes, std::map<int, int> & nodeIndexMap,
                           std::vector<double> & full_f_vector, 
                           std::vector<double> & f_vector)
@@ -238,48 +431,19 @@ void assembleFlocal(const int &i, const int &el,
     for (int j = 0; j < 2*numNodes; ++j)
     {
         first_node_index = (int) j/2;
-        first_node = nodeTags[i][numNodes*el+first_node_index];
+        first_node = nodeTags[numNodes*elIndex+first_node_index];
         my_row = nodeIndexMap[first_node] + j % 2; 
         #pragma omp atomic update
         full_f_vector[my_row] +=  f_vector[j];
     }
 }
 
-/*-----------computing the f vector components related to this particular Neumann BC---------*/
-void computeNeumannBC(const int &numNodes, const int &elTyp, const std::vector<std::vector<std::size_t>> & elementTags,
-                        const double &tx, const double &ty, std::map<int, int> & nodeIndexMap,
-                        const std::vector<double> & weights, const std::vector<double> & basisFunctions, 
-                        const std::vector<double> & determinants, const std::vector<std::vector<std::size_t>> & nodeTags,
-                        std::vector<double> & full_f_vector)
-{
-    #pragma omp parallel for
-    for (std::size_t el = 0; el < elementTags[elTyp].size(); el++){
-        std::vector<double> f_vector(2*numNodes);
-        //std::cout << "i am thread: " << omp_get_thread_num() << "\n";
-        for (int j = 0; j < 2*numNodes; ++j){
-            f_vector[j] = 0.;
-        }
-        // looping over the Gauss points (formula of slide 17)
-        for (std::size_t j = 0; j < weights.size(); ++j){
-            // looping over the number of shape functions
-            for (int l = 0; l < numNodes; ++l){ 
-                f_vector[2*l] = f_vector[2*l] + basisFunctions[l+numNodes*j]*tx*determinants[j+weights.size()*el]*weights[j]; //pas oublier le dét !
-                f_vector[2*l+1] = f_vector[2*l+1] + basisFunctions[l+numNodes*j]*ty*determinants[j+weights.size()*el]*weights[j]; // le weight non plus !
-            }
-        }
-        /*------------- ASSEMBLY PROCESS ------------*/
-        for (int j = 0; j < 2*numNodes; ++j){
-            int first_node_index = (int) j/2;
-            int first_node = nodeTags[elTyp][numNodes*el+first_node_index];
-            int my_row = nodeIndexMap[first_node] + j % 2;
-            #pragma omp atomic update
-            full_f_vector[my_row] += f_vector[j];
-        }
-    }    
-}
-
-/*-----------focus on neumann BC's (surface traction) into full_f_vector------------*/
-void includeNeumannBC(const std::vector<std::string> & keys, const std::string & integration_rule,
+// fills the "full_f_vector" with the surfacic contribution of the neumann boundary conditions based on the 
+// "nodeIndexMap" associating a global index to each node tag, the local integration of the surfacic forces is 
+// performed using Gauss integration based on "integration rule".
+// "BCkeys" gathers the keys associated to boundary conditions in the .geo file (using SetNumber(...)).
+// "groups" associates a dimension and a tag to each physical group.
+void includeNeumannBC(const std::vector<std::string> & BCkeys, const std::string & integration_rule,
                 std::map<int, int> & nodeIndexMap,
                 std::map<std::string, std::pair<int, int>> & groups,
                 std::vector<double> & full_f_vector)
@@ -300,7 +464,7 @@ void includeNeumannBC(const std::vector<std::string> & keys, const std::string &
     int numComponents, numOrientations;
     std::vector<double> basisFunctions;
 
-    for (auto &key : keys)
+    for (auto &key : BCkeys)
     {
         // get corresponding value
         std::vector<double> value;
@@ -323,9 +487,8 @@ void includeNeumannBC(const std::vector<std::string> & keys, const std::string &
                 if(tx || ty) // there is no need the consider Neumann BC if it is homogeneous
                     groupname = words[1];
             }
-            //here, if we are dealing with neumann BC's, we should have retrieved a non-empty group_name
+            //here, if we are dealing with neumann BC's, we should have retrieved a non-empty groupname
             if(groupname.compare("") != 0){
-                //loop over elements of group_name
                 dim = groups[groupname].first;
                 tag = groups[groupname].second;
                 if (tag == 0)
@@ -333,7 +496,7 @@ void includeNeumannBC(const std::vector<std::string> & keys, const std::string &
                     std::cerr << "Group '" << groupname << "' does not exist!\n";
                     return;
                 }
-                gmsh::model::getEntitiesForPhysicalGroup(dim, tag, tags);
+                gmsh::model::getEntitiesForPhysicalGroup(dim, tag, tags); // entities of particular groupname
 
                 // loop over entities
                 for (std::size_t k = 0; k < tags.size(); ++k)
@@ -351,25 +514,44 @@ void includeNeumannBC(const std::vector<std::string> & keys, const std::string &
                     
                         // get Gauss points coordinates and weights
                         gmsh::model::mesh::getIntegrationPoints(elementTypes[i], 
-                                                                integration_rule, //maybe use different integration method on boundary than in domain
+                                                                integration_rule,
                                                                 localCoords, weights);
 
                         // get determinants and Jacobians (only determinants will be useful)
                         std::vector<double> jacobians, determinants, coords;
                         gmsh::model::mesh::getJacobians(elementTypes[i], 
                                                         localCoords, jacobians, 
-                                                        determinants, coords, tags[k]); // ai dû rajouter mon tags[k] pr focus on this particular entity
+                                                        determinants, coords, tags[k]);
 
                         gmsh::model::mesh::getBasisFunctions(elementTypes[i], localCoords, 
                                                             "Lagrange", numComponents, 
                                                             basisFunctions, 
                                                             numOrientations);
 
-                        /*-----------computing the f vector components related to this Neumann BC---------*/
                         //looping over the element types
-                        computeNeumannBC(numNodes, i, elementTags, tx, ty, nodeIndexMap,
-                                            weights, basisFunctions, 
-                                            determinants, nodeTags, full_f_vector);
+                        #pragma omp parallel for
+                        for (std::size_t el = 0; el < elementTags[i].size(); el++){
+                            std::vector<double> f_vector(2*numNodes);
+                            for (int j = 0; j < 2*numNodes; ++j){
+                                f_vector[j] = 0.;
+                            }
+                            // looping over the Gauss points
+                            for (std::size_t j = 0; j < weights.size(); ++j){
+                                // looping over the number of shape functions
+                                for (int l = 0; l < numNodes; ++l){ 
+                                    f_vector[2*l] = f_vector[2*l] + basisFunctions[l+numNodes*j]*tx*determinants[j+weights.size()*el]*weights[j]; //pas oublier le dét !
+                                    f_vector[2*l+1] = f_vector[2*l+1] + basisFunctions[l+numNodes*j]*ty*determinants[j+weights.size()*el]*weights[j]; // le weight non plus !
+                                }
+                            }
+                            /*------------- ASSEMBLY PROCESS ------------*/
+                            for (int j = 0; j < 2*numNodes; ++j){
+                                int first_node_index = (int) j/2;
+                                int first_node = nodeTags[i][numNodes*el+first_node_index];
+                                int my_row = nodeIndexMap[first_node] + j % 2;
+                                #pragma omp atomic update
+                                full_f_vector[my_row] += f_vector[j];
+                            }
+                        }    
                     }
                 }
             }
@@ -377,364 +559,34 @@ void includeNeumannBC(const std::vector<std::string> & keys, const std::string &
     }
 }
 
-// looping over the boundary conditions imposed in the .geo file, the purpose of the whole loop is
-// to fill the two vectors defined just above
-void fillDOFindicesDirBC(const std::vector<std::string> & keys, 
-                             std::map<int, int> & nodeIndexMap,
-                             std::map<std::string, std::pair<int, int>> & groups, 
-                             std::vector<int> & new_DOFindices,
-                             std::vector<double> & dir_BC)
+// for the coupled solver, fills the "full_f_vector" with the electrostatic pressure contained in the 
+// "electrostaticPressure" map for different element tags, using Gauss integration "integration_rule", the "nodeIndexMap" 
+// associating a global nodal index to each nodeTag, "nodeCoordsMap" gathering the coordinates of each node and "groups"
+// containing the different physical groups of the domain.
+void applyElecPressure(const std::string & integration_rule, std::map<int,std::pair<double,double>> & nodeCoordsMap, 
+                    std::map<int, int> & nodeIndexMap, std::map<std::string, std::pair<int, int>> & groups, 
+                    std::map<int, double> &electrostaticPressure, std::vector<double> & full_f_vector)
 {
-
-    for (std::size_t j = 0; j < new_DOFindices.size(); ++j){
-        new_DOFindices[j] = (int) j;
-    } 
-    for (std::size_t j = 0; j < dir_BC.size(); ++j){
-        dir_BC[j] = 0;
-    }
-
-    double ux; // imposed displacement along x axis
-    double uy; // imposed displacement along y axis
-
-    int dim;
-    int tag;
+    // electrostatic pressure must only be applied on the BEM_FEM_boundary
+    std::string groupname = "BEM_FEM_boundary";
+    int dim = groups[groupname].first;
+    int tag = groups[groupname].second;
     std::vector<int> tags;
-    std::vector<int> elementTypes;
-    std::vector<std::vector<std::size_t>> elementTags;
-    std::vector<std::vector<std::size_t>> nodeTags;
-    std::string elementName;
-    int elDim, order, numNodes, numPrimaryNodes;
-    std::vector<double> localNodeCoord;
+    gmsh::model::getEntitiesForPhysicalGroup(dim, tag, tags);
 
+    int first_nodeTag;
+    int second_nodeTag;
+    double nx;
+    double ny;
+    double p;
+    double tx;
+    double ty;
+    double norm;
     int first_node_index;
     int first_node;
     int my_row;
 
-    for (auto &key : keys)
-    {
-        // get corresponding value
-        std::vector<double> value;
-        gmsh::onelab::getNumber(key, value);
-        // expected key structure is "type/group_name/field"
-        // => split the key string into components
-        std::stringstream ss(key);
-        std::vector<std::string> words;
-        std::string word;
-        while(std::getline(ss, word, '/')) // read string until '/'
-            words.push_back(word);
-        if(words.size()==3) 
-        {
-            if(words[2].compare("ux") == 0 || words[2].compare("uy") == 0){
-                ux = 0;
-                uy = 0;
-                if(words[2].compare("ux") == 0)
-                    ux = value[0];
-                if(words[2].compare("uy") == 0)
-                    uy = value[0];
-                std::string groupname = words[1];
-                
-                //loop over elements of group_name
-                dim = groups[groupname].first;
-                tag = groups[groupname].second;
-                if (tag == 0)
-                {
-                    std::cerr << "Group '" << groupname << "' does not exist!\n";
-                    return;
-                }
-                gmsh::model::getEntitiesForPhysicalGroup(dim, tag, tags);
-
-                for (std::size_t k = 0; k < tags.size(); ++k)
-                {
-                    gmsh::model::mesh::getElements(elementTypes, elementTags,
-                                                nodeTags, dim, tags[k]);
-                    
-                    for (std::size_t i = 0; i < elementTypes.size(); ++i)
-                    {
-                        gmsh::model::mesh::getElementProperties(elementTypes[i],
-                                                        elementName, elDim, order,
-                                                        numNodes, localNodeCoord, 
-                                                        numPrimaryNodes);
-                        //looping over the elements
-                        for (std::size_t el = 0; el < elementTags[i].size(); el++){
-                            for (int j = 0; j < 2*numNodes; ++j){
-                                first_node_index = (int) j/2;
-                                first_node = nodeTags[i][numNodes*el+first_node_index];
-                                my_row = nodeIndexMap[first_node] + j % 2; // j % 2 == 1 corresponds to y nodal displacement
-                                if(words[2].compare("uy") == 0 && j % 2){ // we are dealing with a uy type of node
-                                    dir_BC[my_row] = uy;
-                                    new_DOFindices[my_row] = -1;
-                                }
-                                if(words[2].compare("ux") == 0 && j % 2 == 0){ // ux type of node
-                                    dir_BC[my_row] = ux;
-                                    new_DOFindices[my_row] = -1;
-                                }
-                            }
-                        }
-                    }
-                }
-            }
-        }
-    }
-}
-
-void computeReactionForces(std::map<std::string, std::pair<int, int>> & groups,
-                     std::map<int, int> & nodeIndexMap, std::vector<double> & final_nodal_forces,
-                     const std::vector<std::string> & keys)
-{
-    double Rx;
-    double Ry;
-
-    int dim;
-    int tag;
-    std::vector<int> tags;
-
-    std::vector<std::size_t> nodeTags;
-    std::vector<double> coord; //useless but mandatory
-    std::vector<double> parametricCoord; //useless but mandatory
-
-    for (auto &key : keys)
-    {
-        // get corresponding value
-        std::vector<double> value;
-        gmsh::onelab::getNumber(key, value);
-        // expected key structure is "type/group_name/field"
-        // => split the key string into components
-        std::stringstream ss(key);
-        std::vector<std::string> words;
-        std::string word;
-        while(std::getline(ss, word, '/')) // read string until '/'
-            words.push_back(word);
-        if(words.size()==3) 
-        {
-            if(words[2].compare("ux") == 0 || words[2].compare("uy") == 0){
-                Rx = 0;
-                Ry = 0;
-                std::string groupname = words[1];
-                std::cout << "-------------------- " << groupname << " ------------------- \n";
-                
-                //loop over elements of group_name
-                dim = groups[groupname].first;
-                tag = groups[groupname].second;
-                if (tag == 0)
-                {
-                    std::cerr << "Group '" << groupname << "' does not exist!\n";
-                    return;
-                }
-                gmsh::model::getEntitiesForPhysicalGroup(dim, tag, tags);
-
-                // loop over entities
-                for (std::size_t k = 0; k < tags.size(); ++k)
-                {
-                    // retrieving all the nodes in this particular entity
-                    gmsh::model::mesh::getNodes(nodeTags, coord, parametricCoord, dim, tags[k], true);
-                    
-                    
-                    for (std::size_t j = 0; j < nodeTags.size(); ++j){
-                        if(words[2].compare("ux") == 0){    // computing Rx
-                            int my_new_col = nodeIndexMap[nodeTags[j]]; // le noeud numéro nodeIndexMap[nodeTags[j]] correspond à la ligne/colonne dans K, f
-                            Rx += final_nodal_forces[my_new_col];
-                        }
-                        if(words[2].compare("uy") == 0){    // computing Ry
-                            int my_new_col = nodeIndexMap[nodeTags[j]] + 1;
-                            Ry += final_nodal_forces[my_new_col];
-                        }
-                    }
-                }
-                std::cout << "Rx: " << Rx << " [N/m], Ry: " << Ry << " [N/m] \n";
-            }
-        }
-    }
-}
-
-void plotUxUy(const std::vector<std::size_t> & FEM_nodeTags, const std::vector<double> & full_d_vector, std::vector<std::string> & names)
-{
-    int viewtagUx = gmsh::view::add("ux");
-    int viewtagUy = gmsh::view::add("uy");
-    std::vector<std::vector<double>> dataUx(FEM_nodeTags.size());
-    std::vector<std::vector<double>> dataUy(FEM_nodeTags.size());
-    for (std::size_t i = 0; i < FEM_nodeTags.size(); ++i)
-    {
-        dataUx[i].resize(1);
-        dataUx[i][0] = full_d_vector[2*i]; //ux // à modifier ici
-        dataUy[i].resize(1);
-        dataUy[i][0] = full_d_vector[2*i+1]; //uy
-    }
-
-    gmsh::view::addModelData(viewtagUx, 0, names[0], "NodeData",
-                            FEM_nodeTags, dataUx); //independent of time here
-    gmsh::view::addModelData(viewtagUy, 0, names[0], "NodeData",
-                            FEM_nodeTags, dataUy); //independent of time here
-}
-
-void computeStressStrainElNodal(int & dim, std::vector<int> & tags,
-                           std::vector<std::size_t> & elems2D, const Eigen::Matrix<double, 3, 3> &H_matrix, 
-                           const double nu, const double E, std::map<int, elementData> & FEM_kinematicsMap,
-                           std::vector<std::vector<std::vector<double>>> & dataEps, std::vector<std::vector<std::vector<double>>> & dataSig,
-                           double & max_VM_stress, int & max_VM_nodeTag)
-{
-    int data_index = 0;
-
-    std::vector<int> elementTypes;
-    std::vector<std::vector<std::size_t>> elementTags;
-    std::vector<std::vector<std::size_t>> elnodeTags;
-    for(std::size_t entityTag = 0; entityTag < tags.size(); ++entityTag)
-    {
-        gmsh::model::mesh::getElements(elementTypes, elementTags, elnodeTags, dim, tags[entityTag]);
-
-        std::string name;
-        int dim, order, numNodes, numPrimaryNodes;
-        std::vector<double> paramCoord2D;
-        int numComponents, numOrientations;
-        std::vector<double> basisFunctionsGradient;
-
-        Eigen::Vector3d epsilon; // will contain local values of the strains (Voigt notation)
-        Eigen::Vector3d sigma; // will contain local values of the stresses (Voigt notation)
-
-        Eigen::Matrix<double, 2, 2> jacob2D;
-        Eigen::Matrix<double, 2, 2> jacobinv;
-        Eigen::Matrix<double, 2, 2> jacobinvtrans;
-
-        elementData* element;
-        double theta;
-
-        Eigen::Matrix<double, 2, 2> strain_tensor;
-        Eigen::Matrix<double, 2, 2> stress_tensor;
-        Eigen::Matrix<double, 2, 2> rotation_tensor;
-
-        for (std::size_t ityp = 0; ityp < elementTypes.size(); ++ityp)
-        {
-            // get info about this type of element
-            gmsh::model::mesh::getElementProperties(elementTypes[ityp], name, dim, order,
-                                                    numNodes, paramCoord2D, numPrimaryNodes);
-            //paramCoord contains the coordinates of the nodes in 2D
-            // passage en 3D pour utiliser getJacobians
-            std::vector<double> paramCoord(3*paramCoord2D.size()/2);
-            for(std::size_t j = 0; j < paramCoord2D.size()/2; ++j){
-                paramCoord[3*j] = paramCoord2D[2*j];
-                paramCoord[3*j+1] = paramCoord2D[2*j+1];
-                paramCoord[3*j+2] = 0.;
-            }
-            
-            // select element/node tags for this type of element
-            auto &etags = elementTags[ityp];
-
-            // get determinants and Jacobians (only jacob useful here) AT NODES AND NOT AT GAUSS POINTS
-            std::vector<double> jacobians, determinants, coords;
-            gmsh::model::mesh::getJacobians(elementTypes[ityp], 
-                                            paramCoord, jacobians, 
-                                            determinants, coords, tags[entityTag]);
-
-            // derivatives of the shape functions at the nodes of the reference element
-            // we are here working with derivatives in the reference space, same for all elements
-            gmsh::model::mesh::getBasisFunctions(elementTypes[ityp], paramCoord, 
-                                                    "GradLagrange", numComponents, 
-                                                    basisFunctionsGradient, 
-                                                    numOrientations);
-
-            // loop over elements of type "ityp"
-            for (std::size_t i = 0; i < etags.size(); ++i)
-            {
-                element = & FEM_kinematicsMap[etags[i]];
-                elems2D[data_index] = etags[i];
-
-                theta = element->theta;
-                rotation_tensor(0,0) = cos(theta);
-                rotation_tensor(0,1) = sin(theta);
-                rotation_tensor(1,0) = -sin(theta);
-                rotation_tensor(1,1) = cos(theta);
-
-                dataEps[0][data_index].resize(numNodes);
-                dataEps[1][data_index].resize(numNodes);
-                dataEps[2][data_index].resize(numNodes);
-                dataEps[3][data_index].resize(numNodes);
-                dataSig[0][data_index].resize(numNodes);
-                dataSig[1][data_index].resize(numNodes);
-                dataSig[2][data_index].resize(numNodes);
-                dataSig[3][data_index].resize(numNodes);
-
-                for (int j = 0; j < numNodes; ++j){
-                    // converting jacobian to Eigen format
-                    Eigen::Map<Eigen::Matrix<double, Eigen::Dynamic, Eigen::Dynamic> >
-                        jacob(&jacobians[9*j+9*numNodes*i], 3, 3);
-                    // 3D to 2D (plane stress case)
-                    jacob2D(0,0) = jacob(0,0);
-                    jacob2D(1,0) = jacob(1,0);
-                    jacob2D(0,1) = jacob(0,1);
-                    jacob2D(1,1) = jacob(1,1);
-                            
-                    // computing the inverse
-                    jacobinv = jacob2D.inverse();
-
-                    // computing the transpose of the inverse
-                    jacobinvtrans = jacobinv.transpose();
-
-                    // computing the B matrix (formula slide 19) 
-                    Eigen::Matrix<double, Eigen::Dynamic, Eigen::Dynamic> B_matrix(3,2*numNodes);
-
-                    computeBmatrix(j, numNodes, basisFunctionsGradient, determinants, jacobinvtrans, B_matrix);
-                    
-                    // expressed in local axes (and in Voigt notation)
-                    epsilon = B_matrix*element->pl; 
-                    sigma = H_matrix*epsilon;
-
-                    // converting to tensor notation (still in local axes)
-                    strain_tensor(0,0) = epsilon(0);
-                    strain_tensor(1,0) = epsilon(2)/2; // divide by 2 because engineering shear strain -> pure shear strain
-                    strain_tensor(0,1) = epsilon(2)/2;
-                    strain_tensor(1,1) = epsilon(1);
-
-                    stress_tensor(0,0) = sigma(0);
-                    stress_tensor(1,0) = sigma(2);
-                    stress_tensor(0,1) = sigma(2);
-                    stress_tensor(1,1) = sigma(1);
-
-                    //converting back to global axes // SUREMENT LE CHANGER !!!!
-                    strain_tensor = rotation_tensor.transpose()*strain_tensor*rotation_tensor;
-                    stress_tensor = rotation_tensor.transpose()*stress_tensor*rotation_tensor;
-
-                    dataEps[0][data_index][j] = strain_tensor(0,0);
-                    dataEps[1][data_index][j] = strain_tensor(1,1);
-                    dataEps[2][data_index][j] = strain_tensor(1,0);
-                    dataEps[3][data_index][j] = -nu/E*(stress_tensor(0,0)+stress_tensor(1,1));// formula slide 11/20 elasticity
-                    dataSig[0][data_index][j] = stress_tensor(0,0);
-                    dataSig[1][data_index][j] = stress_tensor(1,1);
-                    dataSig[2][data_index][j] = stress_tensor(1,0);
-                    dataSig[3][data_index][j] = sqrt(stress_tensor(0,0)*stress_tensor(0,0) + stress_tensor(1,1)*stress_tensor(1,1) - stress_tensor(0,0)*stress_tensor(1,1) + 3*stress_tensor(1,0)*stress_tensor(1,0));
-                    
-                    //retrieving maximal nodal von mises stress
-                    if(dataSig[3][data_index][j] > max_VM_stress)
-                    {
-                        max_VM_stress = dataSig[3][data_index][j];
-                        max_VM_nodeTag = (int) elnodeTags[ityp][numNodes*i+j];
-                    }
-                }
-                data_index++;
-            }
-        }
-    }
-}
-
-void applyElecPressure(const std::string & integration_rule, std::map<int,std::pair<double,double>> & nodeCoordsMap, std::map<int, int> & nodeIndexMap, std::map<std::string, std::pair<int, int>> & groups, std::map<int, double> &electrostaticPressure, std::vector<double> & full_f_vector)
-{
-    std::string groupname = "BEM_FEM_boundary";
-    int dim = groups[groupname].first;
-    int tag = groups[groupname].second;
-    std::vector<int> tags;
-    gmsh::model::getEntitiesForPhysicalGroup(dim, tag, tags);
-
-    int first_nodeTag;
-    int second_nodeTag;
-    double nx;
-    double ny;
-    double p;
-    double tx;
-    double ty;
-    double norm;
-    int first_node_index;
-    int first_node;
-    int my_row;
-
-    for(std::size_t i=0; i< tags.size(); ++i)
+    for(std::size_t i=0; i< tags.size(); ++i)
     {
         std::vector<int> elementTypes;
         std::vector<std::vector<std::size_t>> elementTags;
@@ -755,14 +607,14 @@ void applyElecPressure(const std::string & integration_rule, std::map<int,std::p
             // get Gauss points coordinates and weights
             std::vector<double> localCoords, weights;
             gmsh::model::mesh::getIntegrationPoints(elementTypes[j], 
-                                                    integration_rule, //maybe use different integration method on boundary than in domain
+                                                    integration_rule,
                                                     localCoords, weights);
 
             // get determinants and Jacobians (only determinants will be useful)
             std::vector<double> jacobians, determinants, coords;
             gmsh::model::mesh::getJacobians(elementTypes[j], 
                                             localCoords, jacobians, 
-                                            determinants, coords,tags[i]); // ai dû rajouter mon tags[k] pr focus on this particular entity
+                                            determinants, coords,tags[i]);
 
             int numComponents, numOrientations;
             std::vector<double> basisFunctions;
@@ -775,6 +627,8 @@ void applyElecPressure(const std::string & integration_rule, std::map<int,std::p
             for(std::size_t k=0; k < elementTags[j].size(); ++k)
             {
                 std::vector<double> f_vector(2*numNodes);
+                // computing the outward normal to the element, assuming the BEM domain is traveled "aire à gauche", hence
+                // the FEM domain "aire à droite"
                 first_nodeTag = elnodeTags[j][numNodes*k];
                 second_nodeTag = elnodeTags[j][numNodes*k+1];
                 nx = -(nodeCoordsMap[second_nodeTag].second - nodeCoordsMap[first_nodeTag].second);
@@ -783,18 +637,18 @@ void applyElecPressure(const std::string & integration_rule, std::map<int,std::p
                 nx = nx / norm; //normalisation
                 ny = ny / norm;
                 p = electrostaticPressure[elementTags[j][k]];
-                tx = p*nx;
+                tx = p*nx; // projecting the pressure onto the outward normal
                 ty = p*ny;
 
                 for (int n = 0; n < 2*numNodes; ++n){
                     f_vector[n] = 0.;
                 }
-                // looping over the Gauss points (formula of slide 17)
+                // looping over the Gauss points
                 for (std::size_t n = 0; n < weights.size(); ++n){
                     // looping over the number of shape functions
                     for (int l = 0; l < numNodes; ++l){ 
-                        f_vector[2*l] = f_vector[2*l] + basisFunctions[l+numNodes*n]*tx*determinants[n+weights.size()*k]*weights[n]; //pas oublier le dét !
-                        f_vector[2*l+1] = f_vector[2*l+1] + basisFunctions[l+numNodes*n]*ty*determinants[n+weights.size()*k]*weights[n]; // le weight non plus !
+                        f_vector[2*l] = f_vector[2*l] + basisFunctions[l+numNodes*n]*tx*determinants[n+weights.size()*k]*weights[n];
+                        f_vector[2*l+1] = f_vector[2*l+1] + basisFunctions[l+numNodes*n]*ty*determinants[n+weights.size()*k]*weights[n];
                     }
                 }
                 /*------------- ASSEMBLY PROCESS ------------*/
@@ -810,93 +664,123 @@ void applyElecPressure(const std::string & integration_rule, std::map<int,std::p
     }
 }
 
-int initKinematics(std::vector<std::size_t> & FEM_elementTags, std::map<int, elementData> & FEM_kinematicsMap, std::map<int,std::pair<double,double>> & FEM_nodeCoordsMap, int & dim, std::vector<int> & tags)
+// looping over the boundary conditions imposed in the .geo file and gathered in "BCkeys" related to the
+// physical groups "groups", the goal is to fill the two vectors "new_DOFindices" and "dir_BC":
+// "new_DOFindices" vector will contain the new indices of the K matrix after the "number_removed_lines" rows/columns
+// corresponding to dirichlet boundary conditions have been removed. A removed index will correspond to "new_DOFindices"= -1.
+// dir_BC[index] will contain the dirichlet boundary condition prescribed to the node whose global index is retrieved using
+// "nodeIndexMap".
+void fillDOFindicesDirBC(const std::vector<std::string> & BCkeys, 
+                             std::map<int, int> & nodeIndexMap,
+                             std::map<std::string, std::pair<int, int>> & groups, 
+                             std::vector<int> & new_DOFindices,
+                             std::vector<double> & dir_BC, int & number_removed_lines)
 {
+
+    for (std::size_t j = 0; j < new_DOFindices.size(); ++j){
+        new_DOFindices[j] = (int) j;
+    } 
+    for (std::size_t j = 0; j < dir_BC.size(); ++j){
+        dir_BC[j] = 0;
+    }
+
+    double ux; // imposed displacement along x axis
+    double uy; // imposed displacement along y axis
+
+    int dim;
+    int tag;
+    std::vector<int> tags;
     std::vector<int> elementTypes;
     std::vector<std::vector<std::size_t>> elementTags;
-    std::vector<std::vector<std::size_t>> elnodeTags;
-
+    std::vector<std::vector<std::size_t>> nodeTags;
     std::string elementName;
     int elDim, order, numNodes, numPrimaryNodes;
     std::vector<double> localNodeCoord;
-    
-    int nbelems=0; // will store total number of elements in FEM domain
-    for(std::size_t i=0; i< tags.size(); ++i)
+
+    int first_node_index;
+    int first_node;
+    int my_row;
+
+    for (auto &key : BCkeys)
     {
-        gmsh::model::mesh::getElements(elementTypes, elementTags, elnodeTags, dim, tags[i]);
-        for(std::size_t j=0; j< elementTags.size(); ++j)
+        // get corresponding value
+        std::vector<double> value;
+        gmsh::onelab::getNumber(key, value);
+        // expected key structure is "type/group_name/field"
+        // => split the key string into components
+        std::stringstream ss(key);
+        std::vector<std::string> words;
+        std::string word;
+        while(std::getline(ss, word, '/')) // read string until '/'
+            words.push_back(word);
+        if(words.size()==3) 
         {
-            nbelems+=elementTags[j].size();
-            // filling the FEM_elementTags vector
-            FEM_elementTags.insert(FEM_elementTags.end(), elementTags[j].begin(), elementTags[j].end());
-
-            // getting element properties, in particular: number of nodes
-            gmsh::model::mesh::getElementProperties(elementTypes[j],
-                                                        elementName, elDim, order,
-                                                        numNodes, localNodeCoord, 
-                                                        numPrimaryNodes);
-            for(std::size_t k = 0; k < elementTags[j].size(); k++)
-            {
-                elementData element;
-                std::vector<size_t> tmp_nodes(numNodes);
-                std::vector<double> tmp_x(numNodes);
-                std::vector<double> tmp_y(numNodes);
-                std::vector<double> tmp_u(numNodes);
-                std::vector<double> tmp_v(numNodes);
-                std::vector<double> tmp_ui(numNodes);
-                std::vector<double> tmp_vi(numNodes);
-                double tmp_xc = 0;
-                double tmp_yc = 0;
-                for(int l = 0; l < numNodes; l++)
+            if(words[2].compare("ux") == 0 || words[2].compare("uy") == 0){
+                ux = 0;
+                uy = 0;
+                if(words[2].compare("ux") == 0)
+                    ux = value[0];
+                if(words[2].compare("uy") == 0)
+                    uy = value[0];
+                std::string groupname = words[1];
+                
+                //loop over elements of group_name
+                dim = groups[groupname].first;
+                tag = groups[groupname].second;
+                if (tag == 0)
                 {
-                    tmp_nodes[l] = elnodeTags[j][numNodes*k + l];
-                    tmp_x[l] = FEM_nodeCoordsMap[tmp_nodes[l]].first;
-                    tmp_y[l] = FEM_nodeCoordsMap[tmp_nodes[l]].second;
-                    tmp_u[l] = 0; // initializing the displacement at zero
-                    tmp_v[l] = 0;
-                    tmp_ui[l] = 0;
-                    tmp_vi[l] = 0;
-                    tmp_xc += tmp_x[l];
-                    tmp_yc += tmp_y[l];
+                    std::cerr << "Group '" << groupname << "' does not exist!\n";
+                    return;
                 }
-                tmp_xc = tmp_xc / numNodes;
-                tmp_yc = tmp_yc / numNodes;
+                gmsh::model::getEntitiesForPhysicalGroup(dim, tag, tags);
 
-                std::vector<double> tmp_xi(numNodes);
-                std::vector<double> tmp_yi(numNodes);
-                for(int l = 0; l < numNodes; l++)
+                for (std::size_t k = 0; k < tags.size(); ++k)
                 {
-                    tmp_xi[l] = tmp_x[l] - tmp_xc;
-                    tmp_yi[l] = tmp_y[l] - tmp_yc;
+                    gmsh::model::mesh::getElements(elementTypes, elementTags,
+                                                nodeTags, dim, tags[k]);
+                    
+                    for (std::size_t i = 0; i < elementTypes.size(); ++i)
+                    {
+                        gmsh::model::mesh::getElementProperties(elementTypes[i],
+                                                        elementName, elDim, order,
+                                                        numNodes, localNodeCoord, 
+                                                        numPrimaryNodes);
+                        //looping over the elements
+                        for (std::size_t el = 0; el < elementTags[i].size(); el++){
+                            for (int j = 0; j < 2*numNodes; ++j){
+                                first_node_index = (int) j/2;
+                                first_node = nodeTags[i][numNodes*el+first_node_index];
+                                my_row = nodeIndexMap[first_node] + j % 2; // j % 2 == 1 corresponds to y nodal displacement
+                                if(words[2].compare("uy") == 0 && j % 2){ // we are dealing with a uy type of node
+                                    dir_BC[my_row] = uy;
+                                    new_DOFindices[my_row] = -1;
+                                }
+                                if(words[2].compare("ux") == 0 && j % 2 == 0){ // ux type of node
+                                    dir_BC[my_row] = ux;
+                                    new_DOFindices[my_row] = -1;
+                                }
+                            }
+                        }
+                    }
                 }
-                element.nodes = tmp_nodes;
-                element.x = tmp_x;
-                element.y = tmp_y;
-                element.xc = tmp_xc;
-                element.yc = tmp_yc;
-                element.xi = tmp_xi;
-                element.yi = tmp_yi;
-                element.u = tmp_u;
-                element.v = tmp_v;
-                element.uc = 0;
-                element.vc = 0;
-                element.theta = 0;
-                element.ui = tmp_ui;
-                element.vi = tmp_vi;
-                element.pl = Eigen::Matrix<double, Eigen::Dynamic, 1>::Zero(2*numNodes,1);
-                element.A = Eigen::Matrix<double, Eigen::Dynamic, 1>(2*numNodes,1);
-                element.G = Eigen::Matrix<double, 1, Eigen::Dynamic>(1,2*numNodes);
-                element.E = Eigen::Matrix<double, Eigen::Dynamic, Eigen::Dynamic>::Zero(2*numNodes, 2*numNodes);
-
-                FEM_kinematicsMap[elementTags[j][k]] = element;
-                updateKinMatrices(& FEM_kinematicsMap[elementTags[j][k]], false);
             }
         }
     }
-    return nbelems;
+    for (std::size_t j = 0; j < new_DOFindices.size(); ++j){
+        if(new_DOFindices[j] < 0){ // line has to be removed
+            number_removed_lines++;
+        }
+        else{
+            new_DOFindices[j] -= number_removed_lines;
+        }
+    }
 }
 
-void updateKinematics(std::map<int, elementData> & FEM_kinematicsMap, std::vector<double> & full_d_vector, std::map<int, int> & nodeIndexMap, std::vector<std::size_t> & FEM_elementTags)
+// updating the kinematics of the "FEM_elementTags" contained in the "FEM_kinematicsMap",
+// based on the "full_d_vector" gathering the nodal displacements associated to the global
+// node indices contained in "nodeIndexMap".
+void updateKinematics(std::map<int, elementData> & FEM_kinematicsMap, std::vector<double> & full_d_vector, 
+                std::map<int, int> & nodeIndexMap, std::vector<std::size_t> & FEM_elementTags)
 {
     size_t numNodes;
     size_t tmp_nodeTag;
@@ -946,7 +830,7 @@ void updateKinematics(std::map<int, elementData> & FEM_kinematicsMap, std::vecto
         {
             angle2 = angle1 - PI;
         }
-        // filling ui and vi while finding the angle minimizing equation (4) in the paper (what i call the norm)
+        // filling ui and vi while finding the angle minimizing equation (70) in the report (VERIFIER EQUATION)
         std::vector<double> tmp_ui1(numNodes);
         std::vector<double> tmp_ui2(numNodes);
         std::vector<double> tmp_vi1(numNodes);
@@ -965,76 +849,67 @@ void updateKinematics(std::map<int, elementData> & FEM_kinematicsMap, std::vecto
         if(norm1 < norm2)
         {
             element->theta = angle1;
-            element->ui = tmp_ui1;
-            element->vi = tmp_vi1;
-        }
-        else
-        {
-            element->theta = angle2;
-            element->ui = tmp_ui2;
-            element->vi = tmp_vi2;
-        }
-        for(size_t l = 0; l < numNodes; l++)
-        {
-            element->pl(2*l) = element->ui[l];
-            element->pl(2*l+1) = element->vi[l];
-        }
-        updateKinMatrices(element, true);
-    }
-}
-
-void updateKinMatrices(elementData* element, bool computeFl)
-{
-    size_t numNodes = element->nodes.size();
-    // update A matrix
-    for(size_t i = 0; i < numNodes; i++)
-    {
-        element->A(2*i, 0) = -(element->vi[i]+element->yi[i]);
-        element->A(2*i + 1, 0) = element->ui[i]+element->xi[i];
-    }
-
-    // update G matrix
-    double denominator = 0;
-    for(size_t i = 0; i < numNodes; i++)
-    {
-        element->G(0, 2*i) = -element->yi[i];
-        element->G(0, 2*i + 1) = element->xi[i];
-        denominator += element->xi[i]*(element->ui[i]+element->xi[i]) + element->yi[i]*(element->vi[i]+element->yi[i]);
+            element->ui = tmp_ui1;
+            element->vi = tmp_vi1;
+        }
+        else
+        {
+            element->theta = angle2;
+            element->ui = tmp_ui2;
+            element->vi = tmp_vi2;
+        }
+        for(size_t l = 0; l < numNodes; l++)
+        {
+            element->pl(2*l) = element->ui[l];
+            element->pl(2*l+1) = element->vi[l];
+        }
+        updateKinMatrices(element, true);
     }
-    element->G *= 1 / denominator;
-
-    // update P matrix 
-    element->P = Eigen::Matrix<double, Eigen::Dynamic, Eigen::Dynamic>::Identity(2*numNodes,2*numNodes) - element->A*element->G;
+}
 
-    // update E matrix
-    double tmp_cos = cos(element->theta);
-    double tmp_sin = sin(element->theta);
-    for(size_t i = 0; i < numNodes; i++)
+// retrieving the global nodal forces based on local nodal forces of the 
+// "FEM_elementTags" contained in the "FEM_kinematicsMap", 
+// each node Tag corresponds to a global index given by "nodeIndexMap".
+void retrieveTotalNodalForces(std::vector<double> & final_nodal_forces, std::map<int, elementData> & FEM_kinematicsMap, 
+                        std::map<int, int> & nodeIndexMap, std::vector<std::size_t> & FEM_elementTags)
+{
+    for(size_t i = 0; i < final_nodal_forces.size(); i++)
     {
-        element->E(2*i, 2*i) = tmp_cos;
-        element->E(2*i, 2*i + 1) = -tmp_sin;
-        element->E(2*i + 1, 2*i) = tmp_sin;
-        element->E(2*i + 1, 2*i + 1) = tmp_cos;
+        final_nodal_forces[i] = 0;
     }
 
-    //update B matrix
-    element->B = element->P*element->E.transpose();
-
-    //update fl vector, only if Kl has been initialized
-    if(computeFl)
+    #pragma omp parallel for
+    for(size_t i = 0; i < FEM_elementTags.size(); i++)
     {
-        element->fl = element->Kl*element->pl;
+        elementData* element = & FEM_kinematicsMap[FEM_elementTags[i]];
+        int numNodes = (int) element->nodes.size();
+        Eigen::Matrix<double, Eigen::Dynamic, 1> fg = element->C.transpose()*element->fl;
+        for(int j = 0; j < numNodes; j++)
+        {
+            int nodeTag = element->nodes[j];
+            #pragma omp atomic update
+            final_nodal_forces[nodeIndexMap[nodeTag]] += fg(2*j);
+            #pragma omp atomic update
+            final_nodal_forces[nodeIndexMap[nodeTag] + 1] += fg(2*j + 1);
+        }
     }
 }
 
-void assembleGlobalF(std::vector<double> & global_f_vector, std::map<int, elementData> & FEM_kinematicsMap, std::map<int, int> &  nodeIndexMap, std::vector<int> & new_DOFindices, std::vector<std::size_t> & FEM_elementTags)
+// assembles the "global_f_vector" based on the local nodal forces vector of the 
+// "FEM_elementTags" contained in the "FEM_kinematicsMap", 
+// without taking into account the DOFs fixed by a dirichlet boundary condition,
+// the nodes are renumbered using "new_DOFindices".
+// each node Tag corresponds to a global index given by "nodeIndexMap".
+void assembleGlobalF(std::vector<double> & global_f_vector, std::map<int, elementData> & FEM_kinematicsMap, 
+                std::map<int, int> & nodeIndexMap, std::vector<int> & new_DOFindices, 
+                std::vector<std::size_t> & FEM_elementTags)
 {
     #pragma omp parallel for
     for(size_t i = 0; i < FEM_elementTags.size(); i++)
     {
         elementData* element = & FEM_kinematicsMap[FEM_elementTags[i]];
         int numNodes = (int) element->nodes.size();
-        Eigen::Matrix<double, Eigen::Dynamic, 1> fg = element->B.transpose()*element->fl;
+        Eigen::Matrix<double, Eigen::Dynamic, 1> fg = element->C.transpose()*element->fl;
         for(int j = 0; j < numNodes; j++)
         {
             int nodeTag = element->nodes[j];
@@ -1054,21 +929,13 @@ void assembleGlobalF(std::vector<double> & global_f_vector, std::map<int, elemen
     }
 }
 
-double computeResidualNorm(std::vector<double> & global_f_vector, std::vector<double> & f_app_vector, Eigen::Matrix<double, Eigen::Dynamic, 1> & residual)
-{
-    // norm computed as a geometrical norm (one could also use an other norm)
-    double res = 0;
-    for(size_t i = 0; i < global_f_vector.size(); i++)
-    {
-        residual(i,0) = global_f_vector[i] - f_app_vector[i];
-        res += residual(i,0)*residual(i,0);
-    }
-    res = res / f_app_vector.size();
-    res = sqrt(res);
-    return res;
-}
-
-void assembleGlobalKg(Eigen::SparseMatrix<double> & global_tangent_matrix, std::map<int, elementData> & FEM_kinematicsMap, std::map<int, int> & nodeIndexMap, std::vector<int> & new_DOFindices, std::vector<std::size_t> & FEM_elementTags)
+// assembling the global tangent stiffness matrix based on the local elemental stiffness matrices contained
+// in the "FEM_kinematicsMap" associated to each "FEM_elementTags".
+// "new_DOFindices" maps the global initial node index (from "nodeIndexMap") to the index
+// obtained without considering the fixed DOFs.
+void assembleGlobalKg(Eigen::SparseMatrix<double> & global_tangent_matrix, std::map<int, elementData> & FEM_kinematicsMap, 
+                    std::map<int, int> & nodeIndexMap, std::vector<int> & new_DOFindices, 
+                    std::vector<std::size_t> & FEM_elementTags)
 {
     // to allow efficient parallelization, one triplet list is created for every thread, then the different lists are assembled
     // to further increase efficiency, space is reserved for each list
@@ -1100,9 +967,10 @@ void assembleGlobalKg(Eigen::SparseMatrix<double> & global_tangent_matrix, std::
             F(0,2*j+1) = -tmp_F(2*j, 0);
         }
         
+        // see report for the formula
         Eigen::Matrix<double, Eigen::Dynamic, Eigen::Dynamic> Kh = element->E*(-F.transpose()*element->G - element->G.transpose()*F*element->P)*element->E.transpose();
 
-        Eigen::Matrix<double, Eigen::Dynamic, Eigen::Dynamic> Kg = element->B.transpose()*element->Kl*element->B + Kh;
+        Eigen::Matrix<double, Eigen::Dynamic, Eigen::Dynamic> Kg = element->C.transpose()*element->Kl*element->C + Kh;
 
         int threadNum = omp_get_thread_num();
         my_Kg_list = & thread_Kg_list[threadNum];
@@ -1145,7 +1013,10 @@ void assembleGlobalKg(Eigen::SparseMatrix<double> & global_tangent_matrix, std::
     global_tangent_matrix.setFromTriplets(Kg_tripletList.begin(), Kg_tripletList.end());
 }
 
-void updateNodalDispVector(std::vector<double> & full_d_vector, std::vector<double> & global_p_vector, std::vector<int> & new_DOFindices)
+// updating the "full_d_vector" based on the "global_p_vector", using the global and reduced index mapping:
+// "new_DOFindices".
+void updateNodalDispVector(std::vector<double> & full_d_vector, std::vector<double> & global_p_vector, 
+                        std::vector<int> & new_DOFindices)
 {
     for(size_t i = 0; i < full_d_vector.size(); i++)
     {
@@ -1156,99 +1027,350 @@ void updateNodalDispVector(std::vector<double> & full_d_vector, std::vector<doub
     }
 }
 
-void retrieveTotalNodalForces(std::vector<double> & final_nodal_forces, std::map<int, elementData> & FEM_kinematicsMap, std::map<int, int> & nodeIndexMap, std::vector<std::size_t> & FEM_elementTags)
-{
-    for(size_t i = 0; i < final_nodal_forces.size(); i++)
-    {
-        final_nodal_forces[i] = 0;
-    }
+// retrieving the nodal displacements on the BEM_FEM_boundary in order to communicate them to the
+// BEM solver in the two-way coupled solver. Using the "groups" to find the physical group associated
+// to the boundary, all nodes of the boundary are associated through the "boundaryDisplacementMap" their
+// nodal displacement (u,v) retrieved from "full_d_vector" using the indexation of "nodeIndexMap".
+void retrieveBoundaryDisplacements(std::map<int,std::pair<double,double>> & boundaryDisplacementMap, 
+                            const std::vector<double> & full_d_vector, std::map<int, int> & nodeIndexMap, 
+                            std::map<std::string, std::pair<int, int>> & groups)
+{
+    std::string groupname = "BEM_FEM_boundary";
+    int dim = groups[groupname].first;
+    int tag = groups[groupname].second;
+    std::vector<int> tags;
+    gmsh::model::getEntitiesForPhysicalGroup(dim, tag, tags);
+    std::vector<int> elementTypes;
+    std::vector<std::vector<std::size_t>> elementTags;
+    std::vector<std::vector<std::size_t>> elnodeTags;
+
+    int nodeTag;
+    double u;
+    double v;
+
+    std::string elementName;
+    int elDim, order, numNodes, numPrimaryNodes;
+    std::vector<double> localNodeCoord;
+
+    for(std::size_t i=0; i< tags.size(); ++i)
+    {
+        gmsh::model::mesh::getElements(elementTypes, elementTags, elnodeTags, dim, tags[i]);
+        for(std::size_t j=0; j< elementTypes.size(); ++j)
+        {
+
+            // getting element properties
+            gmsh::model::mesh::getElementProperties(elementTypes[j],
+                                                        elementName, elDim, order,
+                                                        numNodes, localNodeCoord, 
+                                                        numPrimaryNodes);
+
+            for(std::size_t k=0; k < elementTags[j].size(); ++k)
+            {
+                for(int l = 0; l < numNodes; l++)
+                {
+                    nodeTag = elnodeTags[j][numNodes*k + l];
+                    u = full_d_vector[nodeIndexMap[nodeTag]];
+                    v = full_d_vector[nodeIndexMap[nodeTag] + 1];
+                    boundaryDisplacementMap[nodeTag] = std::pair<double, double>(u, v);
+                }
+            }
+        }
+    }
+}
+
+// computing the reaction forces on all the physical groups "groups" for which a dirichlet boundary
+// condition has been prescribed (using the "BCkeys" to retrieve the groups from the .geo file),
+// based on the "final_nodal_forces" and the "nodeIndexMap" to retrieve the global index associated
+// to one given node tag.
+void computeReactionForces(std::map<std::string, std::pair<int, int>> & groups,
+                     std::map<int, int> & nodeIndexMap, std::vector<double> & final_nodal_forces,
+                     const std::vector<std::string> & BCkeys)
+{
+    double Rx;
+    double Ry;
+
+    int dim;
+    int tag;
+    std::vector<int> tags;
+
+    std::vector<std::size_t> nodeTags;
+    std::vector<double> coord; //useless but mandatory
+    std::vector<double> parametricCoord; //useless but mandatory
+
+    for (auto &key : BCkeys)
+    {
+        // get corresponding value
+        std::vector<double> value;
+        gmsh::onelab::getNumber(key, value);
+        // expected key structure is "type/group_name/field"
+        // => split the key string into components
+        std::stringstream ss(key);
+        std::vector<std::string> words;
+        std::string word;
+        while(std::getline(ss, word, '/')) // read string until '/'
+            words.push_back(word);
+        if(words.size()==3) 
+        {
+            if(words[2].compare("ux") == 0 || words[2].compare("uy") == 0){
+                Rx = 0;
+                Ry = 0;
+                std::string groupname = words[1];
+                std::cout << "-------------------- " << groupname << " ------------------- \n";
+                
+                //loop over elements of group_name
+                dim = groups[groupname].first;
+                tag = groups[groupname].second;
+                if (tag == 0)
+                {
+                    std::cerr << "Group '" << groupname << "' does not exist!\n";
+                    return;
+                }
+                gmsh::model::getEntitiesForPhysicalGroup(dim, tag, tags);
+
+                // loop over entities
+                for (std::size_t k = 0; k < tags.size(); ++k)
+                {
+                    // retrieving all the nodes in this particular entity
+                    gmsh::model::mesh::getNodes(nodeTags, coord, parametricCoord, dim, tags[k], true);
+                    
+                    
+                    for (std::size_t j = 0; j < nodeTags.size(); ++j){
+                        if(words[2].compare("ux") == 0){    // computing Rx
+                            int my_new_col = nodeIndexMap[nodeTags[j]]; //nodeIndexMap[nodeTags[j]] corresponds to line/column index in K, f
+                            Rx += final_nodal_forces[my_new_col];
+                        }
+                        if(words[2].compare("uy") == 0){    // computing Ry
+                            int my_new_col = nodeIndexMap[nodeTags[j]] + 1;
+                            Ry += final_nodal_forces[my_new_col];
+                        }
+                    }
+                }
+                std::cout << "Rx: " << Rx << " [N/m], Ry: " << Ry << " [N/m] \n";
+            }
+        }
+    }
+}
+
+// plotting the nodal displacement values associated to "FEM_nodeTags" in the "names[0]" model,
+// based on the "full_d_vector".
+void plotUxUy(const std::vector<std::size_t> & FEM_nodeTags, const std::vector<double> & full_d_vector, 
+            std::vector<std::string> & names)
+{
+    int viewtagUx = gmsh::view::add("ux");
+    int viewtagUy = gmsh::view::add("uy");
+    std::vector<std::vector<double>> dataUx(FEM_nodeTags.size());
+    std::vector<std::vector<double>> dataUy(FEM_nodeTags.size());
+    for (std::size_t i = 0; i < FEM_nodeTags.size(); ++i)
+    {
+        dataUx[i].resize(1);
+        dataUx[i][0] = full_d_vector[2*i]; //ux // à modifier ici
+        dataUy[i].resize(1);
+        dataUy[i][0] = full_d_vector[2*i+1]; //uy
+    }
+
+    gmsh::view::addModelData(viewtagUx, 0, names[0], "NodeData",
+                            FEM_nodeTags, dataUx); //independent of time here
+    gmsh::view::addModelData(viewtagUy, 0, names[0], "NodeData",
+                            FEM_nodeTags, dataUy); //independent of time here
+}
+
+// computing the elemental-nodal strains and stresses in the FEM domain (dimension "dim" and entity tags "tags"),
+// based on Hooke's matrix in plane stress "H_matrix", Young's modulus "E", Poisson's ratio "nu", looping over the
+// "nbelems" elements contained in "FEM_kinematicsMap".
+// returning the "max_VM_stress" and the corresponding nodetag "max_VM_nodeTag".
+void computeStressStrainElNodal(int & dim, std::vector<int> & tags, const Eigen::Matrix<double, 3, 3> &H_matrix, 
+                           const double nu, const double E, std::map<int, elementData> & FEM_kinematicsMap,
+                           int & nbelems, double & max_VM_stress, int & max_VM_nodeTag)
+{
+    std::vector<std::string> names;
+    gmsh::model::list(names);
+
+    int viewTagEpsX = gmsh::view::add("nodal_eps_x");
+    int viewTagEpsY = gmsh::view::add("nodal_eps_y");
+    int viewTagEpsXY = gmsh::view::add("nodal_2eps_xy");
+    int viewTagEpsZ = gmsh::view::add("nodal_eps_z");
+    int viewTagSigX = gmsh::view::add("nodal_sig_x");
+    int viewTagSigY = gmsh::view::add("nodal_sig_y");
+    int viewTagSigXY = gmsh::view::add("nodal_sig_xy");
+    int viewTagSigVM = gmsh::view::add("nodal_sig_VM");
+
+    std::vector<std::vector<std::vector<double>>> dataEps(4);
+    dataEps[0].resize(nbelems); // eps X
+    dataEps[1].resize(nbelems); // eps Y
+    dataEps[2].resize(nbelems); // eps XY
+    dataEps[3].resize(nbelems); // eps Z
+
+    std::vector<std::vector<std::vector<double>>> dataSig(4);
+    dataSig[0].resize(nbelems); // Sig X
+    dataSig[1].resize(nbelems); // Sig Y
+    dataSig[2].resize(nbelems); // Sig XY
+    dataSig[3].resize(nbelems); // Sig VM
+    std::vector<std::size_t> elems2D(nbelems);
+
+    int data_index = 0;
+
+    std::vector<int> elementTypes;
+    std::vector<std::vector<std::size_t>> elementTags;
+    std::vector<std::vector<std::size_t>> elnodeTags;
+    for(std::size_t entityTag = 0; entityTag < tags.size(); ++entityTag)
+    {
+        gmsh::model::mesh::getElements(elementTypes, elementTags, elnodeTags, dim, tags[entityTag]);
+
+        std::string name;
+        int dim, order, numNodes, numPrimaryNodes;
+        std::vector<double> paramCoord2D;
+        int numComponents, numOrientations;
+        std::vector<double> basisFunctionsGradient;
+
+        Eigen::Vector3d epsilon; // will contain local values of the strains (Voigt notation)
+        Eigen::Vector3d sigma; // will contain local values of the stresses (Voigt notation)
+
+        Eigen::Matrix<double, 2, 2> jacob2D;
+        Eigen::Matrix<double, 2, 2> jacobinv;
+        Eigen::Matrix<double, 2, 2> jacobinvtrans;
+
+        elementData* element;
+        double theta;
+
+        Eigen::Matrix<double, 2, 2> strain_tensor;
+        Eigen::Matrix<double, 2, 2> stress_tensor;
+        Eigen::Matrix<double, 2, 2> rotation_tensor;
+
+        for (std::size_t ityp = 0; ityp < elementTypes.size(); ++ityp)
+        {
+            // get info about this type of element
+            gmsh::model::mesh::getElementProperties(elementTypes[ityp], name, dim, order,
+                                                    numNodes, paramCoord2D, numPrimaryNodes);
+            //paramCoord contains the coordinates of the nodes in 2D
+            // passage en 3D pour utiliser getJacobians
+            std::vector<double> paramCoord(3*paramCoord2D.size()/2);
+            for(std::size_t j = 0; j < paramCoord2D.size()/2; ++j){
+                paramCoord[3*j] = paramCoord2D[2*j];
+                paramCoord[3*j+1] = paramCoord2D[2*j+1];
+                paramCoord[3*j+2] = 0.;
+            }
+            
+            // select element/node tags for this type of element
+            auto &etags = elementTags[ityp];
+
+            // get determinants and Jacobians (only jacob useful here) AT NODES AND NOT AT GAUSS POINTS
+            std::vector<double> jacobians, determinants, coords;
+            gmsh::model::mesh::getJacobians(elementTypes[ityp], 
+                                            paramCoord, jacobians, 
+                                            determinants, coords, tags[entityTag]);
+
+            // derivatives of the shape functions at the nodes of the reference element
+            // we are here working with derivatives in the reference space, same for all elements
+            gmsh::model::mesh::getBasisFunctions(elementTypes[ityp], paramCoord, 
+                                                    "GradLagrange", numComponents, 
+                                                    basisFunctionsGradient, 
+                                                    numOrientations);
+
+            // loop over elements of type "ityp"
+            for (std::size_t i = 0; i < etags.size(); ++i)
+            {
+                element = & FEM_kinematicsMap[etags[i]];
+                elems2D[data_index] = etags[i];
+
+                theta = element->theta;
+                rotation_tensor(0,0) = cos(theta);
+                rotation_tensor(0,1) = sin(theta);
+                rotation_tensor(1,0) = -sin(theta);
+                rotation_tensor(1,1) = cos(theta);
+
+                dataEps[0][data_index].resize(numNodes);
+                dataEps[1][data_index].resize(numNodes);
+                dataEps[2][data_index].resize(numNodes);
+                dataEps[3][data_index].resize(numNodes);
+                dataSig[0][data_index].resize(numNodes);
+                dataSig[1][data_index].resize(numNodes);
+                dataSig[2][data_index].resize(numNodes);
+                dataSig[3][data_index].resize(numNodes);
+
+                for (int j = 0; j < numNodes; ++j){
+                    // converting jacobian to Eigen format
+                    Eigen::Map<Eigen::Matrix<double, Eigen::Dynamic, Eigen::Dynamic> >
+                        jacob(&jacobians[9*j+9*numNodes*i], 3, 3);
+                    // 3D to 2D (plane stress case)
+                    jacob2D(0,0) = jacob(0,0);
+                    jacob2D(1,0) = jacob(1,0);
+                    jacob2D(0,1) = jacob(0,1);
+                    jacob2D(1,1) = jacob(1,1);
+                            
+                    // computing the inverse
+                    jacobinv = jacob2D.inverse();
 
-    #pragma omp parallel for
-    for(size_t i = 0; i < FEM_elementTags.size(); i++)
-    {
-        elementData* element = & FEM_kinematicsMap[FEM_elementTags[i]];
-        int numNodes = (int) element->nodes.size();
-        Eigen::Matrix<double, Eigen::Dynamic, 1> fg = element->B.transpose()*element->fl;
-        for(int j = 0; j < numNodes; j++)
-        {
-            int nodeTag = element->nodes[j];
-            #pragma omp atomic update
-            final_nodal_forces[nodeIndexMap[nodeTag]] += fg(2*j);
-            #pragma omp atomic update
-            final_nodal_forces[nodeIndexMap[nodeTag] + 1] += fg(2*j + 1);
-        }
-    }
-}
+                    // computing the transpose of the inverse
+                    jacobinvtrans = jacobinv.transpose();
 
-void retrieveBoundaryDisplacements(std::map<int,std::pair<double,double>> & boundaryDisplacementMap, const std::vector<double> & full_d_vector, std::map<int, int> & nodeIndexMap, std::map<std::string, std::pair<int, int>> & groups)
-{
-    std::string groupname = "BEM_FEM_boundary";
-    int dim = groups[groupname].first;
-    int tag = groups[groupname].second;
-    std::vector<int> tags;
-    gmsh::model::getEntitiesForPhysicalGroup(dim, tag, tags);
-    std::vector<int> elementTypes;
-    std::vector<std::vector<std::size_t>> elementTags;
-    std::vector<std::vector<std::size_t>> elnodeTags;
+                    // computing the B matrix (formula slide 19) 
+                    Eigen::Matrix<double, Eigen::Dynamic, Eigen::Dynamic> B_matrix(3,2*numNodes);
 
-    int nodeTag;
-    double u;
-    double v;
+                    computeBmatrix(j, numNodes, basisFunctionsGradient, determinants, jacobinvtrans, B_matrix);
+                    
+                    // expressed in local axes (and in Voigt notation)
+                    epsilon = B_matrix*element->pl; 
+                    sigma = H_matrix*epsilon;
 
-    std::string elementName;
-    int elDim, order, numNodes, numPrimaryNodes;
-    std::vector<double> localNodeCoord;
+                    // converting to tensor notation (still in local axes)
+                    strain_tensor(0,0) = epsilon(0);
+                    strain_tensor(1,0) = epsilon(2)/2; // divide by 2 because engineering shear strain -> pure shear strain
+                    strain_tensor(0,1) = epsilon(2)/2;
+                    strain_tensor(1,1) = epsilon(1);
 
-    for(std::size_t i=0; i< tags.size(); ++i)
-    {
-        gmsh::model::mesh::getElements(elementTypes, elementTags, elnodeTags, dim, tags[i]);
-        for(std::size_t j=0; j< elementTypes.size(); ++j)
-        {
+                    stress_tensor(0,0) = sigma(0);
+                    stress_tensor(1,0) = sigma(2);
+                    stress_tensor(0,1) = sigma(2);
+                    stress_tensor(1,1) = sigma(1);
 
-            // getting element properties
-            gmsh::model::mesh::getElementProperties(elementTypes[j],
-                                                        elementName, elDim, order,
-                                                        numNodes, localNodeCoord, 
-                                                        numPrimaryNodes);
+                    strain_tensor = rotation_tensor.transpose()*strain_tensor*rotation_tensor;
+                    stress_tensor = rotation_tensor.transpose()*stress_tensor*rotation_tensor;
 
-            for(std::size_t k=0; k < elementTags[j].size(); ++k)
-            {
-                for(int l = 0; l < numNodes; l++)
-                {
-                    nodeTag = elnodeTags[j][numNodes*k + l];
-                    u = full_d_vector[nodeIndexMap[nodeTag]];
-                    v = full_d_vector[nodeIndexMap[nodeTag] + 1];
-                    boundaryDisplacementMap[nodeTag] = std::pair<double, double>(u, v);
+                    dataEps[0][data_index][j] = strain_tensor(0,0);
+                    dataEps[1][data_index][j] = strain_tensor(1,1);
+                    dataEps[2][data_index][j] = strain_tensor(1,0);
+                    dataEps[3][data_index][j] = -nu/E*(stress_tensor(0,0)+stress_tensor(1,1));// from plane stress hypothesis
+                    dataSig[0][data_index][j] = stress_tensor(0,0);
+                    dataSig[1][data_index][j] = stress_tensor(1,1);
+                    dataSig[2][data_index][j] = stress_tensor(1,0);
+                    // VM stress in plane stress
+                    dataSig[3][data_index][j] = sqrt(stress_tensor(0,0)*stress_tensor(0,0) + stress_tensor(1,1)*stress_tensor(1,1) - stress_tensor(0,0)*stress_tensor(1,1) + 3*stress_tensor(1,0)*stress_tensor(1,0));
+                    
+                    //retrieving maximal nodal von mises stress
+                    if(dataSig[3][data_index][j] > max_VM_stress)
+                    {
+                        max_VM_stress = dataSig[3][data_index][j];
+                        max_VM_nodeTag = (int) elnodeTags[ityp][numNodes*i+j];
+                    }
                 }
+                data_index++;
             }
         }
     }
-}
-
-void displayTime(double &start, double &end, std::string &function_name, bool &display_time)
-{
-    //displays the time of the desired code section
-    if(display_time)
-    {
-        std::cout << std::endl;
-        std::cout << function_name << std::endl;
-        std::cout << "Elapsed time in nanoseconds: "
-        << end - start << " ns" << std::endl;
-
-        std::cout << "Elapsed time in microseconds: "
-        << end - start << " µs" << std::endl;
-
-        std::cout << "Elapsed time in milliseconds: "
-        << end - start << " ms" << std::endl;
 
-        std::cout << "Elapsed time in seconds: "
-        << end - start << " sec";
-        std::cout << std::endl;
-        std::cout << std::endl;
-    }
+    gmsh::view::addModelData(viewTagEpsX, 0, names[0], "ElementNodeData",
+                                elems2D, dataEps[0], 1); 
+    gmsh::view::addModelData(viewTagEpsY, 0, names[0], "ElementNodeData",
+                                elems2D, dataEps[1], 1); 
+    gmsh::view::addModelData(viewTagEpsXY, 0, names[0], "ElementNodeData",
+                                elems2D, dataEps[2], 1); 
+    gmsh::view::addModelData(viewTagEpsZ, 0, names[0], "ElementNodeData",
+                                elems2D, dataEps[3], 1); 
+    gmsh::view::addModelData(viewTagSigX, 0, names[0], "ElementNodeData",
+                                elems2D, dataSig[0], 1); 
+    gmsh::view::addModelData(viewTagSigY, 0, names[0], "ElementNodeData",
+                                elems2D, dataSig[1], 1); 
+    gmsh::view::addModelData(viewTagSigXY, 0, names[0], "ElementNodeData",
+                                elems2D, dataSig[2], 1); 
+    gmsh::view::addModelData(viewTagSigVM, 0, names[0], "ElementNodeData",
+                                elems2D, dataSig[3], 1); 
 }
 
-void computeWorkDoneByExternalForces(double &externalWork, std::vector<double> & full_d_vector, std::vector<double> & final_nodal_forces)
+// computing the work done by external forces, as the nodal forces are energy consistent, it
+// is sufficient to compute the "externalWork" as the dot product of the nodal displacements
+// "full_d_vector" and the nodal forces "final_nodal_forces".
+void computeWorkDoneByExternalForces(double &externalWork, std::vector<double> & full_d_vector, 
+                                std::vector<double> & final_nodal_forces)
 {
     for(size_t i = 0; i < full_d_vector.size(); i++)
     {
@@ -1256,6 +1378,10 @@ void computeWorkDoneByExternalForces(double &externalWork, std::vector<double> &
     }
 }
 
+// computing the total (internal) strain energy "strain_energy" in the whole FEM domain (dimension "dim" and 
+// entity tags "tags"), using the elementData stored in "FEM_kinematicsMap" gathering the informations for all
+// FEM elements. The Hooke's matrix in plane stress "H_matrix" is used to compute the stresses from the strains.
+// Computed as a sum of integrals over the finite elements, using Gauss integration "integration_rule".
 void computeStrainEnergy(double & strainEnergy, int & dim, std::vector<int> & tags,
                            std::map<int, elementData> & FEM_kinematicsMap, const Eigen::Matrix<double, 3, 3> &H_matrix, 
                            const std::string & integration_rule)
@@ -1271,9 +1397,6 @@ void computeStrainEnergy(double & strainEnergy, int & dim, std::vector<int> & ta
     std::vector<double> basisFunctions;
     std::vector<double> basisFunctionsGradient;
 
-    Eigen::Vector3d epsilon; // will contain local values of the strains
-    Eigen::Vector3d sigma; // will contain local values of the stresses
-
     for (std::size_t k = 0; k < tags.size(); ++k)
     {
         gmsh::model::mesh::getElements(elementTypes, elementTags,
@@ -1311,19 +1434,19 @@ void computeStrainEnergy(double & strainEnergy, int & dim, std::vector<int> & ta
                                                  basisFunctionsGradient, 
                                                  numOrientations);
 
-
+            #pragma omp parallel for
             for (std::size_t el = 0; el < elementTags[i].size(); el++){
 
                 Eigen::Matrix<double, Eigen::Dynamic, 1> local_nodal_displacement = FEM_kinematicsMap[elementTags[i][el]].pl;
 
                 double elementalStrainEnergy = 0;
 
-                // looping over the Gauss points (formula of slide 17)
-                for (std::size_t j = 0; j < weights.size(); ++j){
+                // looping over the Gauss points: Gauss integration
+                for (std::size_t gaussIndex = 0; gaussIndex < weights.size(); ++gaussIndex){
                     // converting jacobian to Eigen format
                     Eigen::Matrix<double, 2, 2> jacob2D;
                     Eigen::Map<Eigen::Matrix<double, Eigen::Dynamic, Eigen::Dynamic> >
-                        jacob(&jacobians[9*j+9*weights.size()*el], 3, 3);
+                        jacob(&jacobians[9*gaussIndex+9*weights.size()*el], 3, 3);
                     // 3D to 2D (plane stress case)
                     jacob2D(0,0) = jacob(0,0);
                     jacob2D(1,0) = jacob(1,0);
@@ -1336,14 +1459,13 @@ void computeStrainEnergy(double & strainEnergy, int & dim, std::vector<int> & ta
                     // computing the transpose of the inverse
                     Eigen::Matrix<double, 2, 2> jacobinvtrans = jacobinv.transpose();
 
-                    // computing the B matrix (formula slide 19) and the f vector (also using gauss integration)
+                    // computing the B matrix
                     Eigen::Matrix<double, Eigen::Dynamic, Eigen::Dynamic> B_matrix(3,2*numNodes);
 
+                    computeBmatrix(gaussIndex, numNodes, basisFunctionsGradient, determinants, jacobinvtrans, B_matrix);
 
-                    computeBmatrix(j, numNodes, basisFunctionsGradient, determinants, jacobinvtrans, B_matrix);
-
-                    epsilon = B_matrix*local_nodal_displacement;
-                    sigma = H_matrix*epsilon;
+                    Eigen::Vector3d epsilon = B_matrix*local_nodal_displacement;
+                    Eigen::Vector3d sigma = H_matrix*epsilon;
 
                     double dot_product = 0;
                     for(int z = 0; z < 3; z++)
@@ -1351,67 +1473,58 @@ void computeStrainEnergy(double & strainEnergy, int & dim, std::vector<int> & ta
                         dot_product += epsilon(z)*sigma(z);
                     }
 
-                    elementalStrainEnergy += 0.5*dot_product*determinants[j+weights.size()*el]*weights[j];
+                    elementalStrainEnergy += 0.5*dot_product*determinants[gaussIndex+weights.size()*el]*weights[gaussIndex];
                 }
                 /*------------- ASSEMBLY PROCESS ------------*/
+                #pragma omp atomic update
                 strainEnergy += elementalStrainEnergy;
             }
         }
     }
 }
 
-/*-------------- FUNCTIONS SPECIFIC TO LINEAR FEM ------------------*/
-
-void assembleKtriplet(const int i, const int el, 
-                          const std::vector<std::vector<std::size_t>> & nodeTags, 
-                          const int numNodes, std::map<int, int> & nodeIndexMap,
-                          std::vector<Eigen::Triplet <double>> & k_tripletList,
-                          Eigen::Matrix<double, Eigen::Dynamic, Eigen::Dynamic> & K_matrix,
-                          std::vector<double> & f_vector)
+//displays the time of the desired code section "function_name", if "display_time" is set as true.
+void displayTime(double &start, double &end, std::string &function_name, bool &display_time)
 {
-    for (int j = 0; j < 2*numNodes; ++j){
-        int first_node_index = (int) j/2;
-        int first_node = nodeTags[i][numNodes*el+first_node_index];
-        int my_row = nodeIndexMap[first_node] + j % 2; // node list begins at 1, '-1' correction to begin at 0
-        for(int l = 0; l <= j; ++l){ // to exploit symmetry
-            int second_node_index = (int) l/2;
-            int second_node = nodeTags[i][numNodes*el+second_node_index];
-            int my_col = nodeIndexMap[second_node] + l % 2;
+    if(display_time)
+    {
+        std::cout << std::endl;
+        std::cout << function_name << std::endl;
+        std::cout << "Elapsed time in nanoseconds: "
+        << end - start << " ns" << std::endl;
 
-            k_tripletList.push_back(Eigen::Triplet<double>(my_row,my_col,K_matrix(j,l)));
-            if(j != l){
-                k_tripletList.push_back(Eigen::Triplet<double>(my_col,my_row,K_matrix(j,l)));
-            }
-        }
-    }
-}
+        std::cout << "Elapsed time in microseconds: "
+        << end - start << " µs" << std::endl;
 
-//assembles the full matrix "f" of one given thread
-void assembleFthread(const int &i, const int &el, 
-                          const std::vector<std::vector<std::size_t>> & nodeTags, 
-                          const int &numNodes, std::map<int, int> & nodeIndexMap,
-                          std::vector<std::pair<int,double>> & thread_f_vector, 
-                          std::vector<double> & f_vector)
-{
-    int first_node_index;
-    int first_node;
-    int my_row;
-    for (int j = 0; j < 2*numNodes; ++j)
-    {
-        first_node_index = (int) j/2;
-        first_node = nodeTags[i][numNodes*el+first_node_index];
-        my_row = nodeIndexMap[first_node] + j % 2; 
-        thread_f_vector.push_back(std::pair<int, double>(my_row, f_vector[j]));
+        std::cout << "Elapsed time in milliseconds: "
+        << end - start << " ms" << std::endl;
+
+        std::cout << "Elapsed time in seconds: "
+        << end - start << " sec";
+        std::cout << std::endl;
+        std::cout << std::endl;
     }
 }
 
+/*-------------- FUNCTIONS SPECIFIC TO LINEAR FEM ------------------*/
 
-void assembleKlistVolumicF(const Eigen::Matrix<double, 3, 3> H_matrix, const double rho, const double bx, const double by,
-              const std::string & integration_rule, 
-              int & dim, std::vector<int> & tags, std::map<int, int>  & nodeIndexMap,
-              std::vector<std::vector<Eigen::Triplet <double>>> & my_k_list, 
-              std::vector<std::vector<std::pair<int,double>>> & thread_f_vector)
+// assembling the full stiffness matrix "full_K_matrix" using first a triplet vector, assembling the volumic part 
+// of the nodal forces "full_f_vector", in the FEM_domain (dimension "dim" and entity tag "tags") using the global
+// node indexation defined by "nodeIndexMap". "H_matrix" is the plane stress Hooke's matrix, "rho" the density of the
+// material, "bx" the volumic force along x, "by" the volumic force along y.
+// Numerical integration using gauss integration "integration_rule".
+void assembleKVolumicF(const Eigen::Matrix<double, 3, 3> H_matrix, const double rho, const double bx, const double by,
+              const std::string & integration_rule, int & dim, std::vector<int> & tags, std::map<int, int> & nodeIndexMap,
+              Eigen::SparseMatrix<double> & full_K_matrix, std::vector<double> & full_f_vector)
 {
+    // the full K matrix will first be assembled as a triplet list (vector) (to increase efficiency),
+    // before initializing the full sparse matrix in one single time;
+    int max_threads = omp_get_max_threads();
+    // one triplet vector declared for every thread (for the K triplet list) (will be gathered at the end of the method)
+    std::vector<std::vector<Eigen::Triplet <double>>> my_k_list(max_threads);
+    // one pair vector declared for every thread (for the f vector) (will be gathered at the end of the method)
+    std::vector<std::vector<std::pair<int,double>>> thread_f_vector(max_threads);
+
     std::vector<int> elementTypes;
     std::vector<std::vector<std::size_t>> elementTags;
     std::vector<std::vector<std::size_t>> nodeTags;
@@ -1428,7 +1541,7 @@ void assembleKlistVolumicF(const Eigen::Matrix<double, 3, 3> H_matrix, const dou
         gmsh::model::mesh::getElements(elementTypes, elementTags,
                                        nodeTags, dim, tags[k]);
         
-        // looping over element types, enlever les déclarations des boucles pr aller plus vite (cfr Boman)
+        // looping over element types
         for (std::size_t i = 0; i < elementTypes.size(); ++i)
         {
             // getting element properties
@@ -1446,9 +1559,7 @@ void assembleKlistVolumicF(const Eigen::Matrix<double, 3, 3> H_matrix, const dou
             std::vector<double> jacobians, determinants, coords;
             gmsh::model::mesh::getJacobians(elementTypes[i], 
                                             localCoords, jacobians, 
-                                            determinants, coords, tags[k]); //big modif
-
-            //std::cout << "entity " << k << ", element type " << i << ",jacobians size: " << jacobians.size() << ", weights size: " << weights.size() << "\n";
+                                            determinants, coords, tags[k]);
 
             // values and derivatives of the shape functions at the Gauss points of the reference element
             // we are here working with derivatives in the reference space, same for all elements
@@ -1462,12 +1573,8 @@ void assembleKlistVolumicF(const Eigen::Matrix<double, 3, 3> H_matrix, const dou
                                                  basisFunctionsGradient, 
                                                  numOrientations);
 
-            //std::cout << "entity " << k << ", element tags " << elementTags[i].size() << ",jacobians size: " << jacobians.size() << ",determinants size: " << determinants.size() << ", weights size: " << weights.size() << ", basis functions size: " << basisFunctions.size() << ", basis functions gradient size: " << basisFunctionsGradient.size() << "\n";
-
             /*---------computing the K matrix (and f vector, only volumic part for now)------*/
             // looping over the elements (computing elemental K matrices)
-            //double test1 = omp_get_wtime();
-            //Eigen::setNbThreads(1);
             #ifdef _MSC_VER
             // https://github.com/tesseract-ocr/tesseract/issues/3285
             // OpenMP 2.0 standard is enforced - we may use /openmp:llvm and keep size_t
@@ -1475,7 +1582,8 @@ void assembleKlistVolumicF(const Eigen::Matrix<double, 3, 3> H_matrix, const dou
             for (int el = 0; el < elementTags[i].size(); el++){
             #else
             #pragma omp parallel for
-            for (std::size_t el = 0; el < elementTags[i].size(); el++){
+            for (std::size_t el = 0; el < elementTags[i].size(); el++)
+            {
             #endif
                 // elemental K matrix and f vector initialization
                 Eigen::Matrix<double, Eigen::Dynamic, Eigen::Dynamic> K_matrix(2*numNodes,2*numNodes);
@@ -1483,19 +1591,17 @@ void assembleKlistVolumicF(const Eigen::Matrix<double, 3, 3> H_matrix, const dou
                 K_matrix.setZero();
                 for (int j = 0; j < 2*numNodes; ++j)f_vector[j] = 0.;
 
-                // looping over the Gauss points (formula of slide 17)
-                for (std::size_t j = 0; j < weights.size(); ++j){
+                // looping over the Gauss points
+                for (std::size_t gaussIndex = 0; gaussIndex < weights.size(); ++gaussIndex){
                     // converting jacobian to Eigen format
                         Eigen::Matrix<double, 2, 2> jacob2D;
                     Eigen::Map<Eigen::Matrix<double, Eigen::Dynamic, Eigen::Dynamic> >
-                        jacob(&jacobians[9*j+9*weights.size()*el], 3, 3);
+                        jacob(&jacobians[9*gaussIndex+9*weights.size()*el], 3, 3);
                     // 3D to 2D (plane stress case)
                     jacob2D(0,0) = jacob(0,0);
                     jacob2D(1,0) = jacob(1,0);
                     jacob2D(0,1) = jacob(0,1);
                     jacob2D(1,1) = jacob(1,1);
-
-                    //std::cout << "element tag: " << elementTags[i][el] << "jacob2d: " << jacob2D << "\n";
                             
                     // computing the inverse
                     Eigen::Matrix<double, 2, 2> jacobinv = jacob2D.inverse();
@@ -1503,41 +1609,107 @@ void assembleKlistVolumicF(const Eigen::Matrix<double, 3, 3> H_matrix, const dou
                     // computing the transpose of the inverse
                     Eigen::Matrix<double, 2, 2> jacobinvtrans = jacobinv.transpose();
 
-                    // computing the B matrix (formula slide 19) and the f vector (also using gauss integration)
+                    // computing the B matrix and the f vector (also using gauss integration)
                     Eigen::Matrix<double, Eigen::Dynamic, Eigen::Dynamic> B_matrix(3,2*numNodes);
 
-
-                    computeBmatrix(j, numNodes, basisFunctionsGradient, determinants, jacobinvtrans, B_matrix);
-
-                    //std::cout << "element tag: " << elementTags[i][el] << "B matrix: " << B_matrix << "\n";
+                    computeBmatrix(gaussIndex, numNodes, basisFunctionsGradient, determinants, jacobinvtrans, B_matrix);
                     
                     for (int l = 0; l < numNodes; ++l){ // looping over the number of shape functions
-                        f_vector[2*l] = f_vector[2*l] + rho*basisFunctions[l+numNodes*j]*bx*determinants[j+weights.size()*el]*weights[j]; //pas oublier le dét !
-                        f_vector[2*l+1] = f_vector[2*l+1] + rho*basisFunctions[l+numNodes*j]*by*determinants[j+weights.size()*el]*weights[j]; // pas oublier le weights[j] non plus !
+                        f_vector[2*l] = f_vector[2*l] + rho*basisFunctions[l+numNodes*gaussIndex]*bx*determinants[gaussIndex+weights.size()*el]*weights[gaussIndex];
+                        f_vector[2*l+1] = f_vector[2*l+1] + rho*basisFunctions[l+numNodes*gaussIndex]*by*determinants[gaussIndex+weights.size()*el]*weights[gaussIndex]; 
                     }
 
-                    K_matrix = K_matrix + B_matrix.transpose()*H_matrix*B_matrix*determinants[j+weights.size()*el]*weights[j];
-                    //std::cout << "determinant: " << determinants[j+weights.size()*el] << ", weight: " << weights[j] << "\n";
+                    K_matrix = K_matrix + B_matrix.transpose()*H_matrix*B_matrix*determinants[gaussIndex+weights.size()*el]*weights[gaussIndex];
                 }
                 /*------------- ASSEMBLY PROCESS ------------*/
                 // USE LIST OF TRIPLETS TO PUSH ONLY ONCE -> more rapid and easier to parallelize
-                assembleKtriplet(i, el, nodeTags, numNodes, nodeIndexMap, my_k_list[omp_get_thread_num()], K_matrix, f_vector);
-                assembleFthread(i, el, nodeTags, numNodes, nodeIndexMap, thread_f_vector[omp_get_thread_num()], f_vector);
+                assembleKtriplet(el, nodeTags[i], numNodes, nodeIndexMap, my_k_list[omp_get_thread_num()], K_matrix);
+                assembleFthread(el, nodeTags[i], numNodes, nodeIndexMap, thread_f_vector[omp_get_thread_num()], f_vector);
+            }
+        }
+    }
+    // gathering all triplet vectors in one single vector
+    std::vector<Eigen::Triplet <double>> k_tripletList = my_k_list[0];
+    if(max_threads > 1)
+    {
+        for (int i = 1; i < max_threads; i++)
+        {
+            k_tripletList.insert(k_tripletList.end(), my_k_list[i].begin(), my_k_list[i].end());
+        }
+    }
+    // assembling the full sparse K matrix
+    full_K_matrix.setFromTriplets(k_tripletList.begin(), k_tripletList.end());
+
+    // re-assembling the full f vector based on the separated thread vectors
+    for(int i = 0; i < max_threads; i++){
+        std::vector<std::pair<int,double>> &tmp_vector = thread_f_vector[i];
+        for(auto &p : tmp_vector) {
+            full_f_vector[p.first] += p.second;
+        }
+    }
+}
+
+// assembles the elemental "K_matrix" into a vector of triplets "k_tripletList" using 
+// the node tags provided by "nodeTags" vector (index of the element is "elIndex").
+// The element has "numNodes" nodes. "nodeIndexMap" provides the global nodal index corresponding to a given node tag.
+void assembleKtriplet(const int &elIndex, 
+                          const std::vector<std::size_t> & nodeTags, 
+                          const int numNodes, std::map<int, int> & nodeIndexMap,
+                          std::vector<Eigen::Triplet <double>> & k_tripletList,
+                          Eigen::Matrix<double, Eigen::Dynamic, Eigen::Dynamic> & K_matrix)
+{
+    //we go through the lower half of the elemental K_matrix before assembling in the triplet vector
+    for (int j = 0; j < 2*numNodes; ++j){
+        int first_node_index = (int) j/2;
+        int first_node = nodeTags[numNodes*elIndex+first_node_index];
+        int my_row = nodeIndexMap[first_node] + j % 2; 
+        for(int l = 0; l <= j; ++l){ // to exploit symmetry
+            int second_node_index = (int) l/2;
+            int second_node = nodeTags[numNodes*elIndex+second_node_index];
+            int my_col = nodeIndexMap[second_node] + l % 2;
 
-                //std::cout << "element tag: " << elementTags[i][el] << "local K matrix: " << K_matrix << "\n";
+            k_tripletList.push_back(Eigen::Triplet<double>(my_row,my_col,K_matrix(j,l)));
+            if(j != l){
+                k_tripletList.push_back(Eigen::Triplet<double>(my_col,my_row,K_matrix(j,l)));
             }
-            //double test2 = omp_get_wtime() - test1;
-            //std::cout << "loop time: " << test2 << "\n";
         }
     }
 }
 
+// assembles the elemental "f_vector" in a vector of pairs called "thread_f_vector" particular to the parallel thread.
+// the element index in "nodeTags" is "elIndex". The element has "numNodes" nodes and their global nodal indices are given
+// by the "nodeIndexMap".
+void assembleFthread(const int &elIndex, 
+                          const std::vector<std::size_t> & nodeTags, 
+                          const int &numNodes, std::map<int, int> & nodeIndexMap,
+                          std::vector<std::pair<int,double>> & thread_f_vector, 
+                          std::vector<double> & f_vector)
+{
+    // we simply go through the elemental f vector
+    int first_node_index;
+    int first_node;
+    int my_row;
+    for (int j = 0; j < 2*numNodes; ++j)
+    {
+        first_node_index = (int) j/2;
+        first_node = nodeTags[numNodes*elIndex+first_node_index];
+        my_row = nodeIndexMap[first_node] + j % 2; 
+        thread_f_vector.push_back(std::pair<int, double>(my_row, f_vector[j]));
+    }
+}
+
+// the "full_f_vector" and "full_K_matrix" are reduced in "reduced_f_vector" and "reduced_K_matrix" respectively,
+// taking into account the dirichlet boundary conditions contained in "dir_BC".
+// "new_DOFindices" gathers the new indices of the DOFs when taking the boundary conditions into account.
 void fillReducedKf(const std::vector<double> & full_f_vector, const Eigen::SparseMatrix<double> & full_K_matrix,
                       const std::vector<int> & new_DOFindices, const std::vector<double> & dir_BC,
                       Eigen::SparseMatrix<double> & reduced_K_matrix, 
-                      std::vector<double> & reduced_f_vector,
-                      std::list<Eigen::Triplet <double>> & reduced_k_tripletList)
+                      std::vector<double> & reduced_f_vector)
 {
+    //the correction term on the f vector comes from the "Finite Element Method" theoretical course (JP Ponthot, ULiege), Chap2 Slide 38/50
+    std::list<Eigen::Triplet <double>> reduced_k_tripletList;
+
+    // initializing the reduced f vector
     for (std::size_t j = 0; j < reduced_f_vector.size(); ++j){
         reduced_f_vector[j] = 0.;
     }
@@ -1547,7 +1719,7 @@ void fillReducedKf(const std::vector<double> & full_f_vector, const Eigen::Spars
     int new_row;
     int new_col;
     double k_value;
-    for (int col=0; col<full_K_matrix.outerSize(); ++col){
+    for (int col=0; col < full_K_matrix.outerSize(); ++col){
         new_col = new_DOFindices[col];
         for (Eigen::SparseMatrix<double>::InnerIterator it(full_K_matrix,col); it; ++it){
             k_value = it.value();
@@ -1573,12 +1745,42 @@ void fillReducedKf(const std::vector<double> & full_f_vector, const Eigen::Spars
     reduced_K_matrix.setFromTriplets(reduced_k_tripletList.begin(), reduced_k_tripletList.end());
 }
 
-void computeStressStrainLinearWay(int & dim, std::vector<int> & tags,
-                           const std::vector<double> &full_d_vector, std::vector<std::size_t> & elems2D,
+
+// computing the elemental-nodal strains and stresses in the FEM domain (dimension "dim" and entity tags "tags"),
+// based on Hooke's matrix in plane stress "H_matrix", Young's modulus "E", Poisson's ratio "nu", and the full nodal
+// displacement vector "full_d_vector" and the global nodal index map "nodeIndexMap".
+// "nbelems" is the number of 2D elements contained in the FEM domain.
+// returning the "max_VM_stress" and the corresponding nodetag "max_VM_nodeTag".
+void computeStressStrainLinearWay(int & dim, std::vector<int> & tags, const std::vector<double> &full_d_vector,
                            const Eigen::Matrix<double, 3, 3> &H_matrix, const double nu, const double E,
-                           std::map<int, int> & nodeIndexMap, std::vector<std::vector<std::vector<double>>> & dataEps, 
-                           std::vector<std::vector<std::vector<double>>> & dataSig)
+                           std::map<int, int> & nodeIndexMap, int & nbelems, double & max_VM_stress, int & max_VM_nodeTag)
 {
+    std::vector<std::string> names;
+    gmsh::model::list(names);
+
+    int viewTagEpsX = gmsh::view::add("nodal_eps_x");
+    int viewTagEpsY = gmsh::view::add("nodal_eps_y");
+    int viewTagEpsXY = gmsh::view::add("nodal_2eps_xy");
+    int viewTagEpsZ = gmsh::view::add("nodal_eps_z");
+    int viewTagSigX = gmsh::view::add("nodal_sig_x");
+    int viewTagSigY = gmsh::view::add("nodal_sig_y");
+    int viewTagSigXY = gmsh::view::add("nodal_sig_xy");
+    int viewTagSigVM = gmsh::view::add("nodal_sig_VM");
+
+    std::vector<std::vector<std::vector<double>>> dataEps(4);
+    dataEps[0].resize(nbelems); // eps X
+    dataEps[1].resize(nbelems); // eps Y
+    dataEps[2].resize(nbelems); // eps XY
+    dataEps[3].resize(nbelems); // eps Z
+
+    std::vector<std::vector<std::vector<double>>> dataSig(4);
+    dataSig[0].resize(nbelems); // Sig X
+    dataSig[1].resize(nbelems); // Sig Y
+    dataSig[2].resize(nbelems); // Sig XY
+    dataSig[3].resize(nbelems); // Sig VM
+    std::vector<std::size_t> elems2D(nbelems);
+
+
     int data_index = 0;
     std::vector<int> elementTypes;
     std::vector<std::vector<std::size_t>> elementTags;
@@ -1673,7 +1875,7 @@ void computeStressStrainLinearWay(int & dim, std::vector<int> & tags,
                     // computing the transpose of the inverse
                     jacobinvtrans = jacobinv.transpose();
 
-                    // computing the B matrix (formula slide 19) 
+                    // computing the B matrix  
                     Eigen::Matrix<double, Eigen::Dynamic, Eigen::Dynamic> B_matrix(3,2*numNodes);
 
                     computeBmatrix(j, numNodes, basisFunctionsGradient, determinants, jacobinvtrans, B_matrix);
@@ -1684,18 +1886,46 @@ void computeStressStrainLinearWay(int & dim, std::vector<int> & tags,
                     dataEps[0][data_index][j] = epsilon(0);
                     dataEps[1][data_index][j] = epsilon(1);
                     dataEps[2][data_index][j] = epsilon(2);
-                    dataEps[3][data_index][j] = -nu/E*(sigma(0)+sigma(1));// formula slide 11/20 elasticity
+                    dataEps[3][data_index][j] = -nu/E*(sigma(0)+sigma(1));
                     dataSig[0][data_index][j] = sigma(0);
                     dataSig[1][data_index][j] = sigma(1);
                     dataSig[2][data_index][j] = sigma(2);
                     dataSig[3][data_index][j] = sqrt(sigma(0)*sigma(0) + sigma(1)*sigma(1) - sigma(0)*sigma(1) + 3*sigma(2)*sigma(2));
+
+                    //retrieving maximal nodal von mises stress
+                    if(dataSig[3][data_index][j] > max_VM_stress)
+                    {
+                        max_VM_stress = dataSig[3][data_index][j];
+                        max_VM_nodeTag = (int) elnodeTags[ityp][numNodes*i+j];
+                    }
                 }
                 data_index++;
             }
         }
     }
+    
+    gmsh::view::addModelData(viewTagEpsX, 0, names[0], "ElementNodeData",
+                                elems2D, dataEps[0], 1);
+    gmsh::view::addModelData(viewTagEpsY, 0, names[0], "ElementNodeData",
+                                elems2D, dataEps[1], 1);
+    gmsh::view::addModelData(viewTagEpsXY, 0, names[0], "ElementNodeData",
+                                elems2D, dataEps[2], 1);
+    gmsh::view::addModelData(viewTagEpsZ, 0, names[0], "ElementNodeData",
+                                elems2D, dataEps[3], 1);
+    gmsh::view::addModelData(viewTagSigX, 0, names[0], "ElementNodeData",
+                                elems2D, dataSig[0], 1);
+    gmsh::view::addModelData(viewTagSigY, 0, names[0], "ElementNodeData",
+                                elems2D, dataSig[1], 1);
+    gmsh::view::addModelData(viewTagSigXY, 0, names[0], "ElementNodeData",
+                                elems2D, dataSig[2], 1);
+    gmsh::view::addModelData(viewTagSigVM, 0, names[0], "ElementNodeData",
+                                elems2D, dataSig[3], 1);
 }
 
+// computing the total (internal) strain energy "strain_energy" in the whole FEM domain (dimension "dim" and 
+// entity tags "tags"), using the nodal displacements in "full_d_vector". The nodes are indexed using the "nodeIndexMap".
+// The Hooke's matrix in plane stress "H_matrix" is used to compute the stresses from the strains.
+// Computed as a sum of integrals over the finite elements, using Gauss integration "integration_rule".
 void computeStrainEnergyLinearWay(double & strainEnergy, int & dim, std::vector<int> & tags,
                            const std::vector<double> &full_d_vector, const Eigen::Matrix<double, 3, 3> &H_matrix, 
                            std::map<int, int> & nodeIndexMap, const std::string & integration_rule)
@@ -1754,7 +1984,6 @@ void computeStrainEnergyLinearWay(double & strainEnergy, int & dim, std::vector<
                                                 
             Eigen::VectorXd nodal_displacement(2*numNodes); //use eigen vector to use Eigen matric product later
 
-
             for (std::size_t el = 0; el < elementTags[i].size(); el++){
 
                 for (int j = 0; j < numNodes; ++j)
@@ -1766,12 +1995,12 @@ void computeStrainEnergyLinearWay(double & strainEnergy, int & dim, std::vector<
 
                 double elementalStrainEnergy = 0;
 
-                // looping over the Gauss points (formula of slide 17)
-                for (std::size_t j = 0; j < weights.size(); ++j){
+                // looping over the Gauss points
+                for (std::size_t gaussIndex = 0; gaussIndex < weights.size(); ++gaussIndex){
                     // converting jacobian to Eigen format
                     Eigen::Matrix<double, 2, 2> jacob2D;
                     Eigen::Map<Eigen::Matrix<double, Eigen::Dynamic, Eigen::Dynamic> >
-                        jacob(&jacobians[9*j+9*weights.size()*el], 3, 3);
+                        jacob(&jacobians[9*gaussIndex+9*weights.size()*el], 3, 3);
                     // 3D to 2D (plane stress case)
                     jacob2D(0,0) = jacob(0,0);
                     jacob2D(1,0) = jacob(1,0);
@@ -1784,11 +2013,10 @@ void computeStrainEnergyLinearWay(double & strainEnergy, int & dim, std::vector<
                     // computing the transpose of the inverse
                     Eigen::Matrix<double, 2, 2> jacobinvtrans = jacobinv.transpose();
 
-                    // computing the B matrix (formula slide 19) and the f vector (also using gauss integration)
+                    // computing the B matrix 
                     Eigen::Matrix<double, Eigen::Dynamic, Eigen::Dynamic> B_matrix(3,2*numNodes);
 
-
-                    computeBmatrix(j, numNodes, basisFunctionsGradient, determinants, jacobinvtrans, B_matrix);
+                    computeBmatrix(gaussIndex, numNodes, basisFunctionsGradient, determinants, jacobinvtrans, B_matrix);
 
                     epsilon = B_matrix*nodal_displacement;
                     sigma = H_matrix*epsilon;
@@ -1799,7 +2027,7 @@ void computeStrainEnergyLinearWay(double & strainEnergy, int & dim, std::vector<
                         dot_product += epsilon(z)*sigma(z);
                     }
 
-                    elementalStrainEnergy += 0.5*dot_product*determinants[j+weights.size()*el]*weights[j];
+                    elementalStrainEnergy += 0.5*dot_product*determinants[gaussIndex+weights.size()*el]*weights[gaussIndex];
                 }
                 /*------------- ASSEMBLY PROCESS ------------*/
                 strainEnergy += elementalStrainEnergy;
diff --git a/srcs/FEM/functionsFEM.hpp b/srcs/FEM/functionsFEM.hpp
index 4572228..1e9df82 100644
--- a/srcs/FEM/functionsFEM.hpp
+++ b/srcs/FEM/functionsFEM.hpp
@@ -1,10 +1,11 @@
 #include <iostream>
-#include <vector>   // Why should I include this ?
+#include <vector> 
 #include <map>
 #include <Eigen/Dense>
 #include <list>
-#include <Eigen/Sparse> // Eigen library for sparse matrices
+#include <Eigen/Sparse>
 
+// data structure used for the non-linear solver "solverFEMnonLinear"
 struct elementData{
     // will store all the kinematics data
     std::vector<size_t> nodes; // will contain the tags of the nodes
@@ -34,47 +35,253 @@ struct elementData{
 
     Eigen::Matrix<double, Eigen::Dynamic, 1> fl; //local current (iterative) forces under vector form
 
-    Eigen::Matrix<double, Eigen::Dynamic, 1> A;
-    Eigen::Matrix<double, 1, Eigen::Dynamic> G;
-    Eigen::Matrix<double, Eigen::Dynamic, Eigen::Dynamic> P;
-    Eigen::Matrix<double, Eigen::Dynamic, Eigen::Dynamic> E;
-    Eigen::Matrix<double, Eigen::Dynamic, Eigen::Dynamic> B;
+    Eigen::Matrix<double, Eigen::Dynamic, 1> A; // see report for exact definition
+    Eigen::Matrix<double, 1, Eigen::Dynamic> G; // see report for exact definition
+    Eigen::Matrix<double, Eigen::Dynamic, Eigen::Dynamic> P; // see report for exact definition
+    Eigen::Matrix<double, Eigen::Dynamic, Eigen::Dynamic> E; // see report for exact definition
+    Eigen::Matrix<double, Eigen::Dynamic, Eigen::Dynamic> C; // see report for exact definition
 };
 
+/*------------ "SOLVER" functions -----------------*/
+
+// solves the non-linear (large displacements, no large deformations) elastic problem.
+// if coupled to the BEM solver, "electrostaticPressure" is the pressure computed from the electric field
+// on the BEM_FEM boundary (mapping element tag -> pressure). "boundaryDisplacementMap" is filled for all
+// nodes on the BEM_FEM_boundary as (node tag -> (u,v) displacement)
+// "nbViews" is the current number of views already allocated in the gmsh window.
+// "postProcessing" boolean activates the post processing operations (must only be computed once at the end of the
+// coupled solver). "iteration" denotes the current iteration of the two-way coupled solver (starting at 1).
+// "viewTagU" and "viewTagF" correspond to the view tags of the nodal displacements and the nodal forces in the gmsh window.
+// when "untangleMesh" is activated, the nodal coordinates of the nodes in the FEM_domain are updated according to
+// their final nodal displacements.
+void solverFEMnonLinear(std::map<int, double> &electrostaticPressure, 
+                    std::map<int,std::pair<double,double>> & boundaryDisplacementMap, int &nbViews, bool postProcessing, 
+                    const int iteration, const int viewTagU, const int viewTagF, bool untangleMesh);
+
+// solves the linear elastic problem in the FEM domain.
+// "electrostaticPressure" contains the pressure resulting from the electric field computation on the 
+// BEM_FEM_boundary. "nbViews" is the current number of views already allocated in the gmsh window.
+void solverFEM(std::map<int, double> &electrostaticPressure, int &nbViews);
+
+/*------------ More specific functions called inside the "SOLVER" functions -----------------*/
+
+// returns the boolean non_linear_solver parameter based on what is set in the .geo file,
+// add SetNumber("Non_linear_solver", 0); in .geo file to use linear solver.
 bool getNonLinearParameter();
-Eigen::Matrix<double, 3, 3> computeHmatrix(const double E, const double nu);
-void assembleFlocal(const int &i, const int &el, const std::vector<std::vector<std::size_t>> & nodeTags, const int &numNodes, std::map<int, int> & nodeIndexMap, std::vector<double> & full_f_vector, std::vector<double> & f_vector);
-void computeBmatrix(const int &j, const int &numNodes, const std::vector<double> & basisFunctionsGradient, const std::vector<double> & determinants, const Eigen::Matrix<double, 2, 2> & jacobinvtrans, Eigen::Matrix<double, Eigen::Dynamic, Eigen::Dynamic> & B_matrix);
-void getPhysicalProperties(const std::vector<std::string> & keys, double & E, double & nu, double & rho, double & bx, double & by);
-void fillElementalKandF(const Eigen::Matrix<double, 3, 3> H_matrix, const double rho, const double bx, const double by, const std::string & integration_rule, int & dim, std::vector<int> & tags, std::map<int, int>  & nodeIndexMap, std::vector<double> & full_f_vector, std::map<int, elementData> & FEM_kinematicsMap);
-void computeNeumannBC(const int &numNodes, const int &elTyp, const std::vector<std::vector<std::size_t>> & elementTags, const double &tx, const double &ty, std::map<int, int> & nodeIndexMap, const std::vector<double> & weights, const std::vector<double> & basisFunctions, const std::vector<double> & determinants, const std::vector<std::vector<std::size_t>> & nodeTags, std::vector<double> & full_f_vector);
-void includeNeumannBC(const std::vector<std::string> & keys, const std::string & integration_rule, std::map<int, int> & nodeIndexMap, std::map<std::string, std::pair<int, int>> & groups, std::vector<double> & full_f_vector);
-void fillDOFindicesDirBC(const std::vector<std::string> & keys, std::map<int, int> & nodeIndexMap, std::map<std::string, std::pair<int, int>> & groups, std::vector<int> & new_DOFindices, std::vector<double> & dir_BC);
-void computeReactionForces(std::map<std::string, std::pair<int, int>> & groups, std::map<int, int> & nodeIndexMap, std::vector<double> & nodal_forces, const std::vector<std::string> & keys);
-void plotUxUy(const std::vector<std::size_t> & FEM_nodeTags, const std::vector<double> & full_d_vector, std::vector<std::string> & names);
-void computeStressStrainElNodal(int & dim, std::vector<int> & tags, std::vector<std::size_t> & elems2D, const Eigen::Matrix<double, 3, 3> &H_matrix, const double nu, const double E, std::map<int, elementData> & FEM_kinematicsMap, std::vector<std::vector<std::vector<double>>> & dataEps, std::vector<std::vector<std::vector<double>>> & dataSig, double & max_VM_stress, int & max_VM_nodeTag);
-void applyElecPressure(const std::string & integration_rule, std::map<int,std::pair<double,double>> & nodeCoordsMap, std::map<int, int> & nodeIndexMap, std::map<std::string, std::pair<int, int>> & groups, std::map<int, double> &electrostaticPressure, std::vector<double> & full_f_vector);
-int initKinematics(std::vector<std::size_t> & FEM_elementTags, std::map<int, elementData> & FEM_kinematicsMap, std::map<int,std::pair<double,double>> & FEM_nodeCoordsMap, int & dim, std::vector<int> & tags);
-void updateKinematics(std::map<int, elementData> & FEM_kinematicsMap, std::vector<double> & full_d_vector, std::map<int, int> & nodeIndexMap, std::vector<std::size_t> & FEM_elementTags);
+
+// fills the "FEM_nodeTags" vector with the nodes in the FEM domain, fills the "FEM_nodeCoordsMap" with the coordinates (x,y)
+// corresponding to each nodeTag, looking at entity tags "tags" of dimension "dim" corresponding to the FEM domain.
+void RetrieveFEMNodeTagsAndCoords(std::vector<std::size_t> & FEM_nodeTags, 
+                            std::map<int,std::pair<double,double>> & FEM_nodeCoordsMap, 
+                            int & dim, std::vector<int> & tags);
+
+// fills the "FEM_elementTags" vector with all element tags of the elements of dimension "dim" (in practice: 2)
+// in the entities associated to the entity tags "tags" (corresponding to the FEM domain).
+// associates an initialized elementData structure to each element tag through the "FEM_kinematicsMap" map,
+// using the "FEM_nodeCoordsMap" map gathering the coordinates of each node in the FEM domain.
+int initKinematics(std::vector<std::size_t> & FEM_elementTags, std::map<int, elementData> & FEM_kinematicsMap, 
+                std::map<int,std::pair<double,double>> & FEM_nodeCoordsMap, int & dim, std::vector<int> & tags);
+
+// updates the kinematics matrices of one element "element" based on its updated nodal positions and rotation angle.
+// if "computeFl" is set as true, also computes the local nodal forces vector "fl = Kl*pl", only OK if 
+// the local stiffness matrix Kl has already been initialized, using "fillElementalKandF".
 void updateKinMatrices(elementData* element, bool computeFl);
-void assembleGlobalF(std::vector<double> & global_f_vector, std::map<int, elementData> & FEM_kinematicsMap, std::map<int, int> & nodeIndexMap, std::vector<int> & new_DOFindices, std::vector<std::size_t> & FEM_elementTags);
-double computeResidualNorm(std::vector<double> & global_f_vector, std::vector<double> & f_app_vector, Eigen::Matrix<double, Eigen::Dynamic, 1> & residual);
-void assembleGlobalKg(Eigen::SparseMatrix<double> & global_tangent_matrix, std::map<int, elementData> & FEM_kinematicsMap, std::map<int, int> &  nodeIndexMap, std::vector<int> & new_DOFindices, std::vector<std::size_t> & FEM_elementTags);
-void updateNodalDispVector(std::vector<double> & full_d_vector, std::vector<double> & global_p_vector, std::vector<int> & new_DOFindices);
-void retrieveTotalNodalForces(std::vector<double> & final_nodal_forces, std::map<int, elementData> & FEM_kinematicsMap, std::map<int, int> & nodeIndexMap, std::vector<std::size_t> & FEM_elementTags);
-void retrieveBoundaryDisplacements(std::map<int,std::pair<double,double>> & boundaryDisplacementMap, const std::vector<double> & full_d_vector, std::map<int, int> & nodeIndexMap, std::map<std::string, std::pair<int, int>> & groups);
-void displayTime(double &start, double &end, std::string &function_name, bool &display_time);
-void computeWorkDoneByExternalForces(double &externalWork, std::vector<double> & full_d_vector, std::vector<double> & final_nodal_forces);
-void computeStrainEnergy(double & strainEnergy, int & dim, std::vector<int> & tags, std::map<int, elementData> & FEM_kinematicsMap, const Eigen::Matrix<double, 3, 3> &H_matrix, const std::string & integration_rule);
 
-void solverFEMnonLinear(std::map<int, double> &electrostaticPressure, std::map<int,std::pair<double,double>> & boundaryDisplacementMap, int &nbViews, bool postProcessing, const int iteration, const int viewTagU, const int viewTagF, bool untangleMesh);
+// gets physical properties of FEM_domain: Young's modulus "E", Poisson's ratio "nu",
+// density "rho", volumic force along x "bx", volumic force along y "by".
+void getPhysicalProperties(double & E, double & nu, double & rho, double & bx, double & by);
+
+// computes the Hooke's matrix in plane stress "H_matrix" based on the Young's modulus "E" and Poisson's ratio "nu".
+Eigen::Matrix<double, 3, 3> computeHmatrix(const double E, const double nu);
+
+// looping over every element in the FEM_domain (dimension "dim" and entity tags "tags"), filling "full_f_vector",
+// with the local volumic forces computed based on the volumic force ("bx","by") and density "rho" with the help of
+// "nodeIndexMap" mapping each nodeTag to its global index.
+// filling the local stiffness matrices stored in the elementData structure contained in "FEM_kinematicsMap".
+// numerical integration performed using Gauss integration based on "integration_rule".
+void fillElementalKandF(const Eigen::Matrix<double, 3, 3> H_matrix, const double rho, const double bx, const double by,
+              const std::string & integration_rule, 
+              int & dim, std::vector<int> & tags, std::map<int, int>  & nodeIndexMap,
+              std::vector<double> & full_f_vector,
+              std::map<int, elementData> & FEM_kinematicsMap);
+
+// computing the strain displacement "B_matrix" at the "gaussIndex"-th gaussian point of the element
+// consisting of "numNodes" nodes, based on the "basisFunctionsGradient", "determinants" and the inverse of the jacobian
+// matrix "jacobinvtrans".
+void computeBmatrix(const int &gaussIndex, const int &numNodes, const std::vector<double> & basisFunctionsGradient,
+                      const std::vector<double> & determinants, 
+                      const Eigen::Matrix<double, 2, 2> & jacobinvtrans,
+                      Eigen::Matrix<double, Eigen::Dynamic, Eigen::Dynamic> & B_matrix);
+
+// assembles the local "f_vector" into the full f vector, "elIndex" is the element index with respect to "nodeTags"
+// having "numNodes" nodes, "nodeTags" gathering all the nodeTags of the current element type in the current entity.
+// "nodeIndexMap" associates a global index to each nodetag.
+void assembleFlocal(const int &elIndex, 
+                          const std::vector<std::size_t> & nodeTags, 
+                          const int &numNodes, std::map<int, int> & nodeIndexMap,
+                          std::vector<double> & full_f_vector, 
+                          std::vector<double> & f_vector);
+
+// fills the "full_f_vector" with the surfacic contribution of the neumann boundary conditions based on the 
+// "nodeIndexMap" associating a global index to each node tag, the local integration of the surfacic forces is 
+// performed using Gauss integration based on "integration rule".
+// "BCkeys" gathers the keys associated to boundary conditions in the .geo file (using SetNumber(...)).
+// "groups" associates a dimension and a tag to each physical group.
+void includeNeumannBC(const std::vector<std::string> & BCkeys, const std::string & integration_rule,
+                std::map<int, int> & nodeIndexMap,
+                std::map<std::string, std::pair<int, int>> & groups,
+                std::vector<double> & full_f_vector);
+
+// for the coupled solver, fills the "full_f_vector" with the electrostatic pressure contained in the 
+// "electrostaticPressure" map for different element tags, using Gauss integration "integration_rule", the "nodeIndexMap" 
+// associating a global nodal index to each nodeTag, "nodeCoordsMap" gathering the coordinates of each node and "groups"
+// containing the different physical groups of the domain.
+void applyElecPressure(const std::string & integration_rule, std::map<int,std::pair<double,double>> & nodeCoordsMap, 
+                    std::map<int, int> & nodeIndexMap, std::map<std::string, std::pair<int, int>> & groups, 
+                    std::map<int, double> &electrostaticPressure, std::vector<double> & full_f_vector);
+
+// looping over the boundary conditions imposed in the .geo file and gathered in "BCkeys" related to the
+// physical groups "groups", the goal is to fill the two vectors "new_DOFindices" and "dir_BC":
+// "new_DOFindices" vector will contain the new indices of the K matrix after the "number_removed_lines" rows/columns
+// corresponding to dirichlet boundary conditions have been removed. A removed index will correspond to "new_DOFindices"= -1.
+// dir_BC[index] will contain the dirichlet boundary condition prescribed to the node whose global index is retrieved using
+// "nodeIndexMap".
+void fillDOFindicesDirBC(const std::vector<std::string> & BCkeys, 
+                             std::map<int, int> & nodeIndexMap,
+                             std::map<std::string, std::pair<int, int>> & groups, 
+                             std::vector<int> & new_DOFindices,
+                             std::vector<double> & dir_BC, int & number_removed_lines);
+
+// updating the kinematics of the "FEM_elementTags" contained in the "FEM_kinematicsMap",
+// based on the "full_d_vector" gathering the nodal displacements associated to the global
+// node indices contained in "nodeIndexMap".
+void updateKinematics(std::map<int, elementData> & FEM_kinematicsMap, std::vector<double> & full_d_vector, 
+                std::map<int, int> & nodeIndexMap, std::vector<std::size_t> & FEM_elementTags);
+
+// retrieving the global nodal forces based on local nodal forces of the 
+// "FEM_elementTags" contained in the "FEM_kinematicsMap", 
+// each node Tag corresponds to a global index given by "nodeIndexMap".
+void retrieveTotalNodalForces(std::vector<double> & final_nodal_forces, std::map<int, elementData> & FEM_kinematicsMap, 
+                        std::map<int, int> & nodeIndexMap, std::vector<std::size_t> & FEM_elementTags);
+
+// assembles the "global_f_vector" based on the local nodal forces vector of the 
+// "FEM_elementTags" contained in the "FEM_kinematicsMap", 
+// without taking into account the DOFs fixed by a dirichlet boundary condition,
+// the nodes are renumbered using "new_DOFindices".
+// each node Tag corresponds to a global index given by "nodeIndexMap".
+void assembleGlobalF(std::vector<double> & global_f_vector, std::map<int, elementData> & FEM_kinematicsMap, 
+                std::map<int, int> & nodeIndexMap, std::vector<int> & new_DOFindices, 
+                std::vector<std::size_t> & FEM_elementTags);
+
+// assembling the global tangent stiffness matrix based on the local elemental stiffness matrices contained
+// in the "FEM_kinematicsMap" associated to each "FEM_elementTags".
+// "new_DOFindices" maps the global initial node index (from "nodeIndexMap") to the index
+// obtained without considering the fixed DOFs.
+void assembleGlobalKg(Eigen::SparseMatrix<double> & global_tangent_matrix, std::map<int, elementData> & FEM_kinematicsMap, 
+                    std::map<int, int> & nodeIndexMap, std::vector<int> & new_DOFindices, 
+                    std::vector<std::size_t> & FEM_elementTags);
+
+// updating the "full_d_vector" based on the "global_p_vector", using the global and reduced index mapping:
+// "new_DOFindices".
+void updateNodalDispVector(std::vector<double> & full_d_vector, std::vector<double> & global_p_vector, 
+                        std::vector<int> & new_DOFindices);
+
+// retrieving the nodal displacements on the BEM_FEM_boundary in order to communicate them to the
+// BEM solver in the two-way coupled solver. Using the "groups" to find the physical group associated
+// to the boundary, all nodes of the boundary are associated through the "boundaryDisplacementMap" their
+// nodal displacement (u,v) retrieved from "full_d_vector" using the indexation of "nodeIndexMap".
+void retrieveBoundaryDisplacements(std::map<int,std::pair<double,double>> & boundaryDisplacementMap, 
+                            const std::vector<double> & full_d_vector, std::map<int, int> & nodeIndexMap, 
+                            std::map<std::string, std::pair<int, int>> & groups);
+
+// computing the reaction forces on all the physical groups "groups" for which a dirichlet boundary
+// condition has been prescribed (using the "BCkeys" to retrieve the groups from the .geo file),
+// based on the "final_nodal_forces" and the "nodeIndexMap" to retrieve the global index associated
+// to one given node tag.
+void computeReactionForces(std::map<std::string, std::pair<int, int>> & groups,
+                     std::map<int, int> & nodeIndexMap, std::vector<double> & final_nodal_forces,
+                     const std::vector<std::string> & BCkeys);
+
+// plotting the nodal displacement values associated to "FEM_nodeTags" in the "names[0]" model,
+// based on the "full_d_vector".
+void plotUxUy(const std::vector<std::size_t> & FEM_nodeTags, const std::vector<double> & full_d_vector, 
+            std::vector<std::string> & names);
+
+// computing the elemental-nodal strains and stresses in the FEM domain (dimension "dim" and entity tags "tags"),
+// based on Hooke's matrix in plane stress "H_matrix", Young's modulus "E", Poisson's ratio "nu", looping over the
+// "nbelems" elements contained in "FEM_kinematicsMap".
+// returning the "max_VM_stress" and the corresponding nodetag "max_VM_nodeTag".
+void computeStressStrainElNodal(int & dim, std::vector<int> & tags, const Eigen::Matrix<double, 3, 3> &H_matrix, 
+                           const double nu, const double E, std::map<int, elementData> & FEM_kinematicsMap,
+                           int & nbelems, double & max_VM_stress, int & max_VM_nodeTag);
+
+// computing the work done by external forces, as the nodal forces are energy consistent, it
+// is sufficient to compute the "externalWork" as the dot product of the nodal displacements
+// "full_d_vector" and the nodal forces "final_nodal_forces".
+void computeWorkDoneByExternalForces(double &externalWork, std::vector<double> & full_d_vector, 
+                                std::vector<double> & final_nodal_forces);
+
+// computing the total (internal) strain energy "strain_energy" in the whole FEM domain (dimension "dim" and 
+// entity tags "tags"), using the elementData stored in "FEM_kinematicsMap" gathering the informations for all
+// FEM elements. The Hooke's matrix in plane stress "H_matrix" is used to compute the stresses from the strains.
+// Computed as a sum of integrals over the finite elements, using Gauss integration "integration_rule".
+void computeStrainEnergy(double & strainEnergy, int & dim, std::vector<int> & tags,
+                           std::map<int, elementData> & FEM_kinematicsMap, const Eigen::Matrix<double, 3, 3> &H_matrix, 
+                           const std::string & integration_rule);
+
+//displays the time of the desired code section "function_name", if "display_time" is set as true.
+void displayTime(double &start, double &end, std::string &function_name, bool &display_time);
 
 /*-------------- FUNCTIONS SPECIFIC TO LINEAR FEM ------------------*/
-void solverFEM(std::map<int, double> &electrostaticPressure, int &nbViews);
 
-void assembleKtriplet(const int i, const int el, const std::vector<std::vector<std::size_t>> & nodeTags, const int numNodes, std::map<int, int> & nodeIndexMap, std::vector<Eigen::Triplet <double>> & k_tripletList, Eigen::Matrix<double, Eigen::Dynamic, Eigen::Dynamic> & K_matrix, std::vector<double> & f_vector);
-void assembleFthread(const int &i, const int &el, const std::vector<std::vector<std::size_t>> & nodeTags, const int &numNodes, std::map<int, int> & nodeIndexMap, std::vector<std::pair<int,double>> & thread_f_vector, std::vector<double> & f_vector);
-void assembleKlistVolumicF(const Eigen::Matrix<double, 3, 3> H_matrix, const double rho, const double bx, const double by, const std::string & integration_rule, int & dim, std::vector<int> & tags, std::map<int, int>  & nodeIndexMap, std::vector<std::vector<Eigen::Triplet <double>>> & my_k_list, std::vector<std::vector<std::pair<int,double>>> & thread_f_vector);
-void fillReducedKf(const std::vector<double> & full_f_vector, const Eigen::SparseMatrix<double> & full_K_matrix, const std::vector<int> & new_DOFindices, const std::vector<double> & dir_BC, Eigen::SparseMatrix<double> & reduced_K_matrix, std::vector<double> & reduced_f_vector, std::list<Eigen::Triplet <double>> & reduced_k_tripletList);
-void computeStressStrainLinearWay(int & dim, std::vector<int> & tags, const std::vector<double> &full_d_vector, std::vector<std::size_t> & elems2D, const Eigen::Matrix<double, 3, 3> &H_matrix, const double nu, const double E, std::map<int, int> & nodeIndexMap, std::vector<std::vector<std::vector<double>>> & dataEps, std::vector<std::vector<std::vector<double>>> & dataSig);
-void computeStrainEnergyLinearWay(double & strainEnergy, int & dim, std::vector<int> & tags, const std::vector<double> &full_d_vector, const Eigen::Matrix<double, 3, 3> &H_matrix, std::map<int, int> & nodeIndexMap, const std::string & integration_rule);
\ No newline at end of file
+// assembling the full stiffness matrix "full_K_matrix" using first a triplet vector, assembling the volumic part 
+// of the nodal forces "full_f_vector", in the FEM_domain (dimension "dim" and entity tag "tags") using the global
+// node indexation defined by "nodeIndexMap". "H_matrix" is the plane stress Hooke's matrix, "rho" the density of the
+// material, "bx" the volumic force along x, "by" the volumic force along y.
+// Numerical integration using gauss integration "integration_rule".
+void assembleKVolumicF(const Eigen::Matrix<double, 3, 3> H_matrix, const double rho, const double bx, const double by,
+              const std::string & integration_rule, int & dim, std::vector<int> & tags, std::map<int, int> & nodeIndexMap,
+              Eigen::SparseMatrix<double> & full_K_matrix, std::vector<double> & full_f_vector);
+
+// assembles the elemental "K_matrix" into a vector of triplets "k_tripletList" using 
+// the node tags provided by "nodeTags" vector (index of the element is "elIndex").
+// The element has "numNodes" nodes. "nodeIndexMap" provides the global nodal index corresponding to a given node tag.
+void assembleKtriplet(const int &elIndex, 
+                          const std::vector<std::size_t> & nodeTags, 
+                          const int numNodes, std::map<int, int> & nodeIndexMap,
+                          std::vector<Eigen::Triplet <double>> & k_tripletList,
+                          Eigen::Matrix<double, Eigen::Dynamic, Eigen::Dynamic> & K_matrix);
+
+// assembles the elemental "f_vector" in a vector of pairs called "thread_f_vector" particular to the parallel thread.
+// the element index in "nodeTags" is "elIndex". The element has "numNodes" nodes and their global nodal indices are given
+// by the "nodeIndexMap".
+void assembleFthread(const int &elIndex, 
+                          const std::vector<std::size_t> & nodeTags, 
+                          const int &numNodes, std::map<int, int> & nodeIndexMap,
+                          std::vector<std::pair<int,double>> & thread_f_vector, 
+                          std::vector<double> & f_vector);
+
+// the "full_f_vector" and "full_K_matrix" are reduced in "reduced_f_vector" and "reduced_K_matrix" respectively,
+// taking into account the dirichlet boundary conditions contained in "dir_BC".
+// "new_DOFindices" gathers the new indices of the DOFs when taking the boundary conditions into account.
+void fillReducedKf(const std::vector<double> & full_f_vector, const Eigen::SparseMatrix<double> & full_K_matrix,
+                      const std::vector<int> & new_DOFindices, const std::vector<double> & dir_BC,
+                      Eigen::SparseMatrix<double> & reduced_K_matrix, 
+                      std::vector<double> & reduced_f_vector);
+
+// computing the elemental-nodal strains and stresses in the FEM domain (dimension "dim" and entity tags "tags"),
+// based on Hooke's matrix in plane stress "H_matrix", Young's modulus "E", Poisson's ratio "nu", and the full nodal
+// displacement vector "full_d_vector" and the global nodal index map "nodeIndexMap".
+// "nbelems" is the number of 2D elements contained in the FEM domain.
+// returning the "max_VM_stress" and the corresponding nodetag "max_VM_nodeTag".
+void computeStressStrainLinearWay(int & dim, std::vector<int> & tags, const std::vector<double> &full_d_vector,
+                           const Eigen::Matrix<double, 3, 3> &H_matrix, const double nu, const double E,
+                           std::map<int, int> & nodeIndexMap, int & nbelems, double & max_VM_stress, int & max_VM_nodeTag);
+
+// computing the total (internal) strain energy "strain_energy" in the whole FEM domain (dimension "dim" and 
+// entity tags "tags"), using the nodal displacements in "full_d_vector". The nodes are indexed using the "nodeIndexMap".
+// The Hooke's matrix in plane stress "H_matrix" is used to compute the stresses from the strains.
+// Computed as a sum of integrals over the finite elements, using Gauss integration "integration_rule".
+void computeStrainEnergyLinearWay(double & strainEnergy, int & dim, std::vector<int> & tags,
+                           const std::vector<double> &full_d_vector, const Eigen::Matrix<double, 3, 3> &H_matrix, 
+                           std::map<int, int> & nodeIndexMap, const std::string & integration_rule);
\ No newline at end of file
diff --git a/srcs/FEM/large_rotation_validation.geo b/srcs/FEM/large_rotation_validation.geo
index f2bcf80..113db4e 100644
--- a/srcs/FEM/large_rotation_validation.geo
+++ b/srcs/FEM/large_rotation_validation.geo
@@ -2,7 +2,7 @@
 h = 1;
 H = 10;
 
-n = 48;
+n = 16;
 
 Point(1) = {0, 0, 0, 0.5};
 Point(2) = {0.9*H, 0, 0, 0.5};
diff --git a/srcs/FEM/mainFEM.cpp b/srcs/FEM/mainFEM.cpp
index 02b644c..3883e6b 100644
--- a/srcs/FEM/mainFEM.cpp
+++ b/srcs/FEM/mainFEM.cpp
@@ -4,6 +4,8 @@
 #include <iostream>
 #include <omp.h>
 
+// this program implements a FEM solver, either linear or non-linear (by taking large displacements into account)
+// to choose the linear solver, please add: SetNumber("Non_linear_solver",0); in the .geo file.
 int main(int argc, char **argv)
 {
     if (argc < 2)
@@ -28,18 +30,19 @@ int main(int argc, char **argv)
     // determine if non linear solver must be set
     bool nonLinearSolver = getNonLinearParameter(); 
 
+    // maps the elementTags to a corresponding electrostaticPressure (only useful for coupled solver)
     std::map<int, double> electrostaticPressure;
 
-    int nbViews = 0;
+    int nbViews = 0; // number of views passed to the BEM solver (only useful for coupled solver)
 
     if(nonLinearSolver)
     {
-        std::map<int,std::pair<double,double>> boundaryDisplacementMap;
+        std::map<int,std::pair<double,double>> boundaryDisplacementMap; //(only useful for coupled solver)
 
-        int viewTagU = gmsh::view::add("u"); // à modifier plus tard
-        int viewTagF = gmsh::view::add("f");
+        int viewTagU = gmsh::view::add("u"); //viewtag of the displacement view
+        int viewTagF = gmsh::view::add("f"); //viewtag of the nodal forces view
 
-        solverFEMnonLinear(electrostaticPressure, boundaryDisplacementMap, nbViews, true, 1, viewTagU, viewTagF, false); //iteration randomly set to 1
+        solverFEMnonLinear(electrostaticPressure, boundaryDisplacementMap, nbViews, true, 1, viewTagU, viewTagF, false);
     }
     else
     {
diff --git a/srcs/FEM/solverFEM.cpp b/srcs/FEM/solverFEM.cpp
index 05eb54d..b27c1c8 100644
--- a/srcs/FEM/solverFEM.cpp
+++ b/srcs/FEM/solverFEM.cpp
@@ -1,23 +1,35 @@
 #include "functionsFEM.hpp"
 
 #ifdef _MSC_VER
-#include <gmsh.h_cwrap>       // gmsh main header
+#include <gmsh.h_cwrap>
 #else
-#include <gmsh.h>       // gmsh main header
+#include <gmsh.h>
 #endif
 
-#include <iostream>     // for std::cout
+#include <iostream>
 #include <map>
-#include <sstream>  // for std::stringstream
-#include <Eigen/Dense> // Eigen library
-#include <Eigen/Sparse> // Eigen library for sparse matrices
-#include <Eigen/SparseCholesky> // solving sparse linear systems
+#include <sstream>
+#include <Eigen/Dense>
+#include <Eigen/Sparse>
+#include <Eigen/SparseCholesky>
 #include <Eigen/Core>
 #include <cmath>
-#include <algorithm> // to sort and merge the FEM node vectors
+#include <algorithm>
 #include <omp.h>
 
-void solverFEMnonLinear(std::map<int, double> &electrostaticPressure, std::map<int,std::pair<double,double>> & boundaryDisplacementMap, int &nbViews, bool postProcessing, const int iteration, const int viewTagU, const int viewTagF, bool untangleMesh)
+// solves the non-linear (large displacements, no large deformations) elastic problem.
+// if coupled to the BEM solver, "electrostaticPressure" is the pressure computed from the electric field
+// on the BEM_FEM boundary (mapping element tag -> pressure). "boundaryDisplacementMap" is filled for all
+// nodes on the BEM_FEM_boundary as (node tag -> (u,v) displacement)
+// "nbViews" is the current number of views already allocated in the gmsh window.
+// "postProcessing" boolean activates the post processing operations (must only be computed once at the end of the
+// coupled solver). "iteration" denotes the current iteration of the two-way coupled solver (starting at 1).
+// "viewTagU" and "viewTagF" correspond to the view tags of the nodal displacements and the nodal forces in the gmsh window.
+// when "untangleMesh" is activated, the nodal coordinates of the nodes in the FEM_domain are updated according to
+// their final nodal displacements.
+void solverFEMnonLinear(std::map<int, double> &electrostaticPressure, 
+                    std::map<int,std::pair<double,double>> & boundaryDisplacementMap, int &nbViews, bool postProcessing, 
+                    const int iteration, const int viewTagU, const int viewTagF, bool untangleMesh)
 {
     /*--------------INITIALIZATION OF ITERATIVE ALGORITHM----------------*/
     /* FIRST STEP: 
@@ -26,18 +38,15 @@ void solverFEMnonLinear(std::map<int, double> &electrostaticPressure, std::map<i
     - initialize the full d vector with Dirichlet boundary conditions and set pg = 0
     - important remark: in pg we focus only on the free DOFs (we do not focus on the DOFs fixed by dirichlet BCs)*/
 
-    bool display_time = false;
-    bool validation = false;
+    bool display_time = false; //displays time of every different step of the algorithm
+    bool validation = false; //retrieves displacement of point A for large_rotation_validation.geo
 
     double start = omp_get_wtime();
-    //int max_threads = omp_get_max_threads();
-
 
     /*--------Integration rule used in the code----------*/
-    // could be useful to pass it as an argument in the .geo file
     std::string integration_rule = "CompositeGauss4"; // tested with other methods, OK too.
 
-    /*--------get physical groups--------------*/
+    /*--------get all physical groups--------------*/
     std::map<std::string, std::pair<int, int>> groups;
     gmsh::vectorpair dimTags;
     gmsh::model::getPhysicalGroups(dimTags);
@@ -51,46 +60,16 @@ void solverFEMnonLinear(std::map<int, double> &electrostaticPressure, std::map<i
     }
 
     /*--------get nodes related to FEM-----------------*/
-    std::string groupname = "FEM_domain"; // we consider the domain as containing the FEM 2d model
+    std::string groupname = "FEM_domain"; // we consider the FEM_domain as containing the FEM 2d model
     int dim = groups[groupname].first;
     int tag = groups[groupname].second;
+    // Getting entities of the domain to fill FEM_nodeTags
+    std::vector<int> tags;
+    gmsh::model::getEntitiesForPhysicalGroup(dim, tag, tags);
 
-    //loop over the entities of the domain to retrieve all nodeTags by taking care not to take some nodes twice
-    //using sort and set_union methods + retrieving the node coordinates and storing them in a map
     std::vector<std::size_t> FEM_nodeTags;
-    std::vector<double> FEM_nodecoord;
-    std::vector<double> FEM_nodeparametricCoord;
     std::map<int,std::pair<double,double>> FEM_nodeCoordsMap; // nodeTag -> (x,y)
-    // Getting entities of the domain to fill FEM_nodeTags, en faire une fonction ?
-    std::vector<int> tags;
-    gmsh::model::getEntitiesForPhysicalGroup(dim, tag, tags);
-    std::vector<std::vector<std::size_t>> tmp_nodeTags(tags.size()); // will store the nodeTags associated to one given entity of the domain
-    gmsh::model::mesh::getNodes(tmp_nodeTags[0], FEM_nodecoord, FEM_nodeparametricCoord, dim, tags[0], true); 
-    for(std::size_t j = 0; j < tmp_nodeTags[0].size(); j++)
-    {
-        FEM_nodeCoordsMap[tmp_nodeTags[0][j]] = std::pair<double, double>(FEM_nodecoord[3*j], FEM_nodecoord[3*j+1]);
-    }
-    std::sort(tmp_nodeTags[0].begin(), tmp_nodeTags[0].end()); // need to sort the vectors in order to merge them afterwards
-    for (std::size_t i = 1; i < tags.size(); i++)
-    { // in the case the domain contains multiple entities
-        gmsh::model::mesh::getNodes(tmp_nodeTags[i], FEM_nodecoord, FEM_nodeparametricCoord, dim, tags[i], true);
-        for(std::size_t j = 0; j < tmp_nodeTags[i].size(); j++)
-        {
-            FEM_nodeCoordsMap[tmp_nodeTags[i][j]] = std::pair<double, double>(FEM_nodecoord[3*j], FEM_nodecoord[3*j+1]);
-        }
-        std::sort(tmp_nodeTags[i].begin(), tmp_nodeTags[i].end());
-        std::set_union(tmp_nodeTags[i-1].begin(), tmp_nodeTags[i-1].end(),
-                       tmp_nodeTags[i].begin(), tmp_nodeTags[i].end(), std::back_inserter(FEM_nodeTags)); // works only for 2 domains -> see little trick just below
-        
-    }
-    if(tags.size() == 1){ // if the domain contains one single entity
-        FEM_nodeTags = tmp_nodeTags[0];
-    }
-    else{ // little trick for more than 2 domains
-        std::sort(FEM_nodeTags.begin(), FEM_nodeTags.end());
-        auto last = std::unique(FEM_nodeTags.begin(), FEM_nodeTags.end());
-        FEM_nodeTags.erase(last, FEM_nodeTags.end());
-    }
+    RetrieveFEMNodeTagsAndCoords(FEM_nodeTags, FEM_nodeCoordsMap, dim, tags);
 
     /*-----get elements related to FEM--------------*/
     std::vector<std::size_t> FEM_elementTags;
@@ -113,15 +92,12 @@ void solverFEMnonLinear(std::map<int, double> &electrostaticPressure, std::map<i
     }
 
     /*-----------get physical properties of domain----------------------*/
-    std::vector<std::string> keys;
-    gmsh::onelab::getNames(keys, "(Volumic Forces|Materials).+");
     double E = 0; // Young modulus
     double nu = 0; // Poisson ratio
     double rho = 0; // volumic mass
     double bx = 0; // volumic force along x axis
     double by = 0; // volumic force along y axis
-
-    getPhysicalProperties(keys, E, nu, rho, bx, by);
+    getPhysicalProperties(E, nu, rho, bx, by);
 
     /*-----------------H matrix involved in stress computation------------------*/
     Eigen::Matrix<double, 3, 3> H_matrix = computeHmatrix(E, nu);
@@ -137,7 +113,7 @@ void solverFEMnonLinear(std::map<int, double> &electrostaticPressure, std::map<i
         full_f_vector[i] = 0;
     }
 
-    // Looping over entities and compute local K matrices and volumic f vector
+    // Looping over entities of FEM_dimain and computing local K matrices and volumic f vector
     fillElementalKandF(H_matrix, rho, bx, by, integration_rule, dim, tags, nodeIndexMap, full_f_vector, FEM_kinematicsMap);
 
     double start3 = omp_get_wtime();
@@ -147,12 +123,13 @@ void solverFEMnonLinear(std::map<int, double> &electrostaticPressure, std::map<i
 
     /*-----------------BOUNDARY CONDITIONS------------*/
     /*-----------get group_names related to BC's----------------------*/
-    gmsh::onelab::getNames(keys, "(Boundary Conditions).+");
+    std::vector<std::string> BCkeys;
+    gmsh::onelab::getNames(BCkeys, "(Boundary Conditions).+");
     /*-----------first focus on neumann BC's (surface traction) into full_f_vector------------*/
-    includeNeumannBC(keys, integration_rule, nodeIndexMap, groups, full_f_vector);
+    includeNeumannBC(BCkeys, integration_rule, nodeIndexMap, groups, full_f_vector);
 
     /* taking ELECTROSTATIC PRESSURE into account */
-    if(electrostaticPressure.size() != 0)
+    if(electrostaticPressure.size() != 0) // size == 0 if FEM solver used alone
         applyElecPressure(integration_rule, FEM_nodeCoordsMap, nodeIndexMap, groups, electrostaticPressure, full_f_vector);
     double start4 = omp_get_wtime();
     step_name = "Neumann BC and elec pressure";
@@ -162,26 +139,18 @@ void solverFEMnonLinear(std::map<int, double> &electrostaticPressure, std::map<i
     // new_DOFindices vector will contain the new indices of the K matrix after the rows/columns corresponding
     // to dirichlet boundary conditions have been removed
     // IN THIS IMPLEMENTATION WE DECIDE TO REMOVE EVERY ROW/COL ASSOCIATED TO DIR. BC's
-    // non-homo. dir BC will induce a correction in the RHS term f of the other rows, while homogeneous not
+    // non-homo. dir BC will induce a correction in the RHS term f of the other rows, while homogeneous not,
     // if the given index has been removed, it will be set to -1 in new_DOFindices
     std::vector<int> new_DOFindices(2*FEM_nodeTags.size());
 
     // dir_BC[index] will contain the dirichlet boundary condition prescribed to this node
     std::vector<double> dir_BC(2*FEM_nodeTags.size());
 
-    // Fill the two vectors defined just above
-    fillDOFindicesDirBC(keys, nodeIndexMap, groups, new_DOFindices, dir_BC);
-
+    // Fills the two vectors defined just above
     int number_removed_lines = 0;
-    for (std::size_t j = 0; j < new_DOFindices.size(); ++j){
-        if(new_DOFindices[j] < 0){ // line has to be removed
-            number_removed_lines++;
-        }
-        else{
-            new_DOFindices[j] -= number_removed_lines;
-        }
-    }
+    fillDOFindicesDirBC(BCkeys, nodeIndexMap, groups, new_DOFindices, dir_BC, number_removed_lines);
 
+    // initializing the full_d_vector with the dirichlet boundary conditions
     std::vector<double> full_d_vector(2*FEM_nodeTags.size());
     for (std::size_t j = 0; j < full_d_vector.size(); ++j){
         if(new_DOFindices[j] < 0){ // line was previously removed due to dirichlet boundary conditions
@@ -191,7 +160,7 @@ void solverFEMnonLinear(std::map<int, double> &electrostaticPressure, std::map<i
             full_d_vector[j] = 0;
         }
     }
-    // initializing the applied forces vector (will remain constant) and the global p (p_g) vector
+    // initializing the applied forces vector (will remain constant) and the global p vector (p_g)
     std::vector<double> f_app_vector(2*FEM_nodeTags.size() - number_removed_lines);
     std::vector<double> global_p_vector(2*FEM_nodeTags.size() - number_removed_lines);
     int nb_free_DOFs = 0;
@@ -205,22 +174,11 @@ void solverFEMnonLinear(std::map<int, double> &electrostaticPressure, std::map<i
         }
     }
 
-    // computing the norm of the applied forces vector
-    double f_app_norm = 0;
-    for(size_t i = 0; i < f_app_vector.size(); i++)
-    {
-        f_app_norm += f_app_vector[i]*f_app_vector[i];
-    }
-    f_app_norm = sqrt(f_app_norm);
-    //std::cout << "f app norm: " << f_app_norm << "\n";
-
-    std::vector<double> global_f_vector(nb_free_DOFs);
-
+    // useful during the iterative process
+    std::vector<double> global_f_vector(nb_free_DOFs); // f_g
     Eigen::Matrix<double, Eigen::Dynamic, 1> residual(nb_free_DOFs, 1);
 
-    double residualTolerance = 1e-12; // empirical
-
-    // implementation of second stopping criterion
+    // useful for the stopping criterion
     std::vector<double> previousTotalNodalForces(2*FEM_nodeTags.size());
     std::vector<double> currentTotalNodalForces(2*FEM_nodeTags.size());
     std::vector<double> relativeDifference(2*FEM_nodeTags.size());
@@ -232,6 +190,7 @@ void solverFEMnonLinear(std::map<int, double> &electrostaticPressure, std::map<i
         previousTotalNodalForces[i] = 0;
         currentTotalNodalForces[i] = 0;
     }
+    double residualTolerance = 1e-12; // empirical
 
     double start5 = omp_get_wtime();
     step_name = "further initialization of global variables for the iterative algorithm";
@@ -239,7 +198,7 @@ void solverFEMnonLinear(std::map<int, double> &electrostaticPressure, std::map<i
 
 
     /*---------ITERATIVE PART---------------*/
-    int max_number_of_steps = 200; // purely arbitrary
+    int max_number_of_steps = 200; // can be tuned
     int step = 0;
 
     double num_diverged_steps = 0; // stores the number of times the residual has increased between two steps, after 5 times,
@@ -289,11 +248,11 @@ void solverFEMnonLinear(std::map<int, double> &electrostaticPressure, std::map<i
             }
             if(currentMaxNodalForce > 0)
                 relativeForces = sqrt(relativeForces/(2*FEM_nodeTags.size())/currentMaxNodalForce); // norm made relative
-            else
-                relativeForces = 1e17;
+            else //to avoid division by zero
+                relativeForces = 1e17; 
             std::cout << "step: " << step << ", relativeForces: " << relativeForces << "\n";
 
-            if(previousRelativeForces < relativeForces)
+            if(previousRelativeForces < relativeForces) //the current step induces an increase of nodal difference
             {
                 num_diverged_steps++;
                 if(num_diverged_steps == 5)
@@ -304,7 +263,6 @@ void solverFEMnonLinear(std::map<int, double> &electrostaticPressure, std::map<i
             }
         }
 
-
         /*--------COMPUTING AND ASSEMBLING THE GLOBAL F VECTOR BASED ON PREVIOUS ITERATION-----------*/
         for(int i = 0; i < nb_free_DOFs; i++)
         {
@@ -312,8 +270,11 @@ void solverFEMnonLinear(std::map<int, double> &electrostaticPressure, std::map<i
         }
         assembleGlobalF(global_f_vector, FEM_kinematicsMap, nodeIndexMap, new_DOFindices, FEM_elementTags);
 
-        //double residualNorm = computeResidualNorm(global_f_vector, f_app_vector, residual);
-        computeResidualNorm(global_f_vector, f_app_vector, residual);
+        // computing the residual
+        for(size_t i = 0; i < global_f_vector.size(); i++)
+        {
+            residual(i,0) = global_f_vector[i] - f_app_vector[i];
+        }
 
         double end_assembleglobalf = omp_get_wtime();
         step_name = "assemble global f and residual norm";
@@ -337,7 +298,8 @@ void solverFEMnonLinear(std::map<int, double> &electrostaticPressure, std::map<i
         /*-----------STEP 5: SOLVING THE SYSTEM----------*/
         Eigen::Matrix<double, Eigen::Dynamic, 1> global_delta_p(nb_free_DOFs, 1);
         
-        // solve Kg*Deltapg=-res
+        // solve Kg*Deltapg=-res, using a direct sparse Cholesky factorization,
+        // efficient for sparse positive semi-definite matrix
         Eigen::SimplicialLDLT<Eigen::SparseMatrix<double>> solver;
         solver.compute(global_tangent_matrix);
 
@@ -374,34 +336,13 @@ void solverFEMnonLinear(std::map<int, double> &electrostaticPressure, std::map<i
     std::vector<double> final_nodal_forces(2*FEM_nodeTags.size());
     retrieveTotalNodalForces(final_nodal_forces, FEM_kinematicsMap, nodeIndexMap, FEM_elementTags);
 
-    /*--------------RETRIEVING NODAL DISPLACEMENTS ON BEM-FEM BOUNDARY------------*/
-    //those displacements will be sent to the BEM code in the non-linear iterative solver
-    // mapping nodeTag (of node on the boundary) into (u,v) displacements of the node
-    retrieveBoundaryDisplacements(boundaryDisplacementMap, full_d_vector, nodeIndexMap, groups);
-
     std::vector<std::string> names;
     gmsh::model::list(names);
 
+    /*-------------POST PROCESSING---------------------*/
     if(postProcessing)
     {
-        /*------------- ROTATION VALIDATION: APPLY 90 degree rotation-------------*/
-        //elementData* element = &FEM_kinematicsMap[6];
-        //std::cout << "hello: " <<  element->theta << "\n";
-        //std::cout << "pl: " <<  element->pl << "\n";
-        /*for(size_t i = 0; i < FEM_nodeTags.size(); i++)
-        {
-            std::vector<double> coord, parametricCoord;
-            int dim, tag;
-            double angle = M_PI /4;
-            gmsh::model::mesh::getNode(FEM_nodeTags[i], coord, parametricCoord, dim, tag);
-            std::vector<double> total_disp = {cos(angle)*(coord[0] + full_d_vector[2*i])-sin(angle)*(coord[1] + full_d_vector[2*i + 1]), cos(angle)*(coord[1] + full_d_vector[2*i + 1])+sin(angle)*(coord[0] + full_d_vector[2*i]), coord[2]};
-            full_d_vector[2*i] = total_disp[0] - coord[0];
-            full_d_vector[2*i+1] = total_disp[1] - coord[1];
-            //std::cout << "nodetag: " << FEM_nodeTags[i] << ", new u: " << full_d_vector[2*i] << ", new v: " << full_d_vector[2*i+1] << "\n";
-        }
-        updateKinematics(FEM_kinematicsMap, full_d_vector, nodeIndexMap, FEM_elementTags);*/
-
-        // retrieving maximal nodal displacement
+        // retrieving and displaying maximal nodal displacement
         double max_disp = 0;
         double max_u = 0;
         double max_v = 0;
@@ -423,63 +364,19 @@ void solverFEMnonLinear(std::map<int, double> &electrostaticPressure, std::map<i
         std::cout << "maximal nodal displacement (u,v) = (" << max_u << "," << max_v << ") [m], at node " << max_disp_nodeTag << "\n";
 
         /*--------------REACTION FORCES COMPUTATION------------------*/
-        computeReactionForces(groups, nodeIndexMap, final_nodal_forces, keys);
+        computeReactionForces(groups, nodeIndexMap, final_nodal_forces, BCkeys);
 
         /*--------------PLOTTING NODAL VALUES------------------*/
         plotUxUy(FEM_nodeTags, full_d_vector, names);
 
         /*-------------POST PROCESSING---------------*/
-        /*-------------Computing and saving strains/stresses-----------------*/
-
-
-        /*-------------------ELEMENTAL-NODAL VALUES-----------------------*/
-        int viewTagEpsX = gmsh::view::add("nodal_eps_x");
-        int viewTagEpsY = gmsh::view::add("nodal_eps_y");
-        int viewTagEpsXY = gmsh::view::add("nodal_2eps_xy");
-        int viewTagEpsZ = gmsh::view::add("nodal_eps_z");
-        int viewTagSigX = gmsh::view::add("nodal_sig_x");
-        int viewTagSigY = gmsh::view::add("nodal_sig_y");
-        int viewTagSigXY = gmsh::view::add("nodal_sig_xy");
-        int viewTagSigVM = gmsh::view::add("nodal_sig_VM");
-
-        std::vector<std::vector<std::vector<double>>> dataEps(4);
-        dataEps[0].resize(nbelems); // eps X
-        dataEps[1].resize(nbelems); // eps Y
-        dataEps[2].resize(nbelems); // eps XY
-        dataEps[3].resize(nbelems); // eps Z
-
-        std::vector<std::vector<std::vector<double>>> dataSig(4);
-        dataSig[0].resize(nbelems); // Sig X
-        dataSig[1].resize(nbelems); // Sig Y
-        dataSig[2].resize(nbelems); // Sig XY
-        dataSig[3].resize(nbelems); // Sig VM
-        std::vector<std::size_t> elems2D(nbelems);
-        
-        // computing the elemental nodal data for each physical group of the domain
+        /*-------------Computing and saving elemental-nodal strains/stresses-----------------*/
         double max_VM_stress = 0;
         int max_VM_nodeTag;
-        computeStressStrainElNodal(dim, tags, elems2D, H_matrix, nu, E, FEM_kinematicsMap, dataEps, dataSig, max_VM_stress, max_VM_nodeTag);
+        computeStressStrainElNodal(dim, tags, H_matrix, nu, E, FEM_kinematicsMap, nbelems, max_VM_stress, max_VM_nodeTag);
         std::cout << "-----------------------------------------\n";
         std::cout << "maximal nodal von Mises stress: " << max_VM_stress << " [Pa], at node " << max_VM_nodeTag << "\n";
 
-
-        gmsh::view::addModelData(viewTagEpsX, 0, names[0], "ElementNodeData",
-                                    elems2D, dataEps[0], 1);  // the last ,1 is important!
-        gmsh::view::addModelData(viewTagEpsY, 0, names[0], "ElementNodeData",
-                                    elems2D, dataEps[1], 1);  // the last ,1 is important!
-        gmsh::view::addModelData(viewTagEpsXY, 0, names[0], "ElementNodeData",
-                                    elems2D, dataEps[2], 1);  // the last ,1 is important!
-        gmsh::view::addModelData(viewTagEpsZ, 0, names[0], "ElementNodeData",
-                                    elems2D, dataEps[3], 1);  // the last ,1 is important!
-        gmsh::view::addModelData(viewTagSigX, 0, names[0], "ElementNodeData",
-                                    elems2D, dataSig[0], 1);  // the last ,1 is important!
-        gmsh::view::addModelData(viewTagSigY, 0, names[0], "ElementNodeData",
-                                    elems2D, dataSig[1], 1);  // the last ,1 is important!
-        gmsh::view::addModelData(viewTagSigXY, 0, names[0], "ElementNodeData",
-                                    elems2D, dataSig[2], 1);  // the last ,1 is important!
-        gmsh::view::addModelData(viewTagSigVM, 0, names[0], "ElementNodeData",
-                                    elems2D, dataSig[3], 1);  // the last ,1 is important!
-
         // retrieving work done by external forces
         double externalWork = 0;
         computeWorkDoneByExternalForces(externalWork, full_d_vector, final_nodal_forces);
@@ -497,7 +394,7 @@ void solverFEMnonLinear(std::map<int, double> &electrostaticPressure, std::map<i
         std::cout << "-----------------------------------------\n";
         std::cout << "Total potential energy: " << strainEnergy - externalWork << " [J/m].\n";
     }
-    // representing vector displacement field and nodal forces
+    // representing vector displacement field and nodal forces as vectorial quantities
     std::vector<std::vector<double>> dataU(FEM_nodeTags.size());
     std::vector<std::vector<double>> dataF(FEM_nodeTags.size());
     for (std::size_t i = 0; i < FEM_nodeTags.size(); ++i)
@@ -520,20 +417,29 @@ void solverFEMnonLinear(std::map<int, double> &electrostaticPressure, std::map<i
 
     if(postProcessing)
     {
-        // empeche que tout se plot l'un sur l'autre
-        nbViews = 12 + nbViews; //hardcoded
+        nbViews = 12 + nbViews; // FEM code adds 12 views
+        // avoiding that all the views are superposed
         for( int i = 0; i < nbViews; ++i){
             std::string my_string = "View[" + std::to_string(i) + "].Visible";
             gmsh::option::setNumber(my_string, 0);
         }
-        gmsh::option::setNumber("View[0].VectorType", 5); // plot automatiquement sous forme de déplacement
-        gmsh::option::setNumber("View[0].RangeType", 3); // abscisses par step
+        gmsh::option::setNumber("View[0].VectorType", 5); // plot the displacement in deformed configuration
+        gmsh::option::setNumber("View[0].RangeType", 3);
     }
 
     double end_postPro = omp_get_wtime();
     step_name = "post processing";
     displayTime(start_postPro, end_postPro, step_name, display_time);
 
+    /*--------------RETRIEVING NODAL DISPLACEMENTS ON BEM-FEM BOUNDARY------------*/
+    // those displacements will be sent to the BEM code in the non-linear iterative solver
+    // mapping nodeTag (of node on the boundary) into (u,v) displacements of the node
+    retrieveBoundaryDisplacements(boundaryDisplacementMap, full_d_vector, nodeIndexMap, groups);
+
+    // if mesh untangler is activated, all nodal coordinates are updated according to their final
+    // nodal displacements. It is useful when coupled to the BEM solver in order to untangle
+    // the BEM domain and compute the potential field in the whole BEM domain adapted to the
+    // displacement of the FEM domain.
     if(untangleMesh)
     {
         for(size_t i = 0; i < FEM_nodeTags.size(); i++)
@@ -546,10 +452,11 @@ void solverFEMnonLinear(std::map<int, double> &electrostaticPressure, std::map<i
         }
     }
 
+    // it is only useful when using "large_rotation_validation.geo" file.
+    // printing the displacement of "point A" (see report for graph).
     if(validation)
     {
-        // point A
-        std::string groupname = "point_A"; // we consider the domain as containing the FEM 2d model
+        std::string groupname = "point_A";
         int dim = groups[groupname].first;
         int tag = groups[groupname].second;
         std::vector<int> tags;
@@ -563,17 +470,13 @@ void solverFEMnonLinear(std::map<int, double> &electrostaticPressure, std::map<i
     }
 }
 
-
+// solves the linear elastic problem in the FEM domain.
+// "electrostaticPressure" contains the pressure resulting from the electric field computation on the 
+// BEM_FEM_boundary. "nbViews" is the current number of views already allocated in the gmsh window.
 void solverFEM(std::map<int, double> &electrostaticPressure, int &nbViews)
 {
-    /*--------------INIT----------------*/
-
-    int max_threads = omp_get_max_threads();
-
-
     /*--------Integration rule used in the code----------*/
-    // could be useful to pass it as an argument in the .geo file
-    std::string integration_rule = "CompositeGauss4"; // tested with other methods, OK too.
+    std::string integration_rule = "CompositeGauss4";
 
     /*--------get physical groups--------------*/
     std::map<std::string, std::pair<int, int>> groups;
@@ -593,63 +496,37 @@ void solverFEM(std::map<int, double> &electrostaticPressure, int &nbViews)
     int dim = groups[groupname].first;
     int tag = groups[groupname].second;
 
-    //loop over the entities of the domain to retrieve all nodeTags by taking care not to take some nodes twice
-    //using sort and set_union methods
-    std::vector<std::size_t> FEM_nodeTags;
-    std::vector<double> FEM_nodecoord;
-    std::vector<double> FEM_nodeparametricCoord;
-    // Getting entities of the domain to fill FEM_nodeTags, en faire une fonction ?
+    // Getting entities of the domain to fill FEM_nodeTags
     std::vector<int> tags;
     gmsh::model::getEntitiesForPhysicalGroup(dim, tag, tags);
-    std::vector<std::vector<std::size_t>> tmp_nodeTags(tags.size()); // will store the nodeTags associated to one given entity of the domain
-    gmsh::model::mesh::getNodes(tmp_nodeTags[0], FEM_nodecoord, FEM_nodeparametricCoord, dim, tags[0], true); 
-    std::sort(tmp_nodeTags[0].begin(), tmp_nodeTags[0].end()); // need to sort the vectors in order to merge them afterwards
-    for (std::size_t i = 1; i < tags.size(); i++)
-    { // in the case the domain contains multiple entities
-        gmsh::model::mesh::getNodes(tmp_nodeTags[i], FEM_nodecoord, FEM_nodeparametricCoord, dim, tags[i], true);
-        std::sort(tmp_nodeTags[i].begin(), tmp_nodeTags[i].end());
-        std::set_union(tmp_nodeTags[i-1].begin(), tmp_nodeTags[i-1].end(),
-                       tmp_nodeTags[i].begin(), tmp_nodeTags[i].end(), std::back_inserter(FEM_nodeTags));
-        
-    }
-    if(tags.size() == 1){ // if the domain contains one single entity
-        FEM_nodeTags = tmp_nodeTags[0];
-    }
-    else{ // little trick for more than 2 domains
-        std::sort(FEM_nodeTags.begin(), FEM_nodeTags.end());
-        auto last = std::unique(FEM_nodeTags.begin(), FEM_nodeTags.end());
-        FEM_nodeTags.erase(last, FEM_nodeTags.end());
-    }
 
-    
+    std::vector<std::size_t> FEM_nodeTags;
+    std::map<int,std::pair<double,double>> FEM_nodeCoordsMap; // nodeTag -> (x,y) (useless in this linear case)
+    RetrieveFEMNodeTagsAndCoords(FEM_nodeTags, FEM_nodeCoordsMap, dim, tags);
 
     /*-------map between nodeTag and index of first nodal displacement associated to the node (second one is the same +1)------*/
     std::map<int, int> nodeIndexMap;
     for (std::size_t i = 0; i < FEM_nodeTags.size(); ++i)
     {
-        int nodeTag = FEM_nodeTags[i];// use i à la place de nodeTag pour traiter cas ou le nodeTag n'est pas une suite continue
+        int nodeTag = FEM_nodeTags[i];
         int node_first_index = 2*i; 
         nodeIndexMap[nodeTag] = node_first_index;
     }
 
     /*-----------get physical properties of domain----------------------*/
-    std::vector<std::string> keys;
-    gmsh::onelab::getNames(keys, "(Volumic Forces|Materials).+");
     double E = 0; // Young modulus
     double nu = 0; // Poisson ratio
     double rho = 0; // volumic mass
     double bx = 0; // volumic force along x axis
     double by = 0; // volumic force along y axis
-
-    getPhysicalProperties(keys, E, nu, rho, bx, by);
+    getPhysicalProperties(E, nu, rho, bx, by);
 
     /*-----------------H matrix involved in stress computation------------------*/
     Eigen::Matrix<double, 3, 3> H_matrix = computeHmatrix(E, nu);
-    //double startbis = omp_get_wtime();
-    //std::cout << "preliminary: " << startbis-start << "\n";
 
     /*------------computing K matrix and f vector------------------------*/
     // we first consider the K matrix and the volumic part of the f vector
+    // K matrix declared as sparse matrix for efficiency.
     Eigen::SparseMatrix<double> full_K_matrix(2*FEM_nodeTags.size(),2*FEM_nodeTags.size());
     std::vector<double> full_f_vector(2*FEM_nodeTags.size());
     for (size_t i = 0; i < full_f_vector.size(); i++)
@@ -657,72 +534,21 @@ void solverFEM(std::map<int, double> &electrostaticPressure, int &nbViews)
         full_f_vector[i] = 0;
     }
 
-    std::vector<std::vector<Eigen::Triplet <double>>> my_k_list(max_threads);
-    std::vector<std::vector<std::pair<int,double>>> thread_f_vector(max_threads);
-
-    // Looping over entities and compute full K (under tripletlist form) and F
-    assembleKlistVolumicF(H_matrix, rho, bx, by, integration_rule, dim, tags, nodeIndexMap, my_k_list, thread_f_vector);
-
-
-    std::vector<Eigen::Triplet <double>> k_tripletList = my_k_list[0];
-
-    if(max_threads > 1)
-    {
-        for (int i = 1; i < max_threads; i++)
-        {
-            k_tripletList.insert(k_tripletList.end(), my_k_list[i].begin(), my_k_list[i].end());
-        }
-    }
-    full_K_matrix.setFromTriplets(k_tripletList.begin(), k_tripletList.end());
-    for(int i = 0; i < max_threads; i++){
-        std::vector<std::pair<int,double>> &tmp_vector = thread_f_vector[i];
-        for(auto &p : tmp_vector) {
-            full_f_vector[p.first] += p.second;
-        }
-    }
-
-    //double start2 = omp_get_wtime();
-    //std::cout << "time for K assembly: " << start2-startbis << "\n";
-
+    // Looping over entities and compute full K and volumic F
+    assembleKVolumicF(H_matrix, rho, bx, by, integration_rule, dim, tags, nodeIndexMap, full_K_matrix, full_f_vector);
 
     /*-----------------BOUNDARY CONDITIONS------------*/
     /*-----------get group_names related to BC's----------------------*/
-    gmsh::onelab::getNames(keys, "(Boundary Conditions).+");
+    std::vector<std::string> BCkeys;
+    gmsh::onelab::getNames(BCkeys, "(Boundary Conditions).+");
     /*-----------first focus on neumann BC's (surface traction) into full_f_vector------------*/
-    includeNeumannBC(keys, integration_rule, nodeIndexMap, groups, full_f_vector);
-
-    //double start3 = omp_get_wtime();
-    //std::cout << "time for neumann: " << start3 - start2 << "\n";
+    includeNeumannBC(BCkeys, integration_rule, nodeIndexMap, groups, full_f_vector);
 
     /* ELECTROSTATIC PRESSURE */
-    if(electrostaticPressure.size() != 0)
+    if(electrostaticPressure.size() != 0) // size is only greater than zero if there was already a BEM solver
     {
-        std::string groupnameBoundary = "BEM_FEM_boundary";
-        int dimBoundary = groups[groupnameBoundary].first;
-        int tagBoundary = groups[groupnameBoundary].second;
-        std::vector<int> tagsBoundary;
-        gmsh::model::getEntitiesForPhysicalGroup(dimBoundary, tagBoundary, tagsBoundary);
-        std::vector<int> elementTypes;
-        std::vector<std::vector<std::size_t>> elementTags;
-        std::vector<std::vector<std::size_t>> elnodeTags;
-
-        std::map<int,std::pair<double,double>> nodeCoordsMap;
-        //filling the map with : nodeTag -> (x,y)
-        for(std::size_t i=0; i< tags.size(); ++i)
-        {
-            std::vector<std::size_t> entity_nodeTags;
-            std::vector<double> entity_nodecoord;
-            std::vector<double> entity_nodeparametricCoord;
-            gmsh::model::mesh::getNodes(entity_nodeTags, entity_nodecoord, entity_nodeparametricCoord, dim, tags[i], true); 
-            for ( std::size_t j=0; j < entity_nodeTags.size(); j++){
-                nodeCoordsMap[entity_nodeTags[j]] = std::pair<double, double>(entity_nodecoord[3*j], entity_nodecoord[3*j+1]);
-            }
-        }
-        applyElecPressure(integration_rule, nodeCoordsMap, nodeIndexMap, groups, electrostaticPressure, full_f_vector);
+        applyElecPressure(integration_rule, FEM_nodeCoordsMap, nodeIndexMap, groups, electrostaticPressure, full_f_vector);
     }
-        
-    //double start4 = omp_get_wtime();
-    //std::cout << "time for elec pressure: " << start4 - start3 << "\n";
 
     /*------------FOCUS ON DIRICHLET BC'S---------------*/
     // new_DOFindices vector will contain the new indices of the K matrix after the rows/columns corresponding
@@ -736,29 +562,15 @@ void solverFEM(std::map<int, double> &electrostaticPressure, int &nbViews)
     std::vector<double> dir_BC(2*FEM_nodeTags.size());
 
     // Fill the two vectors defined just above
-    fillDOFindicesDirBC(keys, nodeIndexMap, groups, new_DOFindices, dir_BC);
-
     int number_removed_lines = 0;
-    for (std::size_t j = 0; j < new_DOFindices.size(); ++j){
-        if(new_DOFindices[j] < 0){ // line has to be removed
-            number_removed_lines++;
-        }
-        else{
-            new_DOFindices[j] -= number_removed_lines;
-        }
-    }
-
-    //double start3 = omp_get_wtime();
-    //std::cout << "time for boundary conditions: " << start3 - start2 << "\n";
+    fillDOFindicesDirBC(BCkeys, nodeIndexMap, groups, new_DOFindices, dir_BC, number_removed_lines);
 
     /*------------FILLING THE REDUCED K MATRIX AND F VECTOR--------------*/
-    //the correction term on the f vector comes from the "FEM" theoretical course (JP Ponthot), Chap2 Slide 38/50
+    //the correction term on the f vector comes from the "Finite Element Method" theoretical course (JP Ponthot, ULiege), Chap2 Slide 38/50
     Eigen::SparseMatrix<double> reduced_K_matrix(2*FEM_nodeTags.size() - number_removed_lines,2*FEM_nodeTags.size() - number_removed_lines);
     std::vector<double> reduced_f_vector(2*FEM_nodeTags.size() - number_removed_lines);
 
-    std::list<Eigen::Triplet <double>> reduced_k_tripletList;
-
-    fillReducedKf(full_f_vector, full_K_matrix, new_DOFindices, dir_BC, reduced_K_matrix, reduced_f_vector, reduced_k_tripletList);
+    fillReducedKf(full_f_vector, full_K_matrix, new_DOFindices, dir_BC, reduced_K_matrix, reduced_f_vector);
 
     /*--------------SOLVING THE REDUCED SYSTEM--------------------*/
     Eigen::VectorXd final_reduced_d(2*FEM_nodeTags.size() - number_removed_lines);
@@ -769,15 +581,11 @@ void solverFEM(std::map<int, double> &electrostaticPressure, int &nbViews)
     }
 
     // solve Kd=f
-    Eigen::SimplicialLDLT<Eigen::SparseMatrix<double> > solver;
+    Eigen::SimplicialLDLT<Eigen::SparseMatrix<double>> solver;
     solver.compute(reduced_K_matrix);
 
     final_reduced_d = solver.solve(final_reduced_f);
 
-    //double start4 = omp_get_wtime();
-    //std::cout << "time to solve system: " << start4 -start3 << "\n";
-
-
     /*-----------RECONSTRUCTING FULL NODAL VALUES--------------*/
     std::vector<double> full_d_vector(2*FEM_nodeTags.size());
     for (std::size_t j = 0; j < full_d_vector.size(); ++j){
@@ -803,12 +611,6 @@ void solverFEM(std::map<int, double> &electrostaticPressure, int &nbViews)
         nodal_forces[i] = nodalForces(i);
     }
 
-    /*--------------REACTION FORCES COMPUTATION------------------*/
-    computeReactionForces(groups, nodeIndexMap, nodal_forces, keys);
-
-    //double start5 = omp_get_wtime();
-    //std::cout << "time for rest: " << start5 - start4 << "\n";
-
     /*--------------PLOTTING AND SAVING NODAL VALUES------------------*/
     std::vector<std::string> names;
     gmsh::model::list(names);
@@ -836,49 +638,38 @@ void solverFEM(std::map<int, double> &electrostaticPressure, int &nbViews)
         }
     }
 
+    std::cout << "The FEM domain contains " << nbelems << " element(s) and " << FEM_nodeTags.size() << " nodes\n";
+
+    // retrieving and displaying maximal nodal displacement
+        double max_disp = 0;
+        double max_u = 0;
+        double max_v = 0;
+        int max_disp_nodeTag = 0;
+
+        for(size_t i = 0; i < FEM_nodeTags.size(); i++)
+        {
+            double tmp_index = nodeIndexMap[FEM_nodeTags[i]];
+            double tmp_disp = full_d_vector[tmp_index]*full_d_vector[tmp_index] + full_d_vector[tmp_index + 1]*full_d_vector[tmp_index + 1];
+            if (tmp_disp > max_disp)
+            {
+                max_disp = tmp_disp;
+                max_disp_nodeTag = FEM_nodeTags[i];
+                max_u = full_d_vector[tmp_index];
+                max_v = full_d_vector[tmp_index + 1];
+            }
+        }
+        std::cout << "-----------------------------------------\n";
+        std::cout << "maximal nodal displacement (u,v) = (" << max_u << "," << max_v << ") [m], at node " << max_disp_nodeTag << "\n";
+
+    /*--------------REACTION FORCES COMPUTATION------------------*/
+    computeReactionForces(groups, nodeIndexMap, nodal_forces, BCkeys);
 
     /*-------------------ELEMENTAL-NODAL VALUES-----------------------*/
-    int viewTagEpsX = gmsh::view::add("nodal_eps_x");
-    int viewTagEpsY = gmsh::view::add("nodal_eps_y");
-    int viewTagEpsXY = gmsh::view::add("nodal_2eps_xy");
-    int viewTagEpsZ = gmsh::view::add("nodal_eps_z");
-    int viewTagSigX = gmsh::view::add("nodal_sig_x");
-    int viewTagSigY = gmsh::view::add("nodal_sig_y");
-    int viewTagSigXY = gmsh::view::add("nodal_sig_xy");
-    int viewTagSigVM = gmsh::view::add("nodal_sig_VM");
-
-    std::vector<std::vector<std::vector<double>>> dataEps(4);
-    dataEps[0].resize(nbelems); // eps X
-    dataEps[1].resize(nbelems); // eps Y
-    dataEps[2].resize(nbelems); // eps XY
-    dataEps[3].resize(nbelems); // eps Z
-
-    std::vector<std::vector<std::vector<double>>> dataSig(4);
-    dataSig[0].resize(nbelems); // Sig X
-    dataSig[1].resize(nbelems); // Sig Y
-    dataSig[2].resize(nbelems); // Sig XY
-    dataSig[3].resize(nbelems); // Sig VM
-    std::vector<std::size_t> elems2D(nbelems);
-    
-    // computing the elemental nodal data for each physical group of the domain
-    computeStressStrainLinearWay(dim, tags, full_d_vector, elems2D, H_matrix, nu, E, nodeIndexMap, dataEps, dataSig);
-
-    gmsh::view::addModelData(viewTagEpsX, 0, names[0], "ElementNodeData",
-                                elems2D, dataEps[0], 1);  // the last ,1 is important!
-    gmsh::view::addModelData(viewTagEpsY, 0, names[0], "ElementNodeData",
-                                elems2D, dataEps[1], 1);  // the last ,1 is important!
-    gmsh::view::addModelData(viewTagEpsXY, 0, names[0], "ElementNodeData",
-                                elems2D, dataEps[2], 1);  // the last ,1 is important!
-    gmsh::view::addModelData(viewTagEpsZ, 0, names[0], "ElementNodeData",
-                                elems2D, dataEps[3], 1);  // the last ,1 is important!
-    gmsh::view::addModelData(viewTagSigX, 0, names[0], "ElementNodeData",
-                                elems2D, dataSig[0], 1);  // the last ,1 is important!
-    gmsh::view::addModelData(viewTagSigY, 0, names[0], "ElementNodeData",
-                                elems2D, dataSig[1], 1);  // the last ,1 is important!
-    gmsh::view::addModelData(viewTagSigXY, 0, names[0], "ElementNodeData",
-                                elems2D, dataSig[2], 1);  // the last ,1 is important!
-    gmsh::view::addModelData(viewTagSigVM, 0, names[0], "ElementNodeData",
-                                elems2D, dataSig[3], 1);  // the last ,1 is important!
+    double max_VM_stress = 0;
+    int max_VM_nodeTag;
+    computeStressStrainLinearWay(dim, tags, full_d_vector, H_matrix, nu, E, nodeIndexMap, nbelems, max_VM_stress, max_VM_nodeTag);
+    std::cout << "-----------------------------------------\n";
+    std::cout << "maximal nodal von Mises stress: " << max_VM_stress << " [Pa], at node " << max_VM_nodeTag << "\n";
 
     // representing vector displacement field and nodal forces
     int viewTagU = gmsh::view::add("u");
@@ -904,12 +695,9 @@ void solverFEM(std::map<int, double> &electrostaticPressure, int &nbViews)
     gmsh::view::addModelData(viewTagF, 0, names[0], "NodeData",
                              FEM_nodeTags, dataF); //independent of time here
 
-
-
-    // empeche que tout se plot l'un sur l'autre
-    int nb_views = 12 + nbViews; //hardcoded
+    int nb_views = 12 + nbViews; //FEM code adds 12 views
     for( int i = 0; i < nb_views; ++i){
-        std::string my_string = "View[" + std::to_string(i) + "].Visible";
+        std::string my_string = "View[" + std::to_string(i) + "].Visible"; // avoids that all views are represented on top of each other
         gmsh::option::setNumber(my_string, 0);
     }
 
-- 
GitLab