diff --git a/acoustics/CMakeLists.txt b/acoustics/CMakeLists.txt index 8ce38afa..517f3af9 100644 --- a/acoustics/CMakeLists.txt +++ b/acoustics/CMakeLists.txt @@ -1,4 +1,5 @@ add_executable(Acoustics + FemModule.h FemModule.cc main.cc Fem_axl.h diff --git a/acoustics/Fem.axl b/acoustics/Fem.axl index f65a9f18..34a589b0 100644 --- a/acoustics/Fem.axl +++ b/acoustics/Fem.axl @@ -38,6 +38,100 @@ Type of mesh provided to the solver + + + + + Dirichlet boundary condition + + + + FaceGroup on which to apply these boundary condition + + + + + Value of the boundary condition + + + + + Method via which Dirichlet boundary condition is imposed + + + + + Penalty value for enforcing Dirichlet condition + + + + + + + + Dirichlet boundary condition + + + + Function for Dirichlet boundary condition + + + + + Method via which Dirichlet boundary condition is imposed + + + + + Penalty value for enforcing Dirichlet condition + + + + + Function for manufactured source term condition + + + + + + + + Dirichlet point condition + + + + NodeGroup on which to apply these point Dirichlet condition + + + + + Value of the point Dirichlet condition + + + + + Method via which Dirichlet boundary condition is imposed + + + + + Penalty value for enforcing Dirichlet condition + + + + Neumann boundary condition diff --git a/acoustics/FemModule.cc b/acoustics/FemModule.cc index 2dde2177..7521f911 100644 --- a/acoustics/FemModule.cc +++ b/acoustics/FemModule.cc @@ -11,77 +11,24 @@ /*---------------------------------------------------------------------------*/ /*---------------------------------------------------------------------------*/ -#include -#include -#include - -#include -#include -#include -#include -#include - -#include "IDoFLinearSystemFactory.h" -#include "Fem_axl.h" -#include "FemUtils.h" -#include "DoFLinearSystem.h" -#include "FemDoFsOnNodes.h" - -/*---------------------------------------------------------------------------*/ -/*---------------------------------------------------------------------------*/ - -using namespace Arcane; -using namespace Arcane::FemUtils; +#include "FemModule.h" /*---------------------------------------------------------------------------*/ /** - * @brief A module for finite element method. + * @brief Initializes the FemModule at the start of the simulation. * - * This class handles the initialization and computation for finite element - * method (FEM) simulations, providing methods to set up and solve linear - * systems, assemble FEM operators, and perform result checks. + * This method initializes degrees of freedom (DoFs) on nodes. */ /*---------------------------------------------------------------------------*/ -class FemModule -: public ArcaneFemObject +void FemModule:: +startInit() { - public: - - explicit FemModule(const ModuleBuildInfo& mbi) - : ArcaneFemObject(mbi) - , m_dofs_on_nodes(mbi.subDomain()->traceMng()) - { - ICaseMng* cm = mbi.subDomain()->caseMng(); - cm->setTreatWarningAsError(true); - cm->setAllowUnkownRootElelement(false); - } - - void compute() override; //! Method called at each iteration - void startInit() override; //! Method called at the beginning of the simulation - VersionInfo versionInfo() const override { return VersionInfo(1, 0, 0); } - - private: - - Real m_kc2; - - DoFLinearSystem m_linear_system; - IItemFamily* m_dof_family = nullptr; - FemDoFsOnNodes m_dofs_on_nodes; - - void _doStationarySolve(); - void _getMaterialParameters(); - void _assembleBilinearOperatorTRIA3(); - void _solve(); - void _assembleLinearOperator(); - void _validateResults(); - - FixedMatrix<3, 3> _computeElementMatrixTRIA3(Cell cell); + info() << "Module Fem INIT"; - Real _computeAreaTriangle3(Cell cell); - Real _computeEdgeLength2(Face face); - Real2 _computeEdgeNormal2(Face face); -}; + m_dofs_on_nodes.initialize(mesh(), 1); + m_dof_family = m_dofs_on_nodes.dofFamily(); +} /*---------------------------------------------------------------------------*/ /** @@ -114,30 +61,13 @@ compute() _doStationarySolve(); } -/*---------------------------------------------------------------------------*/ -/** - * @brief Initializes the FemModule at the start of the simulation. - * - * This method initializes degrees of freedom (DoFs) on nodes. - */ -/*---------------------------------------------------------------------------*/ - -void FemModule:: -startInit() -{ - info() << "Module Fem INIT"; - - m_dofs_on_nodes.initialize(mesh(), 1); - m_dof_family = m_dofs_on_nodes.dofFamily(); -} - /*---------------------------------------------------------------------------*/ /** * @brief Performs a stationary solve for the FEM system. * * This method follows a sequence of steps to solve FEM system: * 1. _getMaterialParameters() Retrieves material parameters via - * 2. _assembleBilinearOperatorTRIA3() Assembles the FEM matrix A + * 2. _assembleBilinearOperatorTria3() Assembles the FEM matrix A * 3. _assembleLinearOperator() Assembles the FEM RHS vector b * 4. _solve() Solves for solution vector u = A^-1*b * 5. _validateResults() Regression test @@ -148,7 +78,7 @@ void FemModule:: _doStationarySolve() { _getMaterialParameters(); - _assembleBilinearOperatorTRIA3(); + _assembleBilinearOperatorTria3(); _assembleLinearOperator(); _solve(); _validateResults(); @@ -170,11 +100,7 @@ _getMaterialParameters() * * This method assembles the RHS 'b' of the linear system by: * 1. Initializing the RHS values to zero. - * 2. Iterating over Neumann boundary conditions to apply constant flux terms. - * 2.1 The integral of the flux term is computed over the boundary faces. - * 2.2 Handles both scalar and vector flux components. - * 3. For each boundary face, the edge length and normal are computed. - * 4. The contribution to the RHS is calculated and accumulated for each node. + * 2. Iterating over Neumann boundary conditions to apply it to RHS. */ /*---------------------------------------------------------------------------*/ @@ -187,121 +113,14 @@ _assembleLinearOperator() VariableDoFReal& rhs_values(m_linear_system.rhsVariable()); // Temporary variable for RHS values rhs_values.fill(0.0); - auto node_dof(m_dofs_on_nodes.nodeDoFConnectivityView()); + const auto node_dof(m_dofs_on_nodes.nodeDoFConnectivityView()); // setp 2 for (const auto& bs : options()->neumannBoundaryCondition()) { - FaceGroup group = bs->surface(); - - Real value = 0.0; - Real valueX = 0.0; - Real valueY = 0.0; - bool hasValue = bs->value.isPresent(); - bool hasValueX = bs->valueX.isPresent(); - bool hasValueY = bs->valueY.isPresent(); - - if (hasValue) { - value = bs->value(); - } - else { - if (hasValueX) - valueX = bs->valueX(); - if (hasValueY) - valueY = bs->valueY(); - } - - // setp 2.1 - ENUMERATE_ (Face, iface, group) { - Face face = *iface; - - // step 3. - Real length = _computeEdgeLength2(face); - Real2 normal = _computeEdgeNormal2(face); - - // step 4 - for (Node node : iface->nodes()) { - if (!node.isOwn()) - continue; - Real rhs_value = 0.0; - - // step 2.2 - if (hasValue) { - rhs_value = value * length / 2.0; - } - else { - rhs_value = (normal.x * valueX + normal.y * valueY) * length / 2.0; - } - - rhs_values[node_dof.dofId(node, 0)] += rhs_value; - } - } + ArcaneFemFunctions::BoundaryConditions2D::applyNeumannToRhs(bs, node_dof, m_node_coord, rhs_values); } } -/*---------------------------------------------------------------------------*/ -/** - * @brief Computes the area of a triangle defined by three nodes. - * - * This method calculates the area using the determinant formula for a triangle. - * The area is computed as half the value of the determinant of the matrix - * formed by the coordinates of the triangle's vertices. - */ -/*---------------------------------------------------------------------------*/ - -Real FemModule:: -_computeAreaTriangle3(Cell cell) -{ - Real3 m0 = m_node_coord[cell.nodeId(0)]; - Real3 m1 = m_node_coord[cell.nodeId(1)]; - Real3 m2 = m_node_coord[cell.nodeId(2)]; - return 0.5 * ((m1.x - m0.x) * (m2.y - m0.y) - (m2.x - m0.x) * (m1.y - m0.y)); -} - -/*---------------------------------------------------------------------------*/ -/** - * @brief Computes the length of the edge defined by a given face. - * - * This method calculates Euclidean distance between the two nodes of the face. - */ -/*---------------------------------------------------------------------------*/ - -Real FemModule:: -_computeEdgeLength2(Face face) -{ - Real3 m0 = m_node_coord[face.nodeId(0)]; - Real3 m1 = m_node_coord[face.nodeId(1)]; - - Real dx = m1.x - m0.x; - Real dy = m1.y - m0.y; - - return math::sqrt(dx * dx + dy * dy); -} - -/*---------------------------------------------------------------------------*/ -/** - * @brief Computes the normalized edge normal for a given face. - * - * This method calculates normal vector to the edge defined by nodes of the face, - * normalizes it, and ensures the correct orientation. - */ -/*---------------------------------------------------------------------------*/ - -Real2 FemModule:: -_computeEdgeNormal2(Face face) -{ - Real3 m0 = m_node_coord[face.nodeId(0)]; - Real3 m1 = m_node_coord[face.nodeId(1)]; - - if (!face.isSubDomainBoundaryOutside()) - std::swap(m0, m1); - - Real dx = m1.x - m0.x; - Real dy = m1.y - m0.y; - Real norm_N = math::sqrt(dx * dx + dy * dy); - - return { dy / norm_N, -dx / norm_N }; -} - /*---------------------------------------------------------------------------*/ /** * @brief Computes the element matrix for a triangular element (P1 FE). @@ -310,69 +129,27 @@ _computeEdgeNormal2(Face face) * -(u.dx * v.dx + u.dy * v.dy) + kc2 * u * v * * Steps involved: - * 1. Get the coordinates of the triangle vertices. - * 2. Calculate the area of the triangle. + * 1. Calculate the area of the triangle. + * 2. Compute the integral U*V term. * 3. Compute the gradients of the shape functions. - * 4. Normalize the gradients by the area. - * 5. Compute the element matrix: - * 5.1 first compute -(u.dx * v.dx + u.dy * v.dy) terms - * 5.2 then (kc2 * u * v) term adjusted + * 4. Return -(u.dx * v.dx + u.dy * v.dy) + kc2 * u * v ; */ /*---------------------------------------------------------------------------*/ FixedMatrix<3, 3> FemModule:: -_computeElementMatrixTRIA3(Cell cell) +_computeElementMatrixTria3(Cell cell) { // step 1 - Real3 vertex0 = m_node_coord[cell.nodeId(0)]; - Real3 vertex1 = m_node_coord[cell.nodeId(1)]; - Real3 vertex2 = m_node_coord[cell.nodeId(2)]; + Real area = ArcaneFemFunctions::MeshOperation::computeAreaTria3(cell, m_node_coord); // step 2 - Real area = _computeAreaTriangle3(cell); + Real3x3 UV = ArcaneFemFunctions::FeOperation2D::computeUVTria3(cell, m_node_coord); // step 3 - Real2 gradN0(vertex1.y - vertex2.y, vertex2.x - vertex1.x); - Real2 gradN1(vertex2.y - vertex0.y, vertex0.x - vertex2.x); - Real2 gradN2(vertex0.y - vertex1.y, vertex1.x - vertex0.x); - - // step 4 - Real A2 = 2.0 * area; - gradN0 /= A2; - gradN1 /= A2; - gradN2 /= A2; - - // step 5 - FixedMatrix<3, 3> elementMatrix; - - // step 5.1 - elementMatrix(0, 0) = -area * Arcane::math::dot(gradN0, gradN0); - elementMatrix(0, 1) = -area * Arcane::math::dot(gradN0, gradN1); - elementMatrix(0, 2) = -area * Arcane::math::dot(gradN0, gradN2); - - elementMatrix(1, 0) = -area * Arcane::math::dot(gradN1, gradN0); - elementMatrix(1, 1) = -area * Arcane::math::dot(gradN1, gradN1); - elementMatrix(1, 2) = -area * Arcane::math::dot(gradN1, gradN2); - - elementMatrix(2, 0) = -area * Arcane::math::dot(gradN2, gradN0); - elementMatrix(2, 1) = -area * Arcane::math::dot(gradN2, gradN1); - elementMatrix(2, 2) = -area * Arcane::math::dot(gradN2, gradN2); - - // step 5 .2 - Real uvTerm = m_kc2 * area / 12.0; - elementMatrix(0, 0) += uvTerm * 2.; - elementMatrix(0, 1) += uvTerm; - elementMatrix(0, 2) += uvTerm; - - elementMatrix(1, 0) += uvTerm; - elementMatrix(1, 1) += uvTerm * 2.; - elementMatrix(1, 2) += uvTerm; - - elementMatrix(2, 0) += uvTerm; - elementMatrix(2, 1) += uvTerm; - elementMatrix(2, 2) += uvTerm * 2.; - - return elementMatrix; + Real3 dxU = ArcaneFemFunctions::FeOperation2D::computeGradientXTria3(cell, m_node_coord); + Real3 dyU = ArcaneFemFunctions::FeOperation2D::computeGradientYTria3(cell, m_node_coord); + + return -area * (dxU ^ dxU) - area * (dyU ^ dyU) + m_kc2 * area * UV; } /*---------------------------------------------------------------------------*/ @@ -386,14 +163,14 @@ _computeElementMatrixTRIA3(Cell cell) /*---------------------------------------------------------------------------*/ void FemModule:: -_assembleBilinearOperatorTRIA3() +_assembleBilinearOperatorTria3() { auto node_dof(m_dofs_on_nodes.nodeDoFConnectivityView()); ENUMERATE_ (Cell, icell, allCells()) { Cell cell = *icell; - auto K_e = _computeElementMatrixTRIA3(cell); // element matrix + auto K_e = _computeElementMatrixTria3(cell); // element matrix Int32 n1_index = 0; for (Node node1 : cell.nodes()) { Int32 n2_index = 0; @@ -442,12 +219,12 @@ _solve() /*---------------------------------------------------------------------------*/ /** * @brief Validates and prints the results of the FEM computation. - * + * * This method performs the following actions: * 1. If number of nodes < 200, prints the computed values for each node. * 2. Retrieves the filename for the result file from options. * 3. If a filename is provided, checks the computed results against result file. - * + * * @note The result comparison uses a tolerance of 1.0e-4. */ /*---------------------------------------------------------------------------*/ @@ -455,7 +232,6 @@ _solve() void FemModule:: _validateResults() { - // setp 1 if (allNodes().size() < 200) { ENUMERATE_ (Node, inode, allNodes()) { diff --git a/acoustics/FemModule.h b/acoustics/FemModule.h new file mode 100644 index 00000000..f03f9177 --- /dev/null +++ b/acoustics/FemModule.h @@ -0,0 +1,87 @@ +// -*- tab-width: 2; indent-tabs-mode: nil; coding: utf-8-with-signature -*- +//----------------------------------------------------------------------------- +// Copyright 2000-2024 CEA (www.cea.fr) IFPEN (www.ifpenergiesnouvelles.com) +// See the top-level COPYRIGHT file for details. +// SPDX-License-Identifier: Apache-2.0 +//----------------------------------------------------------------------------- +/*---------------------------------------------------------------------------*/ +/* FemModule.h (C) 2022-2024 */ +/* */ +/* FemModule class definition. */ +/*---------------------------------------------------------------------------*/ +/*---------------------------------------------------------------------------*/ +#ifndef FEMMODULES_H +#define FEMMODULES_H +/*---------------------------------------------------------------------------*/ +/*---------------------------------------------------------------------------*/ + +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "IDoFLinearSystemFactory.h" +#include "Fem_axl.h" +#include "FemUtils.h" +#include "DoFLinearSystem.h" +#include "FemDoFsOnNodes.h" +#include "ArcaneFemFunctions.h" + +/*---------------------------------------------------------------------------*/ +/*---------------------------------------------------------------------------*/ + +using namespace Arcane; +using namespace Arcane::FemUtils; + +/*---------------------------------------------------------------------------*/ +/** + * @brief A module for finite element method. + * + * This class handles the initialization and computation for finite element + * method (FEM) simulations, providing methods to set up and solve linear + * systems, assemble FEM operators, and perform result checks. + */ +/*---------------------------------------------------------------------------*/ + +class FemModule +: public ArcaneFemObject +{ + public: + + explicit FemModule(const ModuleBuildInfo& mbi) + : ArcaneFemObject(mbi) + , m_dofs_on_nodes(mbi.subDomain()->traceMng()) + { + ICaseMng* cm = mbi.subDomain()->caseMng(); + cm->setTreatWarningAsError(true); + cm->setAllowUnkownRootElelement(false); + } + + void startInit() override; //! Method called at the beginning of the simulation + void compute() override; //! Method called at each iteration + VersionInfo versionInfo() const override { return VersionInfo(1, 0, 0); } + + private: + + Real m_kc2; + + DoFLinearSystem m_linear_system; + IItemFamily* m_dof_family = nullptr; + FemDoFsOnNodes m_dofs_on_nodes; + + void _doStationarySolve(); + void _getMaterialParameters(); + void _assembleBilinearOperatorTria3(); + void _solve(); + void _assembleLinearOperator(); + void _validateResults(); + + FixedMatrix<3, 3> _computeElementMatrixTria3(Cell cell); +}; + +#endif diff --git a/doc/Derivatives_fe_field.md b/doc/Derivatives_fe_field.md new file mode 100644 index 00000000..d2156b71 --- /dev/null +++ b/doc/Derivatives_fe_field.md @@ -0,0 +1,109 @@ +## Calculating Derivatives $dx(u)$ and $dy(u)$ Over a Triangular Finite Element + + + + +To calculate the derivatives of a function $u$ with respect to $x$ and $y$ ( denoted as $\frac{\partial u}{\partial x}$, $\frac{\partial u}{\partial y}$, or more simply $dx(u)$, $dy(u)$ ) over a triangular finite element, we follow these steps: + +### 1. Define the Triangle and the P1 Basis Functions + +In a P1 (linear) finite element, the function $u$ is assumed to be linear over each triangle. +Let the nodes of the triangle be $\mathbf{n}_0 = (x_0, y_0)$, $\mathbf{n}_1 = (x_1, y_1)$, and $\mathbf{n}_2 = (x_2, y_2)$, +with corresponding function values $u_0 = u(\mathbf{n}_0)$, $u_1 = u(\mathbf{n}_1)$, and $u_2 = u(\mathbf{n}_2)$. + +The function $u(x, y)$ can be expressed as: + +$$u(x, y) = a_0 + a_1 x + a_2 y$$ + +where $a_0$, $a_1$, and $a_2$ are coefficients to be determined. + +### 2. Set Up the System of Equations + +We solve for the coefficients $a_0$, $a_1$, and $a_2$ using the known values of $u$ at the triangle's vertices: + +$$ +\begin{aligned} +u_0 &= a_0 + a_1 x_0 + a_2 y_0 \\ +u_1 &= a_0 + a_1 x_1 + a_2 y_1 \\ +u_2 &= a_0 + a_1 x_2 + a_2 y_2 +\end{aligned} +$$ + +This system can be written in matrix form: + +$$ +\begin{pmatrix} +1 & x_0 & y_0 \\ +1 & x_1 & y_1 \\ +1 & x_2 & y_2 +\end{pmatrix} +\begin{pmatrix} +a_0 \\ +a_1 \\ +a_2 +\end{pmatrix}= +\begin{pmatrix} +u_0 \\ +u_1 \\ +u_2 +\end{pmatrix} +$$ + + +Solve this system to get $a_0$, $a_1$, and $a_2$. + +### 3. Calculate the Derivatives $dx(u)$ and $dy(u)$ + +- The derivative $\frac{\partial u}{\partial x}$ is simply $a_1$. This is because the partial derivative of $u(x, y)$ with respect to $x$ is: + +$$ +\frac{\partial u}{\partial x} = a_1 +$$ + +Therefore, $dx(u) = a_1$. + +- The derivative $\frac{\partial u}{\partial y}$ is simply $a_2$. This is because the partial derivative of $u(x, y)$ with respect to $y$ is: + +$$ +\frac{\partial u}{\partial y} = a_2 +$$ + +Therefore, $dy(u) = a_2$. + +### 4. Shortcut Using the Area Formula (2D case) + +For a 2D triangular element (i.e., $z = 0$), the coefficients $a_1$ and $a_2$ (which give the derivatives with respect to $x$ and $y$, respectively) can be directly computed using the area of the triangle and the coordinates of its vertices: + +$$ +a_1 = \frac{1}{2A} \left( u_0(y_1 - y_2) + u_1(y_2 - y_0) + u_2(y_0 - y_1) \right) +$$ + +$$ +a_2 = \frac{1}{2A} \left( u_0(x_2 - x_1) + u_1(x_0 - x_2) + u_2(x_1 - x_0) \right) +$$ + +where $A$ is the area of the triangle, given by: + +$$ +A = \frac{1}{2} \left| x_0(y_1 - y_2) + x_1(y_2 - y_0) + x_2(y_0 - y_1) \right| +$$ + +This method directly gives the derivatives $dx(u)$ and $dy(u)$ without solving a system of equations. + +## Implementation in Code: ## +```cpp + static inline Real3 computeGradientTria3(Cell cell, const VariableNodeReal3& node_coord, const VariableNodeReal& u) + { + Real3 n0 = node_coord[cell.nodeId(0)]; + Real3 n1 = node_coord[cell.nodeId(1)]; + Real3 n2 = node_coord[cell.nodeId(2)]; + + Real u0 = u[cell.nodeId(0)]; + Real u1 = u[cell.nodeId(1)]; + Real u2 = u[cell.nodeId(2)]; + + Real A2 = ((n1.x - n0.x) * (n2.y - n0.y) - (n2.x - n0.x) * (n1.y - n0.y)); + + return Real3 ( (u0*(n1.y - n2.y) + u1*(n2.y - n0.y) + u2*(n0.y - n1.y)) / A2 , (u0*(n2.x - n1.x) + u1*(n0.x - n2.x) + u2*(n1.x - n0.x)) / A2 , 0); + } +``` diff --git a/doc/Gradient.md b/doc/Gradient.md new file mode 100644 index 00000000..932e61a6 --- /dev/null +++ b/doc/Gradient.md @@ -0,0 +1,171 @@ +### How do we calculate gradients of shape functions for P1 Tetrahedron Finite Element ### + +For a tetrahedron with nodes we assume four nodes $m_0 , m_1. m_2, m_3$: + + + + $m_0 = (x_0, y_0, z_0)$, $m_1 = (x_1, y_1, z_1)$, $m_2 = (x_2, y_2, z_2)$, and $m_3 = (x_3, y_3, z_3)$, + +The P1 finite element shape functions $\Phi_i$ for a tetrahedron can be expressed as: + +$$ +\Phi_i(x, y, z) = \frac{1}{6V} \left( a_i + b_i x + c_i y + d_i z \right) +$$ + +Where $V$ is the volume of the tetrahedron. + +The gradients of the shape functions $\Phi_0$, $\Phi_1$, $\Phi_2$, $\Phi_3$ with respect to $x$, $y$, and $z$ can be computed as follows: + +## Gradient with Respect to $x$ ($\frac{\partial \Phi_i}{\partial x}$) + +The partial derivative with respect to $x$ is: + +$$ +\frac{\partial \Phi_i}{\partial x} = \frac{b_i}{6V} +$$ + +Here, the coefficients $b_i$ are: + +$$ +b_0 = \det \begin{pmatrix} +y_1 & z_1 \\ +y_2 & z_2 \\ +y_3 & z_3 +\end{pmatrix} +$$ + +$$ +b_1 = \det \begin{pmatrix} +y_0 & z_0 \\ +y_2 & z_2 \\ +y_3 & z_3 +\end{pmatrix} +$$ + +$$ +b_2 = \det \begin{pmatrix} +y_0 & z_0 \\ +y_1 & z_1 \\ +y_3 & z_3 +\end{pmatrix} +$$ + +$$ +b_3 = \det \begin{pmatrix} +y_0 & z_0 \\ +y_1 & z_1 \\ +y_2 & z_2 +\end{pmatrix} +$$ + +## Gradient with Respect to $y$ ($\frac{\partial \Phi_i}{\partial y}$) + +The partial derivative with respect to $y$ is: + +$$ +\frac{\partial \Phi_i}{\partial y} = \frac{c_i}{6V} +$$ + +Where the coefficients $c_i$ are: + +$$ +c_0 = \det \begin{pmatrix} +z_1 & x_1 \\ +z_2 & x_2 \\ +z_3 & x_3 +\end{pmatrix} +$$ + +$$ +c_1 = \det \begin{pmatrix} +z_0 & x_0 \\ +z_2 & x_2 \\ +z_3 & x_3 +\end{pmatrix} +$$ + +$$ +c_2 = \det \begin{pmatrix} +z_0 & x_0 \\ +z_1 & x_1 \\ +z_3 & x_3 +\end{pmatrix} +$$ + +$$ +c_3 = \det \begin{pmatrix} +z_0 & x_0 \\ +z_1 & x_1 \\ +z_2 & x_2 +\end{pmatrix} +$$ + +## Gradient with Respect to $z$ ($\frac{\partial \Phi_i}{\partial z}$) + +The partial derivative with respect to $z$ is: + +$$ +\frac{\partial \Phi_i}{\partial z} = \frac{d_i}{6V} +$$ + +Where the coefficients $d_i$ are: + +$$ +d_0 = \det \begin{pmatrix} +x_1 & y_1 \\ +x_2 & y_2 \\ +x_3 & y_3 +\end{pmatrix} +$$ + +$$ +d_1 = \det \begin{pmatrix} +x_0 & y_0 \\ +x_2 & y_2 \\ +x_3 & y_3 +\end{pmatrix} +$$ + +$$ +d_2 = \det \begin{pmatrix} +x_0 & y_0 \\ +x_1 & y_1 \\ +x_3 & y_3 +\end{pmatrix} +$$ + +$$ +d_3 = \det \begin{pmatrix} +x_0 & y_0 \\ +x_1 & y_1 \\ +x_2 & y_2 +\end{pmatrix} +$$ + +## Implementation in Code for Gradient in $x$ ($\frac{\partial \Phi_i}{\partial x}$): + +```cpp + static inline Real4 computeGradientXTetra4(Cell cell, const VariableNodeReal3& node_coord) + { + Real3 vertex0 = node_coord[cell.nodeId(0)]; + Real3 vertex1 = node_coord[cell.nodeId(1)]; + Real3 vertex2 = node_coord[cell.nodeId(2)]; + Real3 vertex3 = node_coord[cell.nodeId(3)]; + + Real3 v0 = vertex1 - vertex0; + Real3 v1 = vertex2 - vertex0; + Real3 v2 = vertex3 - vertex0; + + // 6 x Volume of tetrahedron + Real V6 = std::abs(Arcane::math::dot(v0, Arcane::math::cross(v1, v2))); + + Real4 dx; + + dx[0] = (vertex1.y * (vertex3.z - vertex2.z) + vertex2.y * (vertex1.z - vertex3.z) + vertex3.y * (vertex2.z - vertex1.z))/V6; + dx[1] = (vertex0.y * (vertex2.z - vertex3.z) + vertex2.y * (vertex3.z - vertex0.z) + vertex3.y * (vertex0.z - vertex2.z))/V6; + dx[2] = (vertex0.y * (vertex3.z - vertex1.z) + vertex1.y * (vertex0.z - vertex3.z) + vertex3.y * (vertex1.z - vertex0.z))/V6; + dx[3] = (vertex0.y * (vertex1.z - vertex2.z) + vertex1.y * (vertex2.z - vertex0.z) + vertex2.y * (vertex0.z - vertex1.z))/V6; + + return dx; + } +``` diff --git a/doc/ImposingDirichletConditions.html b/doc/ImposingDirichletConditions.html deleted file mode 100644 index ff54378b..00000000 --- a/doc/ImposingDirichletConditions.html +++ /dev/null @@ -1,746 +0,0 @@ - - - - - -Readme - -
-

How are Dirichlet Boundary conditions imposed

To enforce Dirichlet boundary conditions in FEM many methods are available, some of the common ones include:

MethodReferenceIn ArcaneFEM
Weak penalty methodBabuška, 1973aYES
Penalty methodBabuška, 1973aYES
Row elimination SOON
Row/Column elimination SOON
Lagrange multiplier methodBabuška, 1973bNO
Nitsche’s methodNitsche, 1971NO

The first four are made available, so that one can choose the most appropriate method for Dirichlet boundary condition implementation. The word 'appropriate' in the preceding sentence is tricky, it depends on the physics, size of the problem, conditioning of the problem, used linear-solver, etc. So choose wisely.

What remains common in these methods is these are applied after one assembles the FEM linear system. As such one could see these methods as a post-processing step to be applied to the assembled linear-systems Ax=b before they are moved to the solving stage. More precisely, these methods alter the vector b and/or matrix A.

Weak penalty method and Penalty method

These methods are not exact rather weak sense of applying Dirichlet boundary condition, however these are the most straightforward way to apply the Dirichlet boundary condition. These methods remain popular among practitioners and can be found in FEM packages such as MOOSE, FreeFEM, etc. Main benefits of these methods include, simplistic implementation, conserved non-zero structure (sparsity) of A as such matrix symmetry may be conserved, and avoiding insertion of zeros into A which is a tedious operation.

In a nutshell, to apply weak penalty method for a DOF i which corresponds to a boundary node in the mesh the following needs to be changed:

ai,i=ai,i+P and bi=gi×P

here, P is the penalty term and gi is the given Dirichlet value.

Similarly to apply penalty method for a DOF i which corresponds to a boundary node in the mesh the following needs to be changed:

ai,i=P and bi=gi×P

here, P is the penalty term and gi is the given Dirichlet value.

The logic is simple as P is a large large value P>>1, the diagonal contribution ai,i dominates the off-diagonal ones (since ai,j<<ai,i), this indeed means solution bigi.

Generally, P is chosen to be large enough e.g, 1×1031 so that the resulting solution x=A1b is precise enough for a double precision calculation. This works most often, however sometimes this may result into A becoming ill-conditioned, eventually this might lead in failed solves and overall accuracy losses. So the user should select and tune the value of P in this case.

Row elimination

To apply a Dirichlet condition gi on the Dirchlet DOF i via row elimination method two operations are performed.

  • First is on the matrix A we zero out the ith row and ith column of the matrix A and impose 1 at the diagonal ai,i=1. For a FEM problem, if ND is the total number of Dirichlet DOFs and if A has the size of NDOF, this method involves altering A:

    • i=1,,ND&j=1,,NDOF -ai,j=0,aj,i=0,&ai,i=1
  • Second operation is on the vector b, for each Dirichlet condition to be imposed on the ith DOF, we substitute the Dirichlet value gi to ith component of the the vector b. Hence, this method involves:

    • i=1,,NDbi=gi

This method is more exact way of imposing Dirichlet boundary conditions , however the matrix A is not symmetric anymore (if it were before), the problem it is algorithmically more challenging to implement as A in comparison to penalty methods.

Row column elimination

To apply a Dirichlet condition gi on the Dirchlet DOF i via row column elimination method two operations are performed.

  • First is on the matrix A we zero out the ith row of the matrix A and impose 1 at the diagonal ai,i=1. For a FEM problem, if ND is the total number of Dirichlet DOFs and if A has the size of NDOF, this method involves altering A:

    • i=1,,ND&j=1,,NDOF -ai,j=0,&ai,i=1
  • Second operation is on the vector b, for each Dirichlet condition to be imposed on the ith DOF, we subtract the ith column from the vector b. Hence, this method involves:

    • i=1,,ND&j=1,,NDOF

      bj=bjaj,i&bi=gi

This method is more exact way of imposing Dirichlet boundary conditions, however it is algorithmically more challenging to implement as A which is a sparse matrix is stored in compressed row format (CRS) then moving the columns to the right-hand side becomes expensive for large systems with many Dirichlet DOFs.

Referneces

  • Babuška, I., 1973. The finite element method with penalty. Mathematics of computation, 27(122), pp.221-228.
  • Babuška, I., 1973. The finite element method with Lagrangian multipliers. Numerische Mathematik, 20(3), pp.179-192.
  • Nitsche, J., 1971, July. Über ein Variationsprinzip zur Lösung von Dirichlet-Problemen bei Verwendung von Teilräumen, die keinen Randbedingungen unterworfen sind. In Abhandlungen aus dem mathematischen Seminar der Universität Hamburg (Vol. 36, No. 1, pp. 9-15). Berlin/Heidelberg: Springer-Verlag.

 

 

 

- - \ No newline at end of file diff --git a/doc/ImposingDirichletConditions.md b/doc/ImposingDirichletConditions.md new file mode 100644 index 00000000..37fc449d --- /dev/null +++ b/doc/ImposingDirichletConditions.md @@ -0,0 +1,83 @@ +### How are Dirichlet Boundary conditions imposed + +To enforce Dirichlet boundary conditions in FEM many methods are available, some of the common ones include: + +| **Method** | **Reference** | In ArcaneFEM | +| :------------------------: | :------------: | :----------: | +| Weak penalty method | Babuška, 1973a | YES | +| Penalty method | Babuška, 1973a | YES | +| Row elimination | | YES | +| Row/Column elimination | | YES | +| Lagrange multiplier method | Babuška, 1973b | NO | +| Nitsche’s method | Nitsche, 1971 | NO | + +The first four are made available, so that one can choose the most appropriate method for Dirichlet boundary condition implementation. The word 'appropriate' in the preceding sentence is tricky, it depends on the physics, size of the problem, conditioning of the problem, used linear-solver, etc. So choose wisely. + +What remains common in these methods is these are applied after one assembles the FEM linear system. As such one could see these methods as a post-processing step to be applied to the assembled linear-systems $\mathbf{Ax=b}$ before they are moved to the solving stage. More precisely, these methods alter the vector $\mathbf{b}$ and/or matrix $\mathbf{A}$. + +#### Weak penalty method and Penalty method #### + +These methods are not exact rather weak sense of applying Dirichlet boundary condition, however these are the most straightforward way to apply the Dirichlet boundary condition. These methods remain popular among practitioners and can be found in FEM packages such as MOOSE, FreeFEM, etc. Main benefits of these methods include, simplistic implementation, conserved non-zero structure (sparsity) of $\mathbf{A}$ as such matrix symmetry may be conserved, and avoiding insertion of zeros into $\mathbf{A}$ which is a tedious operation. + +In a nutshell, to apply weak penalty method for a DOF $i$ which corresponds to a boundary node in the mesh the following needs to be changed: + +$a_{i,i} = a_{i,i} + \mathcal{P}$ and $b_i = g_i \times \mathcal{P}$ + +here, $\mathcal{P}$ is the penalty term and $g_i$ is the given Dirichlet value. + +Similarly to apply penalty method for a DOF $i$ which corresponds to a boundary node in the mesh the following needs to be changed: + +$a_{i,i} = \mathcal{P}$ and $b_i = g_i \times \mathcal{P}$ + +here, $\mathcal{P}$ is the penalty term and $g_i$ is the given Dirichlet value. + +The logic is simple as $\mathcal{P}$ is a large large value $\mathcal{P}>>1$, the diagonal contribution $a_{i,i}$ dominates the off-diagonal ones (since $a_{i,j} << a_{i,i}$), this indeed means solution $b_i \approx g_i$. + +Generally, $\mathcal{P}$ is chosen to be large enough e.g, $1\times10^{31}$ so that the resulting solution $\mathbf{x=A^{-1}b}$ is precise enough for a double precision calculation. This works most often, however sometimes this may result into $\mathbf{A}$ becoming ill-conditioned, eventually this might lead in failed solves and overall accuracy losses. So the user should select and tune the value of $\mathcal{P}$ in this case. + +#### Row elimination #### + +To apply a Dirichlet condition $g_i$ on the Dirchlet DOF $i$ via row elimination method two operations are performed. + +- First is on the matrix $\mathbf{A}$ we zero out the $i$ th row and $i$ th column of the matrix $\mathbf{A}$ and impose $1$ at the diagonal $a_{i,i}=1$. For a FEM problem, if $N_{D}$ is the total number of Dirichlet DOFs and if $\mathbf{A}$ has the size of $N_{DOF}$, this method involves altering $\mathbf{A}$: + +$$\forall i=1, \ldots , N_D \quad ; \quad \forall j = 1 , \ldots , N_{DOF}$$ + +$$ a_{i,j}=0, \quad a_{j,i}=0, \quad a_{i,i} = 1$$ + +- Second operation is on the vector $\mathbf{b}$, for each Dirichlet condition to be imposed on the $i$ th DOF, we substitute the Dirichlet value $g_i$ to $i$ th component of the the vector $\mathbf{b}$. Hence, this method involves: + +$$\forall i=1, \ldots , N_D \quad \quad b_i = g_i$$ + +This method is more exact way of imposing Dirichlet boundary conditions , however the matrix $\mathbf{A}$ is not symmetric anymore (if it were before), the problem it is algorithmically more challenging to implement as $\mathbf{A}$ in comparison to penalty methods. + +#### Row column elimination #### + +To apply a Dirichlet condition $g_i$ on the Dirchlet DOF $i$ via row column elimination method two operations are performed. + +- First is on the matrix $\mathbf{A}$ we zero out the $i$ th row of the matrix $\mathbf{A}$ and impose $1$ at the diagonal $a_{i,i}=1$. For a FEM problem, if $N_{D}$ is the total number of Dirichlet DOFs and if $\mathbf{A}$ has the size of $N_{DOF}$, this method involves altering $\mathbf{A}$: + +$$\forall i=1,\ldots ,N_D \quad ; \quad \forall j = 1 ,\ldots, N_{DOF}$$ + +$$a_{i,j}=0, \quad a_{i,i} = 1$$ + +- Second operation is on the vector $\mathbf{b}$, for each Dirichlet condition to be imposed on the $i$ th DOF, we subtract the $i$ th column from the vector $\mathbf{b}$. Hence, this method involves: + +$$\forall i=1,\ldots ,N_D \quad ; \quad \forall j = 1 ,\ldots, N_{DOF}$$ + +$$b_j = b_j - a_{j,i} , \quad b_i = g_i$$ + +This method is more exact way of imposing Dirichlet boundary conditions, however it is algorithmically more challenging to implement as $\mathbf{A}$ which is a sparse matrix is stored in compressed row format (CRS) then moving the columns to the right-hand side becomes expensive for large systems with many Dirichlet DOFs. + +## Referneces ## + +- Babuška, I., 1973. The finite element method with penalty. *Mathematics of computation*, *27*(122), pp.221-228. + +- Babuška, I., 1973. The finite element method with Lagrangian multipliers. *Numerische Mathematik*, *20*(3), pp.179-192. + +- Nitsche, J., 1971, July. Über ein Variationsprinzip zur Lösung von Dirichlet-Problemen bei Verwendung von Teilräumen, die keinen Randbedingungen unterworfen sind. In *Abhandlungen aus dem mathematischen Seminar der Universität Hamburg* (Vol. 36, No. 1, pp. 9-15). Berlin/Heidelberg: Springer-Verlag. + + + + + diff --git a/electrostatics/Fem.axl b/electrostatics/Fem.axl index d7acb5b9..b78f2ab8 100644 --- a/electrostatics/Fem.axl +++ b/electrostatics/Fem.axl @@ -9,9 +9,6 @@ FEM variable phi on nodes for electrostatic potential - - Boolean which is true if Dirichlet node - electric field vector on cells @@ -32,16 +29,6 @@ Type of mesh provided to the solver - - - Method via which Dirichlet boundary condition is imposed - - - - - Penalty value for enforcing Dirichlet condition - - + + + Method via which Dirichlet boundary condition is imposed + + + + + Penalty value for enforcing Dirichlet condition + + + + + + + + Dirichlet boundary condition + + + + Function for Dirichlet boundary condition + + + + + Method via which Dirichlet boundary condition is imposed + + + + + Penalty value for enforcing Dirichlet condition + + + + + Function for manufactured source term condition + + @@ -115,6 +143,16 @@ Value of the point Dirichlet condition + + + Method via which Dirichlet boundary condition is imposed + + + + + Penalty value for enforcing Dirichlet condition + +
-#include -#include - -#include -#include -#include -#include -#include - -#include "IDoFLinearSystemFactory.h" -#include "Fem_axl.h" -#include "FemUtils.h" -#include "DoFLinearSystem.h" -#include "FemDoFsOnNodes.h" +#include "FemModule.h" /*---------------------------------------------------------------------------*/ +/** + * @brief Initializes the FemModule at the start of the simulation. + * + * - initializes degrees of freedom (DoFs) on nodes. + * - builds support for manufactured test case (optional). + */ /*---------------------------------------------------------------------------*/ -using namespace Arcane; -using namespace Arcane::FemUtils; - -/*---------------------------------------------------------------------------*/ -/*---------------------------------------------------------------------------*/ -/*! - * \brief Module Fem. - */ -class FemModule -: public ArcaneFemObject +void FemModule:: +startInit() { - public: - - explicit FemModule(const ModuleBuildInfo& mbi) - : ArcaneFemObject(mbi) - , m_dofs_on_nodes(mbi.subDomain()->traceMng()) - { - ICaseMng* cm = mbi.subDomain()->caseMng(); - cm->setTreatWarningAsError(true); - cm->setAllowUnkownRootElelement(false); - } - - public: - - //! Method called at each iteration - void compute() override; - - //! Method called at the beginning of the simulation - void startInit() override; - - VersionInfo versionInfo() const override - { - return VersionInfo(1, 0, 0); - } - - private: - - Real rho; - Real epsilon; - Real ElementNodes; - - DoFLinearSystem m_linear_system; - IItemFamily* m_dof_family = nullptr; - FemDoFsOnNodes m_dofs_on_nodes; - - private: - - void _doStationarySolve(); - void _getMaterialParameters(); - void _assembleBilinearOperatorTRIA3(); - void _solve(); - void _getE(); - void _initBoundaryconditions(); - void _assembleLinearOperator(); - void _applyDirichletBoundaryConditions(); - void _checkResultFile(); - FixedMatrix<3, 3> _computeElementMatrixTRIA3(Cell cell); - Real _computeAreaTriangle3(Cell cell); - Real _computeEdgeLength2(Face face); - Real2 _computeEdgeNormal2(Face face); - Real2 _computeDxDyOfRealTRIA3(Cell cell); + info() << "Module Fem INIT"; -}; + m_dofs_on_nodes.initialize(mesh(), 1); + m_dof_family = m_dofs_on_nodes.dofFamily(); +} /*---------------------------------------------------------------------------*/ +/** + * @brief Performs the main computation for the FemModule. + * + * - Stops the time loop after 1 iteration since the equation is steady state. + * - Resets, configures, and initializes the linear system. + * - Executes the stationary solve. + */ /*---------------------------------------------------------------------------*/ void FemModule:: @@ -113,8 +56,7 @@ compute() // This is only used for the first call. { StringList string_list; - string_list.add("-trmalloc"); - string_list.add("-log_trace"); + string_list.add("-ksp_monitor"); CommandLineArguments args(string_list); m_linear_system.setSolverCommandLineArguments(args); } @@ -124,313 +66,87 @@ compute() } /*---------------------------------------------------------------------------*/ -/*---------------------------------------------------------------------------*/ - -void FemModule:: -startInit() -{ - info() << "Module Fem INIT"; - - m_dofs_on_nodes.initialize(mesh(), 1); - m_dof_family = m_dofs_on_nodes.dofFamily(); - - _initBoundaryconditions(); -} - -/*---------------------------------------------------------------------------*/ +/** + * @brief Performs a stationary solve for the FEM system. + * + * This method follows a sequence of steps to solve FEM system: + * 1. _getMaterialParameters() Retrieves material parameters via + * 2. _assembleBilinearOperator() Assembles the FEM matrix A + * 3. _assembleLinearOperator() Assembles the FEM RHS vector b + * 4. _solve() Solves for solution vector u = A^-1*b + * 5. _validateResults() Regression test + */ /*---------------------------------------------------------------------------*/ void FemModule:: _doStationarySolve() { - // # get material parameters _getMaterialParameters(); - - // Assemble the FEM bilinear operator (LHS - matrix A) - _assembleBilinearOperatorTRIA3(); - - // Assemble the FEM linear operator (RHS - vector b) + _assembleBilinearOperator(); _assembleLinearOperator(); - - // # T=linalg.solve(K,RHS) _solve(); - - // Check results - _checkResultFile(); + _validateResults(); } /*---------------------------------------------------------------------------*/ +/** + * @brief Retrieves and sets the material parameters for the simulation. + * + * This method initializes material properties: + * - charge density (`rho`) + * - free space permittivity (`epsilon`) + */ /*---------------------------------------------------------------------------*/ void FemModule:: _getMaterialParameters() { info() << "Get material parameters..."; - rho = options()->rho(); // charge density - epsilon = options()->epsilon(); // freespace permittivity - ElementNodes = 3.; // 3 nodes of triangle + rho = options()->rho(); + epsilon = options()->epsilon(); } /*---------------------------------------------------------------------------*/ -/*---------------------------------------------------------------------------*/ - -void FemModule:: -_initBoundaryconditions() -{ - info() << "Init boundary conditions..."; - - info() << "Apply boundary conditions"; - _applyDirichletBoundaryConditions(); -} - -/*---------------------------------------------------------------------------*/ -/*---------------------------------------------------------------------------*/ - -void FemModule:: -_applyDirichletBoundaryConditions() -{ - // Handle all the Dirichlet boundary conditions. - // In the 'arc' file, there are in the following format: - // - // Haut - // 21.0 - // - - for (const auto& bs : options()->dirichletBoundaryCondition()) { - FaceGroup group = bs->surface(); - Real value = bs->value(); - info() << "Apply Dirichlet boundary condition surface=" << group.name() << " v=" << value; - ENUMERATE_ (Face, iface, group) { - for (Node node : iface->nodes()) { - m_phi[node] = value; - m_phi_dirichlet[node] = true; - } - } - } - - for (const auto& bs : options()->dirichletPointCondition()) { - NodeGroup group = bs->node(); - Real value = bs->value(); - info() << "Apply Dirichlet point condition node=" << group.name() << " v=" << value; - ENUMERATE_ (Node, inode, group) { - Node node = *inode; - m_phi[node] = value; - m_phi_dirichlet[node] = true; - } - } -} - -/*---------------------------------------------------------------------------*/ -// Assemble the FEM linear operator -// - This function enforces a Dirichlet boundary condition in a weak sense -// via the penalty method -// - The method also adds source term +/** + * @brief FEM linear operator for the current simulation step. + * + * This method constructs the linear system by assembling the LHS matrix + * and RHS vector, applying various boundary conditions and source terms. + * + * Steps involved: + * 1. The RHS vector is initialized to zero before applying any conditions. + * 2. If a constant source term is specified (`rho`), apply it to the RHS. + * 3. If Neumann BC are specified applied to the RHS. + * 4. If Dirichlet BC are specified apply to the LHS & RHS. + */ /*---------------------------------------------------------------------------*/ void FemModule:: _assembleLinearOperator() { info() << "Assembly of FEM linear operator "; - info() << "Applying Dirichlet boundary condition via penalty method "; - // Temporary variable to keep values for the RHS part of the linear system VariableDoFReal& rhs_values(m_linear_system.rhsVariable()); rhs_values.fill(0.0); auto node_dof(m_dofs_on_nodes.nodeDoFConnectivityView()); - if (options()->enforceDirichletMethod() == "Penalty") { - - //---------------------------------------------- - // penalty method to enforce Dirichlet BC - //---------------------------------------------- - // Let 'P' be the penalty term and let 'i' be the set of DOF for which - // Dirichlet condition needs to be applied - // - // - For LHS matrix A the diag term corresponding to the Dirichlet DOF - // a_{i,i} = 1. * P - // - // - For RHS vector b the term that corresponds to the Dirichlet DOF - // b_{i} = b_{i} * P - //---------------------------------------------- - - info() << "Applying Dirichlet boundary condition via " - << options()->enforceDirichletMethod() << " method "; - - Real Penalty = options()->penalty(); // 1.0e30 is the default - - ENUMERATE_ (Node, inode, ownNodes()) { - NodeLocalId node_id = *inode; - if (m_phi_dirichlet[node_id]) { - DoFLocalId dof_id = node_dof.dofId(*inode, 0); - m_linear_system.matrixSetValue(dof_id, dof_id, Penalty); - Real u_g = Penalty * m_phi[node_id]; - rhs_values[dof_id] = u_g; - } - } - }else if (options()->enforceDirichletMethod() == "WeakPenalty") { - - //---------------------------------------------- - // weak penalty method to enforce Dirichlet BC - //---------------------------------------------- - // Let 'P' be the penalty term and let 'i' be the set of DOF for which - // Dirichlet condition needs to be applied - // - // - For LHS matrix A the diag term corresponding to the Dirichlet DOF - // a_{i,i} = a_{i,i} + P - // - // - For RHS vector b the term that corresponds to the Dirichlet DOF - // b_{i} = b_{i} * P - //---------------------------------------------- - - info() << "Applying Dirichlet boundary condition via " - << options()->enforceDirichletMethod() << " method "; - - Real Penalty = options()->penalty(); // 1.0e30 is the default - - ENUMERATE_ (Node, inode, ownNodes()) { - NodeLocalId node_id = *inode; - if (m_phi_dirichlet[node_id]) { - DoFLocalId dof_id = node_dof.dofId(*inode, 0); - m_linear_system.matrixAddValue(dof_id, dof_id, Penalty); - Real u_g = Penalty * m_phi[node_id]; - rhs_values[dof_id] = u_g; - } - } - }else if (options()->enforceDirichletMethod() == "RowElimination") { - - //---------------------------------------------- - // Row elimination method to enforce Dirichlet BC - //---------------------------------------------- - // Let 'I' be the set of DOF for which Dirichlet condition needs to be applied - // - // to apply the Dirichlet on 'i'th DOF - // - For LHS matrix A the row terms corresponding to the Dirichlet DOF - // a_{i,j} = 0. : i!=j - // a_{i,j} = 1. : i==j - //---------------------------------------------- - - info() << "Applying Dirichlet boundary condition via " - << options()->enforceDirichletMethod() << " method "; - - }else if (options()->enforceDirichletMethod() == "RowColumnElimination") { - - //---------------------------------------------- - // Row elimination method to enforce Dirichlet BC - //---------------------------------------------- - // Let 'I' be the set of DOF for which Dirichlet condition needs to be applied - // - // to apply the Dirichlet on 'i'th DOF - // - For LHS matrix A the row terms corresponding to the Dirichlet DOF - // a_{i,j} = 0. : i!=j for all j - // a_{i,j} = 1. : i==j - // also the column terms corresponding to the Dirichlet DOF - // a_{i,j} = 0. : i!=j for all i - //---------------------------------------------- - - info() << "Applying Dirichlet boundary condition via " - << options()->enforceDirichletMethod() << " method "; - - }else { - - info() << "Applying Dirichlet boundary condition via " - << options()->enforceDirichletMethod() << " is not supported \n" - << "enforce-Dirichlet-method only supports:\n" - << " - Penalty\n" - << " - WeakPenalty\n" - << " - RowElimination\n" - << " - RowColumnElimination\n"; + if (options()->rho.isPresent()) { + Real qdot = -rho / epsilon; + ArcaneFemFunctions::BoundaryConditions2D::applyConstantSourceToRhs(qdot, mesh(), node_dof, m_node_coord, rhs_values); } + for (const auto& bs : options()->neumannBoundaryCondition()) + ArcaneFemFunctions::BoundaryConditions2D::applyNeumannToRhs(bs, node_dof, m_node_coord, rhs_values); - //---------------------------------------------- - // Constant source term assembly - //---------------------------------------------- - // - // $int_{Omega}((-rho/epsilon)*v^h)$ - // only for noded that are non-Dirichlet - //---------------------------------------------- - ENUMERATE_ (Cell, icell, allCells()) { - Cell cell = *icell; - Real area = _computeAreaTriangle3(cell); - for (Node node : cell.nodes()) { - if (!(m_phi_dirichlet[node]) && node.isOwn()) - rhs_values[node_dof.dofId(node, 0)] += (- rho/epsilon) * area / ElementNodes; - } - } - - //---------------------------------------------- - // Constant flux term assembly - //---------------------------------------------- - // - // only for noded that are non-Dirichlet - // $int_{dOmega_N}((q.n)*v^h)$ - // or - // $int_{dOmega_N}((n_x*q_x + n_y*q_y)*v^h)$ - //---------------------------------------------- - for (const auto& bs : options()->neumannBoundaryCondition()) { - FaceGroup group = bs->surface(); - - if(bs->value.isPresent()) { - Real value = bs->value(); - ENUMERATE_ (Face, iface, group) { - Face face = *iface; - Real length = _computeEdgeLength2(face); - for (Node node : iface->nodes()) { - if (!(m_phi_dirichlet[node]) && node.isOwn()) - rhs_values[node_dof.dofId(node, 0)] += value * length / 2.; - } - } - continue; - } - - if(bs->valueX.isPresent() && bs->valueY.isPresent()) { - Real valueX = bs->valueX(); - Real valueY = bs->valueY(); - ENUMERATE_ (Face, iface, group) { - Face face = *iface; - Real length = _computeEdgeLength2(face); - Real2 Normal = _computeEdgeNormal2(face); - for (Node node : iface->nodes()) { - if (!(m_phi_dirichlet[node]) && node.isOwn()) - rhs_values[node_dof.dofId(node, 0)] += (Normal.x*valueX + Normal.y*valueY) * length / 2.; - } - } - continue; - } - - if(bs->valueX.isPresent()) { - Real valueX = bs->valueX(); - ENUMERATE_ (Face, iface, group) { - Face face = *iface; - Real length = _computeEdgeLength2(face); - Real2 Normal = _computeEdgeNormal2(face); - for (Node node : iface->nodes()) { - if (!(m_phi_dirichlet[node]) && node.isOwn()) - rhs_values[node_dof.dofId(node, 0)] += (Normal.x*valueX) * length / 2.; - } - } - continue; - } - - if(bs->valueY.isPresent()) { - Real valueY = bs->valueY(); - ENUMERATE_ (Face, iface, group) { - Face face = *iface; - Real length = _computeEdgeLength2(face); - Real2 Normal = _computeEdgeNormal2(face); - for (Node node : iface->nodes()) { - if (!(m_phi_dirichlet[node]) && node.isOwn()) - rhs_values[node_dof.dofId(node, 0)] += (Normal.y*valueY) * length / 2.; - } - } - continue; - } - - } + for (const auto& bs : options()->dirichletBoundaryCondition()) + ArcaneFemFunctions::BoundaryConditions2D::applyDirichletToLhsAndRhs(bs, node_dof, m_node_coord, m_linear_system, rhs_values); } /*---------------------------------------------------------------------------*/ +/** + * @brief Computes the Electric Field which is gradient of ∇ Phi = E + */ /*---------------------------------------------------------------------------*/ void FemModule:: @@ -438,155 +154,85 @@ _getE() { info() << "Postprocessing E"; - ENUMERATE_ (Cell, icell, allCells()) { - Cell cell = *icell; - Real2 DX = _computeDxDyOfRealTRIA3(cell); - m_E[cell].x = -DX.x ; - m_E[cell].y = -DX.y ; - m_E[cell].z = 0.0 ; - } - - m_E.synchronize(); -} - -/*---------------------------------------------------------------------------*/ -/*---------------------------------------------------------------------------*/ - -Real FemModule:: -_computeAreaTriangle3(Cell cell) -{ - Real3 m0 = m_node_coord[cell.nodeId(0)]; - Real3 m1 = m_node_coord[cell.nodeId(1)]; - Real3 m2 = m_node_coord[cell.nodeId(2)]; - return 0.5 * ((m1.x - m0.x) * (m2.y - m0.y) - (m2.x - m0.x) * (m1.y - m0.y)); -} - -/*---------------------------------------------------------------------------*/ -/*---------------------------------------------------------------------------*/ - -Real FemModule:: -_computeEdgeLength2(Face face) -{ - Real3 m0 = m_node_coord[face.nodeId(0)]; - Real3 m1 = m_node_coord[face.nodeId(1)]; - return math::sqrt((m1.x-m0.x)*(m1.x-m0.x) + (m1.y-m0.y)*(m1.y - m0.y)); -} - -/*---------------------------------------------------------------------------*/ -/*---------------------------------------------------------------------------*/ + ENUMERATE_ (Cell, icell, allCells()) { + Cell cell = *icell; + m_E[cell] = ArcaneFemFunctions::FeOperation2D::computeGradientTria3(cell, m_node_coord, m_phi); + } -Real2 FemModule:: -_computeEdgeNormal2(Face face) -{ - Real3 m0 = m_node_coord[face.nodeId(0)]; - Real3 m1 = m_node_coord[face.nodeId(1)]; - if (!face.isSubDomainBoundaryOutside()) - std::swap(m0,m1); - Real2 N; - Real norm_N = math::sqrt( (m1.y - m0.y)*(m1.y - m0.y) + (m1.x - m0.x)*(m1.x - m0.x) ); // for normalizing - N.x = (m1.y - m0.y)/ norm_N; - N.y = (m0.x - m1.x)/ norm_N; - return N; + m_E.synchronize(); } /*---------------------------------------------------------------------------*/ +/** + * @brief Computes the element matrix for a triangular element (P1 FE). + * + * This function calculates the integral of the expression: + * (u.dx * v.dx + u.dy * v.dy) + * + * Steps involved: + * 1. Calculate the area of the triangle. + * 2. Compute the gradients of the shape functions. + * 3. Return (u.dx * v.dx + u.dy * v.dy); + */ /*---------------------------------------------------------------------------*/ -Real2 FemModule:: -_computeDxDyOfRealTRIA3(Cell cell) +FixedMatrix<3, 3> FemModule:: +_computeElementMatrixTria3(Cell cell) { - Real3 m0 = m_node_coord[cell.nodeId(0)]; - Real3 m1 = m_node_coord[cell.nodeId(1)]; - Real3 m2 = m_node_coord[cell.nodeId(2)]; - - Real f0 = m_phi[cell.nodeId(0)]; - Real f1 = m_phi[cell.nodeId(1)]; - Real f2 = m_phi[cell.nodeId(2)]; + Real area = ArcaneFemFunctions::MeshOperation::computeAreaTria3(cell, m_node_coord); - Real detA = ( m0.x*(m1.y - m2.y) - m0.y*(m1.x - m2.x) + (m1.x*m2.y - m2.x*m1.y) ); + Real3 dxU = ArcaneFemFunctions::FeOperation2D::computeGradientXTria3(cell, m_node_coord); + Real3 dyU = ArcaneFemFunctions::FeOperation2D::computeGradientYTria3(cell, m_node_coord); - Real2 DX; - DX.y = ( m0.x*(f1 - f2) - f0*(m1.x - m2.x) + (f2*m1.x - f1*m2.x) ) / detA; - DX.x = ( f0*(m1.y - m2.y) - m0.y*(f1 - f2) + (f1*m2.y - f2*m1.y) ) / detA; - - return DX ; + return area * (dxU ^ dxU) + area * (dyU ^ dyU); } /*---------------------------------------------------------------------------*/ +/** + * @brief Calls the right function for LHS assembly given as mesh type. + */ /*---------------------------------------------------------------------------*/ -FixedMatrix<3, 3> FemModule:: -_computeElementMatrixTRIA3(Cell cell) +void FemModule:: +_assembleBilinearOperator() { - // Get coordiantes of the triangle element TRI3 - //------------------------------------------------ - // 0 o - // . . - // . . - // . . - // 1 o . . . o 2 - //------------------------------------------------ - Real3 m0 = m_node_coord[cell.nodeId(0)]; - Real3 m1 = m_node_coord[cell.nodeId(1)]; - Real3 m2 = m_node_coord[cell.nodeId(2)]; - - Real area = _computeAreaTriangle3(cell); // calculate area - - Real2 dPhi0(m1.y - m2.y, m2.x - m1.x); - Real2 dPhi1(m2.y - m0.y, m0.x - m2.x); - Real2 dPhi2(m0.y - m1.y, m1.x - m0.x); - - FixedMatrix<2, 3> b_matrix; - b_matrix(0, 0) = dPhi0.x; - b_matrix(0, 1) = dPhi1.x; - b_matrix(0, 2) = dPhi2.x; - - b_matrix(1, 0) = dPhi0.y; - b_matrix(1, 1) = dPhi1.y; - b_matrix(1, 2) = dPhi2.y; - - b_matrix.multInPlace(1.0 / (2.0 * area)); - - FixedMatrix<3, 3> int_cdPi_dPj = matrixMultiplication(matrixTranspose(b_matrix), b_matrix); - int_cdPi_dPj.multInPlace(area); - - //info() << "Cell=" << cell.localId(); - //std::cout << " int_cdPi_dPj="; - //int_cdPi_dPj.dump(std::cout); - //std::cout << "\n"; - - return int_cdPi_dPj; + if (options()->meshType == "QUAD4") + ARCANE_FATAL("Non supported meshType"); + else if (options()->meshType == "TRIA3") + _assembleBilinear<3>([this](const Cell& cell) { + return _computeElementMatrixTria3(cell); + }); + else + ARCANE_FATAL("Non supported meshType"); } /*---------------------------------------------------------------------------*/ +/** + * @brief Assembles the bilinear operator matrix for the FEM linear system. + * + * The method performs the following steps: + * 1. For each cell, retrieves the cell-specific constant `lambda`. + * 2. Computes element matrix using provided `compute_element_matrix` function. + * 3. Assembles global matrix by adding contributions from each cell's element + * matrix to the corresponding entries in the global matrix. + */ /*---------------------------------------------------------------------------*/ +template void FemModule:: -_assembleBilinearOperatorTRIA3() +_assembleBilinear(const std::function(const Cell&)>& compute_element_matrix) { auto node_dof(m_dofs_on_nodes.nodeDoFConnectivityView()); ENUMERATE_ (Cell, icell, allCells()) { Cell cell = *icell; - if (cell.type() != IT_Triangle3) - ARCANE_FATAL("Only Triangle3 cell type is supported"); - - auto K_e = _computeElementMatrixTRIA3(cell); // element stifness matrix - // # assemble elementary matrix into the global one - // # elementary terms are positionned into K according - // # to the rank of associated node in the mesh.nodes list - // for node1 in elem.nodes: - // inode1=elem.nodes.index(node1) # get position of node1 in nodes list - // for node2 in elem.nodes: - // inode2=elem.nodes.index(node2) - // K[node1.rank,node2.rank]=K[node1.rank,node2.rank]+K_e[inode1,inode2] + + auto K_e = compute_element_matrix(cell); // element matrix based on the provided function Int32 n1_index = 0; for (Node node1 : cell.nodes()) { Int32 n2_index = 0; for (Node node2 : cell.nodes()) { - // K[node1.rank,node2.rank]=K[node1.rank,node2.rank]+K_e[inode1,inode2] Real v = K_e(n1_index, n2_index); - // m_k_matrix(node1.localId(), node2.localId()) += v; if (node1.isOwn()) { m_linear_system.matrixAddValue(node_dof.dofId(node1, 0), node_dof.dofId(node2, 0), v); } @@ -598,6 +244,14 @@ _assembleBilinearOperatorTRIA3() } /*---------------------------------------------------------------------------*/ +/** + * @brief Solves the linear system and updates the solution vector. + * + * This method performs the following actions: + * 1. Solves the linear system to compute the solution. + * 2. Copies the computed solution from the DoF to the node values. + * 3. Synchronizes the updated node values. + */ /*---------------------------------------------------------------------------*/ void FemModule:: @@ -605,10 +259,6 @@ _solve() { m_linear_system.solve(); - // Re-Apply boundary conditions because the solver has modified the value - // of u on all nodes - _applyDirichletBoundaryConditions(); - { VariableDoFReal& dof_u(m_linear_system.solutionVariable()); // Copy RHS DoF to Node u @@ -621,31 +271,35 @@ _solve() } m_phi.synchronize(); - - const bool do_print = (allNodes().size() < 200); - if (do_print) { - ENUMERATE_ (Node, inode, allNodes()) { - Node node = *inode; - info() << "Phi[" << node.localId() << "][" << node.uniqueId() << "] = " - << m_phi[node]; - //info() << "Phi[]" << node.uniqueId() << " " - // << m_phi[node]; - } - } } /*---------------------------------------------------------------------------*/ +/** + * @brief Validates and prints the results of the FEM computation. + * + * This method performs the following actions: + * 1. If number of nodes < 200, prints the computed values for each node. + * 2. Retrieves the filename for the result file from options. + * 3. If a filename is provided, checks the computed results against result file. + * + * @note The result comparison uses a tolerance of 1.0e-4. + */ /*---------------------------------------------------------------------------*/ void FemModule:: -_checkResultFile() +_validateResults() { + if (allNodes().size() < 200) + ENUMERATE_ (Node, inode, allNodes()) { + Node node = *inode; + info() << "phi[" << node.localId() << "][" << node.uniqueId() << "] = " << m_phi[node]; + } + String filename = options()->resultFile(); - info() << "CheckResultFile filename=" << filename; - if (filename.empty()) - return; - const double epsilon = 1.0e-4; - checkNodeResultFile(traceMng(), filename, m_phi, epsilon); + info() << "ValidateResultFile filename=" << filename; + + if (!filename.empty()) + checkNodeResultFile(traceMng(), filename, m_phi, 1.0e-4); } /*---------------------------------------------------------------------------*/ @@ -654,4 +308,4 @@ _checkResultFile() ARCANE_REGISTER_MODULE_FEM(FemModule); /*---------------------------------------------------------------------------*/ -/*---------------------------------------------------------------------------*/ +/*---------------------------------------------------------------------------*/ \ No newline at end of file diff --git a/electrostatics/FemModule.h b/electrostatics/FemModule.h new file mode 100644 index 00000000..54654c58 --- /dev/null +++ b/electrostatics/FemModule.h @@ -0,0 +1,97 @@ +// -*- tab-width: 2; indent-tabs-mode: nil; coding: utf-8-with-signature -*- +//----------------------------------------------------------------------------- +// Copyright 2000-2024 CEA (www.cea.fr) IFPEN (www.ifpenergiesnouvelles.com) +// See the top-level COPYRIGHT file for details. +// SPDX-License-Identifier: Apache-2.0 +//----------------------------------------------------------------------------- +/*---------------------------------------------------------------------------*/ +/* FemModule.cc (C) 2022-2024 */ +/* */ +/* FemModule class definition. */ +/*---------------------------------------------------------------------------*/ +/*---------------------------------------------------------------------------*/ +#ifndef FEMMODULES_H +#define FEMMODULES_H + +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "IDoFLinearSystemFactory.h" +#include "Fem_axl.h" +#include "FemUtils.h" +#include "DoFLinearSystem.h" +#include "FemDoFsOnNodes.h" +#include "ArcaneFemFunctions.h" + +/*---------------------------------------------------------------------------*/ +/*---------------------------------------------------------------------------*/ + +using namespace Arcane; +using namespace Arcane::FemUtils; + +/*---------------------------------------------------------------------------*/ +/*---------------------------------------------------------------------------*/ +/*! + * \brief Module Fem. + */ +class FemModule +: public ArcaneFemObject +{ + public: + + explicit FemModule(const ModuleBuildInfo& mbi) + : ArcaneFemObject(mbi) + , m_dofs_on_nodes(mbi.subDomain()->traceMng()) + { + ICaseMng* cm = mbi.subDomain()->caseMng(); + cm->setTreatWarningAsError(true); + cm->setAllowUnkownRootElelement(false); + } + + public: + + //! Method called at each iteration + void compute() override; + + //! Method called at the beginning of the simulation + void startInit() override; + + VersionInfo versionInfo() const override + { + return VersionInfo(1, 0, 0); + } + + private: + + Real rho; + Real epsilon; + Real ElementNodes; + + DoFLinearSystem m_linear_system; + IItemFamily* m_dof_family = nullptr; + FemDoFsOnNodes m_dofs_on_nodes; + + private: + + void _doStationarySolve(); + void _getMaterialParameters(); + void _assembleBilinearOperator(); + void _solve(); + void _getE(); + void _assembleLinearOperator(); + void _validateResults(); + + FixedMatrix<3, 3> _computeElementMatrixTria3(Cell cell); + + template + void _assembleBilinear( const std::function(const Cell&)>& compute_element_matrix); +}; + +#endif \ No newline at end of file diff --git a/femutils/ArcaneFemFunctions.h b/femutils/ArcaneFemFunctions.h new file mode 100644 index 00000000..cb08e24d --- /dev/null +++ b/femutils/ArcaneFemFunctions.h @@ -0,0 +1,781 @@ +#ifndef ARCANE_FEM_FUNCTIONS_H +#define ARCANE_FEM_FUNCTIONS_H + +#include + +using namespace Arcane; + +/*---------------------------------------------------------------------------*/ +/** + * @brief Contains various functions & operations related to FEM calculations. + * + * The class provides methods organized into different nested classes for: + * - MeshOperation: Mesh related operations. + * - FeOperation2D: Finite element operations at element level. + * - BoundaryConditions2D: Boundary condition related operations. + */ +/*---------------------------------------------------------------------------*/ + +class ArcaneFemFunctions +{ + public: + + /*---------------------------------------------------------------------------*/ + /** + * @brief Provides methods for various mesh-related operations. + * + * This class includes static methods for computing geometric properties + * of mesh elements, such as the area of triangles, the length of edges, + * and the normal vectors of edges. + */ + /*---------------------------------------------------------------------------*/ + class MeshOperation + { + + public: + + /*---------------------------------------------------------------------------*/ + /** + * @brief Computes the volume of a tetrahedra defined by four nodes. + * + * This method calculates the volume using the scalar triple product formula. + * We do the following: + * 1. get the four nodes + * 2. ge the vector representing the edges of tetrahedron + * 3. compute volume using scalar triple product + */ + /*---------------------------------------------------------------------------*/ + + static inline Real computeVolumeTetra4(Cell cell, const VariableNodeReal3& node_coord) + { + Real3 vertex0 = node_coord[cell.nodeId(0)]; + Real3 vertex1 = node_coord[cell.nodeId(1)]; + Real3 vertex2 = node_coord[cell.nodeId(2)]; + Real3 vertex3 = node_coord[cell.nodeId(3)]; + + Real3 v0 = vertex1 - vertex0; + Real3 v1 = vertex2 - vertex0; + Real3 v2 = vertex3 - vertex0; + + return std::abs(Arcane::math::dot(v0, Arcane::math::cross(v1, v2))) / 6.0; + } + + /*---------------------------------------------------------------------------*/ + /** + * @brief Computes the area of a triangle defined by three nodes. + * + * This method calculates the area using the determinant formula for a triangle. + * The area is computed as half the value of the determinant of the matrix + * formed by the coordinates of the triangle's vertices. + */ + /*---------------------------------------------------------------------------*/ + + static inline Real computeAreaTria3(Cell cell, const VariableNodeReal3& node_coord) + { + Real3 vertex0 = node_coord[cell.nodeId(0)]; + Real3 vertex1 = node_coord[cell.nodeId(1)]; + Real3 vertex2 = node_coord[cell.nodeId(2)]; + + return 0.5 * ((vertex1.x - vertex0.x) * (vertex2.y - vertex0.y) - (vertex2.x - vertex0.x) * (vertex1.y - vertex0.y)); + } + + /*---------------------------------------------------------------------------*/ + /** + * @brief Computes the area of a quadrilateral defined by four nodes. + * + * This method calculates the area of a quadrilateral by breaking it down + * into two triangles and using the determinant formula. The area is computed + * as half the value of the determinant of the matrix formed by the coordinates + * of the quadrilateral's vertices. + */ + /*---------------------------------------------------------------------------*/ + + static inline Real computeAreaQuad4(Cell cell, const VariableNodeReal3& node_coord) + { + Real3 vertex0 = node_coord[cell.nodeId(0)]; + Real3 vertex1 = node_coord[cell.nodeId(1)]; + Real3 vertex2 = node_coord[cell.nodeId(2)]; + Real3 vertex3 = node_coord[cell.nodeId(3)]; + + return 0.5 * ((vertex1.x * vertex2.y + vertex2.x * vertex3.y + vertex3.x * vertex0.y + vertex0.x * vertex1.y) - (vertex2.x * vertex1.y + vertex3.x * vertex2.y + vertex0.x * vertex3.y + vertex1.x * vertex0.y)); + } + + /*---------------------------------------------------------------------------*/ + /** + * @brief Computes the barycenter (centroid) of a triangle. + * + * This method calculates the barycenter of a triangle defined by three nodes. + * The barycenter is computed as the average of the vertices' coordinates. + */ + /*---------------------------------------------------------------------------*/ + + static inline Real3 computeBaryCenterTria3(Cell cell, const VariableNodeReal3& node_coord) + { + Real3 vertex0 = node_coord[cell.nodeId(0)]; + Real3 vertex1 = node_coord[cell.nodeId(1)]; + Real3 vertex2 = node_coord[cell.nodeId(2)]; + + Real Center_x = (vertex0.x + vertex1.x + vertex2.x) / 3.; + Real Center_y = (vertex0.y + vertex1.y + vertex2.y) / 3.; + + return { Center_x, Center_y, 0 }; + } + + /*---------------------------------------------------------------------------*/ + /** + * @brief Computes the length of the edge defined by a given face. + * + * This method calculates Euclidean distance between the two nodes of the face. + */ + /*---------------------------------------------------------------------------*/ + + static inline Real computeLengthEdge2(Face face, const VariableNodeReal3& node_coord) + { + Real3 vertex0 = node_coord[face.nodeId(0)]; + Real3 vertex1 = node_coord[face.nodeId(1)]; + + Real dx = vertex1.x - vertex0.x; + Real dy = vertex1.y - vertex0.y; + + return math::sqrt(dx * dx + dy * dy); + } + + /*---------------------------------------------------------------------------*/ + /** + * @brief Computes the normalized edge normal for a given face. + * + * This method calculates normal vector to the edge defined by nodes of the face, + * normalizes it, and ensures the correct orientation. + */ + /*---------------------------------------------------------------------------*/ + + static inline Real2 computeNormalEdge2(Face face, const VariableNodeReal3& node_coord) + { + Real3 vertex0 = node_coord[face.nodeId(0)]; + Real3 vertex1 = node_coord[face.nodeId(1)]; + + if (!face.isSubDomainBoundaryOutside()) + std::swap(vertex0, vertex1); + + Real dx = vertex1.x - vertex0.x; + Real dy = vertex1.y - vertex0.y; + Real norm_N = math::sqrt(dx * dx + dy * dy); + + return { dy / norm_N, -dx / norm_N }; + } + }; + + /*---------------------------------------------------------------------------*/ + /** + * @brief Provides methods for finite element operations in 2D. + * + * This class includes static methods for calculating gradients of basis + * functions and integrals for P1 triangles in 2D finite element analysis. + * + * Reference Tri3 and Quad4 element in Arcane + * + * 0 o 1 o . . . . o 0 + * . . . . + * . . . . + * . . . . + * 1 o . . o 2 2 o . . . . o 3 + */ + /*---------------------------------------------------------------------------*/ + class FeOperation2D + { + + public: + + /*---------------------------------------------------------------------------*/ + /** + * @brief Computes the gradients of given function U for P1 triangles. + * + * This method calculates gradient operator ∇ Ui for a given P1 cell + * with i = 1,..,3 for the three values of Ui hence at cell nodes. + * The output is ∇ Ui is P0 (piece-wise constant) hence Real3 value + * per cell + * + * ∇ Ui = [ ∂U/∂x ∂U/∂y ∂U/∂z ] + * + * ∂U/∂x = ( u1*(y2 − y3) + u2*(y3 − y1) + u3*(y1 − y2) ) / (2*A) + * ∂U/∂y = ( u1*(x3 − x2) + u2*(x1 − x3) + u3*(x2 − x1) ) / (2*A) + * ∂U/∂z = 0 + * + * @note we can adapt the same for 3D by filling the third component + */ + /*---------------------------------------------------------------------------*/ + + static inline Real3 computeGradientTria3(Cell cell, const VariableNodeReal3& node_coord, const VariableNodeReal& u) + { + Real3 n0 = node_coord[cell.nodeId(0)]; + Real3 n1 = node_coord[cell.nodeId(1)]; + Real3 n2 = node_coord[cell.nodeId(2)]; + + Real u0 = u[cell.nodeId(0)]; + Real u1 = u[cell.nodeId(1)]; + Real u2 = u[cell.nodeId(2)]; + + Real A2 = ((n1.x - n0.x) * (n2.y - n0.y) - (n2.x - n0.x) * (n1.y - n0.y)); + + return Real3 ( (u0*(n1.y - n2.y) + u1*(n2.y - n0.y) + u2*(n0.y - n1.y)) / A2 , (u0*(n2.x - n1.x) + u1*(n0.x - n2.x) + u2*(n1.x - n0.x)) / A2 , 0); + } + + /*---------------------------------------------------------------------------*/ + /** + * @brief Computes the gradients of basis functions N for P1 triangles. + * + * This method calculates gradient operator ∇ Ni for a given P1 cell + * with i = 1,..,3 for the three shape function Ni hence output is a + * matrix of size 3x3 + * + * [ ∂N1/∂x ∂N1/∂y ∂N1/∂z ] + * ∇N = [ ∂N2/∂x ∂N2/∂y ∂N2/∂z ] + * [ ∂N3/∂x ∂N3/∂y ∂N3/∂z ] + * + * [ y2​−y3 x3​−x2 0 ] + * ∇N = 1/(2A) [ y3−y1 x1−x3 0 ] + * [ y1−y2 x2−x1 0 ] + * + * @note we can adapt the same for 3D by filling the third component + */ + /*---------------------------------------------------------------------------*/ + + static inline Real3x3 computeGradientTria3(Cell cell, const VariableNodeReal3& node_coord) + { + Real3 vertex0 = node_coord[cell.nodeId(0)]; + Real3 vertex1 = node_coord[cell.nodeId(1)]; + Real3 vertex2 = node_coord[cell.nodeId(2)]; + + Real A2 = ((vertex1.x - vertex0.x) * (vertex2.y - vertex0.y) - (vertex2.x - vertex0.x) * (vertex1.y - vertex0.y)); + + return Real3x3(Real3((vertex1.y - vertex2.y) / A2, (vertex2.x - vertex1.x) / A2, 0), + Real3((vertex2.y - vertex0.y) / A2, (vertex0.x - vertex2.x) / A2, 0), + Real3((vertex0.y - vertex1.y) / A2, (vertex1.x - vertex0.x) / A2, 0)); + } + + /*---------------------------------------------------------------------------*/ + /** + * @brief Computes the X gradients of basis functions N for P1 triangles. + * + * This method calculates gradient operator ∂/∂x of Ni for a given P1 + * cell with i = 1,..,3 for the three shape function Ni hence output + * is a vector of size 3 + * + * ∂N/∂x = [ ∂N1/∂x ∂N1/∂x ∂N3/∂x ] + * + * ∂N/∂x = 1/(2A) [ y2​−y3 y3−y1 y1−y2 ] + */ + /*---------------------------------------------------------------------------*/ + + static inline Real3 computeGradientXTria3(Cell cell, const VariableNodeReal3& node_coord) + { + Real3 vertex0 = node_coord[cell.nodeId(0)]; + Real3 vertex1 = node_coord[cell.nodeId(1)]; + Real3 vertex2 = node_coord[cell.nodeId(2)]; + + Real A2 = ((vertex1.x - vertex0.x) * (vertex2.y - vertex0.y) - (vertex2.x - vertex0.x) * (vertex1.y - vertex0.y)); + + return Real3((vertex1.y - vertex2.y) / A2, (vertex2.y - vertex0.y) / A2, (vertex0.y - vertex1.y) / A2); + } + + /*---------------------------------------------------------------------------*/ + /** + * @brief Computes the Y gradients of basis functions N for P1 triangles. + * + * This method calculates gradient operator ∂/∂y of Ni for a given P1 + * cell with i = 1,..,3 for the three shape function Ni hence output + * is a vector of size 3 + * + * ∂N/∂x = [ ∂N1/∂y ∂N1/∂y ∂N3/∂y ] + * + * ∂N/∂x = 1/(2A) [ x3​−x2 x1−x3 x2−x1 ] + */ + /*---------------------------------------------------------------------------*/ + + static inline Real3 computeGradientYTria3(Cell cell, const VariableNodeReal3& node_coord) + { + Real3 vertex0 = node_coord[cell.nodeId(0)]; + Real3 vertex1 = node_coord[cell.nodeId(1)]; + Real3 vertex2 = node_coord[cell.nodeId(2)]; + + Real A2 = ((vertex1.x - vertex0.x) * (vertex2.y - vertex0.y) - (vertex2.x - vertex0.x) * (vertex1.y - vertex0.y)); + + return Real3((vertex2.x - vertex1.x) / A2, (vertex0.x - vertex2.x) / A2, (vertex1.x - vertex0.x) / A2); + } + + /*---------------------------------------------------------------------------*/ + /** + * @brief Computes the X gradients of basis functions N for P1 Quad. + * + * This method calculates gradient operator ∂/∂x of Ni for a given P1 + * cell with i = 1,..,4 for the four shape function Ni hence output + * is a vector of size 4 + * + * ∂N/∂x = [ ∂N1/∂x ∂N1/∂x ∂N3/∂x ∂N4/∂x ] + * + * ∂N/∂x = 1/(2A) [ y2​−y3 y3−y0 y0−y1 y1−y2 ] + */ + /*---------------------------------------------------------------------------*/ + + static inline Real4 computeGradientXQuad4(Cell cell, const VariableNodeReal3& node_coord) + { + Real3 vertex0 = node_coord[cell.nodeId(0)]; + Real3 vertex1 = node_coord[cell.nodeId(1)]; + Real3 vertex2 = node_coord[cell.nodeId(2)]; + Real3 vertex3 = node_coord[cell.nodeId(3)]; + + Real A2 = ((vertex1.x * vertex2.y + vertex2.x * vertex3.y + vertex3.x * vertex0.y + vertex0.x * vertex1.y) - (vertex2.x * vertex1.y + vertex3.x * vertex2.y + vertex0.x * vertex3.y + vertex1.x * vertex0.y)); + + Real4 dx; + + dx[0] = (vertex2.y - vertex3.y) / A2; + dx[1] = (vertex3.y - vertex0.y) / A2; + dx[2] = (vertex0.y - vertex1.y) / A2; + dx[3] = (vertex1.y - vertex2.y) / A2; + + return dx; + } + + /*---------------------------------------------------------------------------* + /** + * @brief Computes the Y gradients of basis functions N for P1 Quad. + * + * This method calculates gradient operator ∂/∂y of Ni for a given P1 + * cell with i = 1,..,4 for the three shape function Ni hence output + * is a vector of size 4 + * + * ∂N/∂x = [ ∂N1/∂y ∂N1/∂y ∂N3/∂y ∂N4/∂y ] + * + * ∂N/∂x = 1/(2A) [ x3​−x2 x0−x3 x1−x0 x2−x1 ] + */ + /*---------------------------------------------------------------------------*/ + + static inline Real4 computeGradientYQuad4(Cell cell, const VariableNodeReal3& node_coord) + { + Real3 vertex0 = node_coord[cell.nodeId(0)]; + Real3 vertex1 = node_coord[cell.nodeId(1)]; + Real3 vertex2 = node_coord[cell.nodeId(2)]; + Real3 vertex3 = node_coord[cell.nodeId(3)]; + + Real A2 = ((vertex1.x * vertex2.y + vertex2.x * vertex3.y + vertex3.x * vertex0.y + vertex0.x * vertex1.y) - (vertex2.x * vertex1.y + vertex3.x * vertex2.y + vertex0.x * vertex3.y + vertex1.x * vertex0.y)); + + Real4 dy; + + dy[0] = (vertex3.x - vertex2.x) / A2; + dy[1] = (vertex0.x - vertex3.x) / A2; + dy[2] = (vertex1.x - vertex0.x) / A2; + dy[3] = (vertex2.x - vertex1.x) / A2; + + return dy; + } + + /*---------------------------------------------------------------------------*/ + /** + * @brief Computes the integral (u*v) for P1 triangles. + * + * here the element matrix will read + * + * [ 1/6 1/12 1/12 ] + * a = [ 1/12 1/6 1/12 ] + * [ 1/12 1/12 1/6 ] + * + */ + /*---------------------------------------------------------------------------*/ + + static inline Real3x3 computeUVTria3(Cell cell, const VariableNodeReal3& node_coord) + { + Real aii = 1. / 6.; + Real aij = 1. / 12.; + return Real3x3(Real3(aii, aij, aij), Real3(aij, aii, aij), Real3(aij, aij, aii)); + } + }; + + /*---------------------------------------------------------------------------*/ + /** + * @brief Provides methods for finite element operations in 3D. + * + * This class includes static methods for calculating gradients of basis + * functions and integrals for P1 triangles in 3D finite element analysis. + * + * Reference Tetra4 element in Arcane + * + * 3 o + * /|\ + * / | \ + * / | \ + * / o 2 \ + * / . . \ + * 0 o-----------o 1 + */ + /*---------------------------------------------------------------------------*/ + class FeOperation3D + { + public: + /*-------------------------------------------------------------------------*/ + /** + * @brief Computes the X gradients of basis functions N for P1 Tetrahedron. + * + * This method calculates gradient operator ∂/∂x of Ni for a given P1 + * cell with i = 1,..,4 for the four shape function Ni hence output + * is a vector of size 4 + * + * ∂N/∂x = [ ∂N1/∂x ∂N2/∂x ∂N3/∂x ∂N4/∂x ] + * + * ∂N/∂x = 1/(6V) [ b0 b1 b2 b3 ] + * + * where: + * b0 = (m1.y * (m3.z - m2.z) + m2.y * (m1.z - m3.z) + m3.y * (m2.z - m1.z)), + * b1 = (m0.y * (m2.z - m3.z) + m2.y * (m3.z - m0.z) + m3.y * (m0.z - m2.z)), + * b2 = (m0.y * (m3.z - m1.z) + m1.y * (m0.z - m3.z) + m3.y * (m1.z - m0.z)), + * b3 = (m0.y * (m1.z - m2.z) + m1.y * (m2.z - m0.z) + m2.y * (m0.z - m1.z)). + * + */ + /*-------------------------------------------------------------------------*/ + + static inline Real4 computeGradientXTetra4(Cell cell, const VariableNodeReal3& node_coord) + { + Real3 vertex0 = node_coord[cell.nodeId(0)]; + Real3 vertex1 = node_coord[cell.nodeId(1)]; + Real3 vertex2 = node_coord[cell.nodeId(2)]; + Real3 vertex3 = node_coord[cell.nodeId(3)]; + + Real3 v0 = vertex1 - vertex0; + Real3 v1 = vertex2 - vertex0; + Real3 v2 = vertex3 - vertex0; + + // 6 x Volume of tetrahedron + Real V6 = std::abs(Arcane::math::dot(v0, Arcane::math::cross(v1, v2))); + + Real4 dx; + + dx[0] = (vertex1.y * (vertex3.z - vertex2.z) + vertex2.y * (vertex1.z - vertex3.z) + vertex3.y * (vertex2.z - vertex1.z))/V6; + dx[1] = (vertex0.y * (vertex2.z - vertex3.z) + vertex2.y * (vertex3.z - vertex0.z) + vertex3.y * (vertex0.z - vertex2.z))/V6; + dx[2] = (vertex0.y * (vertex3.z - vertex1.z) + vertex1.y * (vertex0.z - vertex3.z) + vertex3.y * (vertex1.z - vertex0.z))/V6; + dx[3] = (vertex0.y * (vertex1.z - vertex2.z) + vertex1.y * (vertex2.z - vertex0.z) + vertex2.y * (vertex0.z - vertex1.z))/V6; + + return dx; + } + + /*-------------------------------------------------------------------------*/ +/** + * @brief Computes the Y gradients of basis functions N for P1 Tetrahedron. + * + * This method calculates gradient operator ∂/∂y of Ni for a given P1 + * cell with i = 1,..,4 for the four shape functions Ni, hence the output + * is a vector of size 4. + * + * ∂N/∂y = [ ∂N1/∂y ∂N2/∂y ∂N3/∂y ∂N4/∂y ] + * + * ∂N/∂y = 1/(6V) [ c0 c1 c2 c3 ] + * + * where: + * c0 = (m1.z * (m3.x - m2.x) + m2.z * (m1.x - m3.x) + m3.z * (m2.x - m1.x)), + * c1 = (m0.z * (m2.x - m3.x) + m2.z * (m3.x - m0.x) + m3.z * (m0.x - m2.x)), + * c2 = (m0.z * (m3.x - m1.x) + m1.z * (m0.x - m3.x) + m3.z * (m1.x - m0.x)), + * c3 = (m0.z * (m1.x - m2.x) + m1.z * (m2.x - m0.x) + m2.z * (m0.x - m1.x)). + * + */ +/*-------------------------------------------------------------------------*/ + +static inline Real4 computeGradientYTetra4(Cell cell, const VariableNodeReal3& node_coord) +{ + Real3 vertex0 = node_coord[cell.nodeId(0)]; + Real3 vertex1 = node_coord[cell.nodeId(1)]; + Real3 vertex2 = node_coord[cell.nodeId(2)]; + Real3 vertex3 = node_coord[cell.nodeId(3)]; + + Real3 v0 = vertex1 - vertex0; + Real3 v1 = vertex2 - vertex0; + Real3 v2 = vertex3 - vertex0; + + // 6 x Volume of tetrahedron + Real V6 = std::abs(Arcane::math::dot(v0, Arcane::math::cross(v1, v2))); + + Real4 dy; + + dy[0] = (vertex1.z * (vertex3.x - vertex2.x) + vertex2.z * (vertex1.x - vertex3.x) + vertex3.z * (vertex2.x - vertex1.x))/V6; + dy[1] = (vertex0.z * (vertex2.x - vertex3.x) + vertex2.z * (vertex3.x - vertex0.x) + vertex3.z * (vertex0.x - vertex2.x))/V6; + dy[2] = (vertex0.z * (vertex3.x - vertex1.x) + vertex1.z * (vertex0.x - vertex3.x) + vertex3.z * (vertex1.x - vertex0.x))/V6; + dy[3] = (vertex0.z * (vertex1.x - vertex2.x) + vertex1.z * (vertex2.x - vertex0.x) + vertex2.z * (vertex0.x - vertex1.x))/V6; + + return dy; +} + +/*-------------------------------------------------------------------------*/ +/** + * @brief Computes the Z gradients of basis functions N for P1 Tetrahedron. + * + * This method calculates gradient operator ∂/∂z of Ni for a given P1 + * cell with i = 1,..,4 for the four shape functions Ni, hence the output + * is a vector of size 4. + * + * ∂N/∂z = [ ∂N1/∂z ∂N2/∂z ∂N3/∂z ∂N4/∂z ] + * + * ∂N/∂z = 1/(6V) [ d0 d1 d2 d3 ] + * + * where: + * d0 = (m1.x * (m3.y - m2.y) + m2.x * (m1.y - m3.y) + m3.x * (m2.y - m1.y)), + * d1 = (m0.x * (m2.y - m3.y) + m2.x * (m3.y - m0.y) + m3.x * (m0.y - m2.y)), + * d2 = (m0.x * (m3.y - m1.y) + m1.x * (m0.y - m3.y) + m3.x * (m1.y - m0.y)), + * d3 = (m0.x * (m1.y - m2.y) + m1.x * (m2.y - m0.y) + m2.x * (m0.y - m1.y)). + * + */ +/*-------------------------------------------------------------------------*/ + +static inline Real4 computeGradientZTetra4(Cell cell, const VariableNodeReal3& node_coord) +{ + Real3 vertex0 = node_coord[cell.nodeId(0)]; + Real3 vertex1 = node_coord[cell.nodeId(1)]; + Real3 vertex2 = node_coord[cell.nodeId(2)]; + Real3 vertex3 = node_coord[cell.nodeId(3)]; + + Real3 v0 = vertex1 - vertex0; + Real3 v1 = vertex2 - vertex0; + Real3 v2 = vertex3 - vertex0; + + // 6 x Volume of tetrahedron + Real V6 = std::abs(Arcane::math::dot(v0, Arcane::math::cross(v1, v2))); + + Real4 dz; + + dz[0] = (vertex1.x * (vertex3.y - vertex2.y) + vertex2.x * (vertex1.y - vertex3.y) + vertex3.x * (vertex2.y - vertex1.y))/V6; + dz[1] = (vertex0.x * (vertex2.y - vertex3.y) + vertex2.x * (vertex3.y - vertex0.y) + vertex3.x * (vertex0.y - vertex2.y))/V6; + dz[2] = (vertex0.x * (vertex3.y - vertex1.y) + vertex1.x * (vertex0.y - vertex3.y) + vertex3.x * (vertex1.y - vertex0.y))/V6; + dz[3] = (vertex0.x * (vertex1.y - vertex2.y) + vertex1.x * (vertex2.y - vertex0.y) + vertex2.x * (vertex0.y - vertex1.y))/V6; + + return dz; +} + + }; + + /*---------------------------------------------------------------------------*/ + /** + * @brief Provides methods for applying boundary conditions in 2D FEM problems. + * + * This class includes static methods for applying Neumann boundary conditions + * to the right-hand side (RHS) of finite element method equations in 2D. + */ + /*---------------------------------------------------------------------------*/ + class BoundaryConditions2D + { + public: + + /*---------------------------------------------------------------------------*/ + /** + * @brief Applies a constant source term to the RHS vector. + * + * This method adds a constant source term `qdot` to the RHS vector for each + * node in the mesh. The contribution to each node is weighted by the area of + * the cell and evenly distributed among the number of nodes of the cell. + * + * @param [IN] qdot : The constant source term. + * @param [IN] mesh : The mesh containing all cells. + * @param [IN] node_dof : DOF connectivity view. + * @param [IN] node_coord : The coordinates of the nodes. + * @param [OUT] rhs_values : The RHS values to update. + */ + /*---------------------------------------------------------------------------*/ + + static inline void applyConstantSourceToRhs(const Real& qdot, IMesh* mesh, const Arcane::IndexedNodeDoFConnectivityView& node_dof, const Arcane::VariableNodeReal3& node_coord, Arcane::VariableDoFReal& rhs_values) + { + ENUMERATE_ (Cell, icell, mesh->allCells()) { + Cell cell = *icell; + Real area = ArcaneFemFunctions::MeshOperation::computeAreaTria3(cell, node_coord); + for (Node node : cell.nodes()) { + if (node.isOwn()) + rhs_values[node_dof.dofId(node, 0)] += qdot * area / cell.nbNode(); + } + } + } + + /*---------------------------------------------------------------------------*/ + /** + * @brief Applies a manufactured source term to the RHS vector. + * + * This method adds a manufactured source term to the RHS vector for each + * node in the mesh. The contribution to each node is weighted by the area of + * the cell and evenly distributed among the nodes of the cell. + * + * @param [IN] qdot : The constant source term. + * @param [IN] mesh : The mesh containing all cells. + * @param [IN] node_dof : DOF connectivity view. + * @param [IN] node_coord : The coordinates of the nodes. + * @param [OUT] rhs_values : The RHS values to update. + */ + /*---------------------------------------------------------------------------*/ + + static inline void applyManufacturedSourceToRhs(IBinaryMathFunctor* manufactured_source, IMesh* mesh, const Arcane::IndexedNodeDoFConnectivityView& node_dof, const Arcane::VariableNodeReal3& node_coord, Arcane::VariableDoFReal& rhs_values) + { + ENUMERATE_ (Cell, icell, mesh->allCells()) { + Cell cell = *icell; + Real area = ArcaneFemFunctions::MeshOperation::computeAreaTria3(cell, node_coord); + Real3 bcenter = ArcaneFemFunctions::MeshOperation::computeBaryCenterTria3(cell, node_coord); + + for (Node node : cell.nodes()) { + if (node.isOwn()) + rhs_values[node_dof.dofId(node, 0)] += manufactured_source->apply(area / cell.nbNode(), bcenter); + } + } + } + + /*---------------------------------------------------------------------------*/ + /** + * @brief Applies Neumann conditions to the right-hand side (RHS) values. + * + * This method updates the RHS values of the finite element method equations + * based on the provided Neumann boundary condition. The boundary condition + * can specify a value or its components along the x and y directions. + * + * @param [IN] bs : The Neumann boundary condition values. + * @param [IN] node_dof : Connectivity view for degrees of freedom at nodes. + * @param [IN] node_coord : Coordinates of the nodes in the mesh. + * @param [OUT] rhs_values : The right-hand side values to be updated. + */ + /*---------------------------------------------------------------------------*/ + + static inline void applyNeumannToRhs(const CaseOptionsFem::CaseOptionNeumannBoundaryConditionValue* bs, const Arcane::IndexedNodeDoFConnectivityView& node_dof, const Arcane::VariableNodeReal3& node_coord, Arcane::VariableDoFReal& rhs_values) + { + FaceGroup group = bs->surface(); + + Real value = 0.0; + Real valueX = 0.0; + Real valueY = 0.0; + bool hasValue = bs->value.isPresent(); + bool hasValueX = bs->valueX.isPresent(); + bool hasValueY = bs->valueY.isPresent(); + + if (hasValue) { + value = bs->value(); + } + else { + if (hasValueX) + valueX = bs->valueX(); + if (hasValueY) + valueY = bs->valueY(); + } + + ENUMERATE_ (Face, iface, group) { + Face face = *iface; + + Real length = ArcaneFemFunctions::MeshOperation::computeLengthEdge2(face, node_coord); + Real2 normal = ArcaneFemFunctions::MeshOperation::computeNormalEdge2(face, node_coord); + + for (Node node : iface->nodes()) { + if (!node.isOwn()) + continue; + Real rhs_value = 0.0; + + if (hasValue) { + rhs_value = value * length / 2.0; + } + else { + rhs_value = (normal.x * valueX + normal.y * valueY) * length / 2.0; + } + + rhs_values[node_dof.dofId(node, 0)] += rhs_value; + } + } + } + + /*---------------------------------------------------------------------------*/ + /** + * @brief Applies Dirichlet boundary conditions to RHS and LHS. + * + * Updates the LHS matrix and RHS vector to enforce Dirichlet conditions. + * + * - For LHS matrix `A`, the diagonal term for the Dirichlet DOF is set to `P`. + * - For RHS vector `b`, the Dirichlet DOF term is scaled by `P`. + * + * @param [IN] bs : Boundary condition values. + * @param [IN] node_dof : DOF connectivity view. + * @param [IN] node_coord : Node coordinates. + * @param [OUT] m_linear_system : Linear system for LHS. + * @param [OUT] rhs_values RHS : RHS values to update. + */ + /*---------------------------------------------------------------------------*/ + static inline void applyDirichletToLhsAndRhs(const CaseOptionsFem::CaseOptionDirichletBoundaryConditionValue* bs, const Arcane::IndexedNodeDoFConnectivityView& node_dof, const Arcane::VariableNodeReal3& node_coord, FemUtils::DoFLinearSystem& m_linear_system, Arcane::VariableDoFReal& rhs_values) + { + FaceGroup group = bs->surface(); + Real value = bs->value(); + Real Penalty = bs->penalty(); + + ENUMERATE_ (Face, iface, group) { + for (Node node : iface->nodes()) { + if (node.isOwn()) { + m_linear_system.matrixSetValue(node_dof.dofId(node, 0), node_dof.dofId(node, 0), Penalty); + Real u_g = Penalty * value; + rhs_values[node_dof.dofId(node, 0)] = u_g; + } + } + } + } + + /*---------------------------------------------------------------------------*/ + /** + * @brief Applies Point Dirichlet boundary conditions to RHS and LHS. + * + * Updates the LHS matrix and RHS vector to enforce the Dirichlet. + * + * - For LHS matrix `A`, the diagonal term for the Dirichlet DOF is set to `P`. + * - For RHS vector `b`, the Dirichlet DOF term is scaled by `P`. + * + * @param [IN] bs : Boundary condition values. + * @param [IN] node_dof : DOF connectivity view. + * @param [IN] node_coord : Node coordinates. + * @param [OUT] m_linear_system : Linear system for LHS. + * @param [OUT] rhs_values RHS : RHS values to update. + */ + /*---------------------------------------------------------------------------*/ + static inline void applyPointDirichletToLhsAndRhs(const CaseOptionsFem::CaseOptionDirichletPointConditionValue* bs, const Arcane::IndexedNodeDoFConnectivityView& node_dof, const Arcane::VariableNodeReal3& node_coord, FemUtils::DoFLinearSystem& m_linear_system, Arcane::VariableDoFReal& rhs_values) + { + NodeGroup group = bs->node(); + Real value = bs->value(); + Real Penalty = bs->penalty(); + + ENUMERATE_ (Node, inode, group) { + Node node = *inode; + if (node.isOwn()) { + m_linear_system.matrixSetValue(node_dof.dofId(node, 0), node_dof.dofId(node, 0), Penalty); + Real u_g = Penalty * value; + rhs_values[node_dof.dofId(node, 0)] = u_g; + } + } + } + + /*---------------------------------------------------------------------------*/ + /** + * @brief Applies Manufactured Dirichlet boundary conditions to RHS and LHS. + * + * Updates the LHS matrix and RHS vector to enforce the Dirichlet. + * + * - For LHS matrix `A`, the diagonal term for the Dirichlet DOF is set to `P`. + * - For RHS vector `b`, the Dirichlet DOF term is scaled by `P`. + * + * @param [IN] manufactured_dirichlet : External function for Dirichlet. + * @param [IN] group : Group of all external faces. + * @param [IN] bs : Boundary condition values. + * @param [IN] node_dof : DOF connectivity view. + * @param [IN] node_coord : Node coordinates. + * @param [OUT] m_linear_system : Linear system for LHS. + * @param [OUT] rhs_values RHS : RHS values to update. + */ + /*---------------------------------------------------------------------------*/ + static inline void applyManufacturedDirichletToLhsAndRhs(IBinaryMathFunctor* manufactured_dirichlet, const Arcane::Real& lambda, const Arcane::FaceGroup& group, const CaseOptionsFem::CaseOptionManufacturedSolutionValue* bs, const Arcane::IndexedNodeDoFConnectivityView& node_dof, const Arcane::VariableNodeReal3& node_coord, FemUtils::DoFLinearSystem& m_linear_system, Arcane::VariableDoFReal& rhs_values) + { + Real Penalty = bs->penalty(); + + ENUMERATE_ (Face, iface, group) { + for (Node node : iface->nodes()) { + if (node.isOwn()) { + m_linear_system.matrixSetValue(node_dof.dofId(node, 0), node_dof.dofId(node, 0), Penalty); + double tt = 1.; + Real u_g = Penalty * manufactured_dirichlet->apply(tt, node_coord[node]); + rhs_values[node_dof.dofId(node, 0)] = u_g; + } + } + } + } + }; +}; + +#endif // ARCANE_FEM_FUNCTIONS_H diff --git a/femutils/CMakeLists.txt b/femutils/CMakeLists.txt index 10ee83af..3376abd4 100644 --- a/femutils/CMakeLists.txt +++ b/femutils/CMakeLists.txt @@ -10,6 +10,7 @@ add_library(FemUtils CsrFormatMatrix.cc FemDoFsOnNodes.h FemDoFsOnNodes.cc + ArcaneFemFunctions.h AlephDoFLinearSystem.cc IDoFLinearSystemFactory.h AlephDoFLinearSystemFactory_axl.h diff --git a/femutils/FemUtils.h b/femutils/FemUtils.h index 79793cce..337f4f30 100644 --- a/femutils/FemUtils.h +++ b/femutils/FemUtils.h @@ -22,11 +22,25 @@ #include #include +#include +#include + #include /*---------------------------------------------------------------------------*/ /*---------------------------------------------------------------------------*/ +struct Real4 +{ + Arcane::Real data[4]; + + Arcane::Real& operator[](std::size_t i) { return data[i]; } + const Arcane::Real& operator[](std::size_t i) const { return data[i]; } +}; + +/*---------------------------------------------------------------------------*/ +/*---------------------------------------------------------------------------*/ + namespace Arcane::FemUtils { @@ -84,11 +98,131 @@ class FixedMatrix } } + //! Define the addition operator + FixedMatrix operator+(const FixedMatrix& other) const + { + FixedMatrix result; + for (Arcane::Int32 i = 0; i < N; ++i) { + for (Arcane::Int32 j = 0; j < M; ++j) { + result(i, j) = (*this)(i, j) + other(i, j); + } + } + return result; + } + + //! Define the subtraction operator + FixedMatrix operator-(const FixedMatrix& other) const + { + FixedMatrix result; + for (Arcane::Int32 i = 0; i < N; ++i) { + for (Arcane::Int32 j = 0; j < M; ++j) { + result(i, j) = (*this)(i, j) - other(i, j); + } + } + return result; + } + + //! Define the unary negation operator + FixedMatrix operator-() const + { + FixedMatrix result; + for (Arcane::Int32 i = 0; i < N; ++i) { + for (Arcane::Int32 j = 0; j < M; ++j) { + result(i, j) = -(*this)(i, j); + } + } + return result; + } + + //! Scalar multiplication: FixedMatrix * scalar + FixedMatrix operator*(Real scalar) const + { + FixedMatrix result; + for (Arcane::Int32 i = 0; i < N; ++i) { + for (Arcane::Int32 j = 0; j < M; ++j) { + result(i, j) = (*this)(i, j) * scalar; + } + } + return result; + } + + //! Friend function for scalar multiplication: scalar * FixedMatrix + friend FixedMatrix operator*(Real scalar, const FixedMatrix& matrix) + { + FixedMatrix result; + for (Arcane::Int32 i = 0; i < N; ++i) { + for (Arcane::Int32 j = 0; j < M; ++j) { + result(i, j) = scalar * matrix(i, j); + } + } + return result; + } + private: std::array m_values = {}; }; +/*---------------------------------------------------------------------------*/ +// Outer product of two Real3 vectors to produce a FixedMatrix<3, 3> +/*---------------------------------------------------------------------------*/ +inline FixedMatrix<3, 3> operator^(const Arcane::Real3& lhs, const Arcane::Real3& rhs) +{ + FixedMatrix<3, 3> result; + for (Arcane::Int32 i = 0; i < 3; ++i) { + for (Arcane::Int32 j = 0; j < 3; ++j) { + result(i, j) = lhs[i] * rhs[j]; + } + } + return result; +} + +/*---------------------------------------------------------------------------*/ +// Outer product of two Real4 vectors to produce a FixedMatrix<4, 4> +/*---------------------------------------------------------------------------*/ +inline FixedMatrix<4, 4> operator^(const Real4& lhs, const Real4& rhs) +{ + FixedMatrix<4, 4> result; + for (Arcane::Int32 i = 0; i < 4; ++i) { + for (Arcane::Int32 j = 0; j < 4; ++j) { + result(i, j) = lhs[i] * rhs[j]; + } + } + return result; +} + +/*---------------------------------------------------------------------------*/ +// Define the conversion from Real3x3 to FixedMatrix<3, 3> +/*---------------------------------------------------------------------------*/ +inline FixedMatrix<3, 3> convertReal3x3ToFixedMatrix(const Arcane::Real3x3& rhs) +{ + FixedMatrix<3, 3> result; + for (Arcane::Int32 i = 0; i < 3; ++i) { + for (Arcane::Int32 j = 0; j < 3; ++j) { + result(i, j) = rhs(i, j); + } + } + return result; +} + +/*---------------------------------------------------------------------------*/ +// Overload operator+ to handle FixedMatrix<3, 3> + Real3x3 +/*---------------------------------------------------------------------------*/ +inline FixedMatrix<3, 3> operator+(const FixedMatrix<3, 3>& lhs, const Arcane::Real3x3& rhs) +{ + FixedMatrix<3, 3> converted_rhs = convertReal3x3ToFixedMatrix(rhs); + return lhs + converted_rhs; +} + +/*---------------------------------------------------------------------------*/ +// Overload operator+ to handle Real3x3 + FixedMatrix<3, 3> +/*---------------------------------------------------------------------------*/ +inline FixedMatrix<3, 3> operator+(const Arcane::Real3x3& lhs, const FixedMatrix<3, 3>& rhs) +{ + FixedMatrix<3, 3> converted_lhs = convertReal3x3ToFixedMatrix(lhs); + return converted_lhs + rhs; +} + /*---------------------------------------------------------------------------*/ /*---------------------------------------------------------------------------*/ diff --git a/fourier/CMakeLists.txt b/fourier/CMakeLists.txt index a943ff5c..22f6f422 100644 --- a/fourier/CMakeLists.txt +++ b/fourier/CMakeLists.txt @@ -1,4 +1,5 @@ add_executable(Fourier + FemModule.h FemModule.cc main.cc Fem_axl.h diff --git a/fourier/Fem.axl b/fourier/Fem.axl index 062c89f4..99d60d40 100644 --- a/fourier/Fem.axl +++ b/fourier/Fem.axl @@ -15,9 +15,6 @@ manufactured FEM solution on nodes - - Boolean which is true if Dirichlet node - Node Coordinates from Arcane variable @@ -35,26 +32,6 @@ Type of mesh provided to the solver - - - Method via which Dirichlet boundary condition is imposed - - - - - Penalty value for enforcing Dirichlet condition - - - - - Function for Dirichlet boundary condition - - - - - Function for Source condition - - + + + Method via which Dirichlet boundary condition is imposed + + + + + Penalty value for enforcing Dirichlet condition + + + + + + + + Dirichlet boundary condition + + + + Function for Dirichlet boundary condition + + + + + Method via which Dirichlet boundary condition is imposed + + + + + Penalty value for enforcing Dirichlet condition + + + + + Function for manufactured source term condition + + @@ -128,6 +146,16 @@ Value of the point Dirichlet condition + + + Method via which Dirichlet boundary condition is imposed + + + + + Penalty value for enforcing Dirichlet condition + + diff --git a/fourier/FemModule.cc b/fourier/FemModule.cc index cf4840b2..01cc540c 100644 --- a/fourier/FemModule.cc +++ b/fourier/FemModule.cc @@ -11,94 +11,60 @@ /*---------------------------------------------------------------------------*/ /*---------------------------------------------------------------------------*/ -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include - -#include "IDoFLinearSystemFactory.h" -#include "Fem_axl.h" -#include "FemUtils.h" -#include "DoFLinearSystem.h" -#include "FemDoFsOnNodes.h" +#include "FemModule.h" /*---------------------------------------------------------------------------*/ +/** + * @brief Initializes the FemModule at the start of the simulation. + * + * - initializes degrees of freedom (DoFs) on nodes. + * - builds support for manufactured test case (optional). + */ /*---------------------------------------------------------------------------*/ -using namespace Arcane; -using namespace Arcane::FemUtils; - -/*---------------------------------------------------------------------------*/ -/*---------------------------------------------------------------------------*/ -/*! - * \brief Module Fem. - */ -class FemModule -: public ArcaneFemObject +void FemModule:: +startInit() { - public: - - explicit FemModule(const ModuleBuildInfo& mbi) - : ArcaneFemObject(mbi) - , m_dofs_on_nodes(mbi.subDomain()->traceMng()) - { - ICaseMng* cm = mbi.subDomain()->caseMng(); - cm->setTreatWarningAsError(true); - cm->setAllowUnkownRootElelement(false); - } - - public: + info() << "Module Fem INIT"; - //! Method called at each iteration - void compute() override; + m_dofs_on_nodes.initialize(mesh(), 1); + m_dof_family = m_dofs_on_nodes.dofFamily(); - //! Method called at the beginning of the simulation - void startInit() override; + if (options()->manufacturedSolution.isPresent()) { + const auto& bs = options()->manufacturedSolution()[0]; + + if (bs->manufacturedSource.isPresent()) { + ICaseFunction* opt_function_source = bs->manufacturedSource.function(); + IStandardFunction* scf_source = bs->manufacturedSource.standardFunction(); + if (!scf_source) + ARCANE_FATAL("No standard case function for option 'manufactured-source-condition'"); + auto* functorS = scf_source->getFunctorRealReal3ToReal(); + if (!functorS) + ARCANE_FATAL("Standard function '{0}' is not convertible to f(Real,Real3) -> Real", opt_function_source->name()); + m_manufactured_source = functorS; + } - VersionInfo versionInfo() const override - { - return VersionInfo(1, 0, 0); + if (bs->manufacturedDirichlet.isPresent()) { + ICaseFunction* opt_function = bs->manufacturedDirichlet.function(); + IStandardFunction* scf = bs->manufacturedDirichlet.standardFunction(); + if (!scf) + ARCANE_FATAL("No standard case function for option 'manufactured-dirichlet-condition'"); + auto* functor = scf->getFunctorRealReal3ToReal(); + if (!functor) + ARCANE_FATAL("Standard function '{0}' is not convertible to f(Real,Real3) -> Real", opt_function->name()); + m_manufactured_dirichlet = functor; + } } - - private: - - Real lambda; - Real qdot; - Real ElementNodes; - - DoFLinearSystem m_linear_system; - IItemFamily* m_dof_family = nullptr; - FemDoFsOnNodes m_dofs_on_nodes; - - private: - - void _doStationarySolve(); - void _getMaterialParameters(); - void _assembleBilinearOperatorTRIA3(); - void _assembleBilinearOperatorQUAD4(); - void _solve(); - void _assembleLinearOperator(); - void _applyDirichletBoundaryConditions(); - void _checkResultFile(); - FixedMatrix<3, 3> _computeElementMatrixTRIA3(Cell cell); - FixedMatrix<4, 4> _computeElementMatrixQUAD4(Cell cell); - Real _computeAreaTriangle3(Cell cell); - Real _computeAreaQuad4(Cell cell); - Real _computeEdgeLength2(Face face); - Real2 _computeEdgeNormal2(Face face); - IBinaryMathFunctor* m_manufactured_dirichlet = nullptr; - IBinaryMathFunctor* m_manufactured_source = nullptr; - -}; +} /*---------------------------------------------------------------------------*/ +/** + * @brief Performs the main computation for the FemModule. + * + * - Stops the time loop after 1 iteration since the equation is steady state. + * - Resets, configures, and initializes the linear system. + * - Executes the stationary solve. + */ /*---------------------------------------------------------------------------*/ void FemModule:: @@ -126,608 +92,206 @@ compute() } /*---------------------------------------------------------------------------*/ -/*---------------------------------------------------------------------------*/ - -void FemModule:: -startInit() -{ - info() << "Module Fem INIT"; - - m_dofs_on_nodes.initialize(mesh(), 1); - m_dof_family = m_dofs_on_nodes.dofFamily(); - - // Check we have user function for manufactured boundary/source condition - if(options()->manufacturedDirichletCondition()){ - ICaseFunction* opt_function = options()->manufacturedDirichletCondition.function(); - IStandardFunction* scf = options()->manufacturedDirichletCondition.standardFunction(); - if (!scf) - ARCANE_FATAL("No standard case function for option 'manufactured-dirichlet-condition'"); - auto* functor = scf->getFunctorRealReal3ToReal(); - if (!functor) - ARCANE_FATAL("Standard function '{0}' is not convertible to f(Real,Real3) -> Real", opt_function->name()); - m_manufactured_dirichlet = functor; - } - - if(options()->manufacturedSourceCondition()){ - ICaseFunction* opt_function_source = options()->manufacturedSourceCondition.function(); - IStandardFunction* scf_source = options()->manufacturedSourceCondition.standardFunction(); - if (!scf_source) - ARCANE_FATAL("No standard case function for option 'manufactured-source-condition'"); - auto* functorS = scf_source->getFunctorRealReal3ToReal(); - if (!functorS) - ARCANE_FATAL("Standard function '{0}' is not convertible to f(Real,Real3) -> Real", opt_function_source->name()); - m_manufactured_source = functorS; - } - - -} - -/*---------------------------------------------------------------------------*/ +/** + * @brief Performs a stationary solve for the FEM system. + * + * This method follows a sequence of steps to solve FEM system: + * 1. _getMaterialParameters() Retrieves material parameters via + * 2. _assembleBilinearOperator() Assembles the FEM matrix A + * 3. _assembleLinearOperator() Assembles the FEM RHS vector b + * 4. _solve() Solves for solution vector u = A^-1*b + * 5. _validateResults() Regression test + */ /*---------------------------------------------------------------------------*/ void FemModule:: _doStationarySolve() { - // get material parameters _getMaterialParameters(); - - // apply Dirichlet BC - _applyDirichletBoundaryConditions(); - - // Assemble the FEM bilinear operator (LHS - matrix A) - if (options()->meshType == "QUAD4") - _assembleBilinearOperatorQUAD4(); - else - _assembleBilinearOperatorTRIA3(); - - // Assemble the FEM linear operator (RHS - vector b) + _assembleBilinearOperator(); _assembleLinearOperator(); - - // # T=linalg.solve(K,RHS) _solve(); - - // Check results - _checkResultFile(); + _validateResults(); } /*---------------------------------------------------------------------------*/ +/** + * @brief Retrieves and sets the material parameters for the simulation. + * + * This method initializes: + * - material properties: + * # thermal conductivity coefficient (`lambda`) + * # heat source term (`qdot`) + */ /*---------------------------------------------------------------------------*/ void FemModule:: _getMaterialParameters() { - info() << "Get material parameters..."; - lambda = options()->lambda(); - qdot = options()->qdot(); - ElementNodes = 3.; + info() << "Get material parameters "; - if (options()->meshType == "QUAD4") - ElementNodes = 4.; + lambda = options()->lambda(); + qdot = options()->qdot(); ENUMERATE_ (Cell, icell, allCells()) { Cell cell = *icell; m_cell_lambda[cell] = lambda; - } + } for (const auto& bs : options()->materialProperty()) { CellGroup group = bs->volume(); Real value = bs->lambda(); - info() << "Lambda for group=" << group.name() << " v=" << value; + info() << "Lambda for group= " << group.name() << " v=" << value; ENUMERATE_ (Cell, icell, group) { Cell cell = *icell; m_cell_lambda[cell] = value; - } - } -} - - -/*---------------------------------------------------------------------------*/ -/*---------------------------------------------------------------------------*/ - -void FemModule:: -_applyDirichletBoundaryConditions() -{ - // Handle all the Dirichlet boundary conditions. - // In the 'arc' file, there are in the following format: - // - // Haut - // 21.0 - // - - for (const auto& bs : options()->dirichletBoundaryCondition()) { - FaceGroup group = bs->surface(); - Real value = bs->value(); - info() << "Apply Dirichlet boundary condition surface=" << group.name() << " v=" << value; - ENUMERATE_ (Face, iface, group) { - for (Node node : iface->nodes()) { - m_u[node] = value; - m_u_dirichlet[node] = true; - } - } - } - - for (const auto& bs : options()->dirichletPointCondition()) { - NodeGroup group = bs->node(); - Real value = bs->value(); - info() << "Apply Dirichlet point condition node=" << group.name() << " v=" << value; - ENUMERATE_ (Node, inode, group) { - Node node = *inode; - m_u[node] = value; - m_u_dirichlet[node] = true; - } - } - - if(options()->manufacturedDirichletCondition()){ - info() << "Apply manufactured Dirichlet boundary condition to all surface"; - ENUMERATE_ (Face, iface, outerFaces()) { - for (Node node : iface->nodes()) { - m_u[node] = m_manufactured_dirichlet->apply(lambda, m_node_coord[node]); - m_u_dirichlet[node] = true; - } } } - } /*---------------------------------------------------------------------------*/ -// Assemble the FEM linear operator -// - This function enforces a Dirichlet boundary condition in a weak sense -// via the penalty method -// - The method also adds source term -// - TODO: external fluxes +/** + * @brief FEM linear operator for the current simulation step. + * + * This method constructs the linear system by assembling the LHS matrix + * and RHS vector, applying various boundary conditions and source terms. + * + * Steps involved: + * 1. The RHS vector is initialized to zero before applying any conditions. + * 2. If a constant source term is specified (`qdot`), apply it to the RHS. + * 3. If Neumann BC are specified applied to the RHS. + * 4. If Dirichlet BC are specified apply to the LHS & RHS. + * 5. If manufactured source conditions is specified, apply to RHS. + * 6. If manufactured Dirichlet BC are specified apply to the LHS & RHS. + */ /*---------------------------------------------------------------------------*/ void FemModule:: _assembleLinearOperator() { - info() << "Assembly of FEM linear operator "; - + info() << "Assembly of FEM linear operator"; - // Temporary variable to keep values for the RHS part of the linear system - VariableDoFReal& rhs_values(m_linear_system.rhsVariable()); + VariableDoFReal& rhs_values(m_linear_system.rhsVariable()); // Temporary variable to keep values for the RHS rhs_values.fill(0.0); auto node_dof(m_dofs_on_nodes.nodeDoFConnectivityView()); - if (options()->enforceDirichletMethod() == "Penalty") { + if (options()->qdot.isPresent()) + ArcaneFemFunctions::BoundaryConditions2D::applyConstantSourceToRhs(qdot, mesh(), node_dof, m_node_coord, rhs_values); - //---------------------------------------------- - // penalty method to enforce Dirichlet BC - //---------------------------------------------- - // Let 'P' be the penalty term and let 'i' be the set of DOF for which - // Dirichlet condition needs to be applied - // - // - For LHS matrix A the diag term corresponding to the Dirichlet DOF - // a_{i,i} = 1. * P - // - // - For RHS vector b the term that corresponds to the Dirichlet DOF - // b_{i} = b_{i} * P - //---------------------------------------------- + for (const auto& bs : options()->neumannBoundaryCondition()) + ArcaneFemFunctions::BoundaryConditions2D::applyNeumannToRhs(bs, node_dof, m_node_coord, rhs_values); - info() << "Applying Dirichlet boundary condition via " - << options()->enforceDirichletMethod() << " method "; + for (const auto& bs : options()->dirichletBoundaryCondition()) + ArcaneFemFunctions::BoundaryConditions2D::applyDirichletToLhsAndRhs(bs, node_dof, m_node_coord, m_linear_system, rhs_values); - Real Penalty = options()->penalty(); // 1.0e30 is the default + if (options()->manufacturedSolution.isPresent()) { + const auto& bs = options()->manufacturedSolution()[0]; - ENUMERATE_ (Node, inode, ownNodes()) { - NodeLocalId node_id = *inode; - if (m_u_dirichlet[node_id]) { - DoFLocalId dof_id = node_dof.dofId(*inode, 0); - m_linear_system.matrixSetValue(dof_id, dof_id, Penalty); - Real u_g = Penalty * m_u[node_id]; - rhs_values[dof_id] = u_g; - } + if (bs->manufacturedSource.isPresent()) { + ARCANE_CHECK_POINTER(m_manufactured_source); + info() << "Apply manufactured Source condition to all cells"; + ArcaneFemFunctions::BoundaryConditions2D::applyManufacturedSourceToRhs(m_manufactured_source, mesh(), node_dof, m_node_coord, rhs_values); } - }else if (options()->enforceDirichletMethod() == "WeakPenalty") { - - //---------------------------------------------- - // weak penalty method to enforce Dirichlet BC - //---------------------------------------------- - // Let 'P' be the penalty term and let 'i' be the set of DOF for which - // Dirichlet condition needs to be applied - // - // - For LHS matrix A the diag term corresponding to the Dirichlet DOF - // a_{i,i} = a_{i,i} + P - // - // - For RHS vector b the term that corresponds to the Dirichlet DOF - // b_{i} = b_{i} * P - //---------------------------------------------- - - info() << "Applying Dirichlet boundary condition via " - << options()->enforceDirichletMethod() << " method "; - - Real Penalty = options()->penalty(); // 1.0e30 is the default - ENUMERATE_ (Node, inode, ownNodes()) { - NodeLocalId node_id = *inode; - if (m_u_dirichlet[node_id]) { - DoFLocalId dof_id = node_dof.dofId(*inode, 0); - m_linear_system.matrixAddValue(dof_id, dof_id, Penalty); - Real u_g = Penalty * m_u[node_id]; - rhs_values[dof_id] = u_g; - } + if (bs->manufacturedDirichlet.isPresent()) { + ARCANE_CHECK_POINTER(m_manufactured_dirichlet); + info() << "Apply manufactured dirichlet condition to all borders"; + FaceGroup group = mesh()->outerFaces(); + ArcaneFemFunctions::BoundaryConditions2D::applyManufacturedDirichletToLhsAndRhs(m_manufactured_dirichlet, lambda, group, bs, node_dof, m_node_coord, m_linear_system, rhs_values); } - }else if (options()->enforceDirichletMethod() == "RowElimination") { - - //---------------------------------------------- - // Row elimination method to enforce Dirichlet BC - //---------------------------------------------- - // Let 'I' be the set of DOF for which Dirichlet condition needs to be applied - // - // to apply the Dirichlet on 'i'th DOF - // - For LHS matrix A the row terms corresponding to the Dirichlet DOF - // a_{i,j} = 0. : i!=j - // a_{i,j} = 1. : i==j - //---------------------------------------------- - - info() << "Applying Dirichlet boundary condition via " - << options()->enforceDirichletMethod() << " method "; - - // TODO - }else if (options()->enforceDirichletMethod() == "RowColumnElimination") { - - //---------------------------------------------- - // Row elimination method to enforce Dirichlet BC - //---------------------------------------------- - // Let 'I' be the set of DOF for which Dirichlet condition needs to be applied - // - // to apply the Dirichlet on 'i'th DOF - // - For LHS matrix A the row terms corresponding to the Dirichlet DOF - // a_{i,j} = 0. : i!=j for all j - // a_{i,j} = 1. : i==j - // also the column terms corresponding to the Dirichlet DOF - // a_{i,j} = 0. : i!=j for all i - //---------------------------------------------- - - info() << "Applying Dirichlet boundary condition via " - << options()->enforceDirichletMethod() << " method "; - - // TODO - }else { - - info() << "Applying Dirichlet boundary condition via " - << options()->enforceDirichletMethod() << " is not supported \n" - << "enforce-Dirichlet-method only supports:\n" - << " - Penalty\n" - << " - WeakPenalty\n" - << " - RowElimination\n" - << " - RowColumnElimination\n"; - } - - - //---------------------------------------------- - // Constant source term assembly - //---------------------------------------------- - // - // $int_{Omega}(qdot*v^h)$ - // only for noded that are non-Dirichlet - //---------------------------------------------- - if(options()->manufacturedSourceCondition()){ - ARCANE_CHECK_POINTER(m_manufactured_source); - info() << "Apply manufactured Source condition to all cells"; - ENUMERATE_ (Cell, icell, allCells()) { - Cell cell = *icell; - - Real area = _computeAreaTriangle3(cell); - - Real3 m0 = m_node_coord[cell.nodeId(0)]; - Real3 m1 = m_node_coord[cell.nodeId(1)]; - Real3 m2 = m_node_coord[cell.nodeId(2)]; - - Real Center_x = (m0.x + m1.x + m2.x)/3; - Real Center_y = (m0.y + m1.y + m2.y)/3; - Real3 bcenter = {Center_x , Center_y, 0}; - - for (Node node : cell.nodes()) { - if (!(m_u_dirichlet[node]) && node.isOwn()){ - rhs_values[node_dof.dofId(node, 0)] += m_manufactured_source->apply(area / ElementNodes, bcenter); - } - } - } - }else{ - ENUMERATE_ (Cell, icell, allCells()) { - Cell cell = *icell; - Real area = _computeAreaTriangle3(cell); - for (Node node : cell.nodes()) { - if (!(m_u_dirichlet[node]) && node.isOwn()) - rhs_values[node_dof.dofId(node, 0)] += qdot * area / ElementNodes; - } - } } - //---------------------------------------------- - // Constant flux term assembly - //---------------------------------------------- - // - // only for noded that are non-Dirichlet - // $int_{dOmega_N}((q.n)*v^h)$ - // or - // $int_{dOmega_N}((n_x*q_x + n_y*q_y)*v^h)$ - //---------------------------------------------- - for (const auto& bs : options()->neumannBoundaryCondition()) { - FaceGroup group = bs->surface(); - - info() << "Apply Neumann boundary condition to all edges on surface" << group.name(); - if(bs->value.isPresent()) { - Real value = bs->value(); - ENUMERATE_ (Face, iface, group) { - Face face = *iface; - Real length = _computeEdgeLength2(face); - for (Node node : iface->nodes()) { - if (!(m_u_dirichlet[node]) && node.isOwn()) - rhs_values[node_dof.dofId(node, 0)] += value * length / 2.; - } - } - continue; - } - - - if(bs->valueX.isPresent() && bs->valueY.isPresent()) { - Real valueX = bs->valueX(); - Real valueY = bs->valueY(); - ENUMERATE_ (Face, iface, group) { - Face face = *iface; - Real length = _computeEdgeLength2(face); - Real2 Normal = _computeEdgeNormal2(face); - for (Node node : iface->nodes()) { - if (!(m_u_dirichlet[node]) && node.isOwn()) - rhs_values[node_dof.dofId(node, 0)] += (Normal.x*valueX + Normal.y*valueY) * length / 2.; - } - } - continue; - } - - if(bs->valueX.isPresent()) { - Real valueX = bs->valueX(); - ENUMERATE_ (Face, iface, group) { - Face face = *iface; - Real length = _computeEdgeLength2(face); - Real2 Normal = _computeEdgeNormal2(face); - for (Node node : iface->nodes()) { - if (!(m_u_dirichlet[node]) && node.isOwn()) - rhs_values[node_dof.dofId(node, 0)] += (Normal.x*valueX) * length / 2.; - } - } - continue; - } - - if(bs->valueY.isPresent()) { - Real valueY = bs->valueY(); - ENUMERATE_ (Face, iface, group) { - Face face = *iface; - Real length = _computeEdgeLength2(face); - Real2 Normal = _computeEdgeNormal2(face); - for (Node node : iface->nodes()) { - if (!(m_u_dirichlet[node]) && node.isOwn()) - rhs_values[node_dof.dofId(node, 0)] += (Normal.y*valueY) * length / 2.; - } - } - continue; - } - - } -} - -/*---------------------------------------------------------------------------*/ -/*---------------------------------------------------------------------------*/ - -Real FemModule:: -_computeAreaQuad4(Cell cell) -{ - Real3 m0 = m_node_coord[cell.nodeId(0)]; - Real3 m1 = m_node_coord[cell.nodeId(1)]; - Real3 m2 = m_node_coord[cell.nodeId(2)]; - Real3 m3 = m_node_coord[cell.nodeId(3)]; - return 0.5 * ( (m1.x*m2.y + m2.x*m3.y + m3.x*m0.y + m0.x*m1.y) - -(m2.x*m1.y + m3.x*m2.y + m0.x*m3.y + m1.x*m0.y) ); -} - -/*---------------------------------------------------------------------------*/ -/*---------------------------------------------------------------------------*/ - -Real FemModule:: -_computeAreaTriangle3(Cell cell) -{ - Real3 m0 = m_node_coord[cell.nodeId(0)]; - Real3 m1 = m_node_coord[cell.nodeId(1)]; - Real3 m2 = m_node_coord[cell.nodeId(2)]; - return 0.5 * ((m1.x - m0.x) * (m2.y - m0.y) - (m2.x - m0.x) * (m1.y - m0.y)); } /*---------------------------------------------------------------------------*/ +/** + * @brief Computes the element matrix for a triangular element (P1 FE). + * + * This function calculates the integral of the expression: + * lambda*(u.dx * v.dx + u.dy * v.dy) + * + * Steps involved: + * 1. Calculate the area of the triangle. + * 2. Compute the gradients of the shape functions. + * 3. Return lambda*(u.dx * v.dx + u.dy * v.dy); + */ /*---------------------------------------------------------------------------*/ -Real FemModule:: -_computeEdgeLength2(Face face) +FixedMatrix<3, 3> FemModule:: +_computeElementMatrixTria3(Cell cell) { - Real3 m0 = m_node_coord[face.nodeId(0)]; - Real3 m1 = m_node_coord[face.nodeId(1)]; - return math::sqrt((m1.x-m0.x)*(m1.x-m0.x) + (m1.y-m0.y)*(m1.y - m0.y)); -} + Real area = ArcaneFemFunctions::MeshOperation::computeAreaTria3(cell, m_node_coord); -/*---------------------------------------------------------------------------*/ -/*---------------------------------------------------------------------------*/ + Real3 dxU = ArcaneFemFunctions::FeOperation2D::computeGradientXTria3(cell, m_node_coord); + Real3 dyU = ArcaneFemFunctions::FeOperation2D::computeGradientYTria3(cell, m_node_coord); -Real2 FemModule:: -_computeEdgeNormal2(Face face) -{ - Real3 m0 = m_node_coord[face.nodeId(0)]; - Real3 m1 = m_node_coord[face.nodeId(1)]; - if (!face.isSubDomainBoundaryOutside()) - std::swap(m0,m1); - Real2 N; - Real norm_N = math::sqrt( (m1.y - m0.y)*(m1.y - m0.y) + (m1.x - m0.x)*(m1.x - m0.x) ); // for normalizing - N.x = (m1.y - m0.y)/ norm_N; - N.y = (m0.x - m1.x)/ norm_N; - return N; + return area * lambda * (dxU ^ dxU) + area * lambda * (dyU ^ dyU); } /*---------------------------------------------------------------------------*/ /*---------------------------------------------------------------------------*/ -FixedMatrix<3, 3> FemModule:: -_computeElementMatrixTRIA3(Cell cell) +FixedMatrix<4, 4> FemModule:: +_computeElementMatrixQuad4(Cell cell) { - // Get coordiantes of the triangle element TRI3 - //------------------------------------------------ - // 0 o - // . . - // . . - // . . - // 1 o . . . o 2 - //------------------------------------------------ - Real3 m0 = m_node_coord[cell.nodeId(0)]; - Real3 m1 = m_node_coord[cell.nodeId(1)]; - Real3 m2 = m_node_coord[cell.nodeId(2)]; - - Real area = _computeAreaTriangle3(cell); // calculate area - - Real2 dPhi0(m1.y - m2.y, m2.x - m1.x); - Real2 dPhi1(m2.y - m0.y, m0.x - m2.x); - Real2 dPhi2(m0.y - m1.y, m1.x - m0.x); - - FixedMatrix<2, 3> b_matrix; - b_matrix(0, 0) = dPhi0.x; - b_matrix(0, 1) = dPhi1.x; - b_matrix(0, 2) = dPhi2.x; - - b_matrix(1, 0) = dPhi0.y; - b_matrix(1, 1) = dPhi1.y; - b_matrix(1, 2) = dPhi2.y; - - b_matrix.multInPlace(1.0 / (2.0 * area)); - - FixedMatrix<3, 3> int_cdPi_dPj = matrixMultiplication(matrixTranspose(b_matrix), b_matrix); - int_cdPi_dPj.multInPlace(area * lambda); - - //info() << "Cell=" << cell.localId(); - //std::cout << " int_cdPi_dPj="; - //int_cdPi_dPj.dump(std::cout); - //std::cout << "\n"; - - return int_cdPi_dPj; -} + Real area = ArcaneFemFunctions::MeshOperation::computeAreaQuad4(cell, m_node_coord); -/*---------------------------------------------------------------------------*/ -/*---------------------------------------------------------------------------*/ + Real4 dxU = ArcaneFemFunctions::FeOperation2D::computeGradientXQuad4(cell, m_node_coord); + Real4 dyU = ArcaneFemFunctions::FeOperation2D::computeGradientYQuad4(cell, m_node_coord); -FixedMatrix<4, 4> FemModule:: -_computeElementMatrixQUAD4(Cell cell) -{ - // Get coordiantes of the quadrangular element QUAD4 - //------------------------------------------------ - // 1 o . . . . o 0 - // . . - // . . - // . . - // 2 o . . . . o 3 - //------------------------------------------------ - Real3 m0 = m_node_coord[cell.nodeId(0)]; - Real3 m1 = m_node_coord[cell.nodeId(1)]; - Real3 m2 = m_node_coord[cell.nodeId(2)]; - Real3 m3 = m_node_coord[cell.nodeId(3)]; - - Real area = _computeAreaQuad4(cell); // calculate area - - Real2 dPhi0(m2.y - m3.y, m3.x - m2.x); - Real2 dPhi1(m3.y - m0.y, m0.x - m3.x); - Real2 dPhi2(m0.y - m1.y, m1.x - m0.x); - Real2 dPhi3(m1.y - m2.y, m2.x - m1.x); - - FixedMatrix<2, 4> b_matrix; - b_matrix(0, 0) = dPhi0.x; - b_matrix(0, 1) = dPhi1.x; - b_matrix(0, 2) = dPhi2.x; - b_matrix(0, 3) = dPhi3.x; - - b_matrix(1, 0) = dPhi0.y; - b_matrix(1, 1) = dPhi1.y; - b_matrix(1, 2) = dPhi2.y; - b_matrix(1, 3) = dPhi3.y; - - b_matrix.multInPlace(1.0 / (2.0 * area)); - - FixedMatrix<4, 4> int_cdPi_dPj = matrixMultiplication(matrixTranspose(b_matrix), b_matrix); - int_cdPi_dPj.multInPlace(area * lambda); - - //info() << "Cell=" << cell.localId(); - //std::cout << " int_cdPi_dPj="; - //int_cdPi_dPj.dump(std::cout); - //std::cout << "\n"; - - return int_cdPi_dPj; + return area * lambda * (dxU ^ dxU) + area * lambda * (dyU ^ dyU); } /*---------------------------------------------------------------------------*/ +/** + * @brief Calls the right function for LHS assembly given as mesh type. + */ /*---------------------------------------------------------------------------*/ void FemModule:: -_assembleBilinearOperatorQUAD4() +_assembleBilinearOperator() { - auto node_dof(m_dofs_on_nodes.nodeDoFConnectivityView()); - - ENUMERATE_ (Cell, icell, allCells()) { - Cell cell = *icell; - if (cell.type() != IT_Quad4) - ARCANE_FATAL("Only Quad4 cell type is supported"); - - lambda = m_cell_lambda[cell]; // lambda is always considered cell constant - auto K_e = _computeElementMatrixQUAD4(cell); // element stiffness matrix - // # assemble elementary matrix into the global one - // # elementary terms are positioned into K according - // # to the rank of associated node in the mesh.nodes list - // for node1 in elem.nodes: - // inode1=elem.nodes.index(node1) # get position of node1 in nodes list - // for node2 in elem.nodes: - // inode2=elem.nodes.index(node2) - // K[node1.rank,node2.rank]=K[node1.rank,node2.rank]+K_e[inode1,inode2] - Int32 n1_index = 0; - for (Node node1 : cell.nodes()) { - Int32 n2_index = 0; - for (Node node2 : cell.nodes()) { - // K[node1.rank,node2.rank]=K[node1.rank,node2.rank]+K_e[inode1,inode2] - Real v = K_e(n1_index, n2_index); - // m_k_matrix(node1.localId(), node2.localId()) += v; - if (node1.isOwn()) { - m_linear_system.matrixAddValue(node_dof.dofId(node1, 0), node_dof.dofId(node2, 0), v); - } - ++n2_index; - } - ++n1_index; - } - } + if (options()->meshType == "QUAD4") + _assembleBilinear<4>([this](const Cell& cell) { + return _computeElementMatrixQuad4(cell); + }); + else if (options()->meshType == "TRIA3") + _assembleBilinear<3>([this](const Cell& cell) { + return _computeElementMatrixTria3(cell); + }); + else + ARCANE_FATAL("Non supported meshType"); } /*---------------------------------------------------------------------------*/ +/** + * @brief Assembles the bilinear operator matrix for the FEM linear system. + * + * The method performs the following steps: + * 1. For each cell, retrieves the cell-specific constant `lambda`. + * 2. Computes element matrix using provided `compute_element_matrix` function. + * 3. Assembles global matrix by adding contributions from each cell's element + * matrix to the corresponding entries in the global matrix. + */ /*---------------------------------------------------------------------------*/ +template void FemModule:: -_assembleBilinearOperatorTRIA3() +_assembleBilinear(const std::function(const Cell&)>& compute_element_matrix) { auto node_dof(m_dofs_on_nodes.nodeDoFConnectivityView()); ENUMERATE_ (Cell, icell, allCells()) { Cell cell = *icell; - if (cell.type() != IT_Triangle3) - ARCANE_FATAL("Only Triangle3 cell type is supported"); - - lambda = m_cell_lambda[cell]; // lambda is always considered cell constant - auto K_e = _computeElementMatrixTRIA3(cell); // element stiffness matrix - // # assemble elementary matrix into the global one - // # elementary terms are positioned into K according - // # to the rank of associated node in the mesh.nodes list - // for node1 in elem.nodes: - // inode1=elem.nodes.index(node1) # get position of node1 in nodes list - // for node2 in elem.nodes: - // inode2=elem.nodes.index(node2) - // K[node1.rank,node2.rank]=K[node1.rank,node2.rank]+K_e[inode1,inode2] + + lambda = m_cell_lambda[cell]; // lambda is always considered cell constant + auto K_e = compute_element_matrix(cell); // element matrix based on the provided function Int32 n1_index = 0; for (Node node1 : cell.nodes()) { Int32 n2_index = 0; for (Node node2 : cell.nodes()) { - // K[node1.rank,node2.rank]=K[node1.rank,node2.rank]+K_e[inode1,inode2] Real v = K_e(n1_index, n2_index); - // m_k_matrix(node1.localId(), node2.localId()) += v; if (node1.isOwn()) { m_linear_system.matrixAddValue(node_dof.dofId(node1, 0), node_dof.dofId(node2, 0), v); } @@ -739,6 +303,14 @@ _assembleBilinearOperatorTRIA3() } /*---------------------------------------------------------------------------*/ +/** + * @brief Solves the linear system and updates the solution vector. + * + * This method performs the following actions: + * 1. Solves the linear system to compute the solution. + * 2. Copies the computed solution from the DoF to the node values. + * 3. Synchronizes the updated node values. + */ /*---------------------------------------------------------------------------*/ void FemModule:: @@ -746,20 +318,17 @@ _solve() { m_linear_system.solve(); - // Re-Apply boundary conditions because the solver has modified the value - // of u on all nodes - _applyDirichletBoundaryConditions(); - - { + { // copies solution (and optionally exact solution) to FEM output VariableDoFReal& dof_u(m_linear_system.solutionVariable()); - // Copy RHS DoF to Node u auto node_dof(m_dofs_on_nodes.nodeDoFConnectivityView()); + ENUMERATE_ (Node, inode, ownNodes()) { Node node = *inode; Real v = dof_u[node_dof.dofId(node, 0)]; m_u[node] = v; } - if(options()->manufacturedDirichletCondition()){ + + if (options()->manufacturedSolution.isPresent()) { ENUMERATE_ (Node, inode, ownNodes()) { Node node = *inode; m_u_exact[node] = m_manufactured_dirichlet->apply(lambda, m_node_coord[node]); @@ -768,40 +337,37 @@ _solve() } m_u.synchronize(); - if(options()->manufacturedDirichletCondition()) - m_u_exact.synchronize(); - // def update_T(self,T): - // """Update u value on nodes after the FE resolution""" - // for i in range(0,len(self.mesh.nodes)): - // node=self.mesh.nodes[i] - // # don't update T imposed by Dirichlet BC - // if not node.is_T_fixed: - // self.mesh.nodes[i].T=T[i] - - const bool do_print = (allNodes().size() < 200); - if (do_print) { - ENUMERATE_ (Node, inode, allNodes()) { - Node node = *inode; - info() << "T[" << node.localId() << "][" << node.uniqueId() << "] = " - << m_u[node]; - //info() << "T[]" << node.uniqueId() << " " - // << m_u[node]; - } - } + if (options()->manufacturedSolution.isPresent()) + m_u_exact.synchronize(); } /*---------------------------------------------------------------------------*/ +/** + * @brief Validates and prints the results of the FEM computation. + * + * This method performs the following actions: + * 1. If number of nodes < 200, prints the computed values for each node. + * 2. Retrieves the filename for the result file from options. + * 3. If a filename is provided, checks the computed results against result file. + * + * @note The result comparison uses a tolerance of 1.0e-4. + */ /*---------------------------------------------------------------------------*/ void FemModule:: -_checkResultFile() +_validateResults() { + if (allNodes().size() < 200) + ENUMERATE_ (Node, inode, allNodes()) { + Node node = *inode; + info() << "u[" << node.localId() << "][" << node.uniqueId() << "] = " << m_u[node]; + } + String filename = options()->resultFile(); - info() << "CheckResultFile filename=" << filename; - if (filename.empty()) - return; - const double epsilon = 1.0e-4; - checkNodeResultFile(traceMng(), filename, m_u, epsilon); + info() << "ValidateResultFile filename=" << filename; + + if (!filename.empty()) + checkNodeResultFile(traceMng(), filename, m_u, 1.0e-4); } /*---------------------------------------------------------------------------*/ diff --git a/fourier/FemModule.h b/fourier/FemModule.h new file mode 100644 index 00000000..218c1d99 --- /dev/null +++ b/fourier/FemModule.h @@ -0,0 +1,99 @@ +// -*- tab-width: 2; indent-tabs-mode: nil; coding: utf-8-with-signature -*- +//----------------------------------------------------------------------------- +// Copyright 2000-2024 CEA (www.cea.fr) IFPEN (www.ifpenergiesnouvelles.com) +// See the top-level COPYRIGHT file for details. +// SPDX-License-Identifier: Apache-2.0 +//----------------------------------------------------------------------------- +/*---------------------------------------------------------------------------*/ +/* FemModule.h (C) 2022-2024 */ +/* */ +/* FemModule class definition. */ +/*---------------------------------------------------------------------------*/ +/*---------------------------------------------------------------------------*/ +#ifndef FEMMODULES_H +#define FEMMODULES_H + +/*---------------------------------------------------------------------------*/ +/*---------------------------------------------------------------------------*/ + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "IDoFLinearSystemFactory.h" +#include "Fem_axl.h" +#include "FemUtils.h" +#include "DoFLinearSystem.h" +#include "FemDoFsOnNodes.h" +#include "ArcaneFemFunctions.h" + +/*---------------------------------------------------------------------------*/ +/*---------------------------------------------------------------------------*/ + +using namespace Arcane; +using namespace Arcane::FemUtils; + +/*---------------------------------------------------------------------------*/ +/** + * @brief A module for finite element method. + * + * This class handles the initialization and computation for finite element + * method (FEM) simulations, providing methods to set up and solve linear + * systems, assemble FEM operators, and perform result checks. + */ +/*---------------------------------------------------------------------------*/ + +class FemModule +: public ArcaneFemObject +{ + public: + + explicit FemModule(const ModuleBuildInfo& mbi) + : ArcaneFemObject(mbi) + , m_dofs_on_nodes(mbi.subDomain()->traceMng()) + { + ICaseMng* cm = mbi.subDomain()->caseMng(); + cm->setTreatWarningAsError(true); + cm->setAllowUnkownRootElelement(false); + } + + void startInit() override; //! Method called at the beginning of the simulation + void compute() override; //! Method called at each iteration + VersionInfo versionInfo() const override { return VersionInfo(1, 0, 0); } + + private: + + Real lambda; + Real qdot; + Real ElementNodes; + + DoFLinearSystem m_linear_system; + IItemFamily* m_dof_family = nullptr; + FemDoFsOnNodes m_dofs_on_nodes; + + void _doStationarySolve(); + void _getMaterialParameters(); + void _assembleBilinearOperator(); + void _solve(); + void _assembleLinearOperator(); + void _validateResults(); + + FixedMatrix<3, 3> _computeElementMatrixTria3(Cell cell); + FixedMatrix<4, 4> _computeElementMatrixQuad4(Cell cell); + + IBinaryMathFunctor* m_manufactured_dirichlet = nullptr; + IBinaryMathFunctor* m_manufactured_source = nullptr; + + template + void _assembleBilinear( const std::function(const Cell&)>& compute_element_matrix); +}; + +#endif \ No newline at end of file diff --git a/fourier/Test.conduction.arc b/fourier/Test.conduction.arc index 5cccc068..45dedbdb 100644 --- a/fourier/Test.conduction.arc +++ b/fourier/Test.conduction.arc @@ -23,17 +23,21 @@ 1.75 1e5 test1_results.txt - WeakPenalty - 1.e12 + Penalty + 1.e12 Cercle 50.0 + Penalty + 1.e12 Bas 5.0 + Penalty + 1.e12 Haut 21.0 diff --git a/fourier/Test.manufacture.solution.arc b/fourier/Test.manufacture.solution.arc index 6bff475b..66bd5e14 100644 --- a/fourier/Test.manufacture.solution.arc +++ b/fourier/Test.manufacture.solution.arc @@ -28,8 +28,11 @@ - true - true + + true + true + Penalty + 1.0 diff --git a/laplace/Fem.axl b/laplace/Fem.axl index 7527427e..a4fcc334 100644 --- a/laplace/Fem.axl +++ b/laplace/Fem.axl @@ -9,9 +9,6 @@ FEM variable u on nodes - - Boolean which is true if Dirichlet node - Node coordinates from Arcane variable @@ -23,69 +20,91 @@ Type of mesh provided to the solver - - - Method via which Dirichlet boundary condition is imposed - - - - - Penalty value for enforcing Dirichlet condition - - - - + Dirichlet boundary condition - + - FaceGroup on which to apply these boundary condition + Function for Dirichlet boundary condition - - + + - Value of the boundary condition + Method via which Dirichlet boundary condition is imposed + + + + + Penalty value for enforcing Dirichlet condition + + + + + Function for manufactured source term condition - - + - Neumann boundary condition + Dirichlet boundary condition FaceGroup on which to apply these boundary condition - + Value of the boundary condition - + - Value of the Neumann load in x + Method via which Dirichlet boundary condition is imposed - + - Value of the Neumann load in y + Penalty value for enforcing Dirichlet condition + + + Neumann boundary condition + + + FaceGroup on which to apply the boundary condition + + + + Value of the boundary condition + + + + Neumann load value in x-direction + + + + Neumann load value in y-direction + + + + + + + Method via which Dirichlet boundary condition is imposed + + + + + Penalty value for enforcing Dirichlet condition + + -#include -#include - -#include -#include -#include -#include -#include - -#include "IDoFLinearSystemFactory.h" -#include "Fem_axl.h" -#include "FemUtils.h" -#include "DoFLinearSystem.h" -#include "FemDoFsOnNodes.h" +#include "FemModule.h" /*---------------------------------------------------------------------------*/ +/** + * @brief Initializes the FemModule at the start of the simulation. + * + * This method initializes degrees of freedom (DoFs) on nodes. + */ /*---------------------------------------------------------------------------*/ -using namespace Arcane; -using namespace Arcane::FemUtils; - -/*---------------------------------------------------------------------------*/ -/*---------------------------------------------------------------------------*/ -/*! - * \brief Module Fem. - */ -class FemModule -: public ArcaneFemObject +void FemModule:: +startInit() { - public: - - explicit FemModule(const ModuleBuildInfo& mbi) - : ArcaneFemObject(mbi) - , m_dofs_on_nodes(mbi.subDomain()->traceMng()) - { - ICaseMng* cm = mbi.subDomain()->caseMng(); - cm->setTreatWarningAsError(true); - cm->setAllowUnkownRootElelement(false); - } - - public: - - //! Method called at each iteration - void compute() override; - - //! Method called at the beginning of the simulation - void startInit() override; - - VersionInfo versionInfo() const override - { - return VersionInfo(1, 0, 0); - } - - private: - - Real ElementNodes; - - DoFLinearSystem m_linear_system; - IItemFamily* m_dof_family = nullptr; - FemDoFsOnNodes m_dofs_on_nodes; - - private: - - void _doStationarySolve(); - void _getMaterialParameters(); - void _assembleBilinearOperatorTRIA3(); - void _assembleBilinearOperatorTETRA4(); - void _solve(); - void _initBoundaryconditions(); - void _assembleLinearOperator(); - void _applyDirichletBoundaryConditions(); - void _checkResultFile(); - FixedMatrix<3, 3> _computeElementMatrixTRIA3(Cell cell); - FixedMatrix<4, 4> _computeElementMatrixTETRA4(Cell cell); - Real _computeAreaTriangle3(Cell cell); - Real _computeAreaTetra4(Cell cell); - Real _computeEdgeLength2(Face face); - Real2 _computeEdgeNormal2(Face face); - -}; + info() << "Module Fem INIT"; + m_dofs_on_nodes.initialize(mesh(), 1); + m_dof_family = m_dofs_on_nodes.dofFamily(); +} /*---------------------------------------------------------------------------*/ +/** + * @brief Performs the main computation for the FemModule. + * + * This method: + * 1. Stops the time loop after 1 iteration since the equation is steady state. + * 2. Resets, configures, and initializes the linear system. + * 3. Executes the stationary solve. + */ /*---------------------------------------------------------------------------*/ void FemModule:: @@ -112,8 +56,7 @@ compute() // This is only used for the first call. { StringList string_list; - string_list.add("-trmalloc"); - string_list.add("-log_trace"); + string_list.add("-ksp_monitor"); CommandLineArguments args(string_list); m_linear_system.setSolverCommandLineArguments(args); } @@ -122,546 +65,173 @@ compute() } /*---------------------------------------------------------------------------*/ -/*---------------------------------------------------------------------------*/ - -void FemModule:: -startInit() -{ - info() << "Module Fem INIT"; - - m_dofs_on_nodes.initialize(mesh(), 1); - m_dof_family = m_dofs_on_nodes.dofFamily(); - - //_buildDoFOnNodes(); - //Int32 nb_node = allNodes().size(); - //m_k_matrix.resize(nb_node, nb_node); - //m_k_matrix.fill(0.0); - - //m_rhs_vector.resize(nb_node); - //m_rhs_vector.fill(0.0); - - // # init mesh - // # init behavior - // # init behavior on mesh entities - // # init BCs - _initBoundaryconditions(); -} - -/*---------------------------------------------------------------------------*/ +/** + * @brief Performs a stationary solve for the FEM system. + * + * This method follows a sequence of steps to solve FEM system: + * 1. _getMaterialParameters() Retrieves material parameters via + * 2. _assembleBilinearOperator() Assembles the FEM matrix A + * 3. _assembleLinearOperator() Assembles the FEM RHS vector b + * 4. _solve() Solves for solution vector u = A^-1*b + * 5. _validateResults() Regression test + */ /*---------------------------------------------------------------------------*/ void FemModule:: _doStationarySolve() { - // # get material parameters _getMaterialParameters(); - - // Assemble the FEM bilinear operator (LHS - matrix A) - if (options()->meshType == "TETRA4") - _assembleBilinearOperatorTETRA4(); - else - _assembleBilinearOperatorTRIA3(); - - // Assemble the FEM linear operator (RHS - vector b) + _assembleBilinearOperator(); _assembleLinearOperator(); - - // # T=linalg.solve(K,RHS) _solve(); - - // Check results - _checkResultFile(); + _validateResults(); } /*---------------------------------------------------------------------------*/ +/** + * @brief Retrieves and sets the material parameters for the simulation. + */ /*---------------------------------------------------------------------------*/ void FemModule:: _getMaterialParameters() { info() << "Get material parameters..."; - ElementNodes = 3.; - - if (options()->meshType == "TETRA4") - ElementNodes = 4.; -} - -/*---------------------------------------------------------------------------*/ -/*---------------------------------------------------------------------------*/ - -void FemModule:: -_initBoundaryconditions() -{ - info() << "Init boundary conditions..."; - - _applyDirichletBoundaryConditions(); -} - -/*---------------------------------------------------------------------------*/ -/*---------------------------------------------------------------------------*/ - -void FemModule:: -_applyDirichletBoundaryConditions() -{ - // Handle all the Dirichlet boundary conditions. - // In the 'arc' file, there are in the following format: - // - // Haut - // 21.0 - // - - for (const auto& bs : options()->dirichletBoundaryCondition()) { - FaceGroup group = bs->surface(); - Real value = bs->value(); - info() << "Apply Dirichlet boundary condition surface=" << group.name() << " v=" << value; - ENUMERATE_ (Face, iface, group) { - for (Node node : iface->nodes()) { - m_u[node] = value; - m_u_dirichlet[node] = true; - } - } - } - - for (const auto& bs : options()->dirichletPointCondition()) { - NodeGroup group = bs->node(); - Real value = bs->value(); - info() << "Apply Dirichlet point condition node=" << group.name() << " v=" << value; - ENUMERATE_ (Node, inode, group) { - Node node = *inode; - m_u[node] = value; - m_u_dirichlet[node] = true; - } - } } /*---------------------------------------------------------------------------*/ -// Assemble the FEM linear operator -// - This function enforces a Dirichlet boundary condition in a weak sense -// via the penalty method -// - The method also adds source term -// - Also adds external fluxes +/** + * @brief FEM linear operator for the current simulation step. + * + * This method constructs the linear system by assembling the LHS matrix + * and RHS vector, applying various boundary conditions and source terms. + * + * Steps involved: + * 1. The RHS vector is initialized to zero before applying any conditions. + * 2. If Neumann BC are specified applied to the RHS. + * 3. If Dirichlet BC/Point are specified apply to the LHS & RHS. + */ /*---------------------------------------------------------------------------*/ void FemModule:: _assembleLinearOperator() { - info() << "Assembly of FEM linear operator "; - info() << "Applying Dirichlet boundary condition via penalty method "; + info() << "Assembly of FEM linear operator"; - // Temporary variable to keep values for the RHS part of the linear system - VariableDoFReal& rhs_values(m_linear_system.rhsVariable()); + VariableDoFReal& rhs_values(m_linear_system.rhsVariable()); // Temporary variable to keep values for the RHS rhs_values.fill(0.0); auto node_dof(m_dofs_on_nodes.nodeDoFConnectivityView()); - if (options()->enforceDirichletMethod() == "Penalty") { - - //---------------------------------------------- - // penalty method to enforce Dirichlet BC - //---------------------------------------------- - // Let 'P' be the penalty term and let 'i' be the set of DOF for which - // Dirichlet condition needs to be applied - // - // - For LHS matrix A the diag term corresponding to the Dirichlet DOF - // a_{i,i} = 1. * P - // - // - For RHS vector b the term that corresponds to the Dirichlet DOF - // b_{i} = b_{i} * P - //---------------------------------------------- - - info() << "Applying Dirichlet boundary condition via " - << options()->enforceDirichletMethod() << " method "; + for (const auto& bs : options()->neumannBoundaryCondition()) + ArcaneFemFunctions::BoundaryConditions2D::applyNeumannToRhs(bs, node_dof, m_node_coord, rhs_values); - Real Penalty = options()->penalty(); // 1.0e30 is the default - - ENUMERATE_ (Node, inode, ownNodes()) { - NodeLocalId node_id = *inode; - if (m_u_dirichlet[node_id]) { - DoFLocalId dof_id = node_dof.dofId(*inode, 0); - m_linear_system.matrixSetValue(dof_id, dof_id, Penalty); - Real u_g = Penalty * m_u[node_id]; - rhs_values[dof_id] = u_g; - } - } - }else if (options()->enforceDirichletMethod() == "WeakPenalty") { - - //---------------------------------------------- - // weak penalty method to enforce Dirichlet BC - //---------------------------------------------- - // Let 'P' be the penalty term and let 'i' be the set of DOF for which - // Dirichlet condition needs to be applied - // - // - For LHS matrix A the diag term corresponding to the Dirichlet DOF - // a_{i,i} = a_{i,i} + P - // - // - For RHS vector b the term that corresponds to the Dirichlet DOF - // b_{i} = b_{i} * P - //---------------------------------------------- - - info() << "Applying Dirichlet boundary condition via " - << options()->enforceDirichletMethod() << " method "; - - Real Penalty = options()->penalty(); // 1.0e30 is the default + for (const auto& bs : options()->dirichletBoundaryCondition()) + ArcaneFemFunctions::BoundaryConditions2D::applyDirichletToLhsAndRhs(bs, node_dof, m_node_coord, m_linear_system, rhs_values); - ENUMERATE_ (Node, inode, ownNodes()) { - NodeLocalId node_id = *inode; - if (m_u_dirichlet[node_id]) { - DoFLocalId dof_id = node_dof.dofId(*inode, 0); - m_linear_system.matrixAddValue(dof_id, dof_id, Penalty); - Real u_g = Penalty * m_u[node_id]; - rhs_values[dof_id] = u_g; - } - } - }else if (options()->enforceDirichletMethod() == "RowElimination") { - - //---------------------------------------------- - // Row elimination method to enforce Dirichlet BC - //---------------------------------------------- - // Let 'I' be the set of DOF for which Dirichlet condition needs to be applied - // - // to apply the Dirichlet on 'i'th DOF - // - For LHS matrix A the row terms corresponding to the Dirichlet DOF - // a_{i,j} = 0. : i!=j - // a_{i,j} = 1. : i==j - //---------------------------------------------- - - info() << "Applying Dirichlet boundary condition via " - << options()->enforceDirichletMethod() << " method "; - - // TODO - }else if (options()->enforceDirichletMethod() == "RowColumnElimination") { - - //---------------------------------------------- - // Row elimination method to enforce Dirichlet BC - //---------------------------------------------- - // Let 'I' be the set of DOF for which Dirichlet condition needs to be applied - // - // to apply the Dirichlet on 'i'th DOF - // - For LHS matrix A the row terms corresponding to the Dirichlet DOF - // a_{i,j} = 0. : i!=j for all j - // a_{i,j} = 1. : i==j - // also the column terms corresponding to the Dirichlet DOF - // a_{i,j} = 0. : i!=j for all i - //---------------------------------------------- - - info() << "Applying Dirichlet boundary condition via " - << options()->enforceDirichletMethod() << " method "; - - // TODO - }else { - - info() << "Applying Dirichlet boundary condition via " - << options()->enforceDirichletMethod() << " is not supported \n" - << "enforce-Dirichlet-method only supports:\n" - << " - Penalty\n" - << " - WeakPenalty\n" - << " - RowElimination\n" - << " - RowColumnElimination\n"; - } - - //---------------------------------------------- - // Constant flux term assembly - //---------------------------------------------- - // - // only for noded that are non-Dirichlet - // $int_{dOmega_N}((q.n)*v^h)$ - // or - // $int_{dOmega_N}((n_x*q_x + n_y*q_y)*v^h)$ - //---------------------------------------------- - for (const auto& bs : options()->neumannBoundaryCondition()) { - FaceGroup group = bs->surface(); - - if(bs->value.isPresent()) { - Real value = bs->value(); - ENUMERATE_ (Face, iface, group) { - Face face = *iface; - Real length = _computeEdgeLength2(face); - for (Node node : iface->nodes()) { - if (!(m_u_dirichlet[node]) && node.isOwn()) - rhs_values[node_dof.dofId(node, 0)] += value * length / 2.; - } - } - continue; - } - - - if(bs->valueX.isPresent() && bs->valueY.isPresent()) { - Real valueX = bs->valueX(); - Real valueY = bs->valueY(); - ENUMERATE_ (Face, iface, group) { - Face face = *iface; - Real length = _computeEdgeLength2(face); - Real2 Normal = _computeEdgeNormal2(face); - for (Node node : iface->nodes()) { - if (!(m_u_dirichlet[node]) && node.isOwn()) - rhs_values[node_dof.dofId(node, 0)] += (Normal.x*valueX + Normal.y*valueY) * length / 2.; - } - } - continue; - } - - if(bs->valueX.isPresent()) { - Real valueX = bs->valueX(); - ENUMERATE_ (Face, iface, group) { - Face face = *iface; - Real length = _computeEdgeLength2(face); - Real2 Normal = _computeEdgeNormal2(face); - for (Node node : iface->nodes()) { - if (!(m_u_dirichlet[node]) && node.isOwn()) - rhs_values[node_dof.dofId(node, 0)] += (Normal.x*valueX) * length / 2.; - } - } - continue; - } - - if(bs->valueY.isPresent()) { - Real valueY = bs->valueY(); - ENUMERATE_ (Face, iface, group) { - Face face = *iface; - Real length = _computeEdgeLength2(face); - Real2 Normal = _computeEdgeNormal2(face); - for (Node node : iface->nodes()) { - if (!(m_u_dirichlet[node]) && node.isOwn()) - rhs_values[node_dof.dofId(node, 0)] += (Normal.y*valueY) * length / 2.; - } - } - continue; - } - } -} - -/*---------------------------------------------------------------------------*/ -/*---------------------------------------------------------------------------*/ - -Real FemModule:: -_computeAreaTetra4(Cell cell) -{ - Real3 m0 = m_node_coord[cell.nodeId(0)]; - Real3 m1 = m_node_coord[cell.nodeId(1)]; - Real3 m2 = m_node_coord[cell.nodeId(2)]; - Real3 m3 = m_node_coord[cell.nodeId(3)]; - - // Calculate vectors representing edges of the tetrahedron - Real3 v0 = m1 - m0; - Real3 v1 = m2 - m0; - Real3 v2 = m3 - m0; - - // Compute volume using scalar triple product - return std::abs(Arcane::math::dot(v0, Arcane::math::cross(v1, v2))) / 6.0; + for (const auto& bs : options()->dirichletPointCondition()) + ArcaneFemFunctions::BoundaryConditions2D::applyPointDirichletToLhsAndRhs(bs, node_dof, m_node_coord, m_linear_system, rhs_values); } /*---------------------------------------------------------------------------*/ +/** + * @brief Computes the element matrix for a tetrahedral element (P1 FE). + * + * This function calculates the integral of the expression: + * integral3D (u.dx * v.dx + u.dy * v.dy) + * + * Steps involved: + * 1. Calculate the area of the triangle. + * 2. Compute the gradients of the shape functions. + * 3. Return (u.dx * v.dx + u.dy * v.dy); + */ /*---------------------------------------------------------------------------*/ -Real FemModule:: -_computeAreaTriangle3(Cell cell) +FixedMatrix<4, 4> FemModule:: +_computeElementMatrixTetra4(Cell cell) { - Real3 m0 = m_node_coord[cell.nodeId(0)]; - Real3 m1 = m_node_coord[cell.nodeId(1)]; - Real3 m2 = m_node_coord[cell.nodeId(2)]; - return 0.5 * ((m1.x - m0.x) * (m2.y - m0.y) - (m2.x - m0.x) * (m1.y - m0.y)); -} + Real volume = ArcaneFemFunctions::MeshOperation::computeVolumeTetra4(cell, m_node_coord); -/*---------------------------------------------------------------------------*/ -/*---------------------------------------------------------------------------*/ + Real4 dxU = ArcaneFemFunctions::FeOperation3D::computeGradientXTetra4(cell, m_node_coord); + Real4 dyU = ArcaneFemFunctions::FeOperation3D::computeGradientYTetra4(cell, m_node_coord); + Real4 dzU = ArcaneFemFunctions::FeOperation3D::computeGradientZTetra4(cell, m_node_coord); -Real FemModule:: -_computeEdgeLength2(Face face) -{ - Real3 m0 = m_node_coord[face.nodeId(0)]; - Real3 m1 = m_node_coord[face.nodeId(1)]; - return math::sqrt((m1.x-m0.x)*(m1.x-m0.x) + (m1.y-m0.y)*(m1.y - m0.y)); + return volume * (dxU ^ dxU) + volume * (dyU ^ dyU) + volume * (dzU ^ dzU); } /*---------------------------------------------------------------------------*/ +/** + * @brief Computes the element matrix for a triangular element (P1 FE). + * + * This function calculates the integral of the expression: + * integral2D (u.dx * v.dx + u.dy * v.dy) + * + * Steps involved: + * 1. Calculate the area of the triangle. + * 2. Compute the gradients of the shape functions. + * 3. Return (u.dx * v.dx + u.dy * v.dy); + */ /*---------------------------------------------------------------------------*/ -Real2 FemModule:: -_computeEdgeNormal2(Face face) +FixedMatrix<3, 3> FemModule:: +_computeElementMatrixTria3(Cell cell) { - Real3 m0 = m_node_coord[face.nodeId(0)]; - Real3 m1 = m_node_coord[face.nodeId(1)]; - if (!face.isSubDomainBoundaryOutside()) - std::swap(m0,m1); - Real2 N; - Real norm_N = math::sqrt( (m1.y - m0.y)*(m1.y - m0.y) + (m1.x - m0.x)*(m1.x - m0.x) ); // for normalizing - N.x = (m1.y - m0.y)/ norm_N; - N.y = (m0.x - m1.x)/ norm_N; - return N; -} + Real area = ArcaneFemFunctions::MeshOperation::computeAreaTria3(cell, m_node_coord); -/*---------------------------------------------------------------------------*/ -/*---------------------------------------------------------------------------*/ + Real3 dxU = ArcaneFemFunctions::FeOperation2D::computeGradientXTria3(cell, m_node_coord); + Real3 dyU = ArcaneFemFunctions::FeOperation2D::computeGradientYTria3(cell, m_node_coord); -FixedMatrix<4, 4> FemModule:: -_computeElementMatrixTETRA4(Cell cell) -{ - // Get coordinates of the triangle element TETRA4 - //------------------------------------------------ - // 3 o - // /|\ - // / | \ - // / | \ - // / o 2 \ - // / . . \ - // o-----------o - // 0 1 - //------------------------------------------------ - Real3 m0 = m_node_coord[cell.nodeId(0)]; - Real3 m1 = m_node_coord[cell.nodeId(1)]; - Real3 m2 = m_node_coord[cell.nodeId(2)]; - Real3 m3 = m_node_coord[cell.nodeId(3)]; - - Real volume = _computeAreaTetra4(cell); - - // Compute gradients of shape functions - Real3 dPhi0 = Arcane::math::cross(m2 - m1, m1 - m3) ; - Real3 dPhi1 = Arcane::math::cross(m3 - m0, m0 - m2) ; - Real3 dPhi2 = Arcane::math::cross(m1 - m0, m0 - m3) ; - Real3 dPhi3 = Arcane::math::cross(m0 - m1, m1 - m2) ; - - // Construct the B-matrix - FixedMatrix<3, 4> b_matrix; - b_matrix(0, 0) = dPhi0.x; - b_matrix(1, 0) = dPhi0.y; - b_matrix(2, 0) = dPhi0.z; - - b_matrix(0, 1) = dPhi1.x; - b_matrix(1, 1) = dPhi1.y; - b_matrix(2, 1) = dPhi1.z; - - b_matrix(0, 2) = dPhi2.x; - b_matrix(1, 2) = dPhi2.y; - b_matrix(2, 2) = dPhi2.z; - - b_matrix(0, 3) = dPhi3.x; - b_matrix(1, 3) = dPhi3.y; - b_matrix(2, 3) = dPhi3.z; - - b_matrix.multInPlace(1.0 / (6.0 * volume)); - - // Compute the element matrix - FixedMatrix<4, 4> int_cdPi_dPj = matrixMultiplication(matrixTranspose(b_matrix), b_matrix); - int_cdPi_dPj.multInPlace(volume); - -/* - cout << " Ae \n" - << "\t" << int_cdPi_dPj(0,0)<<"\t"<< int_cdPi_dPj(0,1)<<"\t"<< int_cdPi_dPj(0,2)<<"\t"<< int_cdPi_dPj(0,3)<<"\n" - << "\t" << int_cdPi_dPj(1,0)<<"\t"<< int_cdPi_dPj(1,1)<<"\t"<< int_cdPi_dPj(1,2)<<"\t"<< int_cdPi_dPj(1,3)<<"\n" - << "\t" << int_cdPi_dPj(2,0)<<"\t"<< int_cdPi_dPj(2,1)<<"\t"<< int_cdPi_dPj(2,2)<<"\t"<< int_cdPi_dPj(2,3)<<"\n" - << "\t" << int_cdPi_dPj(3,0)<<"\t"<< int_cdPi_dPj(3,1)<<"\t"<< int_cdPi_dPj(3,2)<<"\t"<< int_cdPi_dPj(3,3)<<"\n" - << endl; -*/ - - return int_cdPi_dPj; -} - -/*---------------------------------------------------------------------------*/ -/*---------------------------------------------------------------------------*/ - -FixedMatrix<3, 3> FemModule:: -_computeElementMatrixTRIA3(Cell cell) -{ - // Get coordinates of the triangle element TRI3 - //------------------------------------------------ - // 0 o - // . . - // . . - // . . - // 1 o . . . o 2 - //------------------------------------------------ - Real3 m0 = m_node_coord[cell.nodeId(0)]; - Real3 m1 = m_node_coord[cell.nodeId(1)]; - Real3 m2 = m_node_coord[cell.nodeId(2)]; - - Real area = _computeAreaTriangle3(cell); // calculate area - - Real2 dPhi0(m1.y - m2.y, m2.x - m1.x); - Real2 dPhi1(m2.y - m0.y, m0.x - m2.x); - Real2 dPhi2(m0.y - m1.y, m1.x - m0.x); - - FixedMatrix<2, 3> b_matrix; - b_matrix(0, 0) = dPhi0.x; - b_matrix(0, 1) = dPhi1.x; - b_matrix(0, 2) = dPhi2.x; - - b_matrix(1, 0) = dPhi0.y; - b_matrix(1, 1) = dPhi1.y; - b_matrix(1, 2) = dPhi2.y; - - b_matrix.multInPlace(1.0 / (2.0 * area)); - - FixedMatrix<3, 3> int_cdPi_dPj = matrixMultiplication(matrixTranspose(b_matrix), b_matrix); - int_cdPi_dPj.multInPlace(area); - - return int_cdPi_dPj; + return area * (dxU ^ dxU) + area * (dyU ^ dyU); } /*---------------------------------------------------------------------------*/ +/** + * @brief Calls the right function for LHS assembly given as mesh type. + */ /*---------------------------------------------------------------------------*/ void FemModule:: -_assembleBilinearOperatorTRIA3() +_assembleBilinearOperator() { - auto node_dof(m_dofs_on_nodes.nodeDoFConnectivityView()); - - ENUMERATE_ (Cell, icell, allCells()) { - Cell cell = *icell; - if (cell.type() != IT_Triangle3) - ARCANE_FATAL("Only Triangle3 cell type is supported"); - - auto K_e = _computeElementMatrixTRIA3(cell); // element stifness matrix - // # assemble elementary matrix into the global one - // # elementary terms are positionned into K according - // # to the rank of associated node in the mesh.nodes list - // for node1 in elem.nodes: - // inode1=elem.nodes.index(node1) # get position of node1 in nodes list - // for node2 in elem.nodes: - // inode2=elem.nodes.index(node2) - // K[node1.rank,node2.rank]=K[node1.rank,node2.rank]+K_e[inode1,inode2] - Int32 n1_index = 0; - for (Node node1 : cell.nodes()) { - Int32 n2_index = 0; - for (Node node2 : cell.nodes()) { - // K[node1.rank,node2.rank]=K[node1.rank,node2.rank]+K_e[inode1,inode2] - Real v = K_e(n1_index, n2_index); - // m_k_matrix(node1.localId(), node2.localId()) += v; - if (node1.isOwn()) { - m_linear_system.matrixAddValue(node_dof.dofId(node1, 0), node_dof.dofId(node2, 0), v); - } - ++n2_index; - } - ++n1_index; - } - } + if (options()->meshType == "TETRA4") + _assembleBilinear<4>([this](const Cell& cell) { + return _computeElementMatrixTetra4(cell); + }); + else if (options()->meshType == "TRIA3") + _assembleBilinear<3>([this](const Cell& cell) { + return _computeElementMatrixTria3(cell); + }); + else + ARCANE_FATAL("Non supported meshType"); } + /*---------------------------------------------------------------------------*/ +/** + * @brief Assembles the bilinear operator matrix for the FEM linear system. + * + * The method performs the following steps: + * 1. For each cell, retrieves the cell-specific constant `lambda`. + * 2. Computes element matrix using provided `compute_element_matrix` function. + * 3. Assembles global matrix by adding contributions from each cell's element + * matrix to the corresponding entries in the global matrix. + */ /*---------------------------------------------------------------------------*/ +template void FemModule:: -_assembleBilinearOperatorTETRA4() +_assembleBilinear(const std::function(const Cell&)>& compute_element_matrix) { auto node_dof(m_dofs_on_nodes.nodeDoFConnectivityView()); ENUMERATE_ (Cell, icell, allCells()) { Cell cell = *icell; - auto K_e = _computeElementMatrixTETRA4(cell); // element stiffness matrix - - // # assemble elementary matrix into the global one - // # elementary terms are positioned into K according - // # to the rank of associated node in the mesh.nodes list - // for node1 in elem.nodes: - // inode1=elem.nodes.index(node1) # get position of node1 in nodes list - // for node2 in elem.nodes: - // inode2=elem.nodes.index(node2) - // K[node1.rank,node2.rank]=K[node1.rank,node2.rank]+K_e[inode1,inode2] + auto K_e = compute_element_matrix(cell); // element matrix based on the provided function Int32 n1_index = 0; for (Node node1 : cell.nodes()) { Int32 n2_index = 0; for (Node node2 : cell.nodes()) { - // K[node1.rank,node2.rank]=K[node1.rank,node2.rank]+K_e[inode1,inode2] Real v = K_e(n1_index, n2_index); - // m_k_matrix(node1.localId(), node2.localId()) += v; if (node1.isOwn()) { m_linear_system.matrixAddValue(node_dof.dofId(node1, 0), node_dof.dofId(node2, 0), v); } @@ -670,10 +240,17 @@ _assembleBilinearOperatorTETRA4() ++n1_index; } } - } /*---------------------------------------------------------------------------*/ +/** + * @brief Solves the linear system and updates the solution vector. + * + * This method performs the following actions: + * 1. Solves the linear system to compute the solution. + * 2. Copies the computed solution from the DoF to the node values. + * 3. Synchronizes the updated node values. + */ /*---------------------------------------------------------------------------*/ void FemModule:: @@ -681,10 +258,6 @@ _solve() { m_linear_system.solve(); - // Re-Apply boundary conditions because the solver has modified the value - // of u on all nodes - _applyDirichletBoundaryConditions(); - { VariableDoFReal& dof_u(m_linear_system.solutionVariable()); // Copy RHS DoF to Node u @@ -697,31 +270,35 @@ _solve() } m_u.synchronize(); - - const bool do_print = (allNodes().size() < 200); - if (do_print) { - ENUMERATE_ (Node, inode, allNodes()) { - Node node = *inode; - info() << "u[" << node.localId() << "][" << node.uniqueId() << "] = " - << m_u[node]; - //info() << "u[]" << node.uniqueId() << " " - // << m_u[node]; - } - } } /*---------------------------------------------------------------------------*/ +/** + * @brief Validates and prints the results of the FEM computation. + * + * This method performs the following actions: + * 1. If number of nodes < 200, prints the computed values for each node. + * 2. Retrieves the filename for the result file from options. + * 3. If a filename is provided, checks the computed results against result file. + * + * @note The result comparison uses a tolerance of 1.0e-4. + */ /*---------------------------------------------------------------------------*/ void FemModule:: -_checkResultFile() +_validateResults() { + if (allNodes().size() < 200) + ENUMERATE_ (Node, inode, allNodes()) { + Node node = *inode; + info() << "u[" << node.localId() << "][" << node.uniqueId() << "] = " << m_u[node]; + } + String filename = options()->resultFile(); - info() << "CheckResultFile filename=" << filename; - if (filename.empty()) - return; - const double epsilon = 1.0e-4; - checkNodeResultFile(traceMng(), filename, m_u, epsilon); + info() << "ValidateResultFile filename=" << filename; + + if (!filename.empty()) + checkNodeResultFile(traceMng(), filename, m_u, 1.0e-4); } /*---------------------------------------------------------------------------*/ @@ -730,4 +307,4 @@ _checkResultFile() ARCANE_REGISTER_MODULE_FEM(FemModule); /*---------------------------------------------------------------------------*/ -/*---------------------------------------------------------------------------*/ +/*---------------------------------------------------------------------------*/ \ No newline at end of file diff --git a/laplace/FemModule.h b/laplace/FemModule.h new file mode 100644 index 00000000..a423597c --- /dev/null +++ b/laplace/FemModule.h @@ -0,0 +1,89 @@ +// -*- tab-width: 2; indent-tabs-mode: nil; coding: utf-8-with-signature -*- +//----------------------------------------------------------------------------- +// Copyright 2000-2024 CEA (www.cea.fr) IFPEN (www.ifpenergiesnouvelles.com) +// See the top-level COPYRIGHT file for details. +// SPDX-License-Identifier: Apache-2.0 +//----------------------------------------------------------------------------- +/*---------------------------------------------------------------------------*/ +/* FemModule.h (C) 2022-2024 */ +/* */ +/* FemModule class definition. */ +/*---------------------------------------------------------------------------*/ +/*---------------------------------------------------------------------------*/ +#ifndef FEMMODULES_H +#define FEMMODULES_H +/*---------------------------------------------------------------------------*/ +/*---------------------------------------------------------------------------*/ + +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "IDoFLinearSystemFactory.h" +#include "Fem_axl.h" +#include "FemUtils.h" +#include "DoFLinearSystem.h" +#include "FemDoFsOnNodes.h" +#include "ArcaneFemFunctions.h" + +/*---------------------------------------------------------------------------*/ +/*---------------------------------------------------------------------------*/ + +using namespace Arcane; +using namespace Arcane::FemUtils; + +/*---------------------------------------------------------------------------*/ +/** + * @brief A module for finite element method. + * + * This class handles the initialization and computation for finite element + * method (FEM) simulations, providing methods to set up and solve linear + * systems, assemble FEM operators, and perform result checks. + */ +/*---------------------------------------------------------------------------*/ + +class FemModule +: public ArcaneFemObject +{ + public: + + explicit FemModule(const ModuleBuildInfo& mbi) + : ArcaneFemObject(mbi) + , m_dofs_on_nodes(mbi.subDomain()->traceMng()) + { + ICaseMng* cm = mbi.subDomain()->caseMng(); + cm->setTreatWarningAsError(true); + cm->setAllowUnkownRootElelement(false); + } + + void startInit() override; //! Method called at the beginning of the simulation + void compute() override; //! Method called at each iteration + VersionInfo versionInfo() const override { return VersionInfo(1, 0, 0); } + + private: + + DoFLinearSystem m_linear_system; + IItemFamily* m_dof_family = nullptr; + FemDoFsOnNodes m_dofs_on_nodes; + + void _doStationarySolve(); + void _getMaterialParameters(); + void _assembleBilinearOperator(); + void _solve(); + void _assembleLinearOperator(); + void _validateResults(); + + FixedMatrix<3, 3> _computeElementMatrixTria3(Cell cell); + FixedMatrix<4, 4> _computeElementMatrixTetra4(Cell cell); + + template + void _assembleBilinear( const std::function(const Cell&)>& compute_element_matrix); +}; + +#endif \ No newline at end of file