Skip to content

Commit 3d2e9af

Browse files
[feat] quad4/hex8 support
1 parent 95f7f30 commit 3d2e9af

File tree

14 files changed

+465
-27
lines changed

14 files changed

+465
-27
lines changed

femutils/ArcaneFemFunctions.h

Lines changed: 53 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -710,6 +710,25 @@ class ArcaneFemFunctions
710710

711711
return { grad_x, grad_y, 0.0 };
712712
}
713+
714+
/*---------------------------------------------------------------------------*/
715+
/**
716+
* @brief Computes the shape functions for a Quad4 element at a given point (ξ, η).
717+
*
718+
* @param xi The ξ coordinate of the evaluation point (-1 to 1).
719+
* @param eta The η coordinate of the evaluation point (-1 to 1).
720+
* @return A RealVector<4> containing the shape functions {𝑁₁, 𝑁₂, 𝑁₃, 𝑁₄}.
721+
*/
722+
/*---------------------------------------------------------------------------*/
723+
static inline RealVector<4> computeShapeFunctionsQuad4(Real xi, Real eta)
724+
{
725+
RealVector<4> N;
726+
N(0) = 0.25 * (1. - xi) * (1. - eta); // 𝑁₁
727+
N(1) = 0.25 * (1. + xi) * (1. - eta); // 𝑁₂
728+
N(2) = 0.25 * (1. + xi) * (1. + eta); // 𝑁₃
729+
N(3) = 0.25 * (1. - xi) * (1. + eta); // 𝑁₄
730+
return N;
731+
}
713732
};
714733

715734
/*---------------------------------------------------------------------------*/
@@ -908,7 +927,7 @@ class ArcaneFemFunctions
908927

909928
/*---------------------------------------------------------------------------*/
910929
/**
911-
* @brief Holds information for a Quad4 element at a single Gauss point.
930+
* @brief Holds information for a Hexa8 element at a single Gauss point.
912931
*
913932
* This includes the gradients of the shape functions in the physical space (𝑥,𝑦,𝑧)
914933
* and the determinant of the Jacobian matrix.
@@ -1073,6 +1092,39 @@ class ArcaneFemFunctions
10731092

10741093
return { grad_x, grad_y, grad_z };
10751094
}
1095+
1096+
1097+
/*---------------------------------------------------------------------------*/
1098+
/**
1099+
* @brief Computes the shape functions for a Hexa8 element at a given point (ξ,η,ζ).
1100+
*
1101+
* @param xi The ξ coordinate of the evaluation point (-1 to 1).
1102+
* @param eta The η coordinate of the evaluation point (-1 to 1).
1103+
* @param zeta The ζ coordinate of the evaluation point (-1 to 1).
1104+
* @return A RealVector<8> containing the shape functions {𝑁₁, 𝑁₂, 𝑁₃, 𝑁₄, 𝑁₅, 𝑁₆, 𝑁₇, 𝑁₈}.
1105+
*/
1106+
/*---------------------------------------------------------------------------*/
1107+
static inline RealVector<8> computeShapeFunctionsHexa8(Real xi, Real eta, Real zeta)
1108+
{
1109+
RealVector<8> N;
1110+
const Real one_minus_eta = 1.0 - eta;
1111+
const Real one_plus_eta = 1.0 + eta;
1112+
const Real one_minus_xi = 1.0 - xi;
1113+
const Real one_plus_xi = 1.0 + xi;
1114+
const Real one_minus_zeta = 1.0 - zeta;
1115+
const Real one_plus_zeta = 1.0 + zeta;
1116+
1117+
N(0) = 0.125 * one_minus_xi * one_minus_eta * one_minus_zeta; // 𝑁₁
1118+
N(1) = 0.125 * one_plus_xi * one_minus_eta * one_minus_zeta; // 𝑁₂
1119+
N(2) = 0.125 * one_plus_xi * one_plus_eta * one_minus_zeta; // 𝑁₃
1120+
N(3) = 0.125 * one_minus_xi * one_plus_eta * one_minus_zeta; // 𝑁₄
1121+
N(4) = 0.125 * one_minus_xi * one_minus_eta * one_plus_zeta; // 𝑁₅
1122+
N(5) = 0.125 * one_plus_xi * one_minus_eta * one_plus_zeta; // 𝑁₆
1123+
N(6) = 0.125 * one_plus_xi * one_plus_eta * one_plus_zeta; // 𝑁₇
1124+
N(7) = 0.125 * one_minus_xi * one_plus_eta * one_plus_zeta; // 𝑁₈
1125+
1126+
return N;
1127+
}
10761128
};
10771129

10781130
/*---------------------------------------------------------------------------*/

meshes/med/sphere_in_sphere.py

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
#!/usr/bin/env python
2+
3+
###
4+
### This file is generated automatically by SALOME v9.14.0 with dump python functionality
5+
###
6+
7+
import sys
8+
import salome
9+
10+
salome.salome_init()
11+
import salome_notebook
12+
notebook = salome_notebook.NoteBook()
13+
14+
###
15+
### SHAPER component
16+
###
17+
18+
from salome.shaper import model
19+
20+
model.begin()
21+
partSet = model.moduleDocument()
22+
23+
### Create Part
24+
Part_1 = model.addPart(partSet)
25+
Part_1_doc = Part_1.document()
26+
27+
### Create Sphere
28+
Sphere_1 = model.addSphere(Part_1_doc, model.selection("VERTEX", "PartSet/Origin"), 0.1)
29+
30+
### Create Sphere
31+
Sphere_2 = model.addSphere(Part_1_doc, model.selection("VERTEX", "PartSet/Origin"), 0.01)
32+
33+
### Create Cut
34+
Cut_1 = model.addCut(Part_1_doc, [model.selection("COMPOUND", "all-in-Sphere_1")], [model.selection("SOLID", "Sphere_2_1")], keepSubResults = True)
35+
36+
### Create Group
37+
Group_1 = model.addGroup(Part_1_doc, "Faces", [model.selection("FACE", "Sphere_2_1/Face_1")])
38+
Group_1.setName("inner")
39+
Group_1.result().setName("inner")
40+
41+
### Create Group
42+
Group_2 = model.addGroup(Part_1_doc, "Faces", [model.selection("FACE", "Sphere_1_1/Face_1")])
43+
Group_2.setName("outer")
44+
Group_2.result().setName("outer")
45+
46+
### Create Group
47+
Group_3 = model.addGroup(Part_1_doc, "Solids", [model.selection("SOLID", "Cut_1_1")])
48+
Group_3.setName("vol")
49+
Group_3.result().setName("vol")
50+
51+
model.end()
52+
53+
###
54+
### SHAPERSTUDY component
55+
###
56+
57+
model.publishToShaperStudy()
58+
import SHAPERSTUDY
59+
Cut_1_1, inner, outer, vol, = SHAPERSTUDY.shape(model.featureStringId(Cut_1))
60+
61+
if salome.sg.hasDesktop():
62+
salome.sg.updateObjBrowser()

meshes/med/truncated_cube.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@
1010
salome.salome_init()
1111
import salome_notebook
1212
notebook = salome_notebook.NoteBook()
13-
sys.path.insert(0, r'/home/catA/mb258512/Install/arcanefem/arcanefem/meshes/med')
1413

1514
###
1615
### SHAPER component
648 KB
Binary file not shown.

meshes/msh/sub.quad.msh

49.1 KB
Binary file not shown.

modules/acoustics/CMakeLists.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,9 @@ file(COPY "inputs" DESTINATION ${CMAKE_CURRENT_BINARY_DIR})
2121
file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/meshes)
2222
set(MESH_FILES
2323
sub.msh
24+
sub.quad.msh
2425
sub_3d.msh
26+
sphere_in_sphere.hexa.msh
2527
)
2628
foreach(MESH_FILE IN LISTS MESH_FILES)
2729
file(COPY ${MSH_DIR}/${MESH_FILE} DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/meshes)
@@ -32,7 +34,9 @@ target_link_libraries(Acoustics PUBLIC FemUtils)
3234
enable_testing()
3335

3436
add_test(NAME [acoustics]2D_submarine COMMAND Acoustics inputs/sub.arc)
37+
add_test(NAME [acoustics]2D_submarine_quad COMMAND Acoustics inputs/sub.quad.arc)
3538
add_test(NAME [acoustics]3D_sphere COMMAND Acoustics inputs/3d_sub.arc)
39+
add_test(NAME [acoustics]3D_sphere_hexa COMMAND Acoustics inputs/3d_sphere_in_sphere.hexa.arc)
3640

3741

3842
if(FEMUTILS_HAS_SOLVER_BACKEND_PETSC)
Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
// -*- tab-width: 2; indent-tabs-mode: nil; coding: utf-8-with-signature -*-
2+
//-----------------------------------------------------------------------------
3+
// Copyright 2000-2025 CEA (www.cea.fr) IFPEN (www.ifpenergiesnouvelles.com)
4+
// See the top-level COPYRIGHT file for details.
5+
// SPDX-License-Identifier: Apache-2.0
6+
//-----------------------------------------------------------------------------
7+
/*---------------------------------------------------------------------------*/
8+
/* ElementMatrixHexQuad.h (C) 2022-2025 */
9+
/* */
10+
/* Contains functions to compute the FEM element matrices for Acoustics */
11+
/*---------------------------------------------------------------------------*/
12+
/*---------------------------------------------------------------------------*/
13+
14+
/*---------------------------------------------------------------------------*/
15+
/**
16+
* @brief Computes the element matrix for a quadrilateral element (QUAD4, ℙ1 FE).
17+
*
18+
* This function calculates the integral of:
19+
* 𝑎(𝑢,𝑣) = ∫∫ (∂𝑢/∂𝑥 ∂𝑣/∂𝑥 + ∂𝑢/∂𝑦 ∂𝑣/∂𝑦)dΩ + ∫∫ 𝑘²𝑢𝑣 dΩ
20+
*
21+
* Steps involved:
22+
* 1. Define Gauss points (2x2) and weights.
23+
* 2. Loop over Gauss points to compute the gradients in physical space
24+
* and the determinant of the Jacobian, via computeGradientsAndJacobianQuad4.
25+
* 3. Compute the integration weight.
26+
* 4. Assemble the element matrix using the computed gradients.
27+
*
28+
* @param cell The cell for which the element matrix is computed.
29+
* @return The computed element matrix.
30+
*/
31+
/*---------------------------------------------------------------------------*/
32+
33+
RealMatrix<4, 4> FemModule::_computeElementMatrixQuad4(Cell cell)
34+
{
35+
// Gauss points and weights for 2x2 quadrature
36+
constexpr Real gp[2] = { -M_SQRT1_3, M_SQRT1_3 }; // [-1/sqrt(3) , 1/sqrt(3)]
37+
constexpr Real w = 1.0;
38+
39+
// Initialize the element matrix
40+
RealMatrix<4, 4> ae;
41+
ae.fill(0.0);
42+
43+
// Loop over Gauss points
44+
for (Int8 ixi = 0; ixi < 2; ++ixi) {
45+
for (Int8 ieta = 0; ieta < 2; ++ieta) {
46+
47+
// Get the coordinates of the Gauss point in natural coordinates (ξ,η,ζ)
48+
const Real xi = gp[ixi];
49+
const Real eta = gp[ieta];
50+
51+
// Get shape function gradients w.r.t (𝑥,𝑦) and determinant of Jacobian
52+
const auto gp_info = ArcaneFemFunctions::FeOperation2D::computeGradientsAndJacobianQuad4(cell, m_node_coord, xi, eta);
53+
const RealVector<4>& dxU = gp_info.dN_dx;
54+
const RealVector<4>& dyU = gp_info.dN_dy;
55+
const Real detJ = gp_info.det_j;
56+
57+
// Shape functions at the Gauss point
58+
RealVector<4> N = ArcaneFemFunctions::FeOperation2D::computeShapeFunctionsQuad4(xi, eta);
59+
60+
// Integration weight
61+
const Real integration_weight = detJ * w * w;
62+
63+
// stiffness matrix assembly
64+
ae += (dxU ^ dxU) * integration_weight + (dyU ^ dyU) * integration_weight +(N ^ N) * m_kc2 * integration_weight;
65+
}
66+
}
67+
return ae;
68+
}
69+
70+
/*---------------------------------------------------------------------------*/
71+
/**
72+
* @brief Computes the element matrix for a hexahedral element (HEXA8, ℙ1 FE).
73+
*
74+
* This function calculates the integral of:
75+
* 𝑎(𝑢,𝑣) = ∫∫∫ (∂𝑢/∂𝑥 ∂𝑣/∂𝑥 + ∂𝑢/∂𝑦 ∂𝑣/∂𝑦 + ∂𝑢/∂𝑧 ∂𝑣/∂𝑧)dΩ + ∫∫∫ 𝑘²𝑢𝑣 dΩ
76+
*
77+
* Steps involved:
78+
* 1. Define Gauss points (2x2x2) and weights.
79+
* 2. Loop over Gauss points to compute the gradients in physical space (𝑥,𝑦,𝑧)
80+
* and the determinant of the Jacobian via computeGradientsAndJacobianHexa8.
81+
* 3. Compute the integration weight.
82+
* 4. Assemble the element matrix using the computed gradients.
83+
*
84+
* @param cell The cell for which the element matrix is computed.
85+
* @return The computed element matrix.
86+
*/
87+
/*---------------------------------------------------------------------------*/
88+
89+
RealMatrix<8, 8> FemModule::_computeElementMatrixHexa8(Cell cell)
90+
{
91+
// 2x2x2 Gauss points and weights for [-1,1]^3
92+
constexpr Real gp[2] = { -M_SQRT1_3, M_SQRT1_3 }; // -1/sqrt(3), 1/sqrt(3)
93+
constexpr Real w = 1.0;
94+
95+
// Initialize the element matrix
96+
RealMatrix<8, 8> ae;
97+
ae.fill(0.0);
98+
99+
// Loop over Gauss points
100+
for (Int8 ixi = 0; ixi < 2; ++ixi) {
101+
for (Int8 ieta = 0; ieta < 2; ++ieta) {
102+
for (Int8 izeta = 0; izeta < 2; ++izeta) {
103+
104+
// Get the coordinates of Gauss points in natural coordinates (ξ,η,ζ)
105+
const Real xi = gp[ixi];
106+
const Real eta = gp[ieta];
107+
const Real zeta = gp[izeta];
108+
109+
// Get shape function gradients w.r.t (𝑥,𝑦,𝑧) and determinant of Jacobian
110+
const auto gp_info = ArcaneFemFunctions::FeOperation3D::computeGradientsAndJacobianHexa8(cell, m_node_coord, xi, eta, zeta);
111+
const RealVector<8>& dxU = gp_info.dN_dx;
112+
const RealVector<8>& dyU = gp_info.dN_dy;
113+
const RealVector<8>& dzU = gp_info.dN_dz;
114+
const Real detJ = gp_info.det_j;
115+
116+
// Shape functions at the Gauss point
117+
RealVector<8> N = ArcaneFemFunctions::FeOperation3D::computeShapeFunctionsHexa8(xi, eta, zeta);
118+
119+
// Integration weight
120+
const Real integration_weight = detJ * w * w;
121+
122+
// Assemble element matrix (variational form)
123+
ae += (dxU ^ dxU) * integration_weight + (dyU ^ dyU) * integration_weight + (dzU ^ dzU) * integration_weight +
124+
(N ^ N) * m_kc2 * integration_weight;
125+
}
126+
}
127+
}
128+
return ae;
129+
}

modules/acoustics/Fem.axl

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@
3737
<description>Which matrix format to use DOK|BSR|AF-BSR.</description>
3838
</simple>
3939

40-
<simple name="petsc-flags" type="string" default="" optional="true">
40+
<simple name="petsc-flags" type="string" default="-mat_type baij" optional="true">
4141
<description>Flags for PETSc from commandline.</description>
4242
</simple>
4343

@@ -53,6 +53,10 @@
5353
<description>Boolean to enable cross validation.</description>
5454
</simple>
5555

56+
<simple name="hex-quad-mesh" type="bool" default="false" optional="true">
57+
<description>Boolean to use hexa or quad element mesh.</description>
58+
</simple>
59+
5660
<!-- Linear system service instance -->
5761
<service-instance name="linear-system" type="Arcane::FemUtils::IDoFLinearSystemFactory" default="AlephLinearSystem" />
5862

modules/acoustics/FemModule.cc

Lines changed: 24 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313

1414
#include "FemModule.h"
1515
#include "ElementMatrix.h"
16+
#include "ElementMatrixHexQuad.h"
1617

1718
/*---------------------------------------------------------------------------*/
1819
/**
@@ -36,6 +37,7 @@ startInit()
3637
m_solve_linear_system = options()->solveLinearSystem();
3738
m_cross_validation = options()->crossValidation();
3839
m_petsc_flags = options()->petscFlags();
40+
m_hex_quad_mesh = options()->hexQuadMesh();
3941

4042
elapsedTime = platform::getRealTime() - elapsedTime;
4143
ArcaneFemFunctions::GeneralFunctions::printArcaneFemTime(traceMng(), "initialize", elapsedTime);
@@ -144,23 +146,19 @@ _assembleLinearOperator()
144146

145147
const auto node_dof(m_dofs_on_nodes.nodeDoFConnectivityView());
146148

147-
auto applyBoundaryConditions = [&](auto BCFunctions) {
148-
BC::IArcaneFemBC* bc = options()->boundaryConditions();
149-
if (bc) {
150-
for (BC::INeumannBoundaryCondition* bs : bc->neumannBoundaryConditions()) {
151-
BCFunctions.applyNeumannToRhs(bs, node_dof, m_node_coord, rhs_values);
152-
}
153-
}
154-
};
155-
156-
// Apply the correct boundary conditions based on mesh dimension
157-
if (mesh()->dimension() == 3) {
158-
using BCFunctions = ArcaneFemFunctions::BoundaryConditions3D;
159-
applyBoundaryConditions(BCFunctions());
160-
}
161-
else {
162-
using BCFunctions = ArcaneFemFunctions::BoundaryConditions2D;
163-
applyBoundaryConditions(BCFunctions());
149+
BC::IArcaneFemBC* bc = options()->boundaryConditions();
150+
if (bc) {
151+
for (BC::INeumannBoundaryCondition* bs : bc->neumannBoundaryConditions())
152+
if (mesh()->dimension() == 2)
153+
if (m_hex_quad_mesh)
154+
ArcaneFemFunctions::BoundaryConditions2D::applyNeumannToRhsQuad4(bs, node_dof, m_node_coord, rhs_values);
155+
else
156+
ArcaneFemFunctions::BoundaryConditions2D::applyNeumannToRhs(bs, node_dof, m_node_coord, rhs_values);
157+
else
158+
if (m_hex_quad_mesh)
159+
ArcaneFemFunctions::BoundaryConditions3D::applyNeumannToRhsHexa8(bs, node_dof, m_node_coord, rhs_values);
160+
else
161+
ArcaneFemFunctions::BoundaryConditions3D::applyNeumannToRhs(bs, node_dof, m_node_coord, rhs_values);
164162
}
165163

166164
elapsedTime = platform::getRealTime() - elapsedTime;
@@ -180,13 +178,16 @@ _assembleBilinearOperator()
180178
Real elapsedTime = platform::getRealTime();
181179

182180
if (mesh()->dimension() == 3)
183-
_assembleBilinear<4>([this](const Cell& cell) {
184-
return _computeElementMatrixTetra4(cell);
185-
});
181+
if(m_hex_quad_mesh)
182+
_assembleBilinear<8>([this](const Cell& cell) { return _computeElementMatrixHexa8(cell); });
183+
else
184+
_assembleBilinear<4>([this](const Cell& cell) { return _computeElementMatrixTetra4(cell); });
185+
186186
if (mesh()->dimension() == 2)
187-
_assembleBilinear<3>([this](const Cell& cell) {
188-
return _computeElementMatrixTria3(cell);
189-
});
187+
if(m_hex_quad_mesh)
188+
_assembleBilinear<4>([this](const Cell& cell) { return _computeElementMatrixQuad4(cell); });
189+
else
190+
_assembleBilinear<3>([this](const Cell& cell) { return _computeElementMatrixTria3(cell); });
190191

191192
elapsedTime = platform::getRealTime() - elapsedTime;
192193
ArcaneFemFunctions::GeneralFunctions::printArcaneFemTime(traceMng(), "lhs-matrix-assembly", elapsedTime);

0 commit comments

Comments
 (0)