From 56e89d6a7d98ac9582036198d1187cc0871195a9 Mon Sep 17 00:00:00 2001 From: itchinda Date: Sun, 17 May 2026 16:30:41 -0400 Subject: [PATCH 1/9] Refactor DynamicMatrix and introduce robust MatrixView support - Added lightweight MatrixView / ConstMatrixView abstractions for non-owning matrix access and submatrix extraction. - Added row-based std::span accessors for safer contiguous access. - Implemented subView support with stride-aware views. - Added contiguous-memory detection via isContiguous(). - Improved const-correctness and added [[nodiscard]] annotations. - Added utility methods: - empty() - sameShape() - isSquare() - row() - Improved transpose() robustness: - Added alias-safe handling for M.transpose(M) - Added optimized in-place transpose for square matrices - Added safe fallback path for rectangular matrices - Improved resize/assign documentation and API consistency. - Expanded unit tests: - MatrixView and nested subviews - transpose aliasing - reshape preservation - const row access - move semantics - empty/square/shape utilities - contiguous vs non-contiguous views Preparation work for future batched FEM kernels and view-based assembly optimizations. --- libs/fem/fespace/include/FESpace.h | 21 +- libs/fem/fespace/src/DGSpace.cpp | 33 +- libs/fem/fespace/src/FESpace.cpp | 57 +- libs/fem/fespace/src/LagrangeSpace.cpp | 17 +- .../include/ReferenceElement2.h | 373 +++++++ .../include/ReferenceElement2.tpp | 341 +++++++ libs/utils/include/DynamicMatrix.h | 755 ++++++++++----- libs/utils/include/Tensor2D.h | 15 +- libs/utils/include/Tensor3D.h | 907 ++++++++++-------- libs/utils/include/Vector2D.h | 15 +- libs/utils/include/Vector3D.h | 10 +- tests/CMakeLists.txt | 1 + tests/utils/tensors/test_tensor_addition.cpp | 14 +- tests/utils/tensors/test_tensor_product.cpp | 14 +- .../tensors/test_tensor_substraction.cpp | 14 +- tests/utils/test_dynamic_matrix.cpp | 282 +++++- 16 files changed, 2071 insertions(+), 798 deletions(-) create mode 100644 libs/fem/reference_element/include/ReferenceElement2.h create mode 100644 libs/fem/reference_element/include/ReferenceElement2.tpp diff --git a/libs/fem/fespace/include/FESpace.h b/libs/fem/fespace/include/FESpace.h index 9ef85bc..b1709d6 100644 --- a/libs/fem/fespace/include/FESpace.h +++ b/libs/fem/fespace/include/FESpace.h @@ -37,7 +37,7 @@ namespace cfem { SymTensor2D, // 3 [xx, yy, xy] SkewSymTensor3D, // 3 [yz, xz, xy] Tensor2D, // 4 [xx, xy, yx, yy] - AxisSymTensor, // 4 [xx, yy, zz, xy] + AxiSymTensor3D, // 4 [xx, yy, zz, xy] SymTensor3D, // 6 [xx, yy, zz, yz, xz, xy] Tensor3D // 9 Full 3x3 matrix }; @@ -57,7 +57,7 @@ namespace cfem { case FieldType::SymTensor2D: return 3; case FieldType::SkewSymTensor3D: return 3; case FieldType::Tensor2D: return 4; - case FieldType::AxisSymTensor: return 4; + case FieldType::AxiSymTensor3D: return 4; case FieldType::SymTensor3D: return 6; case FieldType::Tensor3D: return 9; default: @@ -79,13 +79,13 @@ class FESpace : public MeshObserver, public std::enable_shared_from_this(m_order); } - const std::shared_ptr& getMesh() const { return m_mesh; } + constexpr CfemInt getNumComponents() const noexcept { return m_numComponents; } + constexpr FieldType getFieldType() const noexcept { return m_fieldType; } + constexpr Order getOrder() const noexcept { return m_order; } + constexpr CfemInt getOrderInt() const noexcept { return static_cast(m_order); } + constexpr const std::shared_ptr& getMesh() const noexcept { return m_mesh; } - Continuity getContinuity() const { return m_continuity; } + constexpr Continuity getContinuity() const noexcept { return m_continuity; } // Geometry & Reference Elements const ReferenceElement& getRefElement(CellType type) const; @@ -173,9 +173,7 @@ class FESpace : public MeshObserver, public std::enable_shared_from_this mesh, Order order, Continuity cont, FieldType fieldType) : m_mesh(std::move(mesh)), m_order(order), m_continuity(cont), m_fieldType(fieldType), m_numComponents(getComponentCountFromType(fieldType)) - { - initReferenceElements(); - } + { } std::shared_ptr m_mesh; Order m_order; @@ -189,7 +187,6 @@ class FESpace : public MeshObserver, public std::enable_shared_from_this> m_observers; std::map> m_refElements; - void initReferenceElements(); virtual void validate() const; }; diff --git a/libs/fem/fespace/src/DGSpace.cpp b/libs/fem/fespace/src/DGSpace.cpp index 680568e..14feb65 100644 --- a/libs/fem/fespace/src/DGSpace.cpp +++ b/libs/fem/fespace/src/DGSpace.cpp @@ -26,8 +26,8 @@ DGSpace::DGSpace(ConstructorToken, std::shared_ptr mesh, Order order, Fiel : FESpace(mesh, order, Continuity::DG, field_type) { if (order == Order::Mini){ - CFEM_ERROR << "Mini elements are not supported in DGSpace. " - << "Mini elements (P1+bubble) are designed for H1-stability; for DG, use P1 or P2 instead."; + CFEM_THROW("Mini elements are not supported in DGSpace. " + << "Mini elements (P1+bubble) are designed for H1-stability; for DG, use P1 or P2 instead."); } } @@ -37,8 +37,8 @@ std::shared_ptr DGSpace::create(std::shared_ptr mesh, FieldType field_type) { if (order == Order::Mini){ - CFEM_ERROR << "Mini elements are not supported in DGSpace. " - << "Mini elements (P1+bubble) are designed for H1-stability; for DG, use P1 or P2 instead."; + CFEM_THROW("Mini elements are not supported in DGSpace. " + << "Mini elements (P1+bubble) are designed for H1-stability; for DG, use P1 or P2 instead."); } if (order == Order::P2 && mesh->getOrder() == MeshOrder::Linear) { @@ -69,7 +69,8 @@ void DGSpace::enumerateDofs() { CfemInt currentOffset = 0; for (CfemInt i = 0; i < numCells; ++i) { m_cellOffsets[i] = currentOffset; - const auto& refEl = ReferenceElementFactory::getReferenceElement(m_mesh->getCell(i).type, getOrderInt()); + const Cell& cell = m_mesh->getCell(i); + const ReferenceElement& refEl = ReferenceElementFactory::getReferenceElement( cell.type, getOrderInt() ); currentOffset += refEl.getNumNodes(); } m_cellOffsets[numCells] = currentOffset; // Offset total final @@ -90,11 +91,15 @@ CfemInt DGSpace::getGlobalDofIdx(CfemInt cellId, CfemInt localNodeIdx, CfemInt c } CfemInt DGSpace::getDofsPerCell(CfemInt cellId) const { - return getRefElement(m_mesh->getCell(cellId).type).getNumNodes() * m_numComponents; + const Cell& cell = m_mesh->getCell(cellId); + const ReferenceElement& refEl = ReferenceElementFactory::getReferenceElement( cell.type, getOrderInt() ); + return refEl.getNumNodes() * m_numComponents; } void DGSpace::getElementDofs(CfemInt cellId, std::vector& globalIndices) const { - CfemInt nNodes = getRefElement(m_mesh->getCell(cellId).type).getNumNodes(); + const Cell& cell = m_mesh->getCell(cellId); + const ReferenceElement& refEl = ReferenceElementFactory::getReferenceElement( cell.type, getOrderInt() ); + CfemInt nNodes = refEl.getNumNodes(); globalIndices.resize(nNodes * m_numComponents); for (CfemInt c = 0; c < m_numComponents; ++c) { @@ -141,7 +146,8 @@ void DGSpace::computeSparsityPattern(std::vector& nnz) const { // On remplit le tableau nnz pour chaque composante et chaque noeud local de la cellule // Rappel : Notre numérotation est Block-wise [Comp 0][Comp 1]... for (CfemInt comp = 0; comp < m_numComponents; ++comp) { - CfemInt nNodes = getRefElement(cell.type).getNumNodes(); + const ReferenceElement& refEl = ReferenceElementFactory::getReferenceElement( cell.type, getOrderInt() ); + CfemInt nNodes = refEl.getNumNodes(); for (CfemInt n = 0; n < nNodes; ++n) { CfemInt globalIdx = getGlobalDofIdx(i, n, comp); nnz[globalIdx] = totalRowNNZ; @@ -155,17 +161,22 @@ void DGSpace::computeSparsityPattern(std::vector& nnz) const { // because they both use the ReferenceElement interface! void DGSpace::evaluateBasis(CfemInt cellId, const Vector3D& xi, std::vector& values) const { ShapeInfo shape; - getRefElement(m_mesh->getCell(cellId).type).evaluate(xi, ShapeEvaluationFlags::Value, shape); + const Cell& cell = m_mesh->getCell(cellId); + const ReferenceElement& refEl = ReferenceElementFactory::getReferenceElement( cell.type, getOrderInt() ); + refEl.evaluate(xi, ShapeEvaluationFlags::Value, shape); values.assign(shape.values.begin(), shape.values.end()); } void DGSpace::evaluateGradients(CfemInt cellId, const Vector3D& xi, std::vector& grads) const { static thread_local DynamicVector physCoords; m_mesh->getCellVerticesCoords(cellId, physCoords); + + const Cell& cell = m_mesh->getCell(cellId); + const ReferenceElement& refEl = ReferenceElementFactory::getReferenceElement( cell.type, getOrderInt() ); ShapeInfo shape; MappingInfo map; - getRefElement(m_mesh->getCell(cellId).type).computeMapping(xi, physCoords, ShapeEvaluationFlags::Gradient, shape, map); + refEl.computeMapping(xi, physCoords, ShapeEvaluationFlags::Gradient, shape, map); grads.assign(map.physicalShapeGradients.begin(), map.physicalShapeGradients.end()); } @@ -196,7 +207,7 @@ void DGSpace::getEntityDofs(CfemInt internalEntityId, std::vector& enti if (cellId == -1) continue; const auto& cell = m_mesh->getCell(cellId); - const auto& refEl = getRefElement(cell.type); + const ReferenceElement& refEl = ReferenceElementFactory::getReferenceElement( cell.type, getOrderInt() ); // 2. Trouver quels nœuds locaux de la cellule sont sur cette facette précise // On récupère la connectivité de la facette pour comparer diff --git a/libs/fem/fespace/src/FESpace.cpp b/libs/fem/fespace/src/FESpace.cpp index 4121f5e..01e95cf 100644 --- a/libs/fem/fespace/src/FESpace.cpp +++ b/libs/fem/fespace/src/FESpace.cpp @@ -24,11 +24,13 @@ namespace cfem { /** * @brief Registers a finite element field (e.g., Temperature, Velocity) associated with this space. - * * This method guarantees that: + * + * This method guarantees that: * 1. The field's name adheres to the proper naming policy rules. * 2. There are no global naming collisions across the mesh. * 3. The space keeps track of the field to notify it during mesh/topology updates. - * * @param field Shared pointer to the FEField to be registered. + * + * @param field Shared pointer to the FEField to be registered. */ void FESpace::registerField(std::shared_ptr field) { @@ -39,7 +41,7 @@ namespace cfem // Ensure the field name is unique within the context of the mesh if (m_mesh->nameAlreadyUsed(name)) { - CFEM_ERROR << "Global naming collision: Field with name '" << name << "' already exists."; + CFEM_THROW("Global naming collision: Field with name '" << name << "' already exists."); } // Store a weak reference to the field to avoid circular dependency memory leaks @@ -49,19 +51,21 @@ namespace cfem void FESpace::validate() const { if (m_mesh->getNumCells() == 0) { - CFEM_ERROR << "FESpace Error: Mesh is empty."; + CFEM_THROW("FESpace Error: Mesh is empty."); } if (m_numComponents < 1) { - CFEM_ERROR << "FESpace Error: Field dimension must be >= 1."; + CFEM_THROW("FESpace Error: Field dimension must be >= 1."); } } /** * @brief Entry point for the update cascade triggered by a mesh/topology modification. - * * When the mesh changes (e.g., due to refinement or deformation), the continuous + * + * When the mesh changes (e.g., due to refinement or deformation), the continuous * or discontinuous space must recalculate its global numbering. Consequently, all * fields that rely on this space must resize their algebraic data structures (like PETSc vectors). - * * The cascade works as follows: + * + * The cascade works as follows: * 1. Validate the current state of the space. * 2. Re-enumerate the Degrees of Freedom (DOFs) via the specialized subclass implementation. * 3. Notify all living associated FEFields to reallocate their internal PETSc vectors. @@ -85,43 +89,4 @@ namespace cfem } } - /** - * @brief Initializes all supported reference elements based on the mesh dimension. - * Covers both Linear (P1/Q1) and Quadratic (P2/Q2) elements across 1D, 2D, and 3D. - */ - void FESpace::initReferenceElements() { - m_refElements.clear(); - CfemInt dim = m_mesh->getTopologicalDimension(); - - // Utilisation de la même logique de filtrage que LagrangeSpace - // Mais adaptée pour supporter les éléments DG (souvent P0 ou P1 discontinu) - if (dim == 1) { - m_refElements[CellType::LINE2] = std::make_unique(); - m_refElements[CellType::LINE3] = std::make_unique(); - } else if (dim == 2) { - m_refElements[CellType::TRI3] = std::make_unique(); - m_refElements[CellType::QUAD4] = std::make_unique(); - - m_refElements[CellType::TRI6] = std::make_unique(); - m_refElements[CellType::QUAD9] = std::make_unique(); - } else if (dim == 3) { - m_refElements[CellType::TET4] = std::make_unique(); - m_refElements[CellType::PRISM6] = std::make_unique(); - m_refElements[CellType::HEX8] = std::make_unique(); - - m_refElements[CellType::TET10] = std::make_unique(); - m_refElements[CellType::PRISM18] = std::make_unique(); - m_refElements[CellType::HEX27] = std::make_unique(); - } - } - - - const ReferenceElement& FESpace::getRefElement(CellType type) const { - auto it = m_refElements.find(type); - if (it == m_refElements.end()) { - CFEM_ERROR << "FESpace: Element type not initialized for type " << static_cast(type); - } - return *it->second; - } - } // namespace cfem \ No newline at end of file diff --git a/libs/fem/fespace/src/LagrangeSpace.cpp b/libs/fem/fespace/src/LagrangeSpace.cpp index 2295210..f6fab31 100644 --- a/libs/fem/fespace/src/LagrangeSpace.cpp +++ b/libs/fem/fespace/src/LagrangeSpace.cpp @@ -18,6 +18,7 @@ #include "LagrangeSpace.h" #include "ReferenceElements.h" +#include "ReferenceElementFactory.h" #include "Mesh.h" #include @@ -35,8 +36,8 @@ namespace cfem { : FESpace(mesh, order, Continuity::CG, field_type) { if (m_order == Order::P0){ - CFEM_ERROR << "Invalid Order: LagrangeSpace is designed for C0-continuity and requires at least Order::P1. " - << "For piecewise constants, use a DGSpace."; + CFEM_THROW( "Invalid Order: LagrangeSpace is designed for C0-continuity and requires at least Order::P1. " + << "For piecewise constants, use a DGSpace."); } } @@ -44,7 +45,7 @@ namespace cfem { FESpace::validate(); MeshOrder meshOrder = m_mesh->getOrder(); if (m_order == Order::P2 && (meshOrder == MeshOrder::None || meshOrder == MeshOrder::Linear)) { - CFEM_ERROR << "LagrangeSpace Order Mismatch: P2 Space requires a Quadratic Mesh."; + CFEM_THROW("LagrangeSpace Order Mismatch: P2 Space requires a Quadratic Mesh."); } } @@ -61,8 +62,8 @@ namespace cfem { FieldType field_type) { if (order == Order::P0) { - CFEM_ERROR << "LagrangeSpace Error: P0 is a discontinuous space and is not supported by LagrangeSpace. " - "Please use DGSpace or upgrade the order to P1."; + CFEM_THROW("LagrangeSpace Error: P0 is a discontinuous space and is not supported by LagrangeSpace. " + << "Please use DGSpace or upgrade the order to P1."); } if (order == Order::P2 && mesh->getOrder() == MeshOrder::Linear) { @@ -250,7 +251,7 @@ void LagrangeSpace::getElementDofs(CfemInt cellId, std::vector& globalI void LagrangeSpace::evaluateBasis(CfemInt cellId, const Vector3D& xi, std::vector& values) const { const Cell& cell = m_mesh->getCell(cellId); - const ReferenceElement& refEl = getRefElement(cell.type); + const ReferenceElement& refEl = ReferenceElementFactory::getReferenceElement( cell.type, getOrderInt() ); ShapeInfo shape; refEl.evaluate(xi, ShapeEvaluationFlags::Value, shape); @@ -260,7 +261,7 @@ void LagrangeSpace::evaluateBasis(CfemInt cellId, const Vector3D& xi, std::vecto void LagrangeSpace::evaluateGradients(CfemInt cellId, const Vector3D& xi, std::vector& grads) const { const Cell& cell = m_mesh->getCell(cellId); - const ReferenceElement& refEl = getRefElement(cell.type); + const ReferenceElement& refEl = ReferenceElementFactory::getReferenceElement( cell.type, getOrderInt() ); // Thread-local scratchpad to ensure OpenMP safety and avoid allocations static thread_local DynamicVector t_physicalCoords; @@ -273,7 +274,7 @@ void LagrangeSpace::evaluateGradients(CfemInt cellId, const Vector3D& xi, std::v grads.assign(map.physicalShapeGradients.begin(), map.physicalShapeGradients.end()); } -// --- PETSc & SYSTEM SUPPORT --- +//PETSc & SYSTEM SUPPORT /** * @brief Computes the number of non-zeros (nnz) per row for PETSc matrix pre-allocation. diff --git a/libs/fem/reference_element/include/ReferenceElement2.h b/libs/fem/reference_element/include/ReferenceElement2.h new file mode 100644 index 0000000..be1cb37 --- /dev/null +++ b/libs/fem/reference_element/include/ReferenceElement2.h @@ -0,0 +1,373 @@ +//************************************************************************ +// --- CFEM++ Library +// --- +// --- Copyright 2024-2026 Ismaël Tchinda Ngueyong et al. +// --- ALL RIGHTS RESERVED +// --- +// --- This software is protected by international copyright laws. +// --- Use, distribution, or modification of this software in any form, +// --- source or binary, for personal, academic, or non-commercial +// --- purposes is permitted free of charge, provided that this +// --- copyright notice and this permission notice appear in all +// --- copies and supporting documentation. +// --- +// --- The authors and contributors provide this code "as is" without +// --- any express or implied warranty. In no event shall they be +// --- held liable for any damages arising from the use of this software. +//************************************************************************ + +#ifndef CFEM_REFERENCE_ELEMENT_H +#define CFEM_REFERENCE_ELEMENT_H + +#include "cfemutils.h" +#include "Vector3D.h" +#include "DynamicVector.h" +#include "DynamicMatrix.h" +#include "Tensor3D.h" +#include "ShapeEvaluationFlags.h" + +namespace cfem::reference_elem +{ + static const std::vector> line_face_nodes = { + {0}, // Face 0 (Nœud gauche) + {1} // Face 1 (Nœud droit) + }; +} + +namespace cfem { + enum class Order { P0 = 0, P1 = 1, P2 = 2 , Mini = 101}; + +/** + * @struct ShapeInfo + * @brief Storage buffer for shape function evaluations at a specific quadrature point. + * @note Re-use this object across multiple integration points to avoid frequent heap allocations. + */ +struct ShapeInfo { + CfemInt numNodes = 0; + bool shapeMustBeUpdated = true; + ShapeEvaluationFlags validFlags = ShapeEvaluationFlags::None; + + // Valeurs (N_i) + DynamicVector values; + + // Gradients locaux éclatés (dN_i/dxi) + DynamicVector dN_dxi; + DynamicVector dN_deta; + DynamicVector dN_dzeta; + + // Hessiens locaux (d2N_i/dxi2) + DynamicVector hessians; + + /** + * @brief Prepares buffer sizes to prevent runtime reallocations. + * @param nNodes Number of nodes in the element. + * @param includeHessians If true, allocates memory for Hessian tensors. + */ + void setup(CfemInt nNodes, bool includeHessians = false) { + numNodes = nNodes; + + if (values.size() != static_cast(numNodes)) { + values.resize(numNodes); + dN_dxi.resize(numNodes); + dN_deta.resize(numNodes); + dN_dzeta.resize(numNodes); + } + + if (includeHessians && hessians.size() != static_cast(numNodes)) { + hessians.resize(numNodes); + } + + validFlags = ShapeEvaluationFlags::None; + } + + /** @brief Checks if a specific field (Value/Gradient/Hessian) is marked as valid. */ + bool has(ShapeEvaluationFlags field) const { + return hasFlag(validFlags, field); + } +}; + +// struct ShapeInfo { +// DynamicVector values; ///< Shape function values (N_i) +// DynamicVector gradients; ///< Local gradients w.r.t reference coordinates (dN_i/dxi) +// DynamicVector hessians; ///< Local Hessians w.r.t reference coordinates (d2N_i/dxi2) +// bool shapeMustBeUpdated = true; +// ShapeEvaluationFlags validFlags = ShapeEvaluationFlags::None; + +// /** +// * @brief Prepares buffer sizes to prevent runtime reallocations. +// * @param numNodes Number of nodes in the element. +// * @param includeHessians If true, allocates memory for Hessian tensors. +// */ +// void setup(CfemInt numNodes, bool includeHessians = false) { +// if (values.size() != static_cast(numNodes)) { +// values.resize(numNodes); +// gradients.resize(numNodes); +// } +// if (includeHessians && hessians.size() != static_cast(numNodes)) { +// hessians.resize(numNodes); +// } +// validFlags = ShapeEvaluationFlags::None; +// } + +// /** +// * @brief Checks if a specific field (Value/Gradient/Hessian) is marked as valid. +// */ +// bool has(cfem::ShapeEvaluationFlags field) const { +// return hasFlag(validFlags, field); +// } +// }; + +// d2Ndxdx, d2Ndxdy, d2Ndxdz +// d2Ndydy, d2Ndydz, d2Ndzdz +/** + * @struct MappingInfo + * @brief Stores metric data resulting from the geometric transformation (Reference -> Physical). + */ +struct MappingInfo { + Vector3D physicalPosition; ///< The mapped x, y, z coordinates + Tensor3D jacobian; ///< Jacobian matrix (dx/dxi) + Tensor3D invJacobian; ///< Inverse Jacobian matrix (dxi/dx) + CfemReal detJ; ///< Determinant of the Jacobian + Vector3D normal; + + DynamicVector physicalShapeGradients; ///< dN_i/dx (Gradients in physical space) + DynamicVector physicalShapeHessians; ///< d2N_i/dx2 (Hessians in physical space) + + /** + * @brief Prepares the mapping buffers for a specific element size. + * @param numNodes Number of nodes in the element. + * @param includeHessians Whether to allocate the Hessian buffer. + */ + void setup(CfemInt numNodes, bool includeHessians = false) { + if (physicalShapeGradients.size() != static_cast(numNodes)) { + physicalShapeGradients.resize(numNodes); + } + + if (includeHessians && physicalShapeHessians.size() != static_cast(numNodes)) { + physicalShapeHessians.resize(numNodes); + } + } + + // Quick accessors for physical gradients + inline CfemReal dNdx(CfemInt i) const { return physicalShapeGradients[i].x; } + inline CfemReal dNdy(CfemInt i) const { return physicalShapeGradients[i].y; } + inline CfemReal dNdz(CfemInt i) const { return physicalShapeGradients[i].z; } + + // Quick accessors for physical Hessians (components) + inline CfemReal d2Ndxdx(CfemInt nodeIdx) const { return physicalShapeHessians[nodeIdx].xx(); } + inline CfemReal d2Ndydy(CfemInt nodeIdx) const { return physicalShapeHessians[nodeIdx].yy(); } + inline CfemReal d2Ndzdz(CfemInt nodeIdx) const { return physicalShapeHessians[nodeIdx].zz(); } + inline CfemReal d2Ndydz(CfemInt nodeIdx) const { return physicalShapeHessians[nodeIdx].yz(); } + inline CfemReal d2Ndxdz(CfemInt nodeIdx) const { return physicalShapeHessians[nodeIdx].xz(); } + inline CfemReal d2Ndxdy(CfemInt nodeIdx) const { return physicalShapeHessians[nodeIdx].xy(); } + + void computePhysicalGradientsOnly(const ShapeInfo& fieldShape) { + // Le fait de charger dans des variables locales (registres) + // empêche le CPU de faire des allers-retours mémoire. + const CfemReal i00 = invJacobian(0,0), i01 = invJacobian(0,1), i02 = invJacobian(0,2); + const CfemReal i10 = invJacobian(1,0), i11 = invJacobian(1,1), i12 = invJacobian(1,2); + const CfemReal i20 = invJacobian(2,0), i21 = invJacobian(2,1), i22 = invJacobian(2,2); + + for (size_t i = 0; i < fieldShape.gradients.size(); ++i) { + const auto& g = fieldShape.gradients[i]; + physicalShapeGradients[i].x = i00 * g.x + i10 * g.y + i20 * g.z; + physicalShapeGradients[i].y = i01 * g.x + i11 * g.y + i21 * g.z; + physicalShapeGradients[i].z = i02 * g.x + i12 * g.y + i22 * g.z; + } + } + +}; + +struct ShapeInfoBatch { + CfemInt numQuadraturePoints = 0; + CfemInt numNodes = 0; + ShapeEvaluationFlags validFlags = ShapeEvaluationFlags::None; + + // [qp][node] - 1D contiguous memory ! + DynamicMatrix values; + + DynamicMatrix dN_dxi; + DynamicMatrix dN_deta; + DynamicMatrix dN_dzeta; + + // Hessiens (gardé tel quel pour l'instant comme compromis lisibilité/perf) + DynamicMatrix hessians; + + void setup(CfemInt nQp, CfemInt nNodes, bool withHessians = false) { + numQuadraturePoints = nQp; + numNodes = nNodes; + + // Resize préserve la capacité sous-jacente si la taille ne change pas + // (ex: passage d'un hexaèdre à un autre avec le même nb de points de Gauss) + values.resize(nQp, nNodes); + dN_dxi.resize(nQp, nNodes); + dN_deta.resize(nQp, nNodes); + dN_dzeta.resize(nQp, nNodes); + + if (withHessians) { + hessians.resize(nQp, nNodes); + } + validFlags = ShapeEvaluationFlags::None; + } +}; + +struct MappingInfoBatch { + CfemInt numQuadraturePoints = 0; + CfemInt numNodes = 0; + + // Données par point de quadrature [qp] + DynamicVector physicalPositions; + DynamicVector jacobians; + DynamicVector invJacobians; + DynamicVector detJs; + DynamicVector normals; + + // Gradients physiques éclatés [qp][node] + DynamicMatrix dNdx; + DynamicMatrix dNdy; + DynamicMatrix dNdz; + + DynamicMatrix physicalHessians; + + void setup(CfemInt nQp, CfemInt nNodes, bool withHessians = false) { + numQuadraturePoints = nQp; + numNodes = nNodes; + + physicalPositions.resize(nQp); + jacobians.resize(nQp); + invJacobians.resize(nQp); + detJs.resize(nQp); + normals.resize(nQp); + + dNdx.resize(nQp, nNodes); + dNdy.resize(nQp, nNodes); + dNdz.resize(nQp, nNodes); + + if (withHessians) { + physicalHessians.resize(nQp, nNodes); + } + } +}; + + + + +/** + * @class ReferenceElement + * @brief Abstract base class for Finite Element reference definitions. + * Provides the machinery to compute geometric mappings, Jacobians, and + * physical derivatives from local shape functions. + */ +class ReferenceElement { +protected: + DynamicVector m_nodes; ///< Node coordinates in reference space (xi) + + CfemInt m_numNodes; + CfemInt m_dimension; + +public: + virtual ~ReferenceElement() = default; + + CfemInt dimension() const { return m_dimension; }; + CfemInt getNumNodes() const { return m_numNodes; }; + virtual std::span getFaceNodes(CfemInt iFace) const = 0; + /** + * @brief Evaluates shape functions and their local derivatives at point xi. + * @param xi Target point in reference space. + * @param requested Bitmask of fields to calculate (Value, Gradient, Hessian). + * @param out ShapeInfo buffer to be filled. + */ + virtual void evaluate(const Vector3D& xi, + ShapeEvaluationFlags requested, + ShapeInfo& out) const = 0; + + virtual Vector3D projectFacetRefCoordToCellRefCoord(CfemInt localFacetId, const Vector3D& xiFacet) const = 0; + /** + * @brief Returns the coordinates of the nodes in the reference space. + */ + const DynamicVector& referenceNodes() const { return m_nodes; } + const Vector3D& getNodeCoords( CfemInt i) const { CFEM_ASSERT( i < m_nodes.size() && i >= 0 ); return m_nodes[i]; } + + + + /** + * @brief Computes the full geometric mapping at a given point xi. + * @param xi Target poCfemInt in reference space. + * @param physicalNodesCoords Actual physical coordinates of the element's nodes. + * @param requestedFields Bitmask of physical fields required (Gradients, Hessians). + * @param shape Work buffer for reference shape functions. + * @param out MappingInfo structure to be populated. + */ + void computeMapping(const Vector3D& xi, + const DynamicVector& physicalNodesCoords, + ShapeEvaluationFlags requestedFields, + ShapeInfo& shape, + MappingInfo& out) const; + + inline void computeMapping(const Vector3D &xi, + const DynamicVector &physicalNodesCoords, + ShapeEvaluationFlags requestedFields, + const ShapeInfo &shape, + MappingInfo &out) const; + + /** + * @brief Computes the Jacobian, its determinant (metric), and the pseudo-inverse. + * @note This version supports 1D/2D elements embedded in 3D space using + * the Moore-Penrose pseudo-inverse logic. + */ + void computeJacobian(const ShapeInfo& shape, + const DynamicVector& physicalNodesCoords, + MappingInfo& out) const; + + + /** + * @brief Transforms reference gradients to physical space using J^-T. + * @param shape Work buffer for reference shape functions. + * @param out MappingInfo structure to be populated. + */ + void computePhysicalGradients(const ShapeInfo& shape, + MappingInfo& out) const; + + /** + * @brief Computes physical Hessians including the geometric curvature term. + * @param physicalNodesCoords Actual physical coordinates of the element's nodes. + * @param shape Work buffer for reference shape functions. + * @param out MappingInfo structure to be populated. + * @note Reference: H_phys = J^-T * [H_ref - grad_phys(N) * H_geom] * J^-1 + */ + void computePhysicalHessians(const DynamicVector& physicalNodesCoords, + const ShapeInfo& shape, + MappingInfo& out) const; + + void computeGeometricNormal(const ShapeInfo &shape, MappingInfo &out) const; + + /** + * @brief Interpolates a field (scalar or vector) at a point evaluated in ShapeInfo. + * @tparam T The field type (e.g., CfemReal, Vector3D, or even Tensor types). + * @param shape Pre-evaluated shape functions at point xi. + * @param nodalValues The values of the field at the element nodes. + */ + template + T interpolate(const ShapeInfo& shape, const DynamicVector& nodalValues) const; + + /** + * @brief Interpolates the physical gradient of a scalar field at a point. + * @return Vector3D containing [du/dx, du/dy, du/dz] + */ + Vector3D interpolateGradient(const MappingInfo& map, + const DynamicVector& nodalValues) const; + + + /** + * @brief Interpolates the physical Hessian of a scalar field. + * @return SymTensor3D representing the 2nd derivative matrix. + */ + SymTensor3D interpolateHessian(const MappingInfo& map, + const DynamicVector& nodalValues) const; +}; + +} + +#include "ReferenceElement.tpp" + +#endif \ No newline at end of file diff --git a/libs/fem/reference_element/include/ReferenceElement2.tpp b/libs/fem/reference_element/include/ReferenceElement2.tpp new file mode 100644 index 0000000..408ab01 --- /dev/null +++ b/libs/fem/reference_element/include/ReferenceElement2.tpp @@ -0,0 +1,341 @@ +//************************************************************************ +// --- CFEM++ Library +// --- +// --- Copyright 2024-2026 Ismaël Tchinda Ngueyong et al. +// --- ALL RIGHTS RESERVED +// --- +// --- This software is protected by international copyright laws. +// --- Use, distribution, or modification of this software in any form, +// --- source or binary, for personal, academic, or non-commercial +// --- purposes is permitted free of charge, provided that this +// --- copyright notice and this permission notice appear in all +// --- copies and supporting documentation. +// --- +// --- The authors and contributors provide this code "as is" without +// --- any express or implied warranty. In no event shall they be +// --- held liable for any damages arising from the use of this software. +//************************************************************************ + +#ifndef CFEM_REFERENCE_ELEMENT_TPP_ +#define CFEM_REFERENCE_ELEMENT_TPP_ + +#include "ReferenceElement.h" + +#endif + +namespace cfem +{ + + inline void ReferenceElement::computeMapping(const Vector3D &xi, + const DynamicVector &physicalNodesCoords, + ShapeEvaluationFlags requestedFields, + ShapeInfo &shape, + MappingInfo &out) const + { + const bool wantHessians = hasFlag(requestedFields, ShapeEvaluationFlags::Hessian); + + // On prépare uniquement l'objet shape (qui est non-const ici) + shape.setup(m_numNodes, wantHessians); + + if (shape.shapeMustBeUpdated) { + evaluate(xi, requestedFields, shape); + } + + // Force the use of the optimized const_cast version + computeMapping(xi, physicalNodesCoords, requestedFields, static_cast(shape), out); + } + + inline void ReferenceElement::computeMapping(const Vector3D &xi, + const DynamicVector &physicalNodesCoords, + ShapeEvaluationFlags requestedFields, + const ShapeInfo &shape, + MappingInfo &out) const + { + const CfemInt n = m_numNodes; + const bool wantPhysicalPosition = hasFlag(requestedFields, ShapeEvaluationFlags::PhysicalPosition); + const bool wantJacobian = hasFlag(requestedFields, ShapeEvaluationFlags::Jacobian); + const bool wantGradients = hasFlag(requestedFields, ShapeEvaluationFlags::Gradient); + const bool wantHessians = hasFlag(requestedFields, ShapeEvaluationFlags::Hessian); + const bool wantNormal = hasFlag(requestedFields, ShapeEvaluationFlags::Normal); + + out.setup(n, wantHessians); + + CFEM_ASSERT(shape.values.size() >= m_numNodes, "Shape Info is not initialized?"); + + if (wantPhysicalPosition){ + out.physicalPosition = {0, 0, 0}; + for (CfemInt i = 0; i < n; ++i){ + out.physicalPosition += physicalNodesCoords[i] * shape.values[i]; + } + } + + if (wantJacobian || wantGradients || wantHessians) { + CFEM_ASSERT(n > 1, "Jacobian/Gradients requested for P0 element!"); + computeJacobian(shape, physicalNodesCoords, out); + } + + if (wantGradients){ + computePhysicalGradients(shape, out); + } + + if (wantHessians) { + computePhysicalHessians(physicalNodesCoords, shape, out); + } + + if (wantNormal) { + computeGeometricNormal(shape, out); + } + } + + inline void ReferenceElement::computeJacobian(const ShapeInfo &shape, + const DynamicVector &physicalNodesCoords, + MappingInfo &out) const + { + const CfemInt n = static_cast(shape.gradients.size()); + CFEM_ASSERT(n>0, "Shape Gradient Info must be ready prior to calling computeJacobian" ); + const CfemInt dim = m_dimension; + + // OPTIMISATION MAJEURE : Accumulation dans les registres CPU. + // On initialise 9 doubles locaux. Le compilateur les placera dans les registres xmm/ymm. + CfemReal j00 = 0.0, j01 = 0.0, j02 = 0.0; + CfemReal j10 = 0.0, j11 = 0.0, j12 = 0.0; + CfemReal j20 = 0.0, j21 = 0.0, j22 = 0.0; + + for (CfemInt i = 0; i < n; ++i) + { + const Vector3D &X = physicalNodesCoords[i]; + const Vector3D &gradN = shape.gradients[i]; + + j00 += X.x * gradN.x; j10 += X.y * gradN.x; j20 += X.z * gradN.x; + j01 += X.x * gradN.y; j11 += X.y * gradN.y; j21 += X.z * gradN.y; + j02 += X.x * gradN.z; j12 += X.y * gradN.z; j22 += X.z * gradN.z; + } + + // On écrit le résultat dans la mémoire structure une seule fois à la fin. + out.jacobian(0, 0) = j00; out.jacobian(0, 1) = j01; out.jacobian(0, 2) = j02; + out.jacobian(1, 0) = j10; out.jacobian(1, 1) = j11; out.jacobian(1, 2) = j12; + out.jacobian(2, 0) = j20; out.jacobian(2, 1) = j21; out.jacobian(2, 2) = j22; + + if (dim == 3) + { + out.detJ = det(out.jacobian); + CFEM_ASSERT(std::abs(out.detJ) > ZERO_TOLERANCE, "Degenerate Jacobian detected"); + out.invJacobian = inverse(out.jacobian, out.detJ); + } + else if (dim == 2) + { + // tangent vectors + CfemReal g11 = j00*j00 + j10*j10 + j20*j20; + CfemReal g22 = j01*j01 + j11*j11 + j21*j21; + CfemReal g12 = j00*j01 + j10*j11 + j20*j21; + + CfemReal detG = g11 * g22 - g12 * g12; + out.detJ = std::sqrt(std::max(0.0, detG)); + + CFEM_ASSERT(std::abs(out.detJ) > ZERO_TOLERANCE, "Degenerate Jacobian detected"); + CfemReal invDetG = 1.0 / detG; + for (CfemInt i = 0; i < 3; ++i) + { + out.invJacobian(0, i) = (g22 * out.jacobian(i, 0) - g12 * out.jacobian(i, 1)) * invDetG; + out.invJacobian(1, i) = (g11 * out.jacobian(i, 1) - g12 * out.jacobian(i, 0)) * invDetG; + out.invJacobian(2, i) = 0.0; + } + } + else if (dim == 1) + { + CfemReal g11 = j00*j00 + j10*j10 + j20*j20; + out.detJ = std::sqrt(g11); + CfemReal invG11 = 1.0 / g11; + for (CfemInt i = 0; i < 3; ++i) + { + out.invJacobian(i, 0) = out.jacobian(i, 0) * invG11; + out.invJacobian(i, 1) = 0.0; + out.invJacobian(i, 2) = 0.0; + } + } + + } + + inline void ReferenceElement::computePhysicalGradients(const ShapeInfo &shape, + MappingInfo &out) const + { + const CfemInt n = static_cast(shape.gradients.size()); + + // Cache de la transposée de l'inverse du Jacobien dans des variables locales. + // J^{-T} appliqué au vecteur local. + const CfemReal i00 = out.invJacobian(0,0), i01 = out.invJacobian(0,1), i02 = out.invJacobian(0,2); + const CfemReal i10 = out.invJacobian(1,0), i11 = out.invJacobian(1,1), i12 = out.invJacobian(1,2); + const CfemReal i20 = out.invJacobian(2,0), i21 = out.invJacobian(2,1), i22 = out.invJacobian(2,2); + + for (CfemInt a = 0; a < n; ++a) + { + const Vector3D& gRef = shape.gradients[a]; + + out.physicalShapeGradients[a].x = i00 * gRef.x + i10 * gRef.y + i20 * gRef.z; + out.physicalShapeGradients[a].y = i01 * gRef.x + i11 * gRef.y + i21 * gRef.z; + out.physicalShapeGradients[a].z = i02 * gRef.x + i12 * gRef.y + i22 * gRef.z; + } + } + + inline void ReferenceElement::computePhysicalHessians(const DynamicVector &physicalNodesCoords, + const ShapeInfo &shape, + MappingInfo &out) const + { + const CfemInt n = static_cast(shape.hessians.size()); + + // Calcul de la courbure géométrique (Hessienne de la transformation) + // On utilise des variables locales pour accumuler les 6 composantes indépendantes (xx, yy, zz, xy, xz, yz) + // pour chaque direction (X, Y, Z). + CfemReal hX_xx = 0, hX_yy = 0, hX_zz = 0, hX_xy = 0, hX_xz = 0, hX_yz = 0; + CfemReal hY_xx = 0, hY_yy = 0, hY_zz = 0, hY_xy = 0, hY_xz = 0, hY_yz = 0; + CfemReal hZ_xx = 0, hZ_yy = 0, hZ_zz = 0, hZ_xy = 0, hZ_xz = 0, hZ_yz = 0; + + for (CfemInt a = 0; a < n; ++a) + { + const auto &hRef = shape.hessians[a]; + const auto &pos = physicalNodesCoords[a]; + + hX_xx += hRef.xx() * pos.x; hX_yy += hRef.yy() * pos.x; hX_zz += hRef.zz() * pos.x; + hX_xy += hRef.xy() * pos.x; hX_xz += hRef.xz() * pos.x; hX_yz += hRef.yz() * pos.x; + + hY_xx += hRef.xx() * pos.y; hY_yy += hRef.yy() * pos.y; hY_zz += hRef.zz() * pos.y; + hY_xy += hRef.xy() * pos.y; hY_xz += hRef.xz() * pos.y; hY_yz += hRef.yz() * pos.y; + + hZ_xx += hRef.xx() * pos.z; hZ_yy += hRef.yy() * pos.z; hZ_zz += hRef.zz() * pos.z; + hZ_xy += hRef.xy() * pos.z; hZ_xz += hRef.xz() * pos.z; hZ_yz += hRef.yz() * pos.z; + } + + const CfemReal i00 = out.invJacobian(0,0), i01 = out.invJacobian(0,1), i02 = out.invJacobian(0,2); + const CfemReal i10 = out.invJacobian(1,0), i11 = out.invJacobian(1,1), i12 = out.invJacobian(1,2); + const CfemReal i20 = out.invJacobian(2,0), i21 = out.invJacobian(2,1), i22 = out.invJacobian(2,2); + + // Helper lambda pour le calcul de chaque composante p,q + auto computeComp = [&](CfemInt p, CfemInt q, CfemReal r_xx, CfemReal r_yy, CfemReal r_zz, + CfemReal r_xy, CfemReal r_xz, CfemReal r_yz) { + // Puisque R est symétrique : R01=R10, R02=R20, R12=R21 + // Termes diagonaux de R : i=j + CfemReal res = out.invJacobian(0,p)*r_xx*out.invJacobian(0,q) + + out.invJacobian(1,p)*r_yy*out.invJacobian(1,q) + + out.invJacobian(2,p)*r_zz*out.invJacobian(2,q); + // Termes croisés de R : i != j (facteur 2 car R_ij = R_ji) + res += (out.invJacobian(0,p)*r_xy*out.invJacobian(1,q) + out.invJacobian(1,p)*r_xy*out.invJacobian(0,q)) + + (out.invJacobian(0,p)*r_xz*out.invJacobian(2,q) + out.invJacobian(2,p)*r_xz*out.invJacobian(0,q)) + + (out.invJacobian(1,p)*r_yz*out.invJacobian(2,q) + out.invJacobian(2,p)*r_yz*out.invJacobian(1,q)); + return res; + }; + + // Transformation vers l'espace physique + for (CfemInt a = 0; a < n; ++a) + { + const auto &hRef = shape.hessians[a]; + const CfemReal dNdx = out.dNdx(a); + const CfemReal dNdy = out.dNdy(a); + const CfemReal dNdz = out.dNdz(a); + + // R = H_ref - sum( dN/dx_k * H_geom_k ) + // On calcule les 6 composantes de R directement + CfemReal r_xx = hRef.xx() - (dNdx * hX_xx + dNdy * hY_xx + dNdz * hZ_xx); + CfemReal r_yy = hRef.yy() - (dNdx * hX_yy + dNdy * hY_yy + dNdz * hZ_yy); + CfemReal r_zz = hRef.zz() - (dNdx * hX_zz + dNdy * hY_zz + dNdz * hZ_zz); + CfemReal r_xy = hRef.xy() - (dNdx * hX_xy + dNdy * hY_xy + dNdz * hZ_xy); + CfemReal r_xz = hRef.xz() - (dNdx * hX_xz + dNdy * hY_xz + dNdz * hZ_xz); + CfemReal r_yz = hRef.yz() - (dNdx * hX_yz + dNdy * hY_yz + dNdz * hZ_yz); + + // Transformation de congruence (équivalent à J^-T * R * J^-1) + // Pour une matrice symétrique R, la composante (p,q) de J^-T R J^-1 est: + // H_phys_pq = sum_i sum_j (invJ_ip * R_ij * invJ_jq) + auto& hPhys = out.physicalShapeHessians[a]; + + hPhys.xx() = computeComp(0, 0, r_xx, r_yy, r_zz, r_xy, r_xz, r_yz); + hPhys.yy() = computeComp(1, 1, r_xx, r_yy, r_zz, r_xy, r_xz, r_yz); + hPhys.zz() = computeComp(2, 2, r_xx, r_yy, r_zz, r_xy, r_xz, r_yz); + hPhys.xy() = computeComp(0, 1, r_xx, r_yy, r_zz, r_xy, r_xz, r_yz); + hPhys.xz() = computeComp(0, 2, r_xx, r_yy, r_zz, r_xy, r_xz, r_yz); + hPhys.yz() = computeComp(1, 2, r_xx, r_yy, r_zz, r_xy, r_xz, r_yz); + } + } + + + inline void ReferenceElement::computeGeometricNormal(const ShapeInfo &shape, MappingInfo &out) const + { + if (m_dimension == 2) + { + // Variété 2D (Surface, Triangle/Quad) dans l'espace 3D + // Les deux vecteurs tangents sont les deux premières colonnes du Jacobien + Vector3D t1(out.jacobian(0, 0), out.jacobian(1, 0), out.jacobian(2, 0)); + Vector3D t2(out.jacobian(0, 1), out.jacobian(1, 1), out.jacobian(2, 1)); + + out.normal = (t1.cross(t2)).normalized(); + + } + else if (m_dimension == 1) + { + // Variété 1D (Ligne) dans un espace 2D (ou 3D z=0) + CfemReal dx = out.jacobian(0, 0); + CfemReal dy = out.jacobian(1, 0); + CfemReal norm = std::sqrt(dx * dx + dy * dy); + + if (norm > cfem::ZERO_TOLERANCE) { + // Règle de la main droite (rotation de -90 degrés du vecteur tangent) + // L'orientation extérieure dépend de la convention de rotation de ton maillage + out.normal = Vector3D(dy / norm, -dx / norm, 0.0); + } else { + out.normal = Vector3D(0.0, 0.0, 0.0); + } + } + else + { + // Dimension 3 (Volume). Geometric normal undefined inside a 3D cell. + out.normal = Vector3D(0.0, 0.0, 0.0); + } + } + // ---------------------------------------------------------------------- + // ------------------------------- interpolation ------------------------ + template + inline T ReferenceElement::interpolate(const ShapeInfo &shape, const DynamicVector &nodalValues) const + { + const CfemInt n = m_numNodes; + CFEM_ASSERT(shape.values.size() >= n, "ShapeInfo not evaluated for this element type"); + CFEM_ASSERT(nodalValues.size() == n, "Nodal values size mismatch"); + + T result{}; // T must have a default constructor that initialize the field with 0. + + for (CfemInt i = 0; i < n; ++i) + { + result += nodalValues[i] * shape.values[i]; + } + return result; + } + + inline Vector3D ReferenceElement::interpolateGradient(const MappingInfo &map, + const DynamicVector &nodalValues) const + { + const CfemInt n = m_numNodes; + CFEM_ASSERT(map.physicalShapeGradients.size() >= n, "MappingInfo gradients not computed"); + CFEM_ASSERT(nodalValues.size() == n, "Nodal values size mismatch"); + + Vector3D grad{0, 0, 0}; + for (CfemInt i = 0; i < n; ++i) + { + grad += map.physicalShapeGradients[i] * nodalValues[i]; + } + return grad; + } + + inline SymTensor3D ReferenceElement::interpolateHessian(const MappingInfo &map, + const DynamicVector &nodalValues) const + { + const CfemInt n = m_numNodes; + CFEM_ASSERT(map.physicalShapeHessians.size() >= n, "MappingInfo hessians not computed"); + CFEM_ASSERT(nodalValues.size() == n, "Nodal values size mismatch"); + + SymTensor3D hess; + hess.setZero(); + for (CfemInt i = 0; i < n; ++i) + { + hess += map.physicalShapeHessians[i] * nodalValues[i]; + } + return hess; + } + +} // namespace cfem \ No newline at end of file diff --git a/libs/utils/include/DynamicMatrix.h b/libs/utils/include/DynamicMatrix.h index cdae7a2..56f518e 100644 --- a/libs/utils/include/DynamicMatrix.h +++ b/libs/utils/include/DynamicMatrix.h @@ -6,13 +6,13 @@ // --- // --- This software is protected by international copyright laws. // --- Use, distribution, or modification of this software in any form, -// --- source or binary, for personal, academic, or non-commercial -// --- purposes is permitted free of charge, provided that this -// --- copyright notice and this permission notice appear in all +// --- source or binary, for personal, academic, or non-commercial +// --- purposes is permitted free of charge, provided that this +// --- copyright notice and this permission notice appear in all // --- copies and supporting documentation. // --- -// --- The authors and contributors provide this code "as is" without -// --- any express or implied warranty. In no event shall they be +// --- The authors and contributors provide this code "as is" without +// --- any express or implied warranty. In no event shall they be // --- held liable for any damages arising from the use of this software. //************************************************************************ @@ -34,296 +34,561 @@ #include #include #include +#include + +namespace cfem +{ + + template + class MatrixView + { + private: + T *m_data = nullptr; + + CfemInt m_rows = 0; + CfemInt m_cols = 0; + + // Distance entre deux lignes successives + // (en nombre d’éléments) + CfemInt m_stride = 0; + + public: + using value_type = T; + + constexpr MatrixView() noexcept = default; + + constexpr MatrixView( T *data, + CfemInt rows, + CfemInt cols, + CfemInt stride) noexcept + : m_data(data), + m_rows(rows), + m_cols(cols), + m_stride(stride) + { } + + // Cas matrice compacte contiguë + constexpr MatrixView( T *data, + CfemInt rows, + CfemInt cols) noexcept + : m_data(data), + m_rows(rows), + m_cols(cols), + m_stride(cols) + {} + + constexpr CfemInt n_rows() const noexcept { return m_rows; } + constexpr CfemInt n_cols() const noexcept { return m_cols; } + constexpr CfemInt stride() const noexcept { return m_stride; } + constexpr size_t size() const noexcept { return static_cast(m_rows) * static_cast(m_cols); } + constexpr bool empty() const noexcept { return size() == 0; } + + constexpr T *data() noexcept{ return m_data; } + constexpr const T *data() const noexcept{ return m_data; } + + constexpr T *rowData(CfemInt r) + { + CFEM_ASSERT(r >= 0 && r < m_rows); + return m_data + r * m_stride; + } -namespace cfem { + constexpr const T *rowData(CfemInt r) const + { + CFEM_ASSERT(r >= 0 && r < m_rows); + return m_data + r * m_stride; + } -/** - * @class DynamicMatrix - * @brief A heap-allocated, dynamically-sized dense matrix. - * * Storage is row-major: index = r * cols + c. - * @tparam T The numeric type (e.g., CfemReal, double). - */ -template -class DynamicMatrix { -private: - std::vector m_data; - CfemInt m_rows = 0; - CfemInt m_cols = 0; - -public: - // --- Constructors --- - - /** - * @brief Default constructor (empty matrix). - */ - DynamicMatrix() = default; + std::span operator[](CfemInt r) + { + CFEM_ASSERT(r >= 0 && r < m_rows); + return std::span( rowData(r), static_cast(m_cols) ); + } - /** - * @brief Size constructor. - * @param r Number of rows. - * @param c Number of columns. - * @param init Initial value for all elements. - */ - DynamicMatrix(CfemInt r, CfemInt c, T init = static_cast(0)) - : m_rows(r), m_cols(c), m_data(static_cast(r * c), init) {} + std::span operator[](CfemInt r) const + { + CFEM_ASSERT(r >= 0 && r < m_rows); + return std::span( rowData(r), static_cast(m_cols) ); + } - /** - * @brief Initializer list constructor (row-major). - */ - DynamicMatrix(CfemInt r, CfemInt c, std::initializer_list list) - : m_rows(r), m_cols(c), m_data(list) { - CFEM_ASSERT(m_data.size() == static_cast(r * c)); - } + constexpr T &operator()(CfemInt r, CfemInt c) + { + CFEM_ASSERT(r >= 0 && r < m_rows); + CFEM_ASSERT(c >= 0 && c < m_cols); + + return m_data[r * m_stride + c]; + } + + constexpr const T &operator()(CfemInt r, CfemInt c) const + { + CFEM_ASSERT(r >= 0 && r < m_rows); + CFEM_ASSERT(c >= 0 && c < m_cols); + + return m_data[r * m_stride + c]; + } + + std::span row(CfemInt r) noexcept { + return (*this)[r]; + } + + std::span row(CfemInt r) const noexcept { + return (*this)[r]; + } + + void fill(const T &value) + { + for (CfemInt i = 0; i < m_rows; ++i){ + auto row = (*this)[i]; + std::fill( row.begin(), row.end(), value); + } + } + + constexpr MatrixView subView( CfemInt row0, + CfemInt col0, + CfemInt subRows, + CfemInt subCols) const + { + CFEM_ASSERT(row0 >= 0); + CFEM_ASSERT(col0 >= 0); + CFEM_ASSERT(subRows >= 0); + CFEM_ASSERT(subCols >= 0); + CFEM_ASSERT(row0 + subRows <= m_rows); + CFEM_ASSERT(col0 + subCols <= m_cols); + CFEM_ASSERT(m_data != nullptr || subRows * subCols == 0); + + return MatrixView( m_data + row0 * m_stride + col0, + subRows, + subCols, + m_stride); + } + + constexpr bool isContiguous() const noexcept { + return m_stride == m_cols; + } + + [[nodiscard]] constexpr bool sameShape( const MatrixView& other) const noexcept { + return m_rows == other.m_rows && m_cols == other.m_cols; + } + }; + + template + using ConstMatrixView = MatrixView; /** - * @brief Conversion constructor from FixedMatrix. + * @class DynamicMatrix + * @brief A heap-allocated, dynamically-sized dense matrix. + * * Storage is row-major: index = r * cols + c. + * @tparam T The numeric type (e.g., CfemReal, double). */ - template - DynamicMatrix(const FixedMatrix& fixed) - : m_rows(R), m_cols(C), m_data(std::begin(fixed.data), std::end(fixed.data)) {} - - // Move constructor - DynamicMatrix(DynamicMatrix&& other) noexcept - : m_data(std::move(other.m_data)), m_rows(other.m_rows), m_cols(other.m_cols) { - other.m_rows = 0; - other.m_cols = 0; - } + template + class DynamicMatrix + { + private: + std::vector m_data; + CfemInt m_rows = 0; + CfemInt m_cols = 0; + + public: + // --- Constructors --- + + /** + * @brief Default constructor (empty matrix). + */ + DynamicMatrix() = default; + + /** + * @brief Size constructor. + * @param r Number of rows. + * @param c Number of columns. + * @param init Initial value for all elements. + */ + DynamicMatrix(CfemInt r, CfemInt c, T init = static_cast(0)) + : m_rows(r), m_cols(c), m_data(static_cast(r * c), init) {} + + /** + * @brief Initializer list constructor (row-major). + */ + DynamicMatrix(CfemInt r, CfemInt c, std::initializer_list list) + : m_rows(r), m_cols(c), m_data(list) + { + CFEM_ASSERT(m_data.size() == static_cast(r * c)); + } - // move assignment - DynamicMatrix& operator=(DynamicMatrix&& other) noexcept { - if (this != &other) { - m_data = std::move(other.m_data); - m_rows = other.m_rows; - m_cols = other.m_cols; + /** + * @brief Conversion constructor from FixedMatrix. + */ + template + DynamicMatrix(const FixedMatrix &fixed) + : m_rows(R), m_cols(C), m_data(std::begin(fixed.data), std::end(fixed.data)) {} + + // Move constructor + DynamicMatrix(DynamicMatrix &&other) noexcept + : m_data(std::move(other.m_data)), m_rows(other.m_rows), m_cols(other.m_cols) + { other.m_rows = 0; other.m_cols = 0; } - return *this; - } - DynamicMatrix(const DynamicMatrix& other) = default; - DynamicMatrix& operator=(const DynamicMatrix& other) = default; + // move assignment + DynamicMatrix &operator=(DynamicMatrix &&other) noexcept + { + if (this != &other) + { + m_data = std::move(other.m_data); + m_rows = other.m_rows; + m_cols = other.m_cols; + other.m_rows = 0; + other.m_cols = 0; + } + return *this; + } - inline CfemInt rows() const noexcept { return m_rows; } - inline CfemInt cols() const noexcept { return m_cols; } + DynamicMatrix(const DynamicMatrix &other) = default; + DynamicMatrix &operator=(const DynamicMatrix &other) = default; - void clear() { - m_data.clear(); - m_rows = 0; - m_cols = 0; - } + [[nodiscard]] constexpr inline CfemInt n_rows() const noexcept { return m_rows; } + [[nodiscard]] constexpr inline CfemInt n_cols() const noexcept { return m_cols; } + [[nodiscard]] constexpr inline size_t capacity() const noexcept { return m_data.capacity(); } + [[nodiscard]] constexpr inline size_t size() const noexcept { return m_rows * m_cols; } - /** - * @brief Resizes the matrix. Note: data might be lost during resize. - */ - void resize(CfemInt r, CfemInt c) { - m_rows = r; - m_cols = c; - m_data.resize(static_cast(r * c)); - } + void clear() + { + m_data.clear(); + m_rows = 0; + m_cols = 0; + } - void reserve(CfemInt r, CfemInt c) { - m_data.reserve(static_cast(r * c)); - } + /** + * @brief Resizes the matrix. + * @warning This operation preserves the underlying 1D memory but DESTROYS the 2D layout + * if the number of columns changes. Old data should be considered invalidated. + */ + inline void resize(CfemInt r, CfemInt c) + { + CFEM_ASSERT(r >= 0, "rows size should be a positive integer"); + CFEM_ASSERT(c >= 0, "columns size should be a positive integer"); + m_rows = r; + m_cols = c; + m_data.resize(static_cast(r * c)); + } - /** - * @brief Redimensionne et initialise la matrice. - * @param rows Nombre de lignes - * @param cols Nombre de colonnes - * @param value Valeur d'initialisation (par défaut 0) - */ - void assign(CfemInt rows, CfemInt cols, const T& value = T()) { - m_rows = rows; - m_cols = cols; - m_data.assign(rows * cols, value); - } + inline void reserve(CfemInt r, CfemInt c) + { + CFEM_ASSERT(r >= 0, "rows size should be a positive integer"); + CFEM_ASSERT(c >= 0, "columns size should be a positive integer"); + m_data.reserve(static_cast(r * c)); + } - /** - * @brief Resets all entries to zero. - */ - void setZero() { std::fill(m_data.begin(), m_data.end(), static_cast(0)); } + inline void reshape(CfemInt r, CfemInt c) + { + CFEM_ASSERT(r >= 0); + CFEM_ASSERT(c >= 0); + CFEM_ASSERT( static_cast(r) * static_cast(c) == size() ); + m_rows = r; + m_cols = c; + } - /** - * @brief Sets a square matrix to identity. - */ - void setIdentity() { - CFEM_ASSERT(m_rows == m_cols); - setZero(); - for (CfemInt i = 0; i < m_rows; ++i) (*this)(i, i) = static_cast(1); - } + /** + * @brief Redimensionne et initialise la matrice. + * @param rows Nombre de lignes + * @param cols Nombre de colonnes + * @param value Valeur d'initialisation (par défaut 0) + */ + inline void assign(CfemInt r, CfemInt c, const T &value = T()) + { + CFEM_ASSERT(r >= 0, "rows size should be a positive integer"); + CFEM_ASSERT(c >= 0, "columns size should be a positive integer"); + + m_rows = r; + m_cols = c; + m_data.assign(static_cast(r * c), value); + } - T* data() noexcept { return m_data.data(); } - const T* data() const noexcept { return m_data.data(); } + /** + * @brief Resets all entries to zero. + */ + void setZero() { std::fill(m_data.begin(), m_data.end(), static_cast(0)); } + + /** + * @brief Sets a square matrix to identity. + */ + void setIdentity() + { + CFEM_ASSERT(m_rows == m_cols); + setZero(); + for (CfemInt i = 0; i < m_rows; ++i) + (*this)(i, i) = static_cast(1); + } + [[nodiscard]] T *data() noexcept { return m_data.data(); } + [[nodiscard]] const T *data() const noexcept { return m_data.data(); } - /** @brief Itérateur sur le début des données. */ - auto begin() noexcept { return m_data.begin(); } - auto end() noexcept { return m_data.end(); } + /** @brief Itérateur sur le début des données. */ + auto begin() noexcept { return m_data.begin(); } + auto end() noexcept { return m_data.end(); } - /** @brief Itérateur constant sur le début des données. */ - auto begin() const noexcept { return m_data.begin(); } - auto end() const noexcept { return m_data.end(); } + /** @brief Itérateur constant sur le début des données. */ + auto begin() const noexcept { return m_data.begin(); } + auto end() const noexcept { return m_data.end(); } - /** @brief Itérateur constant explicite (cbegin/cend). */ - auto cbegin() const noexcept { return m_data.cbegin(); } - auto cend() const noexcept { return m_data.cend(); } + /** @brief Itérateur constant explicite (cbegin/cend). */ + auto cbegin() const noexcept { return m_data.cbegin(); } + auto cend() const noexcept { return m_data.cend(); } - /** - * @brief Element access (read-write). - */ - inline T& operator()(CfemInt r, CfemInt c) { - CFEM_ASSERT(r >= 0 && r < m_rows && c >= 0 && c < m_cols); - return m_data[static_cast(r * m_cols + c)]; - } + inline std::span operator[](CfemInt r) + { + CFEM_ASSERT(r >= 0 && r < m_rows); + return std::span(m_data.data() + r * m_cols, static_cast(m_cols)); + } - /** - * @brief Element access (read-only). - */ - inline const T& operator()(CfemInt r, CfemInt c) const { - CFEM_ASSERT(r >= 0 && r < m_rows && c >= 0 && c < m_cols); - return m_data[static_cast(r * m_cols + c)]; - } + [[nodiscard]] inline std::span operator[](CfemInt r) const + { + CFEM_ASSERT(r >= 0 && r < m_rows); + return std::span(m_data.data() + r * m_cols, static_cast(m_cols)); + } - // --- Arithmetic --- + std::span row(CfemInt r) { + CFEM_ASSERT(r >= 0 && r < m_rows); + return (*this)[r]; + } - DynamicMatrix operator+(const DynamicMatrix& other) const { - CFEM_ASSERT(m_rows == other.m_rows && m_cols == other.m_cols); - DynamicMatrix res(m_rows, m_cols); - for (size_t i = 0; i < m_data.size(); ++i) res.m_data[i] = m_data[i] + other.m_data[i]; - return res; - } + [[nodiscard]] std::span row(CfemInt r) const { + CFEM_ASSERT(r >= 0 && r < m_rows); + return (*this)[r]; + } - DynamicMatrix operator-(const DynamicMatrix& other) const { - CFEM_ASSERT(m_rows == other.m_rows && m_cols == other.m_cols); - DynamicMatrix res(m_rows, m_cols); - for (size_t i = 0; i < m_data.size(); ++i) res.m_data[i] = m_data[i] - other.m_data[i]; - return res; - } + /** + * @brief Element access (read-write). + */ + inline T &operator()(CfemInt r, CfemInt c) + { + CFEM_ASSERT(r >= 0 && r < m_rows && c >= 0 && c < m_cols); + return m_data[static_cast(r * m_cols + c)]; + } - DynamicMatrix operator*(T scalar) const { - DynamicMatrix res(m_rows, m_cols); - for (size_t i = 0; i < m_data.size(); ++i) res.m_data[i] = m_data[i] * scalar; - return res; - } + /** + * @brief Element access (read-only). + */ + [[nodiscard]] inline const T &operator()(CfemInt r, CfemInt c) const + { + CFEM_ASSERT(r >= 0 && r < m_rows && c >= 0 && c < m_cols); + return m_data[static_cast(r * m_cols + c)]; + } - DynamicMatrix operator/(T scalar) const { - CFEM_ASSERT(std::abs(scalar) > cfem::ZERO_TOLERANCE); - DynamicMatrix res(m_rows, m_cols); - T invScalar = static_cast(1) / scalar; - for (size_t i = 0; i < m_data.size(); ++i) res.m_data[i] = m_data[i] * invScalar; - return res; - } + [[nodiscard]] MatrixView view() { + return MatrixView(data(), n_rows(), n_cols(), n_cols() ); + } - + [[nodiscard]] ConstMatrixView view() const { + return ConstMatrixView(data(), n_rows(), n_cols(), n_cols() ); + } - // --- Compound Operators --- + [[nodiscard]] MatrixView view(CfemInt row0, + CfemInt col0, + CfemInt subRows, + CfemInt subCols ) { + return view().subView( row0, col0, subRows, subCols ); + } - DynamicMatrix& operator+=(const DynamicMatrix& other) { - CFEM_ASSERT(m_rows == other.m_rows && m_cols == other.m_cols); - for (size_t i = 0; i < m_data.size(); ++i) m_data[i] += other.m_data[i]; - return *this; - } + [[nodiscard]] ConstMatrixView view( CfemInt row0, + CfemInt col0, + CfemInt subRows, + CfemInt subCols ) const { + return view().subView( row0, col0, subRows, subCols ); + } - DynamicMatrix& operator-=(const DynamicMatrix& other) { - CFEM_ASSERT(m_rows == other.m_rows && m_cols == other.m_cols); - for (size_t i = 0; i < m_data.size(); ++i) m_data[i] -= other.m_data[i]; - return *this; - } + [[nodiscard]] bool empty() const noexcept { return m_data.empty(); } + [[nodiscard]] bool sameShape(const DynamicMatrix& other) const noexcept { + return m_rows == other.m_rows && m_cols == other.m_cols; + } + [[nodiscard]] bool isSquare() const noexcept { return m_rows == m_cols; } - DynamicMatrix& operator*=(T scalar) { - for (auto& val : m_data) val *= scalar; - return *this; - } + // --- Arithmetic --- - DynamicMatrix& operator/=(T scalar) { - CFEM_ASSERT(std::abs(scalar) > cfem::ZERO_TOLERANCE); - T invScalar = static_cast(1) / scalar; - for (auto& val : m_data) val *= invScalar; - return *this; - } + [[nodiscard]] DynamicMatrix operator+(const DynamicMatrix &other) const + { + CFEM_ASSERT(m_rows == other.m_rows && m_cols == other.m_cols); + DynamicMatrix res(m_rows, m_cols); + for (size_t i = 0; i < m_data.size(); ++i) + res.m_data[i] = m_data[i] + other.m_data[i]; + return res; + } - // --- Linear Algebra --- + [[nodiscard]] DynamicMatrix operator-(const DynamicMatrix &other) const + { + CFEM_ASSERT(m_rows == other.m_rows && m_cols == other.m_cols); + DynamicMatrix res(m_rows, m_cols); + for (size_t i = 0; i < m_data.size(); ++i) + res.m_data[i] = m_data[i] - other.m_data[i]; + return res; + } - /** - * @brief Returns a new transposed matrix. - */ - DynamicMatrix transpose() const { - DynamicMatrix res(m_cols, m_rows); - for (CfemInt i = 0; i < m_rows; ++i) - for (CfemInt j = 0; j < m_cols; ++j) - res(j, i) = (*this)(i, j); - return res; - } -}; + [[nodiscard]] DynamicMatrix operator*(T scalar) const + { + DynamicMatrix res(m_rows, m_cols); + for (size_t i = 0; i < m_data.size(); ++i) + res.m_data[i] = m_data[i] * scalar; + return res; + } -// --- Non-member Operations --- + [[nodiscard]] DynamicMatrix operator/(T scalar) const + { + CFEM_ASSERT(std::abs(scalar) > cfem::ZERO_TOLERANCE); + DynamicMatrix res(m_rows, m_cols); + T invScalar = static_cast(1) / scalar; + for (size_t i = 0; i < m_data.size(); ++i) + res.m_data[i] = m_data[i] * invScalar; + return res; + } -/** - * @brief Matrix-Vector multiplication: y = A * x - */ -template -inline DynamicVector operator*(const DynamicMatrix& A, const DynamicVector& x) { - CFEM_ASSERT(A.cols() == x.size()); - DynamicVector res(A.rows()); - for (CfemInt i = 0; i < A.rows(); ++i) { - T sum = 0; - for (CfemInt j = 0; j < A.cols(); ++j) { - sum += A(i, j) * x[j]; - } - res[i] = sum; + // Compound Operators + + DynamicMatrix &operator+=(const DynamicMatrix &other) + { + CFEM_ASSERT(m_rows == other.m_rows && m_cols == other.m_cols); + for (size_t i = 0; i < m_data.size(); ++i) + m_data[i] += other.m_data[i]; + return *this; + } + + DynamicMatrix &operator-=(const DynamicMatrix &other) + { + CFEM_ASSERT(m_rows == other.m_rows && m_cols == other.m_cols); + for (size_t i = 0; i < m_data.size(); ++i) + m_data[i] -= other.m_data[i]; + return *this; + } + + DynamicMatrix &operator*=(T scalar) + { + for (auto &val : m_data) + val *= scalar; + return *this; + } + + DynamicMatrix &operator/=(T scalar) + { + CFEM_ASSERT(std::abs(scalar) > cfem::ZERO_TOLERANCE); + T invScalar = static_cast(1) / scalar; + for (auto &val : m_data) + val *= invScalar; + return *this; + } + + // Linear Algebra + + inline void transpose(DynamicMatrix &out) const + { + //Case M.transpose(M); + if (this == &out) + { + if (isSquare()) { + for (CfemInt i = 0; i < m_rows; ++i) { + for (CfemInt j = i + 1; j < m_cols; ++j) { + std::swap(out(i, j), out(j, i)); + } + } + } else { + // Fallback for rectangular matrices + DynamicMatrix temp = this->transpose(); + out = std::move(temp); + } + return; + } + + // --- Comportement normal out-of-place (this != &out) --- + if (out.n_rows() != m_cols || out.n_cols() != m_rows) + out.resize(m_cols, m_rows); + + for (CfemInt i = 0; i < m_rows; ++i) { + for (CfemInt j = 0; j < m_cols; ++j) { + out(j, i) = (*this)(i, j); + } + } + } + + /** + * @brief Returns a new transposed matrix. + */ + inline DynamicMatrix transpose() const + { + DynamicMatrix res; + transpose(res); + return res; + } + }; + + // Non-member Operations + + /** + * @brief Matrix-Vector multiplication: y = A * x + */ + template + [[nodiscard]] inline DynamicVector operator*(const DynamicMatrix &A, const DynamicVector &x) + { + CFEM_ASSERT(A.n_cols() == x.size()); + DynamicVector res(A.n_rows()); + for (CfemInt i = 0; i < A.n_rows(); ++i) { + T sum = 0; + for (CfemInt j = 0; j < A.n_cols(); ++j) { + sum += A(i, j) * x[j]; + } + res[i] = sum; + } + return res; } - return res; -} -/** - * @brief Matrix-Matrix multiplication: C = A * B (ikj optimized) - * - * @tparam T The matrix element type - * @param A The left matrix - * @param B The right matrix - * @return DynamicMatrix the result of the multiplication - */ -template -inline DynamicMatrix operator*(const DynamicMatrix& A, const DynamicMatrix& B) { - CFEM_ASSERT(A.cols() == B.rows()); - DynamicMatrix res(A.rows(), B.cols()); - res.setZero(); - for (CfemInt i = 0; i < A.rows(); ++i) { - for (CfemInt k = 0; k < A.cols(); ++k) { - T val = A(i, k); - for (CfemInt j = 0; j < B.cols(); ++j) { - res(i, j) += val * B(k, j); + /** + * @brief Matrix-Matrix multiplication: C = A * B (ikj optimized) + * + * @tparam T The matrix element type + * @param A The left matrix + * @param B The right matrix + * @return DynamicMatrix the result of the multiplication + */ + template + inline DynamicMatrix operator*(const DynamicMatrix &A, const DynamicMatrix &B) + { + CFEM_ASSERT(A.n_cols() == B.n_rows()); + + DynamicMatrix res(A.n_rows(), B.n_cols(), static_cast(0)); + for (CfemInt i = 0; i < A.n_rows(); ++i) { + for (CfemInt k = 0; k < A.n_cols(); ++k) { + T val = A(i, k); + for (CfemInt j = 0; j < B.n_cols(); ++j) { + res(i, j) += val * B(k, j); + } } } + return res; } - return res; -} -/** - * @brief Left-side scalar multiplication. - */ -template -inline DynamicMatrix operator*(T scalar, const DynamicMatrix& m) { - return m * scalar; -} + /** + * @brief Left-side scalar multiplication. + */ + template + inline DynamicMatrix operator*(T scalar, const DynamicMatrix &m) + { + return m * scalar; + } -/** - * @brief Output stream formatting. - */ -template -inline std::ostream& operator<<(std::ostream& os, const DynamicMatrix& m) { - os << "[size=" << m.rows() << "x" << m.cols() << "]\n"; - for (CfemInt i = 0; i < m.rows(); ++i) { - os << "| "; - for (CfemInt j = 0; j < m.cols(); ++j) { - os << m(i, j) << (j == m.cols() - 1 ? "" : ", "); - } - os << " |\n"; + /** + * @brief Output stream formatting. + */ + template + inline std::ostream &operator<<(std::ostream &os, const DynamicMatrix &m) + { + os << "[size=" << m.n_rows() << "x" << m.n_cols() << "]\n"; + for (CfemInt i = 0; i < m.n_rows(); ++i) { + os << "| "; + for (CfemInt j = 0; j < m.n_cols(); ++j) { + os << m(i, j) << (j == m.n_cols() - 1 ? "" : ", "); + } + os << " |\n"; + } + return os; } - return os; -} -} //namespace +} // namespace #endif // CFEM_DYNAMIC_MATRIX \ No newline at end of file diff --git a/libs/utils/include/Tensor2D.h b/libs/utils/include/Tensor2D.h index b569980..d99aed3 100644 --- a/libs/utils/include/Tensor2D.h +++ b/libs/utils/include/Tensor2D.h @@ -185,12 +185,10 @@ namespace cfem data[idx_xy] * scalar}; } - [[nodiscard]] inline SymTensor2D operator/(CfemReal scalar) const noexcept + [[nodiscard]] constexpr SymTensor2D operator/(CfemReal scalar) const { - if (std::abs(scalar) < cfem::ZERO_TOLERANCE) - { - CFEM_ERROR << "Division by zero in SymTensor2D::operator/"; - } + CFEM_ASSERT(std::abs(scalar) > ZERO_TOLERANCE, "Division by zero in SymTensor2D::operator/"); + const CfemReal inv = 1.0 / scalar; return {data[idx_xx] * inv, data[idx_yy] * inv, @@ -229,12 +227,9 @@ namespace cfem return *this; } - inline SymTensor2D &operator/=(CfemReal scalar) noexcept + constexpr SymTensor2D &operator/=(CfemReal scalar) { - if (std::abs(scalar) < cfem::ZERO_TOLERANCE) - { - CFEM_ERROR << "Division by zero in SymTensor2D::operator/="; - } + CFEM_ASSERT(std::abs(scalar) > ZERO_TOLERANCE, "Division by zero in SymTensor2D::operator/"); CfemReal inv = 1.0 / scalar; data[idx_xx] *= inv; diff --git a/libs/utils/include/Tensor3D.h b/libs/utils/include/Tensor3D.h index ab68822..7e49a2c 100644 --- a/libs/utils/include/Tensor3D.h +++ b/libs/utils/include/Tensor3D.h @@ -81,9 +81,9 @@ namespace cfem constexpr CfemReal &zz() noexcept { return data[idx_zz]; } [[nodiscard]] constexpr Mat3x3 toMat3x3() const noexcept { - return {{{xx(), xy(), xz()}, - {yx(), yy(), yz()}, - {zx(), zy(), zz()}}}; + return {{{data[idx_xx], data[idx_xy], data[idx_xz]}, + {data[idx_yx], data[idx_yy], data[idx_yz]}, + {data[idx_zx], data[idx_zy], data[idx_zz]}}}; } }; @@ -156,9 +156,9 @@ namespace cfem } [[nodiscard]] constexpr Mat3x3 toMat3x3() const noexcept{ - return {{{xx(), xy(), xz()}, - {xy(), yy(), yz()}, - {xz(), yz(), zz()}}}; + return {{{data[idx_xx], data[idx_xy], data[idx_xz]}, + {data[idx_xy], data[idx_yy], data[idx_yz]}, + {data[idx_xz], data[idx_yz], data[idx_zz]}}}; } /**j; @@ -233,12 +233,9 @@ namespace cfem data[idx_yz] * scalar, data[idx_xz] * scalar, data[idx_xy] * scalar}; } - [[nodiscard]] inline SymTensor3D operator/(CfemReal scalar) const noexcept + [[nodiscard]] constexpr SymTensor3D operator/(CfemReal scalar) const { - if (std::abs(scalar) < cfem::ZERO_TOLERANCE) - { - CFEM_WARN << "Division by zero in SymTensor3D::operator/"; - } + CFEM_ASSERT(std::abs(scalar) > ZERO_TOLERANCE, "Division by zero in SymTensor3D::operator/"); CfemReal inv = 1.0 / scalar; return {data[idx_xx] * inv, data[idx_yy] * inv, data[idx_zz] * inv, @@ -258,20 +255,21 @@ namespace cfem { Tensor3D res; - const CfemReal Axx = xx(), Ayy = yy(), Azz = zz(), Axy = xy(), Axz = xz(), Ayz = yz(); - const CfemReal Bxx = B.xx(), Byy = B.yy(), Bzz = B.zz(), Bxy = B.xy(), Bxz = B.xz(), Byz = B.yz(); + const CfemReal Axx = data[idx_xx], Ayy = data[idx_yy], Azz = data[idx_zz], Axy = data[idx_xy], Axz = data[idx_xz], Ayz = data[idx_yz]; + const CfemReal Bxx = B.data[SymTensor3D::idx_xx], Byy = B.data[SymTensor3D::idx_yy], Bzz = B.data[SymTensor3D::idx_zz]; + const CfemReal Bxy = B.data[SymTensor3D::idx_xy], Bxz = B.data[SymTensor3D::idx_xz], Byz = B.data[SymTensor3D::idx_yz]; - res.xx() = Axx * Bxx + Axy * Bxy + Axz * Bxz; - res.xy() = Axx * Bxy + Axy * Byy + Axz * Byz; - res.xz() = Axx * Bxz + Axy * Byz + Axz * Bzz; + res.data[Tensor3D::idx_xx] = Axx * Bxx + Axy * Bxy + Axz * Bxz; + res.data[Tensor3D::idx_xy] = Axx * Bxy + Axy * Byy + Axz * Byz; + res.data[Tensor3D::idx_xz] = Axx * Bxz + Axy * Byz + Axz * Bzz; - res.yx() = Axy * Bxx + Ayy * Bxy + Ayz * Bxz; - res.yy() = Axy * Bxy + Ayy * Byy + Ayz * Byz; - res.yz() = Axy * Bxz + Ayy * Byz + Ayz * Bzz; + res.data[Tensor3D::idx_yx] = Axy * Bxx + Ayy * Bxy + Ayz * Bxz; + res.data[Tensor3D::idx_yy] = Axy * Bxy + Ayy * Byy + Ayz * Byz; + res.data[Tensor3D::idx_yz] = Axy * Bxz + Ayy * Byz + Ayz * Bzz; - res.zx() = Axz * Bxx + Ayz * Bxy + Azz * Bxz; - res.zy() = Axz * Bxy + Ayz * Byy + Azz * Byz; - res.zz() = Axz * Bxz + Ayz * Byz + Azz * Bzz; + res.data[Tensor3D::idx_zx] = Axz * Bxx + Ayz * Bxy + Azz * Bxz; + res.data[Tensor3D::idx_zy] = Axz * Bxy + Ayz * Byy + Azz * Byz; + res.data[Tensor3D::idx_zz] = Axz * Bxz + Ayz * Byz + Azz * Bzz; return res; } @@ -309,12 +307,9 @@ namespace cfem return *this; } - inline SymTensor3D &operator/=(CfemReal scalar) noexcept + constexpr SymTensor3D &operator/=(CfemReal scalar) { - if (std::abs(scalar) < cfem::ZERO_TOLERANCE) - { - CFEM_WARN << "Division by zero in SymTensor3D::operator/="; - } + CFEM_ASSERT(std::abs(scalar) > ZERO_TOLERANCE, "Division by zero in SymTensor3D::operator/="); CfemReal inv = 1.0 / scalar; @@ -361,34 +356,34 @@ namespace cfem // Rappel stockage : [0]=xx, [1]=yy, [2]=zz, [3]=yz, [4]=xz, [5]=xy Tensor3D T; - // Ligne 0 de (*this) : [xx, xy, xz] - const CfemReal m00 = M.xx(), m01 = M.xy(), m02 = M.xz(); - const CfemReal m10 = M.yx(), m11 = M.yy(), m12 = M.yz(); - const CfemReal m20 = M.zx(), m21 = M.zy(), m22 = M.zz(); + const CfemReal m00 = M.data[Tensor3D::idx_xx], m01 = M.data[Tensor3D::idx_xy], m02 = M.data[Tensor3D::idx_xz]; + const CfemReal m10 = M.data[Tensor3D::idx_yx], m11 = M.data[Tensor3D::idx_yy], m12 = M.data[Tensor3D::idx_yz]; + const CfemReal m20 = M.data[Tensor3D::idx_zx], m21 = M.data[Tensor3D::idx_zy], m22 = M.data[Tensor3D::idx_zz]; - T.xx() = data[idx_xx] * m00 + data[idx_xy] * m10 + data[idx_xz] * m20; - T.xy() = data[idx_xx] * m01 + data[idx_xy] * m11 + data[idx_xz] * m21; - T.xz() = data[idx_xx] * m02 + data[idx_xy] * m12 + data[idx_xz] * m22; + // Ligne 0 de (*this) : [xx, xy, xz] + T.data[Tensor3D::idx_xx] = data[idx_xx] * m00 + data[idx_xy] * m10 + data[idx_xz] * m20; + T.data[Tensor3D::idx_xy] = data[idx_xx] * m01 + data[idx_xy] * m11 + data[idx_xz] * m21; + T.data[Tensor3D::idx_xz] = data[idx_xx] * m02 + data[idx_xy] * m12 + data[idx_xz] * m22; // Ligne 1 de (*this) : [xy, yy, yz] - T.yx() = data[idx_xy] * m00 + data[idx_yy] * m10 + data[idx_yz] * m20; - T.yy() = data[idx_xy] * m01 + data[idx_yy] * m11 + data[idx_yz] * m21; - T.yz() = data[idx_xy] * m02 + data[idx_yy] * m12 + data[idx_yz] * m22; + T.data[Tensor3D::idx_yx] = data[idx_xy] * m00 + data[idx_yy] * m10 + data[idx_yz] * m20; + T.data[Tensor3D::idx_yy] = data[idx_xy] * m01 + data[idx_yy] * m11 + data[idx_yz] * m21; + T.data[Tensor3D::idx_yz] = data[idx_xy] * m02 + data[idx_yy] * m12 + data[idx_yz] * m22; // Ligne 2 de (*this) : [xz, yz, zz] - T.zx() = data[idx_xz] * m00 + data[idx_yz] * m10 + data[idx_zz] * m20; - T.zy() = data[idx_xz] * m01 + data[idx_yz] * m11 + data[idx_zz] * m21; - T.zz() = data[idx_xz] * m02 + data[idx_yz] * m12 + data[idx_zz] * m22; + T.data[Tensor3D::idx_zx] = data[idx_xz] * m00 + data[idx_yz] * m10 + data[idx_zz] * m20; + T.data[Tensor3D::idx_zy] = data[idx_xz] * m01 + data[idx_yz] * m11 + data[idx_zz] * m21; + T.data[Tensor3D::idx_zz] = data[idx_xz] * m02 + data[idx_yz] * m12 + data[idx_zz] * m22; // On calcule res = M^T * T // On ne calcule que les 6 composantes symétriques - res[0] = m00 * T.xx() + m10 * T.yx() + m20 * T.zx(); // xx - res[1] = m01 * T.xy() + m11 * T.yy() + m21 * T.zy(); // yy - res[2] = m02 * T.xz() + m12 * T.yz() + m22 * T.zz(); // zz + res[0] = m00 * T.data[Tensor3D::idx_xx] + m10 * T.data[Tensor3D::idx_yx] + m20 * T.data[Tensor3D::idx_zx]; // xx + res[1] = m01 * T.data[Tensor3D::idx_xy] + m11 * T.data[Tensor3D::idx_yy] + m21 * T.data[Tensor3D::idx_zy]; // yy + res[2] = m02 * T.data[Tensor3D::idx_xz] + m12 * T.data[Tensor3D::idx_yz] + m22 * T.data[Tensor3D::idx_zz]; // zz - res[3] = m01 * T.xz() + m11 * T.yz() + m21 * T.zz(); // yz - res[4] = m00 * T.xz() + m10 * T.yz() + m20 * T.zz(); // xz - res[5] = m00 * T.xy() + m10 * T.yy() + m20 * T.zy(); // xy + res[3] = m01 * T.data[Tensor3D::idx_xz] + m11 * T.data[Tensor3D::idx_yz] + m21 * T.data[Tensor3D::idx_zz]; // yz + res[4] = m00 * T.data[Tensor3D::idx_xz] + m10 * T.data[Tensor3D::idx_yz] + m20 * T.data[Tensor3D::idx_zz]; // xz + res[5] = m00 * T.data[Tensor3D::idx_xy] + m10 * T.data[Tensor3D::idx_yy] + m20 * T.data[Tensor3D::idx_zy]; // xy return res; } @@ -471,12 +466,12 @@ namespace cfem [[nodiscard]] constexpr CfemReal doubleDot(const Tensor3D& T) const noexcept { // S : T = S_ij T_ij - return data[idx_xx] * T.xx() + - data[idx_yy] * T.yy() + - data[idx_zz] * T.zz() + - data[idx_xy] * (T.xy() + T.yx()) + - data[idx_xz] * (T.xz() + T.zx()) + - data[idx_yz] * (T.yz() + T.zy()); + return data[idx_xx] * T.data[Tensor3D::idx_xx] + + data[idx_yy] * T.data[Tensor3D::idx_yy] + + data[idx_zz] * T.data[Tensor3D::idx_zz] + + data[idx_xy] *(T.data[Tensor3D::idx_xy] + T.data[Tensor3D::idx_yx]) + + data[idx_xz] *(T.data[Tensor3D::idx_xz] + T.data[Tensor3D::idx_zx]) + + data[idx_yz] *(T.data[Tensor3D::idx_yz] + T.data[Tensor3D::idx_zy]); } [[nodiscard]] constexpr SymTensor3D deviatoric() const noexcept @@ -500,7 +495,7 @@ namespace cfem * @details * Commonly used to build tangent constitutive operators. */ - [[nodiscard]] constexpr FixedMatrix outer(const SymTensor3D& other) const noexcept + [[nodiscard]] constexpr FixedMatrix outer(const SymTensor3D &other) const noexcept { FixedMatrix res; for(CfemInt i = 0; i < 6; ++i) { @@ -519,7 +514,7 @@ namespace cfem * @brief Symmetric Axisymmetric Tensor (4 components). * Components stored as: [xx, yy, zz, xy] (e.g., rr, zz, theta_theta, rz). */ - struct AxisSymTensor + struct AxiSymTensor3D { static constexpr CfemInt idx_xx = 0; static constexpr CfemInt idx_yy = 1; @@ -534,8 +529,8 @@ namespace cfem public: // Constructors - constexpr AxisSymTensor() noexcept = default; - constexpr AxisSymTensor(CfemReal xx, CfemReal yy, CfemReal zz, CfemReal xy) noexcept + constexpr AxiSymTensor3D() noexcept = default; + constexpr AxiSymTensor3D(CfemReal xx, CfemReal yy, CfemReal zz, CfemReal xy) noexcept : data{xx, yy, zz, xy} {} // Accessors @@ -549,57 +544,49 @@ namespace cfem constexpr CfemReal &zz() noexcept { return data[idx_zz]; } constexpr CfemReal &xy() noexcept { return data[idx_xy]; } - [[nodiscard]] constexpr SymTensor3D toSymTensor3D() const noexcept - { - return { xx(), yy(), zz(), 0.0, 0.0, xy() }; - } - inline operator Tensor3D() const noexcept{ - return Tensor3D{ xx(), xy(), 0.0, - xy(), yy(), 0.0, - 0.0, 0.0, zz() }; + return Tensor3D{ data[idx_xx], data[idx_xy], 0.0, + data[idx_xy], data[idx_yy], 0.0, + 0.0, 0.0, data[idx_zz] }; } inline operator SymTensor3D() const noexcept{ - CFEM_INFO << "Implicit converter called"; - return SymTensor3D{ xx(), yy(), zz(), 0.0, 0.0, xy() }; + return SymTensor3D{ data[idx_xx], data[idx_yy], data[idx_zz], 0.0, 0.0, data[idx_xy] }; } [[nodiscard]] constexpr Mat3x3 toMat3x3() const noexcept{ - return {{{xx(), xy(), 0.0}, - {xy(), yy(), 0.0}, - {0.0, 0.0, zz()}}}; + return {{{data[idx_xx], data[idx_xy], 0.0}, + {data[idx_xy], data[idx_yy], 0.0}, + {0.0, 0.0, data[idx_zz]}}}; } - [[nodiscard]] constexpr AxisSymTensor operator-() const noexcept + [[nodiscard]] constexpr AxiSymTensor3D operator-() const noexcept { return {-data[idx_xx], -data[idx_yy], -data[idx_zz], -data[idx_xy]}; } - [[nodiscard]] constexpr AxisSymTensor operator*(CfemReal scalar) const noexcept + [[nodiscard]] constexpr AxiSymTensor3D operator*(CfemReal scalar) const noexcept { - return { xx()*scalar, yy()*scalar, zz()*scalar, xy()*scalar }; + return { data[idx_xx]*scalar, data[idx_yy]*scalar, data[idx_zz]*scalar, data[idx_xy]*scalar }; } - [[nodiscard]] inline AxisSymTensor operator/(CfemReal scalar) const noexcept + [[nodiscard]] constexpr AxiSymTensor3D operator/(CfemReal scalar) const { - if (std::abs(scalar) < ZERO_TOLERANCE){ - CFEM_WARN << "Division by zero in SymTensor3D::operator/"; - } + CFEM_ASSERT(std::abs(scalar) > ZERO_TOLERANCE, "Division by zero in SymTensor3D::operator/"); const CfemReal inv = 1.0 / scalar; - return { xx()*inv, yy()*inv, zz()*inv, xy()*inv }; + return { data[idx_xx]*inv, data[idx_yy]*inv, data[idx_zz]*inv, data[idx_xy]*inv }; } - [[nodiscard]] constexpr AxisSymTensor operator+(const AxisSymTensor& B) const noexcept + [[nodiscard]] constexpr AxiSymTensor3D operator+(const AxiSymTensor3D &A) const noexcept { - return { xx() + B.xx(), yy() + B.yy(), zz() + B.zz(), xy() + B.xy() }; + return { data[idx_xx] + A.data[AxiSymTensor3D::idx_xx], data[idx_yy] + A.data[AxiSymTensor3D::idx_yy], data[idx_zz] + A.data[AxiSymTensor3D::idx_zz], data[idx_xy] + A.data[AxiSymTensor3D::idx_xy] }; } - [[nodiscard]] constexpr AxisSymTensor operator-(const AxisSymTensor& B) const noexcept + [[nodiscard]] constexpr AxiSymTensor3D operator-(const AxiSymTensor3D &A) const noexcept { - return { xx() - B.xx(), yy() - B.yy(), zz() - B.zz(), xy() - B.xy() }; + return { data[idx_xx] - A.data[AxiSymTensor3D::idx_xx], data[idx_yy] - A.data[AxiSymTensor3D::idx_yy], data[idx_zz] - A.data[AxiSymTensor3D::idx_zz], data[idx_xy] - A.data[AxiSymTensor3D::idx_xy] }; } [[nodiscard]] constexpr Vector3D operator*(const Vector3D &v) const noexcept @@ -611,49 +598,47 @@ namespace cfem }; } - [[nodiscard]] constexpr Tensor3D operator*(const AxisSymTensor& B) const noexcept + [[nodiscard]] constexpr Tensor3D operator*(const AxiSymTensor3D &A) const noexcept { Tensor3D res; - res.xx() = xx()*B.xx() + xy()*B.xy(); - res.xy() = xx()*B.xy() + xy()*B.yy(); - res.xz() = 0.0; + res.data[Tensor3D::idx_xx] = data[idx_xx]*A.data[AxiSymTensor3D::idx_xx] + data[idx_xy]*A.data[AxiSymTensor3D::idx_xy]; + res.data[Tensor3D::idx_xy] = data[idx_xx]*A.data[AxiSymTensor3D::idx_xy] + data[idx_xy]*A.data[AxiSymTensor3D::idx_yy]; + res.data[Tensor3D::idx_xz] = 0.0; - res.yx() = xy()*B.xx() + yy()*B.xy(); - res.yy() = xy()*B.xy() + yy()*B.yy(); - res.yz() = 0.0; + res.data[Tensor3D::idx_yx] = data[idx_xy]*A.data[AxiSymTensor3D::idx_xx] + data[idx_yy]*A.data[AxiSymTensor3D::idx_xy]; + res.data[Tensor3D::idx_yy] = data[idx_xy]*A.data[AxiSymTensor3D::idx_xy] + data[idx_yy]*A.data[AxiSymTensor3D::idx_yy]; + res.data[Tensor3D::idx_yz] = 0.0; - res.zx() = 0.0; - res.zy() = 0.0; - res.zz() = zz()*B.zz(); + res.data[Tensor3D::idx_zx] = 0.0; + res.data[Tensor3D::idx_zy] = 0.0; + res.data[Tensor3D::idx_zz] = data[idx_zz]*A.data[AxiSymTensor3D::idx_zz]; return res; } - constexpr AxisSymTensor& operator+=(const AxisSymTensor& S) noexcept + constexpr AxiSymTensor3D& operator+=(const AxiSymTensor3D &A) noexcept { - data[idx_xx] += S.data[idx_xx]; data[idx_yy] += S.data[idx_yy]; - data[idx_zz] += S.data[idx_zz]; data[idx_xy] += S.data[idx_xy]; + data[idx_xx] += A.data[idx_xx]; data[idx_yy] += A.data[idx_yy]; + data[idx_zz] += A.data[idx_zz]; data[idx_xy] += A.data[idx_xy]; return *this; } - constexpr AxisSymTensor& operator-=(const AxisSymTensor& S) noexcept + constexpr AxiSymTensor3D& operator-=(const AxiSymTensor3D &A) noexcept { - data[idx_xx] -= S.data[idx_xx]; data[idx_yy] -= S.data[idx_yy]; - data[idx_zz] -= S.data[idx_zz]; data[idx_xy] -= S.data[idx_xy]; + data[idx_xx] -= A.data[idx_xx]; data[idx_yy] -= A.data[idx_yy]; + data[idx_zz] -= A.data[idx_zz]; data[idx_xy] -= A.data[idx_xy]; return *this; } - constexpr AxisSymTensor& operator*=(CfemReal scalar) noexcept + constexpr AxiSymTensor3D& operator*=(CfemReal scalar) noexcept { data[idx_xx] *= scalar; data[idx_yy] *= scalar; data[idx_zz] *= scalar; data[idx_xy] *= scalar; return *this; } - constexpr AxisSymTensor& operator/=(CfemReal scalar) noexcept + constexpr AxiSymTensor3D& operator/=(CfemReal scalar) { - if (std::abs(scalar) < ZERO_TOLERANCE){ - CFEM_WARN << "Division by zero in SymTensor3D::operator/="; - } + CFEM_ASSERT(std::abs(scalar) > ZERO_TOLERANCE, "Division by zero in AxiSymTensor3D::operator/="); const CfemReal inv = 1.0 / scalar; data[idx_xx] *= inv; data[idx_yy] *= inv; @@ -672,12 +657,12 @@ namespace cfem return data[idx_zz] * (data[idx_xx] * data[idx_yy] - data[idx_xy] * data[idx_xy]); } - [[nodiscard]] constexpr AxisSymTensor transpose() const noexcept + [[nodiscard]] constexpr AxiSymTensor3D transpose() const noexcept { return *this; // Symétrique ! } - [[nodiscard]] constexpr AxisSymTensor deviatoric() const noexcept + [[nodiscard]] constexpr AxiSymTensor3D deviatoric() const noexcept { const CfemReal mean_tr = trace() / 3.0; return { data[idx_xx] - mean_tr, @@ -686,7 +671,7 @@ namespace cfem data[idx_xy] }; } - [[nodiscard]] constexpr AxisSymTensor inverse() const noexcept + [[nodiscard]] constexpr AxiSymTensor3D inverse() const noexcept { const CfemReal det_2d = data[idx_xx] * data[idx_yy] - data[idx_xy] * data[idx_xy]; const CfemReal inv_det_2d = 1.0 / det_2d; @@ -699,25 +684,25 @@ namespace cfem } // Produit doublement contracté (S : S2) = S_ij S2_ij - [[nodiscard]] constexpr CfemReal doubleDot(const AxisSymTensor& S) const noexcept + [[nodiscard]] constexpr CfemReal doubleDot(const AxiSymTensor3D &A) const noexcept { - return data[idx_xx]*S.data[idx_xx] + - data[idx_yy]*S.data[idx_yy] + - data[idx_zz]*S.data[idx_zz] + - 2.0 * data[idx_xy]*S.data[idx_xy]; + return data[idx_xx] * A.data[idx_xx] + + data[idx_yy] * A.data[idx_yy] + + data[idx_zz] * A.data[idx_zz] + + 2.0 * data[idx_xy] * A.data[idx_xy]; } // Produit doublement contracté (S : T) - [[nodiscard]] constexpr CfemReal doubleDot(const Tensor3D& T) const noexcept + [[nodiscard]] constexpr CfemReal doubleDot(const Tensor3D &T) const noexcept { - return data[idx_xx]*T.xx() + - data[idx_yy]*T.yy() + - data[idx_zz]*T.zz() + - data[idx_xy]*(T.xy() + T.yx()); + return data[idx_xx]*T.data[Tensor3D::idx_xx] + + data[idx_yy]*T.data[Tensor3D::idx_yy] + + data[idx_zz]*T.data[Tensor3D::idx_zz] + + data[idx_xy]*(T.data[Tensor3D::idx_xy] + T.data[Tensor3D::idx_yx]); } // Produit tensoriel externe S (x) S2 -> Matrice de Voigt 4x4 (souvent K_tangent local) - [[nodiscard]] constexpr FixedMatrix outer(const AxisSymTensor& other) const noexcept + [[nodiscard]] constexpr FixedMatrix outer(const AxiSymTensor3D& other) const noexcept { FixedMatrix res; for(CfemInt i = 0; i < 4; ++i) { @@ -827,16 +812,16 @@ namespace cfem } [[nodiscard]] constexpr Mat3x3 toMat3x3() const noexcept { - return {{{0.0, xy(), xz()}, - {-xy(), 0.0, yz()}, - {-xz(), -yz(), 0.0}}}; + return {{{0.0, data[idx_xy], data[idx_xz]}, + {-data[idx_xy], 0.0, data[idx_yz]}, + {-data[idx_xz], -data[idx_yz], 0.0}}}; } inline operator Tensor3D() const noexcept{ - return Tensor3D{ 0.0, xy(), xz(), - -xy(), 0.0, yz(), - -xz(), -yz(), 0.0 }; + return Tensor3D{ 0.0, data[idx_xy], data[idx_xz], + -data[idx_xy], 0.0, data[idx_yz], + -data[idx_xz], -data[idx_yz], 0.0 }; } /** @brief Negate operator */ @@ -897,12 +882,10 @@ namespace cfem return {data[idx_yz] * scalar, data[idx_xz] * scalar, data[idx_xy] * scalar}; } - [[nodiscard]] inline SkewSymTensor3D operator/(CfemReal scalar) const noexcept + [[nodiscard]] constexpr SkewSymTensor3D operator/(CfemReal scalar) const { - if (std::abs(scalar) < cfem::ZERO_TOLERANCE) - { - CFEM_ERROR << "Division by zero in SkewSymTensor3D::operator/"; - } + CFEM_ASSERT(std::abs(scalar) > ZERO_TOLERANCE, "Division by zero in SkewSymTensor3D::operator/"); + CfemReal inv = 1.0 / scalar; return {data[idx_yz] * inv, data[idx_xz] * inv, data[idx_xy] * inv}; } @@ -919,23 +902,23 @@ namespace cfem }; } - [[nodiscard]] constexpr Tensor3D operator*(const SkewSymTensor3D &B) const noexcept + [[nodiscard]] constexpr Tensor3D operator*(const SkewSymTensor3D &W) const noexcept { Tensor3D res; - const CfemReal Axy = xy(), Axz = xz(), Ayz = yz(); - const CfemReal Bxy = B.xy(), Bxz = B.xz(), Byz = B.yz(); + const CfemReal Axy = data[idx_xy], Axz = data[idx_xz], Ayz = data[idx_yz]; + const CfemReal Bxy = W.data[SkewSymTensor3D::idx_xy], Bxz = W.data[SkewSymTensor3D::idx_xz], Byz = W.data[SkewSymTensor3D::idx_yz]; - res.xx() = -Axy * Bxy - Axz * Bxz; - res.xy() = -Axz * Byz; - res.xz() = Axy * Byz; + res.data[Tensor3D::idx_xx] = -Axy * Bxy - Axz * Bxz; + res.data[Tensor3D::idx_xy] = -Axz * Byz; + res.data[Tensor3D::idx_xz] = Axy * Byz; - res.yx() = -Ayz * Bxz; - res.yy() = -Axy * Bxy - Ayz * Byz; - res.yz() = -Axy * Bxz; + res.data[Tensor3D::idx_yx] = -Ayz * Bxz; + res.data[Tensor3D::idx_yy] = -Axy * Bxy - Ayz * Byz; + res.data[Tensor3D::idx_yz] = -Axy * Bxz; - res.zx() = Ayz * Bxy; - res.zy() = -Axz * Bxy; - res.zz() = -Axz * Bxz - Ayz * Byz; + res.data[Tensor3D::idx_zx] = Ayz * Bxy; + res.data[Tensor3D::idx_zy] = -Axz * Bxy; + res.data[Tensor3D::idx_zz] = -Axz * Bxz - Ayz * Byz; return res; } @@ -964,12 +947,9 @@ namespace cfem return *this; } - inline SkewSymTensor3D &operator/=(CfemReal scalar) noexcept + constexpr SkewSymTensor3D &operator/=(CfemReal scalar) { - if (std::abs(scalar) < cfem::ZERO_TOLERANCE) - { - CFEM_ERROR << "Division by zero in SkewSymTensor3D::operator/="; - } + CFEM_ASSERT(std::abs(scalar) > ZERO_TOLERANCE, "Division by zero in SkewSymTensor3D::operator/="); CfemReal inv = 1.0/ scalar; data[idx_yz] *= inv; @@ -1006,29 +986,29 @@ namespace cfem // 1. T = (*this) * M Tensor3D T; - const CfemReal m00 = M.xx(), m01 = M.xy(), m02 = M.xz(); - const CfemReal m10 = M.yx(), m11 = M.yy(), m12 = M.yz(); - const CfemReal m20 = M.zx(), m21 = M.zy(), m22 = M.zz(); + const CfemReal m00 = M.data[Tensor3D::idx_xx], m01 = M.data[Tensor3D::idx_xy], m02 = M.data[Tensor3D::idx_xz]; + const CfemReal m10 = M.data[Tensor3D::idx_yx], m11 = M.data[Tensor3D::idx_yy], m12 = M.data[Tensor3D::idx_yz]; + const CfemReal m20 = M.data[Tensor3D::idx_zx], m21 = M.data[Tensor3D::idx_zy], m22 = M.data[Tensor3D::idx_zz]; // Row 0 of W: [0, xy, xz] - T.xx() = data[idx_xy] * m10 + data[idx_xz] * m20; - T.xy() = data[idx_xy] * m11 + data[idx_xz] * m21; - T.xz() = data[idx_xy] * m12 + data[idx_xz] * m22; + T.data[Tensor3D::idx_xx] = data[idx_xy] * m10 + data[idx_xz] * m20; + T.data[Tensor3D::idx_xy] = data[idx_xy] * m11 + data[idx_xz] * m21; + T.data[Tensor3D::idx_xz] = data[idx_xy] * m12 + data[idx_xz] * m22; // Row 1 of W: [-xy, 0, yz] - T.yx() = -data[idx_xy] * m00 + data[idx_yz] * m20; - T.yy() = -data[idx_xy] * m01 + data[idx_yz] * m21; - T.yz() = -data[idx_xy] * m02 + data[idx_yz] * m22; + T.data[Tensor3D::idx_yx] = -data[idx_xy] * m00 + data[idx_yz] * m20; + T.data[Tensor3D::idx_yy] = -data[idx_xy] * m01 + data[idx_yz] * m21; + T.data[Tensor3D::idx_yz] = -data[idx_xy] * m02 + data[idx_yz] * m22; // Row 2 of W: [-xz, -yz, 0] - T.zx() = -data[idx_xz] * m00 - data[idx_yz] * m10; - T.zy() = -data[idx_xz] * m01 - data[idx_yz] * m11; - T.zz() = -data[idx_xz] * m02 - data[idx_yz] * m12; + T.data[Tensor3D::idx_zx] = -data[idx_xz] * m00 - data[idx_yz] * m10; + T.data[Tensor3D::idx_zy] = -data[idx_xz] * m01 - data[idx_yz] * m11; + T.data[Tensor3D::idx_zz] = -data[idx_xz] * m02 - data[idx_yz] * m12; // 2. res = M^T * T (Only extract the 3 skew components) - res.data[idx_yz] = m01 * T.xz() + m11 * T.yz() + m21 * T.zz(); // yz - res.data[idx_xz] = m00 * T.xz() + m10 * T.yz() + m20 * T.zz(); // xz - res.data[idx_xy] = m00 * T.xy() + m10 * T.yy() + m20 * T.zy(); // xy + res.data[idx_yz] = m01 * T.data[Tensor3D::idx_xz] + m11 * T.data[Tensor3D::idx_yz] + m21 * T.data[Tensor3D::idx_zz]; // yz + res.data[idx_xz] = m00 * T.data[Tensor3D::idx_xz] + m10 * T.data[Tensor3D::idx_yz] + m20 * T.data[Tensor3D::idx_zz]; // xz + res.data[idx_xy] = m00 * T.data[Tensor3D::idx_xy] + m10 * T.data[Tensor3D::idx_yy] + m20 * T.data[Tensor3D::idx_zy]; // xy return res; } @@ -1156,7 +1136,7 @@ namespace cfem */ [[nodiscard]] inline CfemReal imaginaryEigenvalueMagnitude() const noexcept { - return std::sqrt( xy()*xy() + xz()*xz() + yz()*yz() ); + return std::sqrt( data[idx_xy]*data[idx_xy] + data[idx_xz]*data[idx_xz] + data[idx_yz]*data[idx_yz] ); } }; @@ -1174,9 +1154,9 @@ namespace cfem return W * scalar; } - [[nodiscard]] constexpr AxisSymTensor operator*(CfemReal scalar, const AxisSymTensor &S) noexcept + [[nodiscard]] constexpr AxiSymTensor3D operator*(CfemReal scalar, const AxiSymTensor3D &A) noexcept { - return S * scalar; + return A * scalar; } /** @@ -1190,48 +1170,48 @@ namespace cfem [[nodiscard]] constexpr Tensor3D operator*(const Tensor3D &T, const SymTensor3D &S) noexcept { Tensor3D res; - const CfemReal Txx = T.xx(), Txy = T.xy(), Txz = T.xz(); - const CfemReal Tyx = T.yx(), Tyy = T.yy(), Tyz = T.yz(); - const CfemReal Tzx = T.zx(), Tzy = T.zy(), Tzz = T.zz(); + const CfemReal Txx = T.data[Tensor3D::idx_xx], Txy = T.data[Tensor3D::idx_xy], Txz = T.data[Tensor3D::idx_xz]; + const CfemReal Tyx = T.data[Tensor3D::idx_yx], Tyy = T.data[Tensor3D::idx_yy], Tyz = T.data[Tensor3D::idx_yz]; + const CfemReal Tzx = T.data[Tensor3D::idx_zx], Tzy = T.data[Tensor3D::idx_zy], Tzz = T.data[Tensor3D::idx_zz]; - const CfemReal Sxx = S.xx(), Syy = S.yy(), Szz = S.zz(), Sxy = S.xy(), Sxz = S.xz(), Syz = S.yz(); + const CfemReal Sxx = S.data[SymTensor3D::idx_xx], Syy = S.data[SymTensor3D::idx_yy], Szz = S.data[SymTensor3D::idx_zz], Sxy = S.data[SymTensor3D::idx_xy], Sxz = S.data[SymTensor3D::idx_xz], Syz = S.data[SymTensor3D::idx_yz]; - res.xx() = Txx * Sxx + Txy * Sxy + Txz * Sxz; - res.xy() = Txx * Sxy + Txy * Syy + Txz * Syz; - res.xz() = Txx * Sxz + Txy * Syz + Txz * Szz; + res.data[Tensor3D::idx_xx] = Txx * Sxx + Txy * Sxy + Txz * Sxz; + res.data[Tensor3D::idx_xy] = Txx * Sxy + Txy * Syy + Txz * Syz; + res.data[Tensor3D::idx_xz] = Txx * Sxz + Txy * Syz + Txz * Szz; - res.yx() = Tyx * Sxx + Tyy * Sxy + Tyz * Sxz; - res.yy() = Tyx * Sxy + Tyy * Syy + Tyz * Syz; - res.yz() = Tyx * Sxz + Tyy * Syz + Tyz * Szz; + res.data[Tensor3D::idx_yx] = Tyx * Sxx + Tyy * Sxy + Tyz * Sxz; + res.data[Tensor3D::idx_yy] = Tyx * Sxy + Tyy * Syy + Tyz * Syz; + res.data[Tensor3D::idx_yz] = Tyx * Sxz + Tyy * Syz + Tyz * Szz; - res.zx() = Tzx * Sxx + Tzy * Sxy + Tzz * Sxz; - res.zy() = Tzx * Sxy + Tzy * Syy + Tzz * Syz; - res.zz() = Tzx * Sxz + Tzy * Syz + Tzz * Szz; + res.data[Tensor3D::idx_zx] = Tzx * Sxx + Tzy * Sxy + Tzz * Sxz; + res.data[Tensor3D::idx_zy] = Tzx * Sxy + Tzy * Syy + Tzz * Syz; + res.data[Tensor3D::idx_zz] = Tzx * Sxz + Tzy * Syz + Tzz * Szz; return res; } - /** Tensor3D x AxisSymTensor */ - [[nodiscard]] constexpr Tensor3D operator*(const Tensor3D &T, const AxisSymTensor &S) noexcept + /** Tensor3D x AxiSymTensor3D */ + [[nodiscard]] constexpr Tensor3D operator*(const Tensor3D &T, const AxiSymTensor3D &A) noexcept { Tensor3D res; - const CfemReal Txx = T.xx(), Txy = T.xy(), Txz = T.xz(); - const CfemReal Tyx = T.yx(), Tyy = T.yy(), Tyz = T.yz(); - const CfemReal Tzx = T.zx(), Tzy = T.zy(), Tzz = T.zz(); + const CfemReal Txx = T.data[Tensor3D::idx_xx], Txy = T.data[Tensor3D::idx_xy], Txz = T.data[Tensor3D::idx_xz]; + const CfemReal Tyx = T.data[Tensor3D::idx_yx], Tyy = T.data[Tensor3D::idx_yy], Tyz = T.data[Tensor3D::idx_yz]; + const CfemReal Tzx = T.data[Tensor3D::idx_zx], Tzy = T.data[Tensor3D::idx_zy], Tzz = T.data[Tensor3D::idx_zz]; - const CfemReal Sxx = S.xx(), Syy = S.yy(), Szz = S.zz(), Sxy = S.xy(); + const CfemReal Sxx = A.data[AxiSymTensor3D::idx_xx], Syy = A.data[AxiSymTensor3D::idx_yy], Szz = A.data[AxiSymTensor3D::idx_zz], Sxy = A.data[AxiSymTensor3D::idx_xy]; - res.xx() = Txx * Sxx + Txy * Sxy; - res.xy() = Txx * Sxy + Txy * Syy; - res.xz() = Txz * Szz; + res.data[Tensor3D::idx_xx] = Txx * Sxx + Txy * Sxy; + res.data[Tensor3D::idx_xy] = Txx * Sxy + Txy * Syy; + res.data[Tensor3D::idx_xz] = Txz * Szz; - res.yx() = Tyx * Sxx + Tyy * Sxy; - res.yy() = Tyx * Sxy + Tyy * Syy; - res.yz() = Tyz * Szz; + res.data[Tensor3D::idx_yx] = Tyx * Sxx + Tyy * Sxy; + res.data[Tensor3D::idx_yy] = Tyx * Sxy + Tyy * Syy; + res.data[Tensor3D::idx_yz] = Tyz * Szz; - res.zx() = Tzx * Sxx + Tzy * Sxy; - res.zy() = Tzx * Sxy + Tzy * Syy; - res.zz() = Tzz * Szz; + res.data[Tensor3D::idx_zx] = Tzx * Sxx + Tzy * Sxy; + res.data[Tensor3D::idx_zy] = Tzx * Sxy + Tzy * Syy; + res.data[Tensor3D::idx_zz] = Tzz * Szz; return res; } @@ -1239,89 +1219,89 @@ namespace cfem [[nodiscard]] constexpr Tensor3D operator*(const Tensor3D &T, const SkewSymTensor3D &W) noexcept { Tensor3D res; - const CfemReal Txx = T.xx(), Txy = T.xy(), Txz = T.xz(); - const CfemReal Tyx = T.yx(), Tyy = T.yy(), Tyz = T.yz(); - const CfemReal Tzx = T.zx(), Tzy = T.zy(), Tzz = T.zz(); + const CfemReal Txx = T.data[Tensor3D::idx_xx], Txy = T.data[Tensor3D::idx_xy], Txz = T.data[Tensor3D::idx_xz]; + const CfemReal Tyx = T.data[Tensor3D::idx_yx], Tyy = T.data[Tensor3D::idx_yy], Tyz = T.data[Tensor3D::idx_yz]; + const CfemReal Tzx = T.data[Tensor3D::idx_zx], Tzy = T.data[Tensor3D::idx_zy], Tzz = T.data[Tensor3D::idx_zz]; - const CfemReal Wxy = W.xy(), Wxz = W.xz(), Wyz = W.yz(); + const CfemReal Wxy = W.data[SkewSymTensor3D::idx_xy], Wxz = W.data[SkewSymTensor3D::idx_xz], Wyz = W.data[SkewSymTensor3D::idx_yz]; - res.xx() = -Txy * Wxy - Txz * Wxz; - res.yx() = -Tyy * Wxy - Tyz * Wxz; - res.zx() = -Tzy * Wxy - Tzz * Wxz; + res.data[Tensor3D::idx_xx] = -Txy * Wxy - Txz * Wxz; + res.data[Tensor3D::idx_yx] = -Tyy * Wxy - Tyz * Wxz; + res.data[Tensor3D::idx_zx] = -Tzy * Wxy - Tzz * Wxz; - res.xy() = Txx * Wxy - Txz * Wyz; - res.yy() = Tyx * Wxy - Tyz * Wyz; - res.zy() = Tzx * Wxy - Tzz * Wyz; + res.data[Tensor3D::idx_xy] = Txx * Wxy - Txz * Wyz; + res.data[Tensor3D::idx_yy] = Tyx * Wxy - Tyz * Wyz; + res.data[Tensor3D::idx_zy] = Tzx * Wxy - Tzz * Wyz; - res.xz() = Txx * Wxz + Txy * Wyz; - res.yz() = Tyx * Wxz + Tyy * Wyz; - res.zz() = Tzx * Wxz + Tzy * Wyz; + res.data[Tensor3D::idx_xz] = Txx * Wxz + Txy * Wyz; + res.data[Tensor3D::idx_yz] = Tyx * Wxz + Tyy * Wyz; + res.data[Tensor3D::idx_zz] = Tzx * Wxz + Tzy * Wyz; return res; } - /** AxisSymTensor x Tensor3D */ - [[nodiscard]] constexpr Tensor3D operator*(const AxisSymTensor &S, const Tensor3D &T) noexcept + /** AxiSymTensor3D x Tensor3D */ + [[nodiscard]] constexpr Tensor3D operator*(const AxiSymTensor3D &A, const Tensor3D &T) noexcept { Tensor3D res; - const CfemReal Txx = T.xx(), Txy = T.xy(), Txz = T.xz(); - const CfemReal Tyx = T.yx(), Tyy = T.yy(), Tyz = T.yz(); - const CfemReal Tzx = T.zx(), Tzy = T.zy(), Tzz = T.zz(); + const CfemReal Txx = T.data[Tensor3D::idx_xx], Txy = T.data[Tensor3D::idx_xy], Txz = T.data[Tensor3D::idx_xz]; + const CfemReal Tyx = T.data[Tensor3D::idx_yx], Tyy = T.data[Tensor3D::idx_yy], Tyz = T.data[Tensor3D::idx_yz]; + const CfemReal Tzx = T.data[Tensor3D::idx_zx], Tzy = T.data[Tensor3D::idx_zy], Tzz = T.data[Tensor3D::idx_zz]; - const CfemReal Sxx = S.xx(), Syy = S.yy(), Szz = S.zz(), Sxy = S.xy(); + const CfemReal Sxx = A.data[AxiSymTensor3D::idx_xx], Syy = A.data[AxiSymTensor3D::idx_yy], Szz = A.data[AxiSymTensor3D::idx_zz], Sxy = A.data[AxiSymTensor3D::idx_xy]; - res.xx() = Sxx * Txx + Sxy * Tyx; - res.xy() = Sxx * Txy + Sxy * Tyy; - res.xz() = Sxx * Txz + Sxy * Tyz; + res.data[Tensor3D::idx_xx] = Sxx * Txx + Sxy * Tyx; + res.data[Tensor3D::idx_xy] = Sxx * Txy + Sxy * Tyy; + res.data[Tensor3D::idx_xz] = Sxx * Txz + Sxy * Tyz; - res.yx() = Sxy * Txx + Syy * Tyx; - res.yy() = Sxy * Txy + Syy * Tyy; - res.yz() = Sxy * Txz + Syy * Tyz; + res.data[Tensor3D::idx_yx] = Sxy * Txx + Syy * Tyx; + res.data[Tensor3D::idx_yy] = Sxy * Txy + Syy * Tyy; + res.data[Tensor3D::idx_yz] = Sxy * Txz + Syy * Tyz; - res.zx() = Szz * Tzx; - res.zy() = Szz * Tzy; - res.zz() = Szz * Tzz; + res.data[Tensor3D::idx_zx] = Szz * Tzx; + res.data[Tensor3D::idx_zy] = Szz * Tzy; + res.data[Tensor3D::idx_zz] = Szz * Tzz; return res; } - /** AxisSymTensor x SymTensor3D */ - [[nodiscard]] constexpr Tensor3D operator*(const AxisSymTensor& A, const SymTensor3D& S) noexcept + /** AxiSymTensor3D x SymTensor3D */ + [[nodiscard]] constexpr Tensor3D operator*(const AxiSymTensor3D &A, const SymTensor3D &S) noexcept { - Tensor3D R; + Tensor3D res; - R.xx() = A.xx()*S.xx() + A.xy()*S.xy(); - R.xy() = A.xx()*S.xy() + A.xy()*S.yy(); - R.xz() = A.xx()*S.xz() + A.xy()*S.yz(); + res.data[Tensor3D::idx_xx] = A.data[AxiSymTensor3D::idx_xx]*S.data[SymTensor3D::idx_xx] + A.data[AxiSymTensor3D::idx_xy]*S.data[SymTensor3D::idx_xy]; + res.data[Tensor3D::idx_xy] = A.data[AxiSymTensor3D::idx_xx]*S.data[SymTensor3D::idx_xy] + A.data[AxiSymTensor3D::idx_xy]*S.data[SymTensor3D::idx_yy]; + res.data[Tensor3D::idx_xz] = A.data[AxiSymTensor3D::idx_xx]*S.data[SymTensor3D::idx_xz] + A.data[AxiSymTensor3D::idx_xy]*S.data[SymTensor3D::idx_yz]; - R.yx() = A.xy()*S.xx() + A.yy()*S.xy(); - R.yy() = A.xy()*S.xy() + A.yy()*S.yy(); - R.yz() = A.xy()*S.xz() + A.yy()*S.yz(); + res.data[Tensor3D::idx_yx] = A.data[AxiSymTensor3D::idx_xy]*S.data[SymTensor3D::idx_xx] + A.data[AxiSymTensor3D::idx_yy]*S.data[SymTensor3D::idx_xy]; + res.data[Tensor3D::idx_yy] = A.data[AxiSymTensor3D::idx_xy]*S.data[SymTensor3D::idx_xy] + A.data[AxiSymTensor3D::idx_yy]*S.data[SymTensor3D::idx_yy]; + res.data[Tensor3D::idx_yz] = A.data[AxiSymTensor3D::idx_xy]*S.data[SymTensor3D::idx_xz] + A.data[AxiSymTensor3D::idx_yy]*S.data[SymTensor3D::idx_yz]; - R.zx() = A.zz()*S.xz(); - R.zy() = A.zz()*S.yz(); - R.zz() = A.zz()*S.zz(); + res.data[Tensor3D::idx_zx] = A.data[AxiSymTensor3D::idx_zz]*S.data[SymTensor3D::idx_xz]; + res.data[Tensor3D::idx_zy] = A.data[AxiSymTensor3D::idx_zz]*S.data[SymTensor3D::idx_yz]; + res.data[Tensor3D::idx_zz] = A.data[AxiSymTensor3D::idx_zz]*S.data[SymTensor3D::idx_zz]; - return R; + return res; } - /** AxisSymTensor x SkewSymTensor3D */ - [[nodiscard]] constexpr Tensor3D operator*(const AxisSymTensor &S, const SkewSymTensor3D &W) noexcept + /** AxiSymTensor3D x SkewSymTensor3D */ + [[nodiscard]] constexpr Tensor3D operator*(const AxiSymTensor3D &A, const SkewSymTensor3D &W) noexcept { Tensor3D res; - const CfemReal Wxy = W.xy(), Wxz = W.xz(), Wyz = W.yz(); - const CfemReal Sxx = S.xx(), Syy = S.yy(), Szz = S.zz(), Sxy = S.xy(); + const CfemReal Wxy = W.data[SkewSymTensor3D::idx_xy], Wxz = W.data[SkewSymTensor3D::idx_xz], Wyz = W.data[SkewSymTensor3D::idx_yz]; + const CfemReal Sxx = A.data[AxiSymTensor3D::idx_xx], Syy = A.data[AxiSymTensor3D::idx_yy], Szz = A.data[AxiSymTensor3D::idx_zz], Sxy = A.data[AxiSymTensor3D::idx_xy]; - res.xx() =-Sxy * Wxy; - res.xy() = Sxx * Wxy; - res.xz() = Sxx * Wxz + Sxy * Wyz; + res.data[Tensor3D::idx_xx] =-Sxy * Wxy; + res.data[Tensor3D::idx_xy] = Sxx * Wxy; + res.data[Tensor3D::idx_xz] = Sxx * Wxz + Sxy * Wyz; - res.yx() =-Syy * Wxy; - res.yy() = Sxy * Wxy; - res.yz() = Sxy * Wxz + Syy * Wyz; + res.data[Tensor3D::idx_yx] =-Syy * Wxy; + res.data[Tensor3D::idx_yy] = Sxy * Wxy; + res.data[Tensor3D::idx_yz] = Sxy * Wxz + Syy * Wyz; - res.zx() =-Szz * Wxz; - res.zy() =-Szz * Wyz; - res.zz() = 0.0; + res.data[Tensor3D::idx_zx] =-Szz * Wxz; + res.data[Tensor3D::idx_zy] =-Szz * Wyz; + res.data[Tensor3D::idx_zz] = 0.0; return res; } @@ -1330,23 +1310,23 @@ namespace cfem [[nodiscard]] constexpr Tensor3D operator*(const SymTensor3D &S, const Tensor3D &T) noexcept { Tensor3D res; - const CfemReal Txx = T.xx(), Txy = T.xy(), Txz = T.xz(); - const CfemReal Tyx = T.yx(), Tyy = T.yy(), Tyz = T.yz(); - const CfemReal Tzx = T.zx(), Tzy = T.zy(), Tzz = T.zz(); + const CfemReal Txx = T.data[Tensor3D::idx_xx], Txy = T.data[Tensor3D::idx_xy], Txz = T.data[Tensor3D::idx_xz]; + const CfemReal Tyx = T.data[Tensor3D::idx_yx], Tyy = T.data[Tensor3D::idx_yy], Tyz = T.data[Tensor3D::idx_yz]; + const CfemReal Tzx = T.data[Tensor3D::idx_zx], Tzy = T.data[Tensor3D::idx_zy], Tzz = T.data[Tensor3D::idx_zz]; - const CfemReal Sxx = S.xx(), Syy = S.yy(), Szz = S.zz(), Sxy = S.xy(), Sxz = S.xz(), Syz = S.yz(); + const CfemReal Sxx = S.data[SymTensor3D::idx_xx], Syy = S.data[SymTensor3D::idx_yy], Szz = S.data[SymTensor3D::idx_zz], Sxy = S.data[SymTensor3D::idx_xy], Sxz = S.data[SymTensor3D::idx_xz], Syz = S.data[SymTensor3D::idx_yz]; - res.xx() = Sxx * Txx + Sxy * Tyx + Sxz * Tzx; - res.xy() = Sxx * Txy + Sxy * Tyy + Sxz * Tzy; - res.xz() = Sxx * Txz + Sxy * Tyz + Sxz * Tzz; + res.data[Tensor3D::idx_xx] = Sxx * Txx + Sxy * Tyx + Sxz * Tzx; + res.data[Tensor3D::idx_xy] = Sxx * Txy + Sxy * Tyy + Sxz * Tzy; + res.data[Tensor3D::idx_xz] = Sxx * Txz + Sxy * Tyz + Sxz * Tzz; - res.yx() = Sxy * Txx + Syy * Tyx + Syz * Tzx; - res.yy() = Sxy * Txy + Syy * Tyy + Syz * Tzy; - res.yz() = Sxy * Txz + Syy * Tyz + Syz * Tzz; + res.data[Tensor3D::idx_yx] = Sxy * Txx + Syy * Tyx + Syz * Tzx; + res.data[Tensor3D::idx_yy] = Sxy * Txy + Syy * Tyy + Syz * Tzy; + res.data[Tensor3D::idx_yz] = Sxy * Txz + Syy * Tyz + Syz * Tzz; - res.zx() = Sxz * Txx + Syz * Tyx + Szz * Tzx; - res.zy() = Sxz * Txy + Syz * Tyy + Szz * Tzy; - res.zz() = Sxz * Txz + Syz * Tyz + Szz * Tzz; + res.data[Tensor3D::idx_zx] = Sxz * Txx + Syz * Tyx + Szz * Tzx; + res.data[Tensor3D::idx_zy] = Sxz * Txy + Syz * Tyy + Szz * Tzy; + res.data[Tensor3D::idx_zz] = Sxz * Txz + Syz * Tyz + Szz * Tzz; return res; } @@ -1355,67 +1335,67 @@ namespace cfem [[nodiscard]] constexpr Tensor3D operator*(const SymTensor3D &S, const SkewSymTensor3D &W) noexcept { Tensor3D res; - const CfemReal Sxx = S.xx(), Syy = S.yy(), Szz = S.zz(), Sxy = S.xy(), Sxz = S.xz(), Syz = S.yz(); - const CfemReal Wxy = W.xy(), Wxz = W.xz(), Wyz = W.yz(); + const CfemReal Sxx = S.data[SymTensor3D::idx_xx], Syy = S.data[SymTensor3D::idx_yy], Szz = S.data[SymTensor3D::idx_zz], Sxy = S.data[SymTensor3D::idx_xy], Sxz = S.data[SymTensor3D::idx_xz], Syz = S.data[SymTensor3D::idx_yz]; + const CfemReal Wxy = W.data[SkewSymTensor3D::idx_xy], Wxz = W.data[SkewSymTensor3D::idx_xz], Wyz = W.data[SkewSymTensor3D::idx_yz]; - res.xx() =-Sxy * Wxy - Sxz * Wxz; - res.xy() = Sxx * Wxy - Sxz * Wyz; - res.xz() = Sxx * Wxz + Sxy * Wyz; + res.data[Tensor3D::idx_xx] =-Sxy * Wxy - Sxz * Wxz; + res.data[Tensor3D::idx_xy] = Sxx * Wxy - Sxz * Wyz; + res.data[Tensor3D::idx_xz] = Sxx * Wxz + Sxy * Wyz; - res.yx() =-Syy * Wxy - Syz * Wxz; - res.yy() = Sxy * Wxy - Syz * Wyz; - res.yz() = Sxy * Wxz + Syy * Wyz; + res.data[Tensor3D::idx_yx] =-Syy * Wxy - Syz * Wxz; + res.data[Tensor3D::idx_yy] = Sxy * Wxy - Syz * Wyz; + res.data[Tensor3D::idx_yz] = Sxy * Wxz + Syy * Wyz; - res.zx() =-Syz * Wxy - Szz * Wxz; - res.zy() = Sxz * Wxy - Szz * Wyz; - res.zz() = Sxz * Wxz + Syz * Wyz; + res.data[Tensor3D::idx_zx] =-Syz * Wxy - Szz * Wxz; + res.data[Tensor3D::idx_zy] = Sxz * Wxy - Szz * Wyz; + res.data[Tensor3D::idx_zz] = Sxz * Wxz + Syz * Wyz; return res; } - /** SymTensor3D x AxisSymTensor */ - [[nodiscard]] constexpr Tensor3D operator*(const SymTensor3D &S, const AxisSymTensor& A) noexcept + /** SymTensor3D x AxiSymTensor3D */ + [[nodiscard]] constexpr Tensor3D operator*(const SymTensor3D &S, const AxiSymTensor3D &A) noexcept { - Tensor3D R; - const CfemReal Sxx = S.xx(), Syy = S.yy(), Szz = S.zz(), Sxy = S.xy(), Sxz = S.xz(), Syz = S.yz(); - const CfemReal Axx = A.xx(), Ayy = A.yy(), Azz = A.zz(), Axy = A.xy(); + Tensor3D res; + const CfemReal Sxx = S.data[SymTensor3D::idx_xx], Syy = S.data[SymTensor3D::idx_yy], Szz = S.data[SymTensor3D::idx_zz], Sxy = S.data[SymTensor3D::idx_xy], Sxz = S.data[SymTensor3D::idx_xz], Syz = S.data[SymTensor3D::idx_yz]; + const CfemReal Axx = A.data[AxiSymTensor3D::idx_xx], Ayy = A.data[AxiSymTensor3D::idx_yy], Azz = A.data[AxiSymTensor3D::idx_zz], Axy = A.data[AxiSymTensor3D::idx_xy]; - R.xx() = Sxx * Axx + Sxy * Axy; - R.xy() = Sxx * Axy + Sxy * Ayy; - R.xz() = Sxz * Azz; + res.data[Tensor3D::idx_xx] = Sxx * Axx + Sxy * Axy; + res.data[Tensor3D::idx_xy] = Sxx * Axy + Sxy * Ayy; + res.data[Tensor3D::idx_xz] = Sxz * Azz; - R.yx() = Sxy * Axx + Syy * Axy; - R.yy() = Sxy * Axy + Syy * Ayy; - R.yz() = Syz * Azz; + res.data[Tensor3D::idx_yx] = Sxy * Axx + Syy * Axy; + res.data[Tensor3D::idx_yy] = Sxy * Axy + Syy * Ayy; + res.data[Tensor3D::idx_yz] = Syz * Azz; - R.zx() = Sxz * Axx + Syz * Axy; - R.zy() = Sxz * Axy + Syz * Ayy; - R.zz() = Szz * Azz; + res.data[Tensor3D::idx_zx] = Sxz * Axx + Syz * Axy; + res.data[Tensor3D::idx_zy] = Sxz * Axy + Syz * Ayy; + res.data[Tensor3D::idx_zz] = Szz * Azz; - return R; + return res; } /** SkewSymTensor3D x Tensor3D */ [[nodiscard]] constexpr Tensor3D operator*(const SkewSymTensor3D &W, const Tensor3D &T) noexcept { Tensor3D res; - const CfemReal Txx = T.xx(), Txy = T.xy(), Txz = T.xz(); - const CfemReal Tyx = T.yx(), Tyy = T.yy(), Tyz = T.yz(); - const CfemReal Tzx = T.zx(), Tzy = T.zy(), Tzz = T.zz(); + const CfemReal Txx = T.data[Tensor3D::idx_xx], Txy = T.data[Tensor3D::idx_xy], Txz = T.data[Tensor3D::idx_xz]; + const CfemReal Tyx = T.data[Tensor3D::idx_yx], Tyy = T.data[Tensor3D::idx_yy], Tyz = T.data[Tensor3D::idx_yz]; + const CfemReal Tzx = T.data[Tensor3D::idx_zx], Tzy = T.data[Tensor3D::idx_zy], Tzz = T.data[Tensor3D::idx_zz]; - const CfemReal Wxy = W.xy(), Wxz = W.xz(), Wyz = W.yz(); + const CfemReal Wxy = W.data[SkewSymTensor3D::idx_xy], Wxz = W.data[SkewSymTensor3D::idx_xz], Wyz = W.data[SkewSymTensor3D::idx_yz]; - res.xx() = Wxy * Tyx + Wxz * Tzx; - res.xy() = Wxy * Tyy + Wxz * Tzy; - res.xz() = Wxy * Tyz + Wxz * Tzz; + res.data[Tensor3D::idx_xx] = Wxy * Tyx + Wxz * Tzx; + res.data[Tensor3D::idx_xy] = Wxy * Tyy + Wxz * Tzy; + res.data[Tensor3D::idx_xz] = Wxy * Tyz + Wxz * Tzz; - res.yx() = -Wxy * Txx + Wyz * Tzx; - res.yy() = -Wxy * Txy + Wyz * Tzy; - res.yz() = -Wxy * Txz + Wyz * Tzz; + res.data[Tensor3D::idx_yx] = -Wxy * Txx + Wyz * Tzx; + res.data[Tensor3D::idx_yy] = -Wxy * Txy + Wyz * Tzy; + res.data[Tensor3D::idx_yz] = -Wxy * Txz + Wyz * Tzz; - res.zx() = -Wxz * Txx - Wyz * Tyx; - res.zy() = -Wxz * Txy - Wyz * Tyy; - res.zz() = -Wxz * Txz - Wyz * Tyz; + res.data[Tensor3D::idx_zx] = -Wxz * Txx - Wyz * Tyx; + res.data[Tensor3D::idx_zy] = -Wxz * Txy - Wyz * Tyy; + res.data[Tensor3D::idx_zz] = -Wxz * Txz - Wyz * Tyz; return res; } @@ -1424,34 +1404,34 @@ namespace cfem [[nodiscard]] constexpr Tensor3D operator*(const SkewSymTensor3D &W, const SymTensor3D &S) noexcept { Tensor3D res; - const CfemReal Sxx = S.xx(), Syy = S.yy(), Szz = S.zz(), Sxy = S.xy(), Sxz = S.xz(), Syz = S.yz(); - const CfemReal Wxy = W.xy(), Wxz = W.xz(), Wyz = W.yz(); + const CfemReal Sxx = S.data[SymTensor3D::idx_xx], Syy = S.data[SymTensor3D::idx_yy], Szz = S.data[SymTensor3D::idx_zz], Sxy = S.data[SymTensor3D::idx_xy], Sxz = S.data[SymTensor3D::idx_xz], Syz = S.data[SymTensor3D::idx_yz]; + const CfemReal Wxy = W.data[SkewSymTensor3D::idx_xy], Wxz = W.data[SkewSymTensor3D::idx_xz], Wyz = W.data[SkewSymTensor3D::idx_yz]; - res.xx() = Wxy * Sxy + Wxz * Sxz; - res.xy() = Wxy * Syy + Wxz * Syz; - res.xz() = Wxy * Syz + Wxz * Szz; + res.data[Tensor3D::idx_xx] = Wxy * Sxy + Wxz * Sxz; + res.data[Tensor3D::idx_xy] = Wxy * Syy + Wxz * Syz; + res.data[Tensor3D::idx_xz] = Wxy * Syz + Wxz * Szz; - res.yx() = -Wxy * Sxx + Wyz * Sxz; - res.yy() = -Wxy * Sxy + Wyz * Syz; - res.yz() = -Wxy * Sxz + Wyz * Szz; + res.data[Tensor3D::idx_yx] = -Wxy * Sxx + Wyz * Sxz; + res.data[Tensor3D::idx_yy] = -Wxy * Sxy + Wyz * Syz; + res.data[Tensor3D::idx_yz] = -Wxy * Sxz + Wyz * Szz; - res.zx() = -Wxz * Sxx - Wyz * Sxy; - res.zy() = -Wxz * Sxy - Wyz * Syy; - res.zz() = -Wxz * Sxz - Wyz * Syz; + res.data[Tensor3D::idx_zx] = -Wxz * Sxx - Wyz * Sxy; + res.data[Tensor3D::idx_zy] = -Wxz * Sxy - Wyz * Syy; + res.data[Tensor3D::idx_zz] = -Wxz * Sxz - Wyz * Syz; return res; } - /** SkewSymTensor3D x AxisSymTensor */ - [[nodiscard]] constexpr Tensor3D operator*(const SkewSymTensor3D &W, const AxisSymTensor &S) noexcept + /** SkewSymTensor3D x AxiSymTensor3D */ + [[nodiscard]] constexpr Tensor3D operator*(const SkewSymTensor3D &W, const AxiSymTensor3D &A) noexcept { Tensor3D res; - const CfemReal Sxx = S.xx(), Syy = S.yy(), Szz = S.zz(), Sxy = S.xy(); - const CfemReal Wxy = W.xy(), Wxz = W.xz(), Wyz = W.yz(); + const CfemReal Sxx = A.data[AxiSymTensor3D::idx_xx], Syy = A.data[AxiSymTensor3D::idx_yy], Szz = A.data[AxiSymTensor3D::idx_zz], Sxy = A.data[AxiSymTensor3D::idx_xy]; + const CfemReal Wxy = W.data[SkewSymTensor3D::idx_xy], Wxz = W.data[SkewSymTensor3D::idx_xz], Wyz = W.data[SkewSymTensor3D::idx_yz]; - res.xx() = Wxy * Sxy; res.xy() = Wxy * Syy; res.xz() = Wxz * Szz; - res.yx() = -Wxy * Sxx; res.yy() =-Wxy * Sxy; res.yz() = Wyz * Szz; - res.zx() = -Wxz * Sxx - Wyz * Sxy; res.zy() =-Wxz * Sxy - Wyz * Syy; res.zz() = 0.0; + res.data[Tensor3D::idx_xx] = Wxy * Sxy; res.data[Tensor3D::idx_xy] = Wxy * Syy; res.data[Tensor3D::idx_xz] = Wxz * Szz; + res.data[Tensor3D::idx_yx] = -Wxy * Sxx; res.data[Tensor3D::idx_yy] =-Wxy * Sxy; res.data[Tensor3D::idx_yz] = Wyz * Szz; + res.data[Tensor3D::idx_zx] = -Wxz * Sxx - Wyz * Sxy; res.data[Tensor3D::idx_zy] =-Wxz * Sxy - Wyz * Syy; res.data[Tensor3D::idx_zz] = 0.0; return res; } @@ -1468,19 +1448,19 @@ namespace cfem [[nodiscard]] constexpr Tensor3D operator+(const Tensor3D &T, const SymTensor3D &S) noexcept { Tensor3D res = T; - res.xx() += S.xx(); res.xy() += S.xy(); res.xz() += S.xz(); - res.yx() += S.xy(); res.yy() += S.yy(); res.yz() += S.yz(); - res.zx() += S.xz(); res.zy() += S.yz(); res.zz() += S.zz(); + res.data[Tensor3D::idx_xx] += S.data[SymTensor3D::idx_xx]; res.data[Tensor3D::idx_xy] += S.data[SymTensor3D::idx_xy]; res.data[Tensor3D::idx_xz] += S.data[SymTensor3D::idx_xz]; + res.data[Tensor3D::idx_yx] += S.data[SymTensor3D::idx_xy]; res.data[Tensor3D::idx_yy] += S.data[SymTensor3D::idx_yy]; res.data[Tensor3D::idx_yz] += S.data[SymTensor3D::idx_yz]; + res.data[Tensor3D::idx_zx] += S.data[SymTensor3D::idx_xz]; res.data[Tensor3D::idx_zy] += S.data[SymTensor3D::idx_yz]; res.data[Tensor3D::idx_zz] += S.data[SymTensor3D::idx_zz]; return res; } - /** Tensor3D + AxisSymTensor */ - [[nodiscard]] constexpr Tensor3D operator+(const Tensor3D &T, const AxisSymTensor &A) noexcept + /** Tensor3D + AxiSymTensor3D */ + [[nodiscard]] constexpr Tensor3D operator+(const Tensor3D &T, const AxiSymTensor3D &A) noexcept { Tensor3D res = T; - res.xx() += A.xx(); res.xy() += A.xy(); - res.yx() += A.xy(); res.yy() += A.yy(); - res.zz() += A.zz(); + res.data[Tensor3D::idx_xx] += A.data[AxiSymTensor3D::idx_xx]; res.data[Tensor3D::idx_xy] += A.data[AxiSymTensor3D::idx_xy]; + res.data[Tensor3D::idx_yx] += A.data[AxiSymTensor3D::idx_xy]; res.data[Tensor3D::idx_yy] += A.data[AxiSymTensor3D::idx_yy]; + res.data[Tensor3D::idx_zz] += A.data[AxiSymTensor3D::idx_zz]; return res; } @@ -1488,9 +1468,9 @@ namespace cfem [[nodiscard]] constexpr Tensor3D operator+(const Tensor3D &T, const SkewSymTensor3D &W) noexcept { Tensor3D res = T; - res.xy() += W.xy(); res.xz() += W.xz(); - res.yx() -= W.xy(); res.yz() += W.yz(); - res.zx() -= W.xz(); res.zy() -= W.yz(); + res.data[Tensor3D::idx_xy] += W.data[SkewSymTensor3D::idx_xy]; res.data[Tensor3D::idx_xz] += W.data[SkewSymTensor3D::idx_xz]; + res.data[Tensor3D::idx_yx] -= W.data[SkewSymTensor3D::idx_xy]; res.data[Tensor3D::idx_yz] += W.data[SkewSymTensor3D::idx_yz]; + res.data[Tensor3D::idx_zx] -= W.data[SkewSymTensor3D::idx_xz]; res.data[Tensor3D::idx_zy] -= W.data[SkewSymTensor3D::idx_yz]; return res; } @@ -1500,11 +1480,14 @@ namespace cfem return T + S; } - /** SymTensor3D + AxisSymTensor */ - [[nodiscard]] constexpr SymTensor3D operator+(const SymTensor3D &S, const AxisSymTensor &A) noexcept + /** SymTensor3D + AxiSymTensor3D */ + [[nodiscard]] constexpr SymTensor3D operator+(const SymTensor3D &S, const AxiSymTensor3D &A) noexcept { SymTensor3D res = S; - res.xx() += A.xx(); res.yy() += A.yy(); res.zz() += A.zz(); res.xy() += A.xy(); + res.data[SymTensor3D::idx_xx] += A.data[AxiSymTensor3D::idx_xx]; + res.data[SymTensor3D::idx_yy] += A.data[AxiSymTensor3D::idx_yy]; + res.data[SymTensor3D::idx_zz] += A.data[AxiSymTensor3D::idx_zz]; + res.data[SymTensor3D::idx_xy] += A.data[AxiSymTensor3D::idx_xy]; return res; } @@ -1512,38 +1495,52 @@ namespace cfem [[nodiscard]] constexpr Tensor3D operator+(const SymTensor3D &S, const SkewSymTensor3D &W) noexcept { Tensor3D res; - res.xx() = S.xx(); res.xy() = S.xy() + W.xy(); res.xz() = S.xz() + W.xz(); - res.yx() = S.xy() - W.xy(); res.yy() = S.yy(); res.yz() = S.yz() + W.yz(); - res.zx() = S.xz() - W.xz(); res.zy() = S.yz() - W.yz(); res.zz() = S.zz(); + res.data[Tensor3D::idx_xx] = S.data[SymTensor3D::idx_xx]; + res.data[Tensor3D::idx_xy] = S.data[SymTensor3D::idx_xy] + W.data[SkewSymTensor3D::idx_xy]; + res.data[Tensor3D::idx_xz] = S.data[SymTensor3D::idx_xz] + W.data[SkewSymTensor3D::idx_xz]; + + res.data[Tensor3D::idx_yx] = S.data[SymTensor3D::idx_xy] - W.data[SkewSymTensor3D::idx_xy]; + res.data[Tensor3D::idx_yy] = S.data[SymTensor3D::idx_yy]; + res.data[Tensor3D::idx_yz] = S.data[SymTensor3D::idx_yz] + W.data[SkewSymTensor3D::idx_yz]; + + res.data[Tensor3D::idx_zx] = S.data[SymTensor3D::idx_xz] - W.data[SkewSymTensor3D::idx_xz]; + res.data[Tensor3D::idx_zy] = S.data[SymTensor3D::idx_yz] - W.data[SkewSymTensor3D::idx_yz]; + res.data[Tensor3D::idx_zz] = S.data[SymTensor3D::idx_zz]; return res; } - /** AxisSymTensor + Tensor3D */ - [[nodiscard]] constexpr Tensor3D operator+(const AxisSymTensor &A, const Tensor3D &T) noexcept + /** AxiSymTensor3D + Tensor3D */ + [[nodiscard]] constexpr Tensor3D operator+(const AxiSymTensor3D &A, const Tensor3D &T) noexcept { return T + A; } - /** AxisSymTensor + SymTensor3D */ - [[nodiscard]] constexpr SymTensor3D operator+(const AxisSymTensor &A, const SymTensor3D &S) noexcept + /** AxiSymTensor3D + SymTensor3D */ + [[nodiscard]] constexpr SymTensor3D operator+(const AxiSymTensor3D &A, const SymTensor3D &S) noexcept { return S + A; } - /** AxisSymTensor + SkewSymTensor3D */ - [[nodiscard]] constexpr Tensor3D operator+(const AxisSymTensor &A, const SkewSymTensor3D &W) noexcept + /** AxiSymTensor3D + SkewSymTensor3D */ + [[nodiscard]] constexpr Tensor3D operator+(const AxiSymTensor3D &A, const SkewSymTensor3D &W) noexcept { Tensor3D res; - res.xx() = A.xx(); res.xy() = A.xy() + W.xy(); res.xz() = W.xz(); - res.yx() = A.xy() - W.xy(); res.yy() = A.yy(); res.yz() = W.yz(); - res.zx() = - W.xz(); res.zy() = - W.yz(); res.zz() = A.zz(); + res.data[Tensor3D::idx_xx] = A.data[AxiSymTensor3D::idx_xx]; + res.data[Tensor3D::idx_xy] = A.data[AxiSymTensor3D::idx_xy] + W.data[SkewSymTensor3D::idx_xy]; + res.data[Tensor3D::idx_xz] = W.data[SkewSymTensor3D::idx_xz]; + res.data[Tensor3D::idx_yx] = A.data[AxiSymTensor3D::idx_xy] - W.data[SkewSymTensor3D::idx_xy]; + res.data[Tensor3D::idx_yy] = A.data[AxiSymTensor3D::idx_yy]; + res.data[Tensor3D::idx_yz] = W.data[SkewSymTensor3D::idx_yz]; + res.data[Tensor3D::idx_zx] = - W.data[SkewSymTensor3D::idx_xz]; + res.data[Tensor3D::idx_zy] = - W.data[SkewSymTensor3D::idx_yz]; + res.data[Tensor3D::idx_zz] = A.data[AxiSymTensor3D::idx_zz]; return res; } /** SkewSymTensor3D + Tensor3D */ - [[nodiscard]] constexpr Tensor3D operator+(const SkewSymTensor3D &S, const Tensor3D &T) noexcept + [[nodiscard]] constexpr Tensor3D operator+(const SkewSymTensor3D &W, const Tensor3D &T) noexcept { - return T + S; + return T + W; } /** SkewSymTensor3D + SymTensor3D */ @@ -1552,8 +1549,8 @@ namespace cfem return S + W; } - /** SkewSymTensor3D + AxisSymTensor */ - [[nodiscard]] constexpr Tensor3D operator+(const SkewSymTensor3D &W, const AxisSymTensor &A) noexcept + /** SkewSymTensor3D + AxiSymTensor3D */ + [[nodiscard]] constexpr Tensor3D operator+(const SkewSymTensor3D &W, const AxiSymTensor3D &A) noexcept { return A + W; } @@ -1570,29 +1567,39 @@ namespace cfem [[nodiscard]] constexpr Tensor3D operator-(const Tensor3D &T, const SymTensor3D &S) noexcept { Tensor3D res = T; - res.xx() -= S.xx(); res.xy() -= S.xy(); res.xz() -= S.xz(); - res.yx() -= S.xy(); res.yy() -= S.yy(); res.yz() -= S.yz(); - res.zx() -= S.xz(); res.zy() -= S.yz(); res.zz() -= S.zz(); + res.data[Tensor3D::idx_xx] -= S.data[SymTensor3D::idx_xx]; + res.data[Tensor3D::idx_xy] -= S.data[SymTensor3D::idx_xy]; + res.data[Tensor3D::idx_xz] -= S.data[SymTensor3D::idx_xz]; + + res.data[Tensor3D::idx_yx] -= S.data[SymTensor3D::idx_xy]; + res.data[Tensor3D::idx_yy] -= S.data[SymTensor3D::idx_yy]; + res.data[Tensor3D::idx_yz] -= S.data[SymTensor3D::idx_yz]; + + res.data[Tensor3D::idx_zx] -= S.data[SymTensor3D::idx_xz]; + res.data[Tensor3D::idx_zy] -= S.data[SymTensor3D::idx_yz]; + res.data[Tensor3D::idx_zz] -= S.data[SymTensor3D::idx_zz]; return res; } - /** Tensor3D - AxisSymTensor */ - [[nodiscard]] constexpr Tensor3D operator-(const Tensor3D &T, const AxisSymTensor &S) noexcept + /** Tensor3D - AxiSymTensor3D */ + [[nodiscard]] constexpr Tensor3D operator-(const Tensor3D &T, const AxiSymTensor3D &A) noexcept { Tensor3D res = T; - res.xx() -= S.xx(); res.xy() -= S.xy(); - res.yx() -= S.xy(); res.yy() -= S.yy(); - res.zz() -= S.zz(); + res.data[Tensor3D::idx_xx] -= A.data[AxiSymTensor3D::idx_xx]; + res.data[Tensor3D::idx_xy] -= A.data[AxiSymTensor3D::idx_xy]; + res.data[Tensor3D::idx_yx] -= A.data[AxiSymTensor3D::idx_xy]; + res.data[Tensor3D::idx_yy] -= A.data[AxiSymTensor3D::idx_yy]; + res.data[Tensor3D::idx_zz] -= A.data[AxiSymTensor3D::idx_zz]; return res; } /** Tensor3D - SkewSymTensor3D */ - [[nodiscard]] constexpr Tensor3D operator-(const Tensor3D &T, const SkewSymTensor3D &S) noexcept + [[nodiscard]] constexpr Tensor3D operator-(const Tensor3D &T, const SkewSymTensor3D &W) noexcept { Tensor3D res = T; - res.xy() -= S.xy(); res.xz() -= S.xz(); - res.yx() += S.xy(); res.yz() -= S.yz(); - res.zx() += S.xz(); res.zy() += S.yz(); + res.data[Tensor3D::idx_xy] -= W.data[SkewSymTensor3D::idx_xy]; res.data[Tensor3D::idx_xz] -= W.data[SkewSymTensor3D::idx_xz]; + res.data[Tensor3D::idx_yx] += W.data[SkewSymTensor3D::idx_xy]; res.data[Tensor3D::idx_yz] -= W.data[SkewSymTensor3D::idx_yz]; + res.data[Tensor3D::idx_zx] += W.data[SkewSymTensor3D::idx_xz]; res.data[Tensor3D::idx_zy] += W.data[SkewSymTensor3D::idx_yz]; return res; } @@ -1601,9 +1608,17 @@ namespace cfem [[nodiscard]] constexpr Tensor3D operator-(const SymTensor3D &S, const Tensor3D &T) noexcept { Tensor3D res; - res.xx() = S.xx() - T.xx(); res.xy() = S.xy() - T.xy(); res.xz() = S.xz() - T.xz(); - res.yx() = S.xy() - T.yx(); res.yy() = S.yy() - T.yy(); res.yz() = S.yz() - T.yz(); - res.zx() = S.xz() - T.zx(); res.zy() = S.yz() - T.zy(); res.zz() = S.zz() - T.zz(); + res.data[Tensor3D::idx_xx] = S.data[SymTensor3D::idx_xx] - T.data[Tensor3D::idx_xx]; + res.data[Tensor3D::idx_xy] = S.data[SymTensor3D::idx_xy] - T.data[Tensor3D::idx_xy]; + res.data[Tensor3D::idx_xz] = S.data[SymTensor3D::idx_xz] - T.data[Tensor3D::idx_xz]; + + res.data[Tensor3D::idx_yx] = S.data[SymTensor3D::idx_xy] - T.data[Tensor3D::idx_yx]; + res.data[Tensor3D::idx_yy] = S.data[SymTensor3D::idx_yy] - T.data[Tensor3D::idx_yy]; + res.data[Tensor3D::idx_yz] = S.data[SymTensor3D::idx_yz] - T.data[Tensor3D::idx_yz]; + + res.data[Tensor3D::idx_zx] = S.data[SymTensor3D::idx_xz] - T.data[Tensor3D::idx_zx]; + res.data[Tensor3D::idx_zy] = S.data[SymTensor3D::idx_yz] - T.data[Tensor3D::idx_zy]; + res.data[Tensor3D::idx_zz] = S.data[SymTensor3D::idx_zz] - T.data[Tensor3D::idx_zz]; return res; } @@ -1611,49 +1626,85 @@ namespace cfem [[nodiscard]] constexpr Tensor3D operator-(const SymTensor3D &S, const SkewSymTensor3D &W) noexcept { Tensor3D res; - res.xx() = S.xx(); res.xy() = S.xy() - W.xy(); res.xz() = S.xz() - W.xz(); - res.yx() = S.xy() + W.xy(); res.yy() = S.yy(); res.yz() = S.yz() - W.yz(); - res.zx() = S.xz() + W.xz(); res.zy() = S.yz() + W.yz(); res.zz() = S.zz(); + res.data[Tensor3D::idx_xx] = S.data[SymTensor3D::idx_xx]; + res.data[Tensor3D::idx_xy] = S.data[SymTensor3D::idx_xy] - W.data[SkewSymTensor3D::idx_xy]; + res.data[Tensor3D::idx_xz] = S.data[SymTensor3D::idx_xz] - W.data[SkewSymTensor3D::idx_xz]; + + res.data[Tensor3D::idx_yx] = S.data[SymTensor3D::idx_xy] + W.data[SkewSymTensor3D::idx_xy]; + res.data[Tensor3D::idx_yy] = S.data[SymTensor3D::idx_yy]; + res.data[Tensor3D::idx_yz] = S.data[SymTensor3D::idx_yz] - W.data[SkewSymTensor3D::idx_yz]; + + res.data[Tensor3D::idx_zx] = S.data[SymTensor3D::idx_xz] + W.data[SkewSymTensor3D::idx_xz]; + res.data[Tensor3D::idx_zy] = S.data[SymTensor3D::idx_yz] + W.data[SkewSymTensor3D::idx_yz]; + res.data[Tensor3D::idx_zz] = S.data[SymTensor3D::idx_zz]; return res; } - /** SymTensor3D - AxisSymTensor */ - [[nodiscard]] constexpr SymTensor3D operator-(const SymTensor3D &S, const AxisSymTensor &A) noexcept + /** SymTensor3D - AxiSymTensor3D */ + [[nodiscard]] constexpr SymTensor3D operator-(const SymTensor3D &S, const AxiSymTensor3D &A) noexcept { - return { S.xx() - A.xx(), S.yy() - A.yy(), S.zz() - A.zz(), S.yz(), S.xz(), S.xy() - A.xy() }; + return { S.data[SymTensor3D::idx_xx] - A.data[AxiSymTensor3D::idx_xx], + S.data[SymTensor3D::idx_yy] - A.data[AxiSymTensor3D::idx_yy], + S.data[SymTensor3D::idx_zz] - A.data[AxiSymTensor3D::idx_zz], + S.data[SymTensor3D::idx_yz], + S.data[SymTensor3D::idx_xz], + S.data[SymTensor3D::idx_xy] - A.data[AxiSymTensor3D::idx_xy] }; } - /** AxisSymTensor - Tensor3D */ - [[nodiscard]] constexpr Tensor3D operator-(const AxisSymTensor &S, const Tensor3D &T) noexcept + /** AxiSymTensor3D - Tensor3D */ + [[nodiscard]] constexpr Tensor3D operator-(const AxiSymTensor3D &A, const Tensor3D &T) noexcept { Tensor3D res; - res.xx() = S.xx() - T.xx(); res.xy() = S.xy() - T.xy(); res.xz() = - T.xz(); - res.yx() = S.xy() - T.yx(); res.yy() = S.yy() - T.yy(); res.yz() = - T.yz(); - res.zx() = - T.zx(); res.zy() = - T.zy(); res.zz() = S.zz() - T.zz(); + res.data[Tensor3D::idx_xx] = A.data[AxiSymTensor3D::idx_xx] - T.data[Tensor3D::idx_xx]; + res.data[Tensor3D::idx_xy] = A.data[AxiSymTensor3D::idx_xy] - T.data[Tensor3D::idx_xy]; + res.data[Tensor3D::idx_xz] = - T.data[Tensor3D::idx_xz]; + res.data[Tensor3D::idx_yx] = A.data[AxiSymTensor3D::idx_xy] - T.data[Tensor3D::idx_yx]; + res.data[Tensor3D::idx_yy] = A.data[AxiSymTensor3D::idx_yy] - T.data[Tensor3D::idx_yy]; + res.data[Tensor3D::idx_yz] = - T.data[Tensor3D::idx_yz]; + res.data[Tensor3D::idx_zx] = - T.data[Tensor3D::idx_zx]; + res.data[Tensor3D::idx_zy] = - T.data[Tensor3D::idx_zy]; + res.data[Tensor3D::idx_zz] = A.data[AxiSymTensor3D::idx_zz] - T.data[Tensor3D::idx_zz]; return res; } - /** AxisSymTensor - SymTensor3D */ - [[nodiscard]] constexpr SymTensor3D operator-(const AxisSymTensor &A, const SymTensor3D &S) noexcept + /** AxiSymTensor3D - SymTensor3D */ + [[nodiscard]] constexpr SymTensor3D operator-(const AxiSymTensor3D &A, const SymTensor3D &S) noexcept { - return { A.xx() - S.xx(), A.yy() - S.yy(), A.zz() - S.zz(), -S.yz(), -S.xz(), A.xy() - S.xy() }; + return { A.data[AxiSymTensor3D::idx_xx] - S.data[SymTensor3D::idx_xx], + A.data[AxiSymTensor3D::idx_yy] - S.data[SymTensor3D::idx_yy], + A.data[AxiSymTensor3D::idx_zz] - S.data[SymTensor3D::idx_zz], + - S.data[SymTensor3D::idx_yz], + - S.data[SymTensor3D::idx_xz], + A.data[AxiSymTensor3D::idx_xy] - S.data[SymTensor3D::idx_xy] }; } - /** AxisSymTensor - SkewSymTensor3D */ - [[nodiscard]] constexpr Tensor3D operator-(const AxisSymTensor &A, const SkewSymTensor3D &W) noexcept + /** AxiSymTensor3D - SkewSymTensor3D */ + [[nodiscard]] constexpr Tensor3D operator-(const AxiSymTensor3D &A, const SkewSymTensor3D &W) noexcept { - return { A.xx(), A.xy() - W.xy(), -W.xz(), - A.xy() + W.xy(), A.yy(), -W.yz(), - W.xz(), W.yz(), A.zz() }; + return { A.data[AxiSymTensor3D::idx_xx], + A.data[AxiSymTensor3D::idx_xy] - W.data[SkewSymTensor3D::idx_xy], + - W.data[SkewSymTensor3D::idx_xz], + A.data[AxiSymTensor3D::idx_xy] + W.data[SkewSymTensor3D::idx_xy], + A.data[AxiSymTensor3D::idx_yy], + - W.data[SkewSymTensor3D::idx_yz], + W.data[SkewSymTensor3D::idx_xz], + W.data[SkewSymTensor3D::idx_yz], + A.data[AxiSymTensor3D::idx_zz] }; } /** SkewSymTensor3D - Tensor3D */ - [[nodiscard]] constexpr Tensor3D operator-(const SkewSymTensor3D &S, const Tensor3D &T) noexcept + [[nodiscard]] constexpr Tensor3D operator-(const SkewSymTensor3D &W, const Tensor3D &T) noexcept { Tensor3D res; - res.xx() = - T.xx(); res.xy() = S.xy() - T.xy(); res.xz() = S.xz() - T.xz(); - res.yx() = -S.xy() - T.yx(); res.yy() = - T.yy(); res.yz() = S.yz() - T.yz(); - res.zx() = -S.xz() - T.zx(); res.zy() =-S.yz() - T.zy(); res.zz() = - T.zz(); + res.data[Tensor3D::idx_xx] = - T.data[Tensor3D::idx_xx]; + res.data[Tensor3D::idx_xy] = W.data[SkewSymTensor3D::idx_xy] - T.data[Tensor3D::idx_xy]; + res.data[Tensor3D::idx_xz] = W.data[SkewSymTensor3D::idx_xz] - T.data[Tensor3D::idx_xz]; + res.data[Tensor3D::idx_yx] =-W.data[SkewSymTensor3D::idx_xy] - T.data[Tensor3D::idx_yx]; + res.data[Tensor3D::idx_yy] = - T.data[Tensor3D::idx_yy]; + res.data[Tensor3D::idx_yz] = W.data[SkewSymTensor3D::idx_yz] - T.data[Tensor3D::idx_yz]; + res.data[Tensor3D::idx_zx] =-W.data[SkewSymTensor3D::idx_xz] - T.data[Tensor3D::idx_zx]; + res.data[Tensor3D::idx_zy] =-W.data[SkewSymTensor3D::idx_yz] - T.data[Tensor3D::idx_zy]; + res.data[Tensor3D::idx_zz] = - T.data[Tensor3D::idx_zz]; return res; } @@ -1661,55 +1712,54 @@ namespace cfem [[nodiscard]] constexpr Tensor3D operator-(const SkewSymTensor3D &W, const SymTensor3D &S) noexcept { Tensor3D res; - res.xx() = -S.xx(); res.xy() = W.xy() - S.xy(); res.xz() = W.xz() - S.xz(); - res.yx() = -W.xy() - S.xy(); res.yy() = -S.yy(); res.yz() = W.yz() - S.yz(); - res.zx() = -W.xz() - S.xz(); res.zy() = -W.yz() - S.yz(); res.zz() = -S.zz(); + res.data[Tensor3D::idx_xx] = - S.data[SymTensor3D::idx_xx]; + res.data[Tensor3D::idx_xy] = W.data[SkewSymTensor3D::idx_xy] - S.data[SymTensor3D::idx_xy]; + res.data[Tensor3D::idx_xz] = W.data[SkewSymTensor3D::idx_xz] - S.data[SymTensor3D::idx_xz]; + res.data[Tensor3D::idx_yx] = -W.data[SkewSymTensor3D::idx_xy] - S.data[SymTensor3D::idx_xy]; + res.data[Tensor3D::idx_yy] = - S.data[SymTensor3D::idx_yy]; + res.data[Tensor3D::idx_yz] = W.data[SkewSymTensor3D::idx_yz] - S.data[SymTensor3D::idx_yz]; + res.data[Tensor3D::idx_zx] = -W.data[SkewSymTensor3D::idx_xz] - S.data[SymTensor3D::idx_xz]; + res.data[Tensor3D::idx_zy] = -W.data[SkewSymTensor3D::idx_yz] - S.data[SymTensor3D::idx_yz]; + res.data[Tensor3D::idx_zz] = - S.data[SymTensor3D::idx_zz]; return res; } - /** SkewSymTensor3D - AxisSymTensor */ - [[nodiscard]] constexpr Tensor3D operator-(const SkewSymTensor3D &W, const AxisSymTensor &A) noexcept + /** SkewSymTensor3D - AxiSymTensor3D */ + [[nodiscard]] constexpr Tensor3D operator-(const SkewSymTensor3D &W, const AxiSymTensor3D &A) noexcept { Tensor3D res; - res.xx() = - A.xx(); res.xy() = W.xy() - A.xy(); res.xz() = W.xz(); - res.yx() = -W.xy() - A.xy(); res.yy() = - A.yy(); res.yz() = W.yz(); - res.zx() = -W.xz(); res.zy() = -W.yz(); res.zz() = - A.zz(); + res.data[Tensor3D::idx_xx] = - A.data[AxiSymTensor3D::idx_xx]; + res.data[Tensor3D::idx_xy] = W.data[SkewSymTensor3D::idx_xy] - A.data[AxiSymTensor3D::idx_xy]; + res.data[Tensor3D::idx_xz] = W.data[SkewSymTensor3D::idx_xz]; + res.data[Tensor3D::idx_yx] = -W.data[SkewSymTensor3D::idx_xy] - A.data[AxiSymTensor3D::idx_xy]; + res.data[Tensor3D::idx_yy] = - A.data[AxiSymTensor3D::idx_yy]; + res.data[Tensor3D::idx_yz] = W.data[SkewSymTensor3D::idx_yz]; + res.data[Tensor3D::idx_zx] = -W.data[SkewSymTensor3D::idx_xz]; + res.data[Tensor3D::idx_zy] = -W.data[SkewSymTensor3D::idx_yz]; + res.data[Tensor3D::idx_zz] = - A.data[AxiSymTensor3D::idx_zz]; return res; } /** @} */ [[nodiscard]] constexpr CfemReal det(const SymTensor3D &S) noexcept { - return S.xx() * (S.yy() * S.zz() - S.yz() * S.yz()) - - S.xy() * (S.xy() * S.zz() - S.yz() * S.xz()) - + S.xz() * (S.xy() * S.yz() - S.yy() * S.xz()); + return S.data[SymTensor3D::idx_xx] * (S.data[SymTensor3D::idx_yy] * S.data[SymTensor3D::idx_zz] - S.data[SymTensor3D::idx_yz] * S.data[SymTensor3D::idx_yz]) + - S.data[SymTensor3D::idx_xy] * (S.data[SymTensor3D::idx_xy] * S.data[SymTensor3D::idx_zz] - S.data[SymTensor3D::idx_yz] * S.data[SymTensor3D::idx_xz]) + + S.data[SymTensor3D::idx_xz] * (S.data[SymTensor3D::idx_xy] * S.data[SymTensor3D::idx_yz] - S.data[SymTensor3D::idx_yy] * S.data[SymTensor3D::idx_xz]); } - [[nodiscard]] constexpr CfemReal det(const SkewSymTensor3D &S) noexcept + [[nodiscard]] constexpr CfemReal det(const AxiSymTensor3D &A) noexcept { - return 0.0; + return ( A.data[AxiSymTensor3D::idx_xx] * A.data[AxiSymTensor3D::idx_yy] + - A.data[AxiSymTensor3D::idx_xy] * A.data[AxiSymTensor3D::idx_xy]) * A.data[AxiSymTensor3D::idx_zz]; } - // [[nodiscard]] inline SymTensor3D inverse(const SymTensor3D &S, CfemReal determinant) noexcept - // { - // CFEM_ASSERT(std::abs(determinant) < cfem::ZERO_TOLERANCE), "Singular 3x3 matrix/Jacobian detected."; - // // if (std::abs(determinant) < cfem::ZERO_TOLERANCE) - // // CFEM_ERROR << "Singular 3x3 matrix/Jacobian detected."; + [[nodiscard]] constexpr CfemReal det(const SkewSymTensor3D &W) noexcept + { + return 0.0; + } - // // En pratique, tu peux ajouter ici un assert(std::abs(det) > 1e-14); - // const CfemReal inv_det = 1.0 / determinant; - - // SymTensor3D res; - // res.xx() = (S.yy() * S.zz() - S.yz() * S.yz()) * inv_det; - // res.yy() = (S.xx() * S.zz() - S.xz() * S.xz()) * inv_det; - // res.zz() = (S.xx() * S.yy() - S.xy() * S.xy()) * inv_det; - // res.xy() = (S.xz() * S.yz() - S.xy() * S.zz()) * inv_det; - // res.xz() = (S.xy() * S.yz() - S.xx() * S.xz()) * inv_det; - // res.yz() = (S.xy() * S.xz() - S.yy() * S.xz()) * inv_det; - // return res; - // } - - [[nodiscard]] inline SymTensor3D inverse(const SymTensor3D &S, CfemReal determinant) + [[nodiscard]] constexpr SymTensor3D inverse(const SymTensor3D &S, CfemReal determinant) { CFEM_ASSERT(std::abs(determinant) > cfem::ZERO_TOLERANCE, "Singular 3x3 matrix/Jacobian detected."); @@ -1730,11 +1780,30 @@ namespace cfem return inverse(S, det(S) ); } + [[nodiscard]] constexpr AxiSymTensor3D inverse(const AxiSymTensor3D &S, CfemReal determinant) + { + CFEM_ASSERT(std::abs(determinant) > cfem::ZERO_TOLERANCE, "Singular 3x3 matrix/Jacobian detected."); + + const CfemReal inv_det = 1.0 / determinant; + + AxiSymTensor3D res; + res.data[AxiSymTensor3D::idx_xx] = ( S.data[AxiSymTensor3D::idx_yy] * S.data[AxiSymTensor3D::idx_zz] ) * inv_det; + res.data[AxiSymTensor3D::idx_yy] = ( S.data[AxiSymTensor3D::idx_xx] * S.data[AxiSymTensor3D::idx_zz] ) * inv_det; + res.data[AxiSymTensor3D::idx_zz] = ( S.data[AxiSymTensor3D::idx_xx] * S.data[AxiSymTensor3D::idx_yy] - S.data[AxiSymTensor3D::idx_xy] * S.data[AxiSymTensor3D::idx_xy] ) * inv_det; + res.data[AxiSymTensor3D::idx_xy] =-( S.data[AxiSymTensor3D::idx_xy] * S.data[AxiSymTensor3D::idx_zz] ) * inv_det; + return res; + } + + [[nodiscard]] inline AxiSymTensor3D inverse(const AxiSymTensor3D &S) + { + return inverse(S, det(S) ); + } + /** * @brief Tries to inverse a skew-symmetric second order tensor (This operation is impossible). * @throw std::runtime_error. */ - [[nodiscard]] inline SkewSymTensor3D inverse(const SkewSymTensor3D &S) + [[nodiscard]] inline SkewSymTensor3D inverse(const SkewSymTensor3D &W) { CFEM_THROW("3x3 skew-symmetric tensor is not invertible since det = 0."); return SkewSymTensor3D{}; @@ -1749,11 +1818,11 @@ namespace cfem * @details * Uses the closed-form Cardano method for symmetric tensors. */ - [[nodiscard]] inline FixedVector eigenvalues(const SymTensor3D& S) noexcept + [[nodiscard]] inline FixedVector eigenvalues(const SymTensor3D &S) noexcept { - const CfemReal I1 = S.xx() + S.yy() + S.zz(); - const CfemReal I2 = S.xx()*S.yy() + S.yy()*S.zz() + S.xx()*S.zz() - - S.xy()*S.xy() - S.yz()*S.yz() - S.xz()*S.xz(); + const CfemReal I1 = S.data[SymTensor3D::idx_xx] + S.data[SymTensor3D::idx_yy] + S.data[SymTensor3D::idx_zz]; + const CfemReal I2 = S.data[SymTensor3D::idx_xx]*S.data[SymTensor3D::idx_yy] + S.data[SymTensor3D::idx_yy]*S.data[SymTensor3D::idx_zz] + S.data[SymTensor3D::idx_xx]*S.data[SymTensor3D::idx_zz] + - S.data[SymTensor3D::idx_xy]*S.data[SymTensor3D::idx_xy] - S.data[SymTensor3D::idx_yz]*S.data[SymTensor3D::idx_yz] - S.data[SymTensor3D::idx_xz]*S.data[SymTensor3D::idx_xz]; const CfemReal I3 = det(S); const CfemReal p = I2 - I1 * I1 / 3.0; diff --git a/libs/utils/include/Vector2D.h b/libs/utils/include/Vector2D.h index f56fe1f..72ff297 100644 --- a/libs/utils/include/Vector2D.h +++ b/libs/utils/include/Vector2D.h @@ -100,10 +100,9 @@ namespace cfem * @note If the scalar is near zero (below cfem::ZERO_TOLERANCE), a CFEM_ERROR is logged, * but the division proceeds (which may result in Inf or NaN values). */ - [[nodiscard]] inline Vector2D operator/(CfemReal scalar) const noexcept + [[nodiscard]] inline Vector2D operator/(CfemReal scalar) const { - if (std::abs(scalar) < cfem::ZERO_TOLERANCE) - CFEM_ERROR << "Division by zero in Vector2D operator/"; + CFEM_ASSERT(std::abs(scalar) > ZERO_TOLERANCE, "Division by zero in Vector2D::operator/"); CfemReal inv = 1.0 / scalar; return {x * inv, y * inv}; @@ -144,10 +143,10 @@ namespace cfem * @note If the scalar is near zero (below cfem::ZERO_TOLERANCE), a CFEM_ERROR is logged, * but the division proceeds (which may result in Inf or NaN values). */ - inline Vector2D &operator/=(CfemReal scalar) noexcept + constexpr Vector2D &operator/=(CfemReal scalar) { - if (std::abs(scalar) < cfem::ZERO_TOLERANCE) - CFEM_ERROR << "Division by zero in Vector2D operator/="; + CFEM_ASSERT(std::abs(scalar) > ZERO_TOLERANCE, "Division by zero in Vect::operator/="); + CfemReal inv = 1.0 / scalar; x *= inv; y *= inv; @@ -192,7 +191,7 @@ namespace cfem /** * @brief Read-only access by index (0=x, 1=y, 2=z). */ - [[nodiscard]] constexpr CfemReal operator[](CfemInt i) const { + [[nodiscard]] inline CfemReal operator[](CfemInt i) const { CFEM_ASSERT(0 <= i && i < 2, "Index out of bound in Vector2D::operator[] const."); return (i == 0) ? x : y; } @@ -200,7 +199,7 @@ namespace cfem /** * @brief Read-write access by index (0=x, 1=y, 2=z). */ - [[nodiscard]] constexpr CfemReal& operator[](CfemInt i) { + [[nodiscard]] inline CfemReal& operator[](CfemInt i) { CFEM_ASSERT(0 <= i && i < 2, "Index out of bound in Vector2D::operator[]."); return (i == 0) ? x : y; } diff --git a/libs/utils/include/Vector3D.h b/libs/utils/include/Vector3D.h index a37bee4..a770622 100644 --- a/libs/utils/include/Vector3D.h +++ b/libs/utils/include/Vector3D.h @@ -97,10 +97,9 @@ namespace cfem * @note If the scalar is near zero (below cfem::ZERO_TOLERANCE), a CFEM_ERROR is logged, * but the division proceeds (which may result in Inf or NaN values). */ - [[nodiscard]] inline Vector3D operator/(CfemReal scalar) const noexcept + [[nodiscard]] inline Vector3D operator/(CfemReal scalar) const { - if (std::abs(scalar) < cfem::ZERO_TOLERANCE) - CFEM_ERROR << "Division by zero in Vector3D operator/"; + CFEM_ASSERT(std::abs(scalar) > ZERO_TOLERANCE, "Division by zero in Vector3D::operator/"); CfemReal inv = 1.0 / scalar; return {x * inv, y * inv, z * inv}; @@ -149,10 +148,9 @@ namespace cfem * @note If the scalar is near zero (below cfem::ZERO_TOLERANCE), a CFEM_ERROR is logged, * but the division proceeds (which may result in Inf or NaN values). */ - inline Vector3D &operator/=(CfemReal scalar) noexcept + inline Vector3D &operator/=(CfemReal scalar) { - if (std::abs(scalar) < cfem::ZERO_TOLERANCE) - CFEM_ERROR << "Division by zero in Vector3D operator/="; + CFEM_ASSERT(std::abs(scalar) > ZERO_TOLERANCE, "Division by zero in Vector3D::operator/="); CfemReal inv = 1.0 / scalar; x *= inv; diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index a3c1c97..df7dea2 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -11,6 +11,7 @@ add_executable(unit_tests fem/test_fefield.cpp mesh/test_mesh.cpp mesh/test_gmsh_parser.cpp + utils/test_dynamic_matrix.cpp utils/test_dynamic_vector.cpp utils/test_vector3d.cpp utils/test_fixed_vector.cpp diff --git a/tests/utils/tensors/test_tensor_addition.cpp b/tests/utils/tensors/test_tensor_addition.cpp index f03e7df..53a8e54 100644 --- a/tests/utils/tensors/test_tensor_addition.cpp +++ b/tests/utils/tensors/test_tensor_addition.cpp @@ -39,7 +39,7 @@ class TensorOperatorsTest : public ::testing::Test { protected: Tensor3D T; SymTensor3D S; - AxisSymTensor A; + AxiSymTensor3D A; SkewSymTensor3D W; void SetUp() override { @@ -64,7 +64,7 @@ TEST_F(TensorOperatorsTest, Tensor3D_plus_SymTensor3D) { ExpectTensorEqMat(res, expected); } -TEST_F(TensorOperatorsTest, Tensor3D_plus_AxisSymTensor) { +TEST_F(TensorOperatorsTest, Tensor3D_plus_AxiSymTensor3D) { auto res = T + A; Mat3x3 expected = ReferenceAdd(T.toMat3x3(), A.toMat3x3()); ExpectTensorEqMat(res, expected); @@ -76,19 +76,19 @@ TEST_F(TensorOperatorsTest, Tensor3D_plus_SkewSymTensor3D) { ExpectTensorEqMat(res, expected); } -TEST_F(TensorOperatorsTest, AxisSymTensor_plus_Tensor3D) { +TEST_F(TensorOperatorsTest, AxiSymTensor3D_plus_Tensor3D) { auto res = A + T; Mat3x3 expected = ReferenceAdd(A.toMat3x3(), T.toMat3x3()); ExpectTensorEqMat(res, expected); } -TEST_F(TensorOperatorsTest, AxisSymTensor_plus_SymTensor3D) { +TEST_F(TensorOperatorsTest, AxiSymTensor3D_plus_SymTensor3D) { auto res = A + S; Mat3x3 expected = ReferenceAdd(A.toMat3x3(), S.toMat3x3()); ExpectTensorEqMat(res, expected); } -TEST_F(TensorOperatorsTest, AxisSymTensor_plus_SkewSymTensor3D) { +TEST_F(TensorOperatorsTest, AxiSymTensor3D_plus_SkewSymTensor3D) { auto res = A + W; Mat3x3 expected = ReferenceAdd(A.toMat3x3(), W.toMat3x3()); ExpectTensorEqMat(res, expected); @@ -106,7 +106,7 @@ TEST_F(TensorOperatorsTest, SymTensor3D_plus_SkewSymTensor3D) { ExpectTensorEqMat(res, expected); } -TEST_F(TensorOperatorsTest, SymTensor3D_plus_AxisSymTensor) { +TEST_F(TensorOperatorsTest, SymTensor3D_plus_AxiSymTensor3D) { auto res = S + A; Mat3x3 expected = ReferenceAdd(S.toMat3x3(), A.toMat3x3()); ExpectTensorEqMat(res, expected); @@ -124,7 +124,7 @@ TEST_F(TensorOperatorsTest, SkewSymTensor3D_plus_SymTensor3D) { ExpectTensorEqMat(res, expected); } -TEST_F(TensorOperatorsTest, SkewSymTensor3D_plus_AxisSymTensor) { +TEST_F(TensorOperatorsTest, SkewSymTensor3D_plus_AxiSymTensor3D) { auto res = W + A; Mat3x3 expected = ReferenceAdd(W.toMat3x3(), A.toMat3x3()); ExpectTensorEqMat(res, expected); diff --git a/tests/utils/tensors/test_tensor_product.cpp b/tests/utils/tensors/test_tensor_product.cpp index 50beb99..36be064 100644 --- a/tests/utils/tensors/test_tensor_product.cpp +++ b/tests/utils/tensors/test_tensor_product.cpp @@ -41,7 +41,7 @@ class TensorOperatorsTest : public ::testing::Test { protected: Tensor3D T; SymTensor3D S; - AxisSymTensor A; + AxiSymTensor3D A; SkewSymTensor3D W; void SetUp() override { @@ -66,7 +66,7 @@ TEST_F(TensorOperatorsTest, Tensor3D_x_SymTensor3D) { ExpectTensorEqMat(res, expected); } -TEST_F(TensorOperatorsTest, Tensor3D_x_AxisSymTensor) { +TEST_F(TensorOperatorsTest, Tensor3D_x_AxiSymTensor3D) { Tensor3D res = T * A; Mat3x3 expected = ReferenceMultiply(T.toMat3x3(), A.toMat3x3()); ExpectTensorEqMat(res, expected); @@ -78,19 +78,19 @@ TEST_F(TensorOperatorsTest, Tensor3D_x_SkewSymTensor3D) { ExpectTensorEqMat(res, expected); } -TEST_F(TensorOperatorsTest, AxisSymTensor_x_Tensor3D) { +TEST_F(TensorOperatorsTest, AxiSymTensor3D_x_Tensor3D) { Tensor3D res = A * T; Mat3x3 expected = ReferenceMultiply(A.toMat3x3(), T.toMat3x3()); ExpectTensorEqMat(res, expected); } -TEST_F(TensorOperatorsTest, AxisSymTensor_x_SymTensor3D) { +TEST_F(TensorOperatorsTest, AxiSymTensor3D_x_SymTensor3D) { Tensor3D res = A * S; Mat3x3 expected = ReferenceMultiply(A.toMat3x3(), S.toMat3x3()); ExpectTensorEqMat(res, expected); } -TEST_F(TensorOperatorsTest, AxisSymTensor_x_SkewSymTensor3D) { +TEST_F(TensorOperatorsTest, AxiSymTensor3D_x_SkewSymTensor3D) { Tensor3D res = A * W; Mat3x3 expected = ReferenceMultiply(A.toMat3x3(), W.toMat3x3()); ExpectTensorEqMat(res, expected); @@ -108,7 +108,7 @@ TEST_F(TensorOperatorsTest, SymTensor3D_x_SkewSymTensor3D) { ExpectTensorEqMat(res, expected); } -TEST_F(TensorOperatorsTest, SymTensor3D_x_AxisSymTensor) { +TEST_F(TensorOperatorsTest, SymTensor3D_x_AxiSymTensor3D) { Tensor3D res = S * A; Mat3x3 expected = ReferenceMultiply(S.toMat3x3(), A.toMat3x3()); ExpectTensorEqMat(res, expected); @@ -126,7 +126,7 @@ TEST_F(TensorOperatorsTest, SkewSymTensor3D_x_SymTensor3D) { ExpectTensorEqMat(res, expected); } -TEST_F(TensorOperatorsTest, SkewSymTensor3D_x_AxisSymTensor) { +TEST_F(TensorOperatorsTest, SkewSymTensor3D_x_AxiSymTensor3D) { auto res = W * A; Mat3x3 expected = ReferenceMultiply(W.toMat3x3(), A.toMat3x3()); ExpectTensorEqMat(res, expected); diff --git a/tests/utils/tensors/test_tensor_substraction.cpp b/tests/utils/tensors/test_tensor_substraction.cpp index 7e0f877..219f1f7 100644 --- a/tests/utils/tensors/test_tensor_substraction.cpp +++ b/tests/utils/tensors/test_tensor_substraction.cpp @@ -39,7 +39,7 @@ class TensorOperatorsTest : public ::testing::Test { protected: Tensor3D T; SymTensor3D S; - AxisSymTensor A; + AxiSymTensor3D A; SkewSymTensor3D W; void SetUp() override { @@ -64,7 +64,7 @@ TEST_F(TensorOperatorsTest, Tensor3D_sub_SymTensor3D) { ExpectTensorEqMat(res, expected); } -TEST_F(TensorOperatorsTest, Tensor3D_sub_AxisSymTensor) { +TEST_F(TensorOperatorsTest, Tensor3D_sub_AxiSymTensor3D) { auto res = T - A; Mat3x3 expected = ReferenceSub(T.toMat3x3(), A.toMat3x3()); ExpectTensorEqMat(res, expected); @@ -76,19 +76,19 @@ TEST_F(TensorOperatorsTest, Tensor3D_sub_SkewSymTensor3D) { ExpectTensorEqMat(res, expected); } -TEST_F(TensorOperatorsTest, AxisSymTensor_sub_Tensor3D) { +TEST_F(TensorOperatorsTest, AxiSymTensor3D_sub_Tensor3D) { auto res = A - T; Mat3x3 expected = ReferenceSub(A.toMat3x3(), T.toMat3x3()); ExpectTensorEqMat(res, expected); } -TEST_F(TensorOperatorsTest, AxisSymTensor_sub_SymTensor3D) { +TEST_F(TensorOperatorsTest, AxiSymTensor3D_sub_SymTensor3D) { auto res = A - S; Mat3x3 expected = ReferenceSub(A.toMat3x3(), S.toMat3x3()); ExpectTensorEqMat(res, expected); } -TEST_F(TensorOperatorsTest, AxisSymTensor_sub_SkewSymTensor3D) { +TEST_F(TensorOperatorsTest, AxiSymTensor3D_sub_SkewSymTensor3D) { auto res = A - W; Mat3x3 expected = ReferenceSub(A.toMat3x3(), W.toMat3x3()); ExpectTensorEqMat(res, expected); @@ -106,7 +106,7 @@ TEST_F(TensorOperatorsTest, SymTensor3D_sub_SkewSymTensor3D) { ExpectTensorEqMat(res, expected); } -TEST_F(TensorOperatorsTest, SymTensor3D_sub_AxisSymTensor) { +TEST_F(TensorOperatorsTest, SymTensor3D_sub_AxiSymTensor3D) { auto res = S - A; Mat3x3 expected = ReferenceSub(S.toMat3x3(), A.toMat3x3()); ExpectTensorEqMat(res, expected); @@ -124,7 +124,7 @@ TEST_F(TensorOperatorsTest, SkewSymTensor3D_sub_SymTensor3D) { ExpectTensorEqMat(res, expected); } -TEST_F(TensorOperatorsTest, SkewSymTensor3D_sub_AxisSymTensor) { +TEST_F(TensorOperatorsTest, SkewSymTensor3D_sub_AxiSymTensor3D) { auto res = W - A; Mat3x3 expected = ReferenceSub(W.toMat3x3(), A.toMat3x3()); ExpectTensorEqMat(res, expected); diff --git a/tests/utils/test_dynamic_matrix.cpp b/tests/utils/test_dynamic_matrix.cpp index b0981d2..fa84785 100644 --- a/tests/utils/test_dynamic_matrix.cpp +++ b/tests/utils/test_dynamic_matrix.cpp @@ -25,14 +25,14 @@ using namespace cfem; TEST(DynamicMatrixTest, DefaultConstructor) { DynamicMatrix M; - EXPECT_EQ(M.rows(), 0); - EXPECT_EQ(M.cols(), 0); + EXPECT_EQ(M.n_rows(), 0); + EXPECT_EQ(M.n_cols(), 0); } TEST(DynamicMatrixTest, SizeConstructor) { DynamicMatrix M(3, 4, 1.5); - EXPECT_EQ(M.rows(), 3); - EXPECT_EQ(M.cols(), 4); + EXPECT_EQ(M.n_rows(), 3); + EXPECT_EQ(M.n_cols(), 4); EXPECT_DOUBLE_EQ(M(0, 0), 1.5); EXPECT_DOUBLE_EQ(M(2, 3), 1.5); } @@ -51,8 +51,8 @@ TEST(DynamicMatrixTest, ConversionFromFixed) { FixedMatrix fixed = {10.0, 20.0, 30.0, 40.0}; DynamicMatrix dynamic(fixed); - ASSERT_EQ(dynamic.rows(), 2); - ASSERT_EQ(dynamic.cols(), 2); + ASSERT_EQ(dynamic.n_rows(), 2); + ASSERT_EQ(dynamic.n_cols(), 2); EXPECT_DOUBLE_EQ(dynamic(0, 0), 10.0); EXPECT_DOUBLE_EQ(dynamic(0, 1), 20.0); EXPECT_DOUBLE_EQ(dynamic(1, 0), 30.0); @@ -82,10 +82,10 @@ TEST(DynamicMatrixTest, ScalarOperations) { EXPECT_DOUBLE_EQ(M(1, 1), 6.0); DynamicMatrix div = M / 2.0; - EXPECT_DOUBLE_EQ(div(0, 0), 2.0); - EXPECT_DOUBLE_EQ(div(0, 1), 2.0); - EXPECT_DOUBLE_EQ(div(1, 0), 2.0); - EXPECT_DOUBLE_EQ(div(1, 1), 2.0); + EXPECT_DOUBLE_EQ(div(0, 0), 3.0); + EXPECT_DOUBLE_EQ(div(0, 1), 3.0); + EXPECT_DOUBLE_EQ(div(1, 0), 3.0); + EXPECT_DOUBLE_EQ(div(1, 1), 3.0); } // --- Tests Algèbre Linéaire --- @@ -124,8 +124,266 @@ TEST(DynamicMatrixTest, Transpose) { DynamicMatrix M(2, 3, {1, 2, 3, 4, 5, 6}); DynamicMatrix MT = M.transpose(); - ASSERT_EQ(MT.rows(), 3); - ASSERT_EQ(MT.cols(), 2); + ASSERT_EQ(MT.n_rows(), 3); + ASSERT_EQ(MT.n_cols(), 2); EXPECT_DOUBLE_EQ(MT(0, 0), 1.0); EXPECT_DOUBLE_EQ(MT(2, 1), 6.0); +} + +TEST(DynamicMatrixTest, SpanRowAccess) { + DynamicMatrix M(3, 3, {1, 2, 3, + 4, 5, 6, + 7, 8, 9}); + + // Extraction d'une ligne via std::span + auto row1 = M[1]; // Doit pointer sur {4, 5, 6} + ASSERT_EQ(row1.size(), 3); + EXPECT_DOUBLE_EQ(row1[0], 4.0); + EXPECT_DOUBLE_EQ(row1[2], 6.0); + + // Modification via le span (doit modifier la matrice) + row1[1] = 50.0; + EXPECT_DOUBLE_EQ(M(1, 1), 50.0); +} + +TEST(DynamicMatrixTest, MatrixViewAndSubView) { + // Matrice 4x4 + DynamicMatrix M(4, 4, { 1, 2, 3, 4, + 5, 6, 7, 8, + 9, 10, 11, 12, + 13, 14, 15, 16}); + + // Extraction d'un bloc 2x2 au centre (lignes 1-2, cols 1-2) + auto view = M.view(1, 1, 2, 2); + + ASSERT_EQ(view.n_rows(), 2); + ASSERT_EQ(view.n_cols(), 2); + EXPECT_EQ(view.stride(), 4); // Le stride doit rester celui de la matrice parent (4) + + // Vérification des valeurs du bloc central + EXPECT_DOUBLE_EQ(view(0, 0), 6.0); + EXPECT_DOUBLE_EQ(view(0, 1), 7.0); + EXPECT_DOUBLE_EQ(view(1, 0), 10.0); + EXPECT_DOUBLE_EQ(view(1, 1), 11.0); + + // Modification via la vue + view(0, 0) = -6.0; + EXPECT_DOUBLE_EQ(M(1, 1), -6.0); // La matrice d'origine doit être impactée +} + +TEST(DynamicMatrixTest, AdditionAndSubtraction) { + DynamicMatrix A(2, 2, {1.0, 2.0, 3.0, 4.0}); + DynamicMatrix B(2, 2, {4.0, 3.0, 2.0, 1.0}); + + DynamicMatrix sum = A + B; + EXPECT_DOUBLE_EQ(sum(0, 0), 5.0); + EXPECT_DOUBLE_EQ(sum(1, 1), 5.0); + + A -= B; // A devient {-3.0, -1.0, 1.0, 3.0} + EXPECT_DOUBLE_EQ(A(0, 0), -3.0); + EXPECT_DOUBLE_EQ(A(0, 1), -1.0); + EXPECT_DOUBLE_EQ(A(1, 0), 1.0); + EXPECT_DOUBLE_EQ(A(1, 1), 3.0); +} + +TEST(DynamicMatrixTest, CopyAndMoveSemantics) { + DynamicMatrix orig(2, 2, {1, 2, 3, 4}); + + // --- Copy Constructor --- + DynamicMatrix copy(orig); + copy(0, 0) = 99.0; + EXPECT_DOUBLE_EQ(orig(0, 0), 1.0); // Orig doit être inchangé (Deep Copy) + EXPECT_DOUBLE_EQ(copy(0, 0), 99.0); + + // --- Move Constructor --- + DynamicMatrix moved(std::move(orig)); + EXPECT_EQ(orig.n_rows(), 0); // L'original doit être vidé + EXPECT_EQ(orig.n_cols(), 0); + EXPECT_EQ(orig.size(), 0); + EXPECT_EQ(moved.n_rows(), 2); // Le nouveau possède les données + EXPECT_DOUBLE_EQ(moved(0, 1), 2.0); +} + +TEST(DynamicMatrixTest, ReshapeAndAssign) { + DynamicMatrix M(2, 3, 1.0); // 2x3 remplie de 1.0 + EXPECT_EQ(M.size(), 6); + + // Reshape sans changer la mémoire totale + M.reshape(3, 2); + EXPECT_EQ(M.n_rows(), 3); + EXPECT_EQ(M.n_cols(), 2); + + // Assign (change la taille et réinitialise) + M.assign(4, 4, 7.0); + EXPECT_EQ(M.n_rows(), 4); + EXPECT_EQ(M.n_cols(), 4); + EXPECT_EQ(M.size(), 16); + EXPECT_DOUBLE_EQ(M(3, 3), 7.0); +} + +TEST(DynamicMatrixTest, ConstMatrixView) { + const DynamicMatrix M(2, 3, + {1, 2, 3, + 4, 5, 6} + ); + auto view = M.view(); + static_assert( std::is_same_v< decltype(view), ConstMatrixView > ); + + EXPECT_EQ(view.n_rows(), 2); + EXPECT_EQ(view.n_cols(), 3); + + EXPECT_DOUBLE_EQ(view(1, 2), 6.0); + + auto row = view.row(0); + EXPECT_DOUBLE_EQ(row[1], 2.0); +} + +TEST(DynamicMatrixTest, MatrixViewContiguous) { + DynamicMatrix M(4, 4, 1.0); + + auto full = M.view(); + EXPECT_TRUE(full.isContiguous()); + + auto sub = M.view(1, 1, 2, 2); + + // stride = 4, cols = 2 + EXPECT_FALSE(sub.isContiguous()); +} + +TEST(DynamicMatrixTest, EmptyMatrix) { + DynamicMatrix M; + + EXPECT_TRUE(M.empty()); + M.assign(2, 2, 1.0); + EXPECT_FALSE(M.empty()); + + M.clear(); + + EXPECT_TRUE(M.empty()); +} + +TEST(DynamicMatrixTest, SameShape) { + DynamicMatrix A(2, 3); + DynamicMatrix B(2, 3); + DynamicMatrix C(3, 2); + + EXPECT_TRUE(A.sameShape(B)); + EXPECT_FALSE(A.sameShape(C)); +} + +TEST(DynamicMatrixTest, IsSquare) { + DynamicMatrix A(3, 3); + DynamicMatrix B(2, 3); + + EXPECT_TRUE(A.isSquare()); + EXPECT_FALSE(B.isSquare()); +} + +TEST(DynamicMatrixTest, NestedSubView) { + DynamicMatrix M(5, 5, + { 1, 2, 3, 4, 5, + 6, 7, 8, 9, 10, + 11, 12, 13, 14, 15, + 16, 17, 18, 19, 20, + 21, 22, 23, 24, 25 } + ); + + auto v1 = M.view(1, 1, 3, 3); + auto v2 = v1.subView(1, 1, 2, 2); + + EXPECT_EQ(v2.n_rows(), 2); + EXPECT_EQ(v2.n_cols(), 2); + + EXPECT_DOUBLE_EQ(v2(0,0), 13.0); + EXPECT_DOUBLE_EQ(v2(1,1), 19.0); + + v2(0,0) = -13.0; + + EXPECT_DOUBLE_EQ(M(2,2), -13.0); +} + +TEST(DynamicMatrixTest, TransposeAliasing) { + DynamicMatrix M(2, 2, + { 1, 2, + 3, 4 } + ); + + M.transpose(M); + + EXPECT_DOUBLE_EQ(M(0,0), 1.0); + EXPECT_DOUBLE_EQ(M(0,1), 3.0); + EXPECT_DOUBLE_EQ(M(1,0), 2.0); + EXPECT_DOUBLE_EQ(M(1,1), 4.0); +} + +TEST(DynamicMatrixTest, ReshapePreservesData) { + DynamicMatrix M(2, 3, + { 1, 2, 3, + 4, 5, 6 } + ); + + M.reshape(3, 2); + + EXPECT_DOUBLE_EQ(M(0,0), 1.0); + EXPECT_DOUBLE_EQ(M(0,1), 2.0); + EXPECT_DOUBLE_EQ(M(1,0), 3.0); + EXPECT_DOUBLE_EQ(M(2,1), 6.0); +} + +TEST(DynamicMatrixTest, MoveAssignment) { + DynamicMatrix A(2, 2, + { 1, 2, + 3, 4 } + ); + + DynamicMatrix B; + + B = std::move(A); + + EXPECT_EQ(A.size(), 0); + + EXPECT_EQ(B.n_rows(), 2); + EXPECT_EQ(B.n_cols(), 2); + + EXPECT_DOUBLE_EQ(B(1,1), 4.0); +} + +TEST(DynamicMatrixTest, ConstRowAccess) { + const DynamicMatrix M(2, 2, + { 1, 2, + 3, 4 } + ); + + auto row = M[1]; + static_assert( std::is_same_v< decltype(row), std::span > ); + EXPECT_DOUBLE_EQ(row[0], 3.0); + EXPECT_DOUBLE_EQ(row[1], 4.0); +} + +TEST(DynamicMatrixTest, MatrixViewStrideAccess) { + DynamicMatrix M(4, 4, + { 1, 2, 3, 4, + 5, 6, 7, 8, + 9, 10, 11, 12, + 13, 14, 15, 16 } + ); + + auto sub = M.view(1, 1, 2, 2); + + EXPECT_EQ(sub.stride(), 4); + + EXPECT_DOUBLE_EQ(sub(0,0), 6.0); + EXPECT_DOUBLE_EQ(sub(1,0), 10.0); +} + +TEST(DynamicMatrixTest, ZeroSizedMatrix) { + DynamicMatrix M(0, 0); + + EXPECT_TRUE(M.empty()); + EXPECT_EQ(M.size(), 0); + + auto v = M.view(); + + EXPECT_EQ(v.n_rows(), 0); + EXPECT_EQ(v.n_cols(), 0); } \ No newline at end of file From 178d11d39e668a6f95e18309b660e760fe4f25d1 Mon Sep 17 00:00:00 2001 From: itchinda Date: Thu, 21 May 2026 08:57:10 -0400 Subject: [PATCH 2/9] Introducing a new shape evaluation approach by enabling batch evaluation --- apps/main.cpp | 8 +- libs/expressions/include/BinaryExpression.h | 5 + libs/expressions/include/CachedExpression.h | 3 + libs/expressions/include/CfemExpression.h | 1 + .../include/CoordinateExpression.h | 2 +- .../include/DivergenceExpression.h | 6 +- libs/expressions/include/EvalContext.h | 328 +++--- .../include/GeometricNormalExpression.h | 6 +- libs/expressions/include/GradExpression.h | 5 +- libs/expressions/include/LambdaExpression.h | 4 +- .../include/LambdaExpressionBase.h | 9 + .../include/MagnitudeSqExpression.h | 3 + libs/expressions/include/ScaledExpression.h | 2 + libs/expressions/include/TensorExpression.h | 12 + libs/expressions/include/UnaryExpression.h | 3 +- libs/expressions/include/VectorExpression.h | 6 + .../symengine/ScalarSymbolicExpression.h | 2 +- libs/expressions/src/MathOperators.cpp | 2 +- .../fem/exportation/src/VTKFamilyExporter.cpp | 41 +- libs/fem/fefield/include/Argument.h | 14 +- libs/fem/fefield/include/FEField.h | 30 +- libs/fem/fefield/include/FEField.tpp | 64 +- libs/fem/fefield/src/FEField.cpp | 346 ++++-- libs/fem/fespace/include/DGSpace.h | 2 +- libs/fem/fespace/include/FESpace.h | 7 +- libs/fem/fespace/include/LagrangeSpace.h | 2 +- libs/fem/fespace/src/DGSpace.cpp | 78 +- libs/fem/fespace/src/FESpace.cpp | 5 +- libs/fem/fespace/src/LagrangeSpace.cpp | 32 +- .../include/EvaluationFlags.h | 171 +++ .../reference_element/include/MappingInfo.h | 256 ++++ .../include/ReferenceElement.h | 273 ++--- .../include/ReferenceElement.tpp | 777 +++++++++--- .../include/ReferenceElement2.h | 373 ------ .../include/ReferenceElement2.tpp | 341 ------ .../include/ReferenceHexahedron.h | 1042 +++++++++++++---- .../include/ReferencePrism.h | 803 ++++++++++--- .../reference_element/include/ReferenceQuad.h | 799 ++++++++++--- .../include/ReferenceSegment.h | 379 ++++-- .../include/ReferenceTetrahedron.h | 748 +++++++++--- .../include/ReferenceTriangle.h | 604 +++++++--- .../include/ShapeEvaluationFlags.h | 88 -- .../fem/reference_element/include/ShapeInfo.h | 194 +++ .../include/FunctionIntegrator.h | 10 +- .../src/FunctionIntegrator.cpp | 160 ++- libs/utils/include/Vector3D.h | 6 + tests/CMakeLists.txt | 2 +- tests/fem/test_fields.cpp | 2 +- tests/fem/test_quadrature.cpp | 42 +- tests/fem/test_reference_element.cpp | 136 ++- tests/test_main.cpp | 2 +- 51 files changed, 5805 insertions(+), 2431 deletions(-) create mode 100644 libs/fem/reference_element/include/EvaluationFlags.h create mode 100644 libs/fem/reference_element/include/MappingInfo.h delete mode 100644 libs/fem/reference_element/include/ReferenceElement2.h delete mode 100644 libs/fem/reference_element/include/ReferenceElement2.tpp delete mode 100644 libs/fem/reference_element/include/ShapeEvaluationFlags.h create mode 100644 libs/fem/reference_element/include/ShapeInfo.h diff --git a/apps/main.cpp b/apps/main.cpp index d75c45f..baa4f42 100644 --- a/apps/main.cpp +++ b/apps/main.cpp @@ -131,14 +131,14 @@ int main(int argc, char **argv) // Vector3D{0., 0., 0.}, // Vector3D{1., 1., 0.} // ), - // Mesh::QuadElementType::Quadrilateral + // Mesh::QuadElementType::TriangleCrissCross // ); // mesh = Mesh::disk(MeshOrder::Quadratic, 0.1, Vector3D{0,0,0}, 0.5); mesh = Mesh::block( MeshOrder::Linear, - Mesh::BlockSubdivisions{10, 10, 10}, // Mesh::BlockSubdivisions{5*(2<dependsOnShape() || m_rhs->dependsOnShape(); } + bool dependsOnNormalGeometricNormal() const override { + return m_lhs->dependsOnNormalGeometricNormal() || + m_rhs->dependsOnNormalGeometricNormal(); + } + ShapeEvaluationFlags requiredShapeFlags() const override { return m_lhs->requiredShapeFlags() | m_rhs->requiredShapeFlags(); } diff --git a/libs/expressions/include/CachedExpression.h b/libs/expressions/include/CachedExpression.h index 96e2bbc..3a28e2e 100644 --- a/libs/expressions/include/CachedExpression.h +++ b/libs/expressions/include/CachedExpression.h @@ -65,6 +65,9 @@ namespace cfem::sym { } bool dependsOnFEField() const override { return m_inner->dependsOnFEField(); } bool dependsOnShape() const override { return m_inner->dependsOnShape(); } + bool dependsOnNormalGeometricNormal() const override { + return m_inner->dependsOnNormalGeometricNormal(); + } ShapeEvaluationFlags requiredShapeFlags() const override { return m_inner->requiredShapeFlags(); } diff --git a/libs/expressions/include/CfemExpression.h b/libs/expressions/include/CfemExpression.h index 0e63d1b..a19e59d 100644 --- a/libs/expressions/include/CfemExpression.h +++ b/libs/expressions/include/CfemExpression.h @@ -110,6 +110,7 @@ namespace cfem::sym virtual bool isConstant() const { return false; } virtual bool dependsOnFEField() const { return false; } virtual bool dependsOnShape() const { return true; } + virtual bool dependsOnNormalGeometricNormal() const { return false; } virtual void bind(const std::map& spaceToId) const {} diff --git a/libs/expressions/include/CoordinateExpression.h b/libs/expressions/include/CoordinateExpression.h index b444443..ee5f872 100644 --- a/libs/expressions/include/CoordinateExpression.h +++ b/libs/expressions/include/CoordinateExpression.h @@ -60,7 +60,7 @@ namespace cfem::sym out[0] = ctx.currentTime; return; } - const Vector3D& globalX = ctx.physPos; + const Vector3D& globalX = ctx.singleWS.physPos; out[0] = globalX[m_dimIndex]; } diff --git a/libs/expressions/include/DivergenceExpression.h b/libs/expressions/include/DivergenceExpression.h index 2c5bcd5..d65ec07 100644 --- a/libs/expressions/include/DivergenceExpression.h +++ b/libs/expressions/include/DivergenceExpression.h @@ -79,12 +79,16 @@ namespace cfem::sym bool isPointwise() const override { return m_expr->isPointwise(); } bool dependsOnFEField() const override { return m_expr->dependsOnFEField(); } bool dependsOnShape() const override { return m_expr->dependsOnShape(); } + + bool dependsOnNormalGeometricNormal() const override { + return m_expr->dependsOnNormalGeometricNormal(); + } ShapeEvaluationFlags requiredShapeFlags() const override { if (!dependsOnShape()) return ShapeEvaluationFlags::None; - return m_expr->requiredShapeFlags() | ShapeEvaluationFlags::Gradient; + return m_expr->requiredShapeFlags() | ShapeEvaluationFlags::FirstDerivatives; } void collectRequiredSpaces(std::set>& spaces) const override { diff --git a/libs/expressions/include/EvalContext.h b/libs/expressions/include/EvalContext.h index fce1a8d..1ab44ca 100644 --- a/libs/expressions/include/EvalContext.h +++ b/libs/expressions/include/EvalContext.h @@ -20,130 +20,128 @@ #include "cfemutils.h" #include "Vector3D.h" -#include "ReferenceElement.h" +#include "ShapeInfo.h" +#include "MappingInfo.h" #include "DynamicVector.h" +#include "DynamicMatrix.h" #include #include #include #include +#include +#include namespace cfem { class FESpace; // Forward declaration + + // Forward declarations des structures Batch (définies dans votre architecture SoA) + struct ShapeInfoBatch; + struct MappingInfoBatch; /** * @struct SpaceDataEntry - * @brief Ultra-fast link between an FE Space and its evaluations at the current integration point. - * * This structure uses raw pointers to avoid the overhead of smart pointers during - * heavy quadrature loops. It groups shape functions and geometric mapping data. + * @brief Ultra-fast link between an FE Space and its evaluations at the current integration cell. + * Uses explicit pointers to safely separate legacy/single-point data from vectorized batch data. */ struct SpaceDataEntry { - const ShapeInfo* shape = nullptr; /**< Pointer to precomputed shape function values/gradients. */ - const MappingInfo* mapping = nullptr; /**< Pointer to geometric mapping info (Jacobians, etc.). */ + const ShapeInfo* shapeSingle = nullptr; + const MappingInfo* mappingSingle = nullptr; + + const ShapeInfoBatch* shapeBatch = nullptr; + const MappingInfoBatch* mappingBatch = nullptr; }; /** * @struct CacheEntry * @brief Stores a pointer-based identifier and its associated data span. - * Used for caching symbolic expression results to avoid redundant computations. */ struct CacheEntry { - const void* id; /**< Unique identifier (usually the expression's 'this' pointer). */ - std::span data; /**< Cached numerical values. */ + const void* id; + std::span data; }; /** * @struct EvalContext * @brief High-performance evaluation context for Finite Element assembly. - * This structure centralizes all data required at a specific integration point (quadrature point). - * It provides: - * 1. O(1) access to geometric and shape data via direct indexing. - * 2. An Arena Memory system to avoid dynamic allocations during assembly. - * 3. A symbolic expression cache to optimize complex variational forms. + * Provides O(1) access to geometric data and an Arena Memory system. + * Structured to support both Single-Point (Probing) and Batch (Assembly) evaluation modes. */ struct EvalContext { - // --- Integration Point Context --- - Vector3D physPos = {}; /**< Physical coordinates of the point. */ - CfemReal currentTime = 0; /**< Current simulation time (for transient problems). */ - CfemInt currentCellId = -1; /**< Index of the cell currently being integrated. */ - Vector3D currentXi = {-1e9, -1e9, -1e9}; /**< Local/Reference coordinates. */ - - // --- Multi-Space Cache (O(1) Access) --- - static constexpr CfemInt MAX_CACHED_SPACES = 16; /**< Maximum number of FE spaces allowed in a single variational term (e.g., for multi-physics). */ + // ==================================================================== + // GLOBAL SIMULATION STATE + // ==================================================================== + CfemReal currentTime = 0.0; /**< Current simulation time (for transient problems). */ + CfemInt currentCellId = -1; /**< Index of the cell currently being integrated. */ + + // ==================================================================== + // SINGLE-POINT WORKSPACE (Legacy / Probing) + // ==================================================================== + struct SingleWorkspace { + Vector3D physPos = {}; /**< Physical coordinates. */ + Vector3D xi = {-1e9, -1e9, -1e9};/**< Reference coordinates. */ + ShapeInfo shape; /**< Buffer for base functions. */ + MappingInfo mapping; /**< Buffer for geometric mapping. */ + } singleWS; + + // ==================================================================== + // BATCH WORKSPACE (Vectorized Element Assembly) + // ==================================================================== + struct BatchWorkspace { + DynamicVector physPos; /**< Physical coordinates for all QPs. */ + DynamicVector xi; /**< Reference coordinates for all QPs. */ + ShapeInfoBatch* shape = nullptr; /**< Pointer to allocated batch shape memory. */ + MappingInfoBatch* mapping = nullptr; /**< Pointer to allocated batch metric memory. */ + } batchWS; + + // ==================================================================== + // MULTI-SPACE CACHE & SHORTCUTS (O(1) Access) + // ==================================================================== + static constexpr CfemInt MAX_CACHED_SPACES = 16; - /** - * @name Space Data Cache - * @brief Storage for FE space evaluations. - * @details Defaults to a fixed-size array for O(1) stack-like speed. - * Define CFEM_USE_DYNAMIC_SPACE_CACHE to use std::vector for unlimited spaces. - * @{ - */ #ifdef CFEM_USE_DYNAMIC_SPACE_CACHE std::vector m_spaceDataCache; #else - std::array m_spaceDataCache; + std::array m_spaceDataCache; #endif - /** @} */ - - // --- Variational Form Shortcuts (Trial/Test) --- - /** - * @name Variational Form Shortcuts - * @brief Shortcuts for standard u (Trial) and v (Test) functions to simplify kernel code. - * @{ - */ - const ShapeInfo* trialShape = nullptr; - const ShapeInfo* testShape = nullptr; - const MappingInfo* trialMapping = nullptr; - const MappingInfo* testMapping = nullptr; - const MappingInfo* geometricMapping = nullptr; - /** @} */ - - CfemInt trialDof = -1; /**< Current local Trial DOF index being processed. */ - CfemInt testDof = -1; /**< Current local Test DOF index being processed. */ - - /** - * @name Pre-allocated Scratchpads - * @brief Buffers used for temporary local computations to avoid heap allocations - * inside the element-level loops. - * @{ - */ - /** @brief Buffer for local basis function values and gradients. */ - ShapeInfo scratchShape; - - /** @brief Buffer for local geometric transformation data (Jacobians, determinants). */ - MappingInfo scratchMapping; - /** @brief Pre-allocated storage for cell vertex coordinates. */ - DynamicVector scratchCoords; + const ShapeInfoBatch* trialShapeBatch = nullptr; + const ShapeInfoBatch* testShapeBatch = nullptr; + const MappingInfoBatch* trialMappingBatch = nullptr; + const MappingInfoBatch* testMappingBatch = nullptr; + const MappingInfoBatch* geometricMappingBatch = nullptr; - /** @brief Reusable buffer for gathering local DOF indices from the global system. */ - std::vector scratchDofs; - /** @} */ + + const MappingInfo* geometricMapping = nullptr; - static constexpr size_t PAGE_SIZE = 4096; /**< Size of a single memory page in doubles. */ + CfemInt trialDof = -1; + CfemInt testDof = -1; + + // ==================================================================== + // PRE-ALLOCATED SCRATCHPADS + // ==================================================================== + DynamicVector scratchCoords; /**< Buffer for cell vertex coordinates. */ + std::vector scratchDofs; /**< Buffer for gathering local DOF indices. */ + DynamicMatrix scratchCellDofs; /**< Buffer for cell DOF values. */ + MappingInfo scratchMapping; /**< Buffer for cell DOF values. */ + ShapeInfo scratchShape; /**< Buffer for cell DOF values. */ + // ==================================================================== + // ARENA MEMORY SYSTEM + // ==================================================================== + static constexpr size_t PAGE_SIZE = 4096; - /** - * @name Arena Memory System - * @brief Memory management to avoid heap allocations during quadrature loops. - * @{ - */ - /** @brief Deque of pages; ensures pointers remain valid when new pages are allocated. */ std::deque> m_pages; - - /** @brief Index of the page currently being used for allocation. */ size_t m_currentPageIdx = 0; - - /** @brief Current position (in CfemReal units) within the active page. */ - size_t m_currentOffset = 0; - /** @} */ + size_t m_currentOffset = 0; - std::vector m_expressionCache; /** Storage for expression caching results. */ + std::vector m_expressionCache; + + // ==================================================================== + // API METHODS + // ==================================================================== - /** - * @brief Initializes the EvalContext with a primary memory page. - */ EvalContext() { m_pages.emplace_back(PAGE_SIZE); @@ -154,90 +152,107 @@ namespace cfem { } #ifdef CFEM_USE_DYNAMIC_SPACE_CACHE - /** - * @brief Prepares the internal space cache for use. - * - * Must be called before any call to setSpaceData() when using - * dynamic space cache mode. - */ inline void prepareSpaceCache(CfemInt n) { m_spaceDataCache.resize(n); } #endif - /** - * @brief Direct access to space data by index. - * @param index The ID assigned by the Integrator before the assembly loop. - * @return A reference to the SpaceDataEntry (O(1)). - */ inline const SpaceDataEntry& getSpaceData(CfemInt index) const { #ifdef CFEM_USE_DYNAMIC_SPACE_CACHE - CFEM_ASSERT(index >= 0 && index < static_cast(m_spaceDataCache.size()), - "EvalContext::getSpaceData index out of bounds (dynamic cache)"); + CFEM_ASSERT(index >= 0 && index < static_cast(m_spaceDataCache.size()), "EvalContext::getSpaceData index out of bounds"); #else - CFEM_ASSERT(index >= 0 && index < MAX_CACHED_SPACES, - "EvalContext::getSpaceData index out of bounds (fixed cache)"); + CFEM_ASSERT(index >= 0 && index < MAX_CACHED_SPACES, "EvalContext::getSpaceData index out of bounds"); #endif - return m_spaceDataCache[index]; } - /** - * @brief Binds FE space data (shape/mapping) to a specific index. - * @param index Slot index assigned to the FE Space. - * @param shape Pointer to shape function evaluations at the current point. - * @param mapping Pointer to geometric mapping info at the current point. - * - * @note Typically called before evaluating terms at a new integration point. - * @warning Pointers must remain valid for the duration of the evaluation. - */ - inline void setSpaceData(CfemInt index, const ShapeInfo* shape, const MappingInfo* mapping) + inline void setSpaceDataBatch(CfemInt index, const ShapeInfoBatch* shape, const MappingInfoBatch* mapping) { CFEM_ASSERT(index >= 0, "Negative space index"); - #ifdef CFEM_USE_DYNAMIC_SPACE_CACHE - CFEM_ASSERT(index < static_cast(m_spaceDataCache.size()), - "Space cache not pre-sized"); + CFEM_ASSERT(index < static_cast(m_spaceDataCache.size()), "Space cache not pre-sized"); #else - CFEM_ASSERT(index < MAX_CACHED_SPACES, - "Space index exceeds MAX_CACHED_SPACES"); + CFEM_ASSERT(index < MAX_CACHED_SPACES, "Space index exceeds MAX_CACHED_SPACES"); #endif + auto& entry = m_spaceDataCache[index]; + entry.shapeBatch = shape; + entry.mappingBatch = mapping; + } + inline void setSpaceData(CfemInt index, const ShapeInfo* shape, const MappingInfo* mapping) + { + CFEM_ASSERT(index >= 0, "Negative space index"); +#ifdef CFEM_USE_DYNAMIC_SPACE_CACHE + CFEM_ASSERT(index < static_cast(m_spaceDataCache.size()), "Space cache not pre-sized"); +#else + CFEM_ASSERT(index < MAX_CACHED_SPACES, "Space index exceeds MAX_CACHED_SPACES"); +#endif auto& entry = m_spaceDataCache[index]; - entry.shape = shape; - entry.mapping = mapping; + entry.shapeSingle = shape; + entry.mappingSingle = mapping; } - /** - * @brief Retrieves precomputed mapping info for a given space index. - * @param index Space identifier. - * @return Pointer to MappingInfo. - * @pre index is valid and mapping has been set. - */ - inline const MappingInfo* getMapping(CfemInt index) const + inline const MappingInfoBatch* getMappingBatch(CfemInt index) const { #ifdef CFEM_USE_DYNAMIC_SPACE_CACHE - CFEM_ASSERT(index >= 0 && index < static_cast(m_spaceDataCache.size()), - "EvalContext::getMapping index out of bounds"); + CFEM_ASSERT(index >= 0 && index < static_cast(m_spaceDataCache.size()), "EvalContext::getMappingBatch index out of bounds"); #else - CFEM_ASSERT(index >= 0 && index < MAX_CACHED_SPACES, - "EvalContext::getMapping index out of bounds"); + CFEM_ASSERT(index >= 0 && index < MAX_CACHED_SPACES, "EvalContext::getMappingBatch index out of bounds"); #endif + const auto* mapping = m_spaceDataCache[index].mappingBatch; + CFEM_ASSERT(mapping != nullptr, "MappingInfoBatch not set"); + return mapping; + } - const auto* mapping = m_spaceDataCache[index].mapping; + inline const MappingInfo* getMapping(CfemInt index) const + { + #ifdef CFEM_USE_DYNAMIC_SPACE_CACHE + CFEM_ASSERT(index >= 0 && index < static_cast(m_spaceDataCache.size()), "EvalContext::getMappingSingle index out of bounds"); + #else + CFEM_ASSERT(index >= 0 && index < MAX_CACHED_SPACES, "EvalContext::getMappingSingle index out of bounds"); + #endif + const auto* mapping = m_spaceDataCache[index].mappingSingle; CFEM_ASSERT(mapping != nullptr, "MappingInfo not set"); - return mapping; } - // --- Arena Memory Management --- + /** + * @brief Retrieves precomputed BATCH shape info for a given space index. + * @param index Space identifier. + * @return Pointer to ShapeInfoBatch. + * @pre index is valid and batch shape has been set. + */ + inline const ShapeInfoBatch* getShapeBatch(CfemInt index) const + { + #ifdef CFEM_USE_DYNAMIC_SPACE_CACHE + CFEM_ASSERT(index >= 0 && index < static_cast(m_spaceDataCache.size()), "EvalContext::getShapeBatch index out of bounds"); + #else + CFEM_ASSERT(index >= 0 && index < MAX_CACHED_SPACES, "EvalContext::getShapeBatch index out of bounds"); + #endif + const auto* shape = m_spaceDataCache[index].shapeBatch; + CFEM_ASSERT(shape != nullptr, "ShapeInfoBatch not set. Did you call setSpaceDataBatch?"); + return shape; + } /** - * @brief Resets all temporary buffers and caches. - * - * Called before moving to a new integration point or cell. + * @brief Retrieves precomputed SINGLE shape info for a given space index. + * @param index Space identifier. + * @return Pointer to ShapeInfo (Legacy/Probing). + * @pre index is valid and single shape has been set. */ + inline const ShapeInfo* getShapeSingle(CfemInt index) const + { + #ifdef CFEM_USE_DYNAMIC_SPACE_CACHE + CFEM_ASSERT(index >= 0 && index < static_cast(m_spaceDataCache.size()), "EvalContext::getShapeSingle index out of bounds"); + #else + CFEM_ASSERT(index >= 0 && index < MAX_CACHED_SPACES, "EvalContext::getShapeSingle index out of bounds"); + #endif + const auto* shape = m_spaceDataCache[index].shapeSingle; + CFEM_ASSERT(shape != nullptr, "ShapeInfo not set. Did you call setSpaceDataSingle?"); + return shape; + } + void resetBuffers() { m_currentPageIdx = 0; m_currentOffset = 0; @@ -276,20 +291,10 @@ namespace cfem { return span; } - - // --- Expression Caching (CfemExpression) --- - - /** - * @brief Attempts to retrieve a result from the expression cache. - * @param exprPtr Pointer to the expression (id). - * @param out Span to receive the cached data. - * @return True if found and copied, false otherwise. - */ bool tryGetCache(const void* exprPtr, std::span out) const { for (const auto& entry : m_expressionCache) { if (entry.id == exprPtr) { - CFEM_ASSERT(out.size() >= entry.data.size(), - "Output buffer too small in tryGetCache"); + CFEM_ASSERT(out.size() >= entry.data.size(), "Output buffer too small in tryGetCache"); std::copy(entry.data.begin(), entry.data.end(), out.begin()); return true; } @@ -297,39 +302,18 @@ namespace cfem { return false; } - /** - * @brief Stores an expression result in the cache for later reuse. - * @param exprPtr Expression identifier. - * @param result Data to be cached. - */ void storeInCache(const void* exprPtr, std::span result) { auto storage = getBuffer(static_cast(result.size())); std::copy(result.begin(), result.end(), storage.begin()); m_expressionCache.push_back({exprPtr, storage}); } - /** - * @struct Bookmark - * @brief Represents a state in the Arena Memory to allow partial rollbacks. - */ struct Bookmark { size_t page; size_t offset; size_t cacheSize; }; - /** - * @brief Captures the current state of the memory Arena. - */ Bookmark getBookmark() const { return { m_currentPageIdx, m_currentOffset, m_expressionCache.size() }; } - /** - * @brief Releases memory and expression cache back to a previously saved bookmark. - * - * This allows partial deallocation within a single integration point. It restores - * the Arena to the saved offset and automatically removes any expression cache - * entries created after the bookmark was taken to prevent data corruption. - * - * @param bm The bookmark captured via getBookmark(). - */ void releaseToBookmark(Bookmark bm) { m_currentPageIdx = bm.page; m_currentOffset = bm.offset; @@ -341,14 +325,11 @@ namespace cfem { */ size_t getArenaMemoryBytes() const { size_t total = 0; - total += sizeof(m_pages); - for (const auto& page : m_pages) { total += sizeof(page); // objet vector total += page.capacity() * sizeof(CfemReal); } - return total; } @@ -357,26 +338,19 @@ namespace cfem { */ void printArenaMemory() const { size_t total = 0; - std::cout << "---- Arena Memory Debug ----\n"; std::cout << "Number of pages: " << m_pages.size() << "\n"; for (size_t i = 0; i < m_pages.size(); ++i) { const auto& page = m_pages[i]; size_t mem = sizeof(page) + page.capacity() * sizeof(CfemReal); - std::cout << "Page " << i - << " | size: " << page.size() - << " | capacity: " << page.capacity() - << " | mem: " << mem / 1024.0 << " KB\n"; - + << " | size: " << page.size() + << " | capacity: " << page.capacity() + << " | mem: " << mem / 1024.0 << " KB\n"; total += mem; } - - std::cout << "TOTAL arena memory: " - << total / (1024.0 * 1024.0) - << " MB\n"; + std::cout << "TOTAL arena memory: " << total / (1024.0 * 1024.0) << " MB\n"; } - }; } \ No newline at end of file diff --git a/libs/expressions/include/GeometricNormalExpression.h b/libs/expressions/include/GeometricNormalExpression.h index 68c2cc9..287170f 100644 --- a/libs/expressions/include/GeometricNormalExpression.h +++ b/libs/expressions/include/GeometricNormalExpression.h @@ -21,7 +21,7 @@ #include "CfemExpression.h" #include "EvalContext.h" #include "Vector3D.h" -#include "ShapeEvaluationFlags.h" +#include "EvaluationFlags.h" namespace cfem::sym { @@ -61,6 +61,8 @@ namespace cfem::sym return ExpressionType::Variable; } + virtual bool dependsOnNormalGeometricNormal() const override { return true; } + /** * @brief Evaluates the normal vector at a specific quadrature point. * @@ -110,7 +112,7 @@ namespace cfem::sym * @return ShapeEvaluationFlags::Normal to ensure the mapping computes the normal vector. */ ShapeEvaluationFlags requiredShapeFlags() const override { - return ShapeEvaluationFlags::Normal; + return ShapeEvaluationFlags::FirstDerivatives; } /** diff --git a/libs/expressions/include/GradExpression.h b/libs/expressions/include/GradExpression.h index 5fe827a..fcdb4bf 100644 --- a/libs/expressions/include/GradExpression.h +++ b/libs/expressions/include/GradExpression.h @@ -63,12 +63,15 @@ namespace cfem::sym ExpressionType getType() const override { return ExpressionType::Gradient; } bool dependsOnFEField() const override { return m_expr->dependsOnFEField(); } bool dependsOnShape() const override { return m_expr->dependsOnShape(); } + bool dependsOnNormalGeometricNormal() const override { + return m_expr->dependsOnNormalGeometricNormal(); + } ShapeEvaluationFlags requiredShapeFlags() const override { if (!dependsOnShape()) return ShapeEvaluationFlags::None; - return m_expr->requiredShapeFlags() | ShapeEvaluationFlags::Gradient; + return m_expr->requiredShapeFlags() | ShapeEvaluationFlags::FirstDerivatives; } void collectRequiredSpaces(std::set>& spaces) const override { diff --git a/libs/expressions/include/LambdaExpression.h b/libs/expressions/include/LambdaExpression.h index 6892cb3..e4eb014 100644 --- a/libs/expressions/include/LambdaExpression.h +++ b/libs/expressions/include/LambdaExpression.h @@ -54,11 +54,11 @@ namespace cfem::sym auto result = [&]() { // Signature: f(Vector3D, CfemReal) -> Spatiotemporal if constexpr (std::is_invocable_v) { - return m_f(ctx.physPos, ctx.currentTime); + return m_f(ctx.singleWS.physPos, ctx.currentTime); } // Signature: f(Vector3D) -> Pure Spatial else if constexpr (std::is_invocable_v) { - return m_f(ctx.physPos); + return m_f(ctx.singleWS.physPos); } else { static_assert(sizeof(F) == 0, "Invalid Lambda signature. Use f(X) or f(X, t)."); diff --git a/libs/expressions/include/LambdaExpressionBase.h b/libs/expressions/include/LambdaExpressionBase.h index 2698ad6..f8a7393 100644 --- a/libs/expressions/include/LambdaExpressionBase.h +++ b/libs/expressions/include/LambdaExpressionBase.h @@ -88,6 +88,15 @@ namespace cfem::sym return dep; } + bool dependsOnNormalGeometricNormal() const override { + bool dep = false; + if(m_gradient) dep = dep || m_gradient->dependsOnNormalGeometricNormal(); + if(m_timeDeriv) dep = dep || m_timeDeriv->dependsOnNormalGeometricNormal(); + return dep; + } + + + bool dependsOnShape() const override { return false; } ShapeEvaluationFlags requiredShapeFlags() const override { diff --git a/libs/expressions/include/MagnitudeSqExpression.h b/libs/expressions/include/MagnitudeSqExpression.h index 3468ceb..d599c43 100644 --- a/libs/expressions/include/MagnitudeSqExpression.h +++ b/libs/expressions/include/MagnitudeSqExpression.h @@ -58,6 +58,9 @@ namespace cfem::sym bool dependsOnFEField() const override { return m_expr->dependsOnFEField(); } bool dependsOnShape() const override { return m_expr->dependsOnShape(); } + bool dependsOnNormalGeometricNormal() const override { + return m_expr->dependsOnNormalGeometricNormal(); + } ShapeEvaluationFlags requiredShapeFlags() const override { return m_expr->requiredShapeFlags(); } diff --git a/libs/expressions/include/ScaledExpression.h b/libs/expressions/include/ScaledExpression.h index 367a994..57d02eb 100644 --- a/libs/expressions/include/ScaledExpression.h +++ b/libs/expressions/include/ScaledExpression.h @@ -50,6 +50,8 @@ namespace cfem::sym { } bool dependsOnFEField() const override { return m_base->dependsOnFEField(); } bool dependsOnShape() const override { return m_base->dependsOnShape(); } + bool dependsOnNormalGeometricNormal() const override { return m_base->dependsOnNormalGeometricNormal(); } + ShapeEvaluationFlags requiredShapeFlags() const override { return m_base->requiredShapeFlags(); } diff --git a/libs/expressions/include/TensorExpression.h b/libs/expressions/include/TensorExpression.h index ac1a802..9e376f6 100644 --- a/libs/expressions/include/TensorExpression.h +++ b/libs/expressions/include/TensorExpression.h @@ -126,6 +126,18 @@ namespace cfem::sym } + bool dependsOnNormalGeometricNormal() const override { + return m_components[0]->dependsOnNormalGeometricNormal() || + m_components[1]->dependsOnNormalGeometricNormal() || + m_components[2]->dependsOnNormalGeometricNormal() || + m_components[3]->dependsOnNormalGeometricNormal() || + m_components[4]->dependsOnNormalGeometricNormal() || + m_components[5]->dependsOnNormalGeometricNormal() || + m_components[6]->dependsOnNormalGeometricNormal() || + m_components[7]->dependsOnNormalGeometricNormal() || + m_components[8]->dependsOnNormalGeometricNormal(); + } + ShapeEvaluationFlags requiredShapeFlags() const override { return m_components[0]->requiredShapeFlags() | m_components[1]->requiredShapeFlags() | diff --git a/libs/expressions/include/UnaryExpression.h b/libs/expressions/include/UnaryExpression.h index 8335120..297af52 100644 --- a/libs/expressions/include/UnaryExpression.h +++ b/libs/expressions/include/UnaryExpression.h @@ -47,7 +47,8 @@ namespace cfem::sym bool dependsOnFEField() const override { return m_expr->dependsOnFEField(); } bool dependsOnShape() const override { return m_expr->dependsOnShape(); } - + bool dependsOnNormalGeometricNormal() const override { return m_expr->dependsOnNormalGeometricNormal();} + ShapeEvaluationFlags requiredShapeFlags() const override { return m_expr->requiredShapeFlags(); } diff --git a/libs/expressions/include/VectorExpression.h b/libs/expressions/include/VectorExpression.h index 6c61a98..39bcbf3 100644 --- a/libs/expressions/include/VectorExpression.h +++ b/libs/expressions/include/VectorExpression.h @@ -98,6 +98,12 @@ namespace cfem::sym m_components[2]->dependsOnShape(); } + bool dependsOnNormalGeometricNormal() const override { + return m_components[0]->dependsOnNormalGeometricNormal() || + m_components[1]->dependsOnNormalGeometricNormal() || + m_components[2]->dependsOnNormalGeometricNormal(); + } + ShapeEvaluationFlags requiredShapeFlags() const override { return m_components[0]->requiredShapeFlags() | m_components[1]->requiredShapeFlags() | diff --git a/libs/expressions/include/symengine/ScalarSymbolicExpression.h b/libs/expressions/include/symengine/ScalarSymbolicExpression.h index 81cef70..42ee92e 100644 --- a/libs/expressions/include/symengine/ScalarSymbolicExpression.h +++ b/libs/expressions/include/symengine/ScalarSymbolicExpression.h @@ -63,7 +63,7 @@ namespace cfem::sym const std::vector> &get_vars() const { return m_vars; } void evaluate(CfemInt, const Vector3D &, std::span out, EvalContext &ctx) const{ - const CfemReal input[4] = { ctx.physPos.x, ctx.physPos.y, ctx.physPos.z, ctx.currentTime}; + const CfemReal input[4] = { ctx.singleWS.physPos.x, ctx.singleWS.physPos.y, ctx.singleWS.physPos.z, ctx.currentTime}; out[0] = static_cast(m_evaluator->evaluate(input)); } diff --git a/libs/expressions/src/MathOperators.cpp b/libs/expressions/src/MathOperators.cpp index f531fe8..9948c55 100644 --- a/libs/expressions/src/MathOperators.cpp +++ b/libs/expressions/src/MathOperators.cpp @@ -924,7 +924,7 @@ namespace cfem::sym::utils { auto eval_func = [expr, idx](const Vector3D& pos, CfemReal t) -> CfemReal { CfemReal temp[9] = {0.0}; EvalContext ctx; - ctx.physPos = pos; + ctx.singleWS.physPos = pos; ctx.currentTime = t; expr->evaluate(-1, pos, temp, ctx); return temp[idx]; diff --git a/libs/fem/exportation/src/VTKFamilyExporter.cpp b/libs/fem/exportation/src/VTKFamilyExporter.cpp index 9e6c140..626ac01 100644 --- a/libs/fem/exportation/src/VTKFamilyExporter.cpp +++ b/libs/fem/exportation/src/VTKFamilyExporter.cpp @@ -149,17 +149,21 @@ namespace cfem bool isFacet = (entity.getTopologicalDimension() == m_mesh.getTopologicalDimension() - 1); size_t numEntries = entries.size(); - ShapeEvaluationFlags fieldFlags = ShapeEvaluationFlags::None; - if (isDiscontinuous && isFacet) fieldFlags |= ShapeEvaluationFlags::Normal; + ShapeEvaluationFlags shapeEvalFlags = ShapeEvaluationFlags::None; + MappingEvaluationFlags mappingEvalFlags = MappingEvaluationFlags::PhysicalPosition; + bool needsNormal = false; + // if (isDiscontinuous && isFacet) fieldFlags |= ShapeEvaluationFlags::Normal; data.fields.resize(numEntries); for (size_t i = 0; i < numEntries; ++i) { entries[i].expr->prepare(); - fieldFlags |= entries[i].expr->requiredShapeFlags(); + shapeEvalFlags |= entries[i].expr->requiredShapeFlags(); + needsNormal = needsNormal || entries[i].expr->dependsOnNormalGeometricNormal(); data.fields[i].resize(geo.numActiveVerts * entries[i].expr->getNumComponents(), 0.0); } - // FIX: O(1) Cache using unordered_map + mappingEvalFlags |= transformShapeToMappingFlags(shapeEvalFlags) | MappingEvaluationFlags::PhysicalPosition; + struct TypeCache { std::vector nodeShapes; const ReferenceElement *refEl; }; std::unordered_map geoCache; CfemInt geoOrder = m_mesh.getOrderInt(); @@ -170,7 +174,9 @@ namespace cfem auto &cache = geoCache[type]; cache.refEl = &ReferenceElementFactory::getReferenceElement(type, geoOrder); for (const auto &xi : cache.refEl->referenceNodes()) { - ShapeInfo info; cache.refEl->evaluate(xi, ShapeEvaluationFlags::Value | fieldFlags, info); + ShapeInfo info; + cache.refEl->evaluateShapes(xi, shapeEvalFlags, info); + info.shapeMustBeUpdated = false; cache.nodeShapes.push_back(info); } @@ -216,17 +222,26 @@ namespace cfem ctx.resetBuffers(); MappingInfo& mapParent = ctx.scratchMapping; - parentCache.refEl->computeMapping(parentCache.refEl->referenceNodes()[parentNodeIdx], physCoordsParent, - ShapeEvaluationFlags::PhysicalPosition | fieldFlags, - parentCache.nodeShapes[parentNodeIdx], mapParent); - - if (isFacet && hasFlag(fieldFlags, ShapeEvaluationFlags::Normal)) { - elemCache.refEl->computeMapping(elemCache.refEl->referenceNodes()[n], physCoordsElem, - ShapeEvaluationFlags::Normal, elemCache.nodeShapes[n], scratchFacetMapping); + parentCache.refEl->computeMapping( + parentCache.refEl->referenceNodes()[parentNodeIdx], + physCoordsParent, + mappingEvalFlags, + parentCache.nodeShapes[parentNodeIdx], + mapParent + ); + + if (isFacet && needsNormal) { + elemCache.refEl->computeMapping( + elemCache.refEl->referenceNodes()[n], + physCoordsElem, + MappingEvaluationFlags::Normal, + elemCache.nodeShapes[n], + scratchFacetMapping + ); mapParent.normal = scratchFacetMapping.normal; } - ctx.physPos = mapParent.physicalPosition; + ctx.singleWS.physPos = mapParent.physicalPosition; ctx.geometricMapping = &mapParent; for (size_t i = 0; i < numEntries; ++i) { diff --git a/libs/fem/fefield/include/Argument.h b/libs/fem/fefield/include/Argument.h index d076c2a..a1886a2 100644 --- a/libs/fem/fefield/include/Argument.h +++ b/libs/fem/fefield/include/Argument.h @@ -179,7 +179,7 @@ class Argument : public CfemExpression { CFEM_ASSERT(m_boundIndex >= 0, "Argument not bound before evaluation!"); const auto& data = ctx.getSpaceData(m_boundIndex); - const ShapeInfo* shape = data.shape; + const ShapeInfo* shape = data.shapeSingle; CFEM_ASSERT(shape != nullptr, "ShapeInfo is null in EvalContext!"); CFEM_ASSERT(out.size() >= m_space->getNumComponents(), "Output span too small!"); @@ -201,19 +201,21 @@ class Argument : public CfemExpression { */ virtual void evaluateBasisGradient(CfemInt localDofIdx, std::span out, EvalContext& ctx) const { const auto& data = ctx.getSpaceData(m_boundIndex); - const MappingInfo* mapping = data.mapping; + const MappingInfo* mapping = data.mappingSingle; const CfemInt dim = m_space->getNumComponents(); const CfemInt node = static_cast(localDofIdx / dim); const CfemInt comp = static_cast(localDofIdx % dim); - const auto& g = mapping->physicalShapeGradients[node]; + const auto& gx = mapping->dN_dx[node]; + const auto& gy = mapping->dN_dy[node]; + const auto& gz = mapping->dN_dz[node]; std::fill(out.begin(), out.end(), 0.0); // Output span contains full gradient: [dxUx, dyUx, dzUx, dxUy, dyUy, dzUy, dxUz, dyUz, dzUz] - out[comp * 3 + 0] = g.x; - out[comp * 3 + 1] = g.y; - out[comp * 3 + 2] = g.z; + out[comp * 3 + 0] = gx; + out[comp * 3 + 1] = gy; + out[comp * 3 + 2] = gz; } /** diff --git a/libs/fem/fefield/include/FEField.h b/libs/fem/fefield/include/FEField.h index 4f5ee3c..a964935 100644 --- a/libs/fem/fefield/include/FEField.h +++ b/libs/fem/fefield/include/FEField.h @@ -110,13 +110,41 @@ namespace cfem SymTensor3D get3DSymTensorFieldAtNode (CfemInt meshNodeId) const; SkewSymTensor3D get3DSkewSymTensorFieldAtNode(CfemInt meshNodeId) const; - CfemReal getAt(CfemInt globalDofIdx, CfemInt component) const; + void extractCellDofs(CfemInt cellId, DynamicMatrix& localDofs, EvalContext& ctx) const; void evaluateWithoutShape(CfemInt cellId, const Vector3D& xi, std::span out, EvalContext &ctx) const; void evaluateWithShape(CfemInt cellId, const ShapeInfo& shape, std::span out) const; void evaluateGradient(CfemInt cellId, const Vector3D &xi, std::span out, EvalContext &ctx) const override; void evaluateGradientWithoutCachedMapping(CfemInt cellId, const Vector3D &xi, std::span out, EvalContext &ctx) const; void evaluateGradientWithCachedMapping(CfemInt cellId, const MappingInfo& mapping, std::span out) const; + // ==================================================================== + // --- Vectorized Batch (SoA) Evaluation Extensions + // ==================================================================== + + /** + * @brief Evaluates the finite element field values across a whole batch of quadrature points. + * @param cellId Local index of the computational cell. + * @param shapeBatch Contiguous batch structure holding precomputed shape functions [nQp x nNodes]. + * @param outValuesBatch Destination matrix to store the physical field results [nQp x m_dim]. + * @param ctx Unified high-performance evaluation context. + */ + void evaluateBatch(CfemInt cellId, + const ShapeInfoBatch& shapeBatch, + DynamicMatrix& outValuesBatch, + EvalContext& ctx) const; + + /** + * @brief Evaluates the physical spatial gradients (du/dx, du/dy, du/dz) across a batch of quadrature points. + * @param cellId Local index of the computational cell. + * @param mappingBatch Contiguous metric batch structure holding physical derivatives (dN/dx, dN/dy, dN/dz). + * @param outGradientsBatch Destination matrix to store the spatial physical gradients [nQp x (m_dim * spaceDim)]. + * @param ctx Unified high-performance evaluation context. + */ + void evaluateGradientBatch(CfemInt cellId, + const MappingInfoBatch& mappingBatch, + DynamicMatrix& outGradientsBatch, + EvalContext& ctx) const; + void interpolate(const sym::CfemExpression& recipe); void interpolate(std::shared_ptr recipe) { if (recipe) interpolate(*recipe); diff --git a/libs/fem/fefield/include/FEField.tpp b/libs/fem/fefield/include/FEField.tpp index 1da144f..83e4e44 100644 --- a/libs/fem/fefield/include/FEField.tpp +++ b/libs/fem/fefield/include/FEField.tpp @@ -41,59 +41,61 @@ namespace cfem */ template requires (!std::is_base_of_v> && - !std::is_convertible_v, std::shared_ptr>) + !std::is_convertible_v, std::shared_ptr>) void FEField::interpolate(const Func& func) { - CFEM_INFO << "Projecting functional lambda/constant into field '" << getName() << "'"; + CFEM_INFO << "Interpolating functional lambda/constant into field '" << getName() << "'"; const auto& mesh = m_space->getMesh(); Vec& petscVec = m_data.getRaw(); - CfemInt nComp = this->getNumComponents(); + const CfemInt nComp = this->getNumComponents(); - // Compile-time type analysis of the input + // Compile-time type analysis of the input parameter constexpr bool is_constant = std::is_arithmetic_v>; constexpr bool is_callable = std::is_invocable_v; + static_assert(is_constant || is_callable, "FEField::interpolate requires either a numeric constant or a callable taking a Vector3D."); - // MPI Ownership Range - CfemInt low, high; + // Retrieve local partition layout boundaries for the parallel MPI distributed vector + CfemInt low = 0; + CfemInt high = 0; VecGetOwnershipRange(petscVec, &low, &high); m_localValues.assign(high - low, 0.0); - CfemInt numPositions = m_space->getNumUniquePositions(); + const CfemInt numPositions = m_space->getNumUniquePositions(); std::vector visited(numPositions, 0); #pragma omp parallel { + // Allocate thread-local context workspace safely isolating scratch matrix buffers EvalContext evctx; #pragma omp for schedule(guided) for (CfemInt cellId = 0; cellId < mesh->getNumCells(); ++cellId) { - auto cell_type = mesh->getCell(cellId).type; + const auto cell_type = mesh->getCell(cellId).type; const auto& refEl = ReferenceElementFactory::getReferenceElement(cell_type, m_space->getOrderInt()); const auto& refElGeo = ReferenceElementFactory::getReferenceElement(cell_type, mesh->getOrderInt()); - // Load coordinates into EvalContext scratchpad + // Load contiguous cell node physical coordinates into context scratchpad mesh->getCellVerticesCoords(cellId, evctx.scratchCoords); - for (CfemInt i = 0; i < (CfemInt)refEl.getNumNodes(); ++i) + const CfemInt nNodes = static_cast(refEl.getNumNodes()); + for (CfemInt i = 0; i < nNodes; ++i) { - CfemInt posId = m_space->getPositionId(cellId, i); + const CfemInt posId = m_space->getPositionId(cellId, i); if (posId == -1) continue; - // Atomic visiting check to handle shared nodes (CG) + // Atomic boundary test-and-set to guarantee unique thread evaluation per geometric node if (visited[posId]) continue; if (__atomic_test_and_set(&visited[posId], __ATOMIC_RELAXED)) continue; if constexpr (is_constant) { - // --- FAST PATH: Constant Value --- - // No geometry mapping needed. Just apply the constant to the DOFs. for (CfemInt d = 0; d < nComp; ++d) { - CfemInt globalIdx = m_space->getGlobalDofIdx(cellId, i, d); + const CfemInt globalIdx = m_space->getGlobalDofIdx(cellId, i, d); if (globalIdx >= low && globalIdx < high) { m_localValues[globalIdx - low] = static_cast(func); @@ -102,28 +104,28 @@ namespace cfem } else { - // --- STANDARD PATH: Callable Lambda --- - // Precise Geometry: Find physical position of the node - Vector3D xi = refEl.getNodeCoords(i); - refElGeo.evaluate(xi, ShapeEvaluationFlags::Value, evctx.scratchShape); + // STANDARD PATH: Callable Analytic Function + const Vector3D xi = refEl.getNodeCoords(i); + refElGeo.computeMapping( xi, - evctx.scratchCoords, - ShapeEvaluationFlags::PhysicalPosition, - static_cast(evctx.scratchShape), - evctx.scratchMapping); - - // Evaluate lambda - auto result = func(evctx.scratchMapping.physicalPosition); + evctx.scratchCoords, + ShapeEvaluationFlags::Value, + MappingEvaluationFlags::PhysicalPosition, + evctx.singleWS.shape, + evctx.singleWS.mapping + ); + + // Evaluate user-provided function lambda at computed physical spatial coordinates + auto result = func(evctx.singleWS.mapping.physicalPosition); - // Safely deduce return type (array vs scalar) inside the isolated branch using LocalResultType = decltype(result); constexpr bool returns_scalar = std::is_arithmetic_v; - // Global Assembly (Local Buffer) + // Map the calculated physical properties to the local distributed ownership buffer for (CfemInt d = 0; d < nComp; ++d) { - CfemInt globalIdx = m_space->getGlobalDofIdx(cellId, i, d); + const CfemInt globalIdx = m_space->getGlobalDofIdx(cellId, i, d); if (globalIdx >= low && globalIdx < high) { if constexpr (returns_scalar) { @@ -138,8 +140,8 @@ namespace cfem } } - // Direct PETSc update - PetscScalar *array; + // Unify distributed MPI vectors into the global parallel algebraic architecture + PetscScalar *array = nullptr; VecGetArray(petscVec, &array); std::copy(m_localValues.begin(), m_localValues.end(), array); VecRestoreArray(petscVec, &array); diff --git a/libs/fem/fefield/src/FEField.cpp b/libs/fem/fefield/src/FEField.cpp index c2b5aa4..b75bd79 100644 --- a/libs/fem/fefield/src/FEField.cpp +++ b/libs/fem/fefield/src/FEField.cpp @@ -61,7 +61,7 @@ namespace cfem { CfemInt totalDofs = m_space->getGlobalNumDofs(); - // 1. Assurer la taille du buffer (allocation unique au début, réutilisée ensuite) + // Assurer la taille du buffer (allocation unique au début, réutilisée ensuite) if (m_localValues.size() != static_cast(totalDofs)) { m_localValues.assign(totalDofs, 0.0); @@ -226,101 +226,277 @@ namespace cfem } /** - * @brief Standard value evaluation at a local coordinate xi within a cell. + * @brief Extracts cell degrees of freedom from the synchronized global local buffer into a contiguous matrix. + * @param cellId Local index of the computational cell. + * @param localDofs Reference to the scratch matrix [nNodes x fieldDimension] allocated in the memory arena. + * @param ctx Unified high-performance evaluation context. + */ + void FEField::extractCellDofs(CfemInt cellId, DynamicMatrix& localDofs, EvalContext& ctx) const + { + const CfemInt nNodes = m_space->getReferenceElement(cellId).getNumNodes(); + const CfemInt fDim = this->getNumComponents(); // m_dim (e.g., 1 for scalar fields, 3 for 3D vectors) + + // Resize the scratch matrix inside the memory arena (zero allocation overhead if capacity suffices) + localDofs.resize(nNodes, fDim); + + // Populate the dense local layout using direct global index lookup from FESpace + for (CfemInt i = 0; i < nNodes; ++i) { + for (CfemInt d = 0; d < fDim; ++d) { + const CfemInt globalDofIdx = m_space->getGlobalDofIdx(cellId, i, d); + + // Safety check: Ensure the global index maps inside our synchronized cache buffer bounds + CFEM_ASSERT(globalDofIdx >= 0 && globalDofIdx < static_cast(m_localValues.size()), + "FEField::extractCellDofs - Resolved global DOF index is out of bounds of the synchronized m_localValues cache."); + + localDofs[i][d] = m_localValues[globalDofIdx]; + } + } + } + + /** + * @brief Evaluates finite element field values across a whole batch of quadrature points using dense matrix contraction. + * @param cellId Local index of the computational cell. + * @param shapeBatch Contiguous batch structure holding precomputed shape functions [nQp x nNodes]. + * @param outValuesBatch Destination matrix to store the physical field results [nQp x m_dim]. + * @param ctx Unified high-performance evaluation context. + */ + void FEField::evaluateBatch(CfemInt cellId, + const ShapeInfoBatch& shapeBatch, + DynamicMatrix& outValuesBatch, + EvalContext& ctx) const + { + // Gather all local cell coefficients into cache-friendly dense layout + this->extractCellDofs(cellId, ctx.scratchCellDofs, ctx); + + const CfemInt nQp = shapeBatch.values.n_rows(); + const CfemInt nNodes = shapeBatch.values.n_cols(); + const CfemInt fDim = this->getNumComponents(); + + outValuesBatch.resize(nQp, fDim); + outValuesBatch.setZero(); + + // Vectorized dense matrix contraction: Out[q][d] = Sum_i ( Shape[q][i] * CellDofs[i][d] ) + for (CfemInt q = 0; q < nQp; ++q) { + for (CfemInt i = 0; i < nNodes; ++i) { + const CfemReal Ni = shapeBatch.values[q][i]; + for (CfemInt d = 0; d < fDim; ++d) { + outValuesBatch[q][d] += Ni * ctx.scratchCellDofs[i][d]; + } + } + } + } + + /** + * @brief Evaluates the standard field value at a single local coordinate xi without pre-computed shapes. + * @param cellId Local index of the computational cell. + * @param xi Local/Reference coordinates inside the reference element space. + * @param out Destination memory span packing the physical field components [m_dim]. + * @param ctx Unified high-performance evaluation context. */ void FEField::evaluateWithoutShape(CfemInt cellId, const Vector3D &xi, std::span out, EvalContext &ctx) const { const Cell &cell = m_space->getMesh()->getCell(cellId); const auto &refEl = ReferenceElementFactory::getReferenceElement(cell.type, m_space->getOrderInt()); - refEl.evaluate(xi, ShapeEvaluationFlags::Value, ctx.scratchShape); - this->evaluateWithShape(cellId, ctx.scratchShape, out); + ctx.singleWS.shape.values.resize(refEl.getNumNodes()); + refEl.evaluateShapesValues(xi, ctx.singleWS.shape.values.data()); + + this->evaluateWithShape(cellId, ctx.singleWS.shape, out); } /** - * @brief Optimized evaluation using pre-computed shape functions. - * Uses the FESpace abstraction to find global DOF indices. + * @brief Interpolates field values at a single integration point using pre-allocated ShapeInfo. + * @param cellId Local index of the computational cell. + * @param shape Single-point reference shape container holding base function values. + * @param out Destination memory span packing the physical field components [m_dim]. */ void FEField::evaluateWithShape(CfemInt cellId, const ShapeInfo &shape, std::span out) const { - CfemInt nComp = this->getNumComponents(); + const CfemInt nComp = this->getNumComponents(); std::fill_n(out.data(), nComp, 0.0); const auto &shapeValues = shape.values; - CfemInt nNodes = static_cast(shapeValues.size()); + const CfemInt nNodes = static_cast(shapeValues.size()); + // Contract nodal degrees of freedom linearly against element values for (CfemInt i = 0; i < nNodes; ++i) { - CfemReal s = shapeValues[i]; + const CfemReal s = shapeValues[i]; for (CfemInt c = 0; c < nComp; ++c) { - // Request the global index from the space abstraction - CfemInt globalDofIdx = m_space->getGlobalDofIdx(cellId, i, c); + const CfemInt globalDofIdx = m_space->getGlobalDofIdx(cellId, i, c); - if (globalDofIdx != -1) { + // Bypass dead or unmapped DOFs safely (e.g., in specialized embedded spaces) + if (globalDofIdx != -1) + { + CFEM_ASSERT(globalDofIdx >= 0 && globalDofIdx < static_cast(m_localValues.size()), + "FEField::evaluateWithShape - Resolved global DOF index out of local cache bounds."); + out[c] += s * m_localValues[globalDofIdx]; } } } } + /** + * @brief Gateway method to evaluate the spatial physical gradient tensor at a single local coordinate. + * @param cellId Local index of the computational cell. + * @param xi Local/Reference coordinates inside the reference element space. + * @param out Destination memory span packing the flattened gradient tensor [m_dim x spaceDim]. + * @param ctx Unified high-performance evaluation context. + */ void FEField::evaluateGradient(CfemInt cellId, const Vector3D &xi, std::span out, EvalContext &ctx) const { - // ÉTAPE A : Est-ce qu'on est dans un intégrateur avec un mapping déjà prêt ? - if (m_boundIndex != -1) { + if (m_boundIndex != -1) + { const MappingInfo* cachedMap = ctx.getMapping(m_boundIndex); - if (cachedMap) { + if (cachedMap) + { this->evaluateGradientWithCachedMapping(cellId, *cachedMap, out); return; } } - // On appelle la version lourde qui recalcule la Jacobienne this->evaluateGradientWithoutCachedMapping(cellId, xi, out, ctx); } + /** + * @brief Evaluates the physical spatial gradient tensor at an un-cached arbitrary local coordinate. + * @param cellId Local index of the computational cell. + * @param xi Local/Reference coordinates inside the reference element space. + * @param out Destination memory span packing the flattened physical gradient tensor. + * @param ctx Unified high-performance evaluation context. + */ void FEField::evaluateGradientWithoutCachedMapping(CfemInt cellId, const Vector3D &xi, std::span out, EvalContext &ctx) const { - const Cell &cell = m_space->getMesh()->getCell(cellId); - const auto &refEl = ReferenceElementFactory::getReferenceElement(cell.type, m_space->getOrderInt()); - + const auto &refEl = m_space->getReferenceElement(cellId); + + // Extract contiguous cell vertex coordinates into context scratchpad m_space->getMesh()->getCellVerticesCoords(cellId, ctx.scratchCoords); - CfemInt expectedNodes = refEl.getNumNodes(); - if (ctx.scratchCoords.size() > (size_t)expectedNodes) { + CFEM_INFO << "HIIIII"; + const CfemInt expectedNodes = refEl.getNumNodes(); + if (ctx.scratchCoords.size() > static_cast(expectedNodes)) { ctx.scratchCoords.resize(expectedNodes); } + + ctx.singleWS.shape.shapeMustBeUpdated = false; + + refEl.computeMapping( + xi, + ctx.scratchCoords, + ShapeEvaluationFlags::FirstDerivatives, + MappingEvaluationFlags::PhysicalFirstDerivatives, // all internal dependency requirements (Jacobian, Determinant, InverseJacobian). + ctx.singleWS.shape, + ctx.singleWS.mapping + ); - refEl.computeMapping(xi, ctx.scratchCoords, ShapeEvaluationFlags::Gradient, - ctx.scratchShape, ctx.scratchMapping); - // Évaluer le gradient physique - this->evaluateGradientWithCachedMapping(cellId, ctx.scratchMapping, out); + // Forward the computed single metric point to our corrected tensor projection method + this->evaluateGradientWithCachedMapping(cellId, ctx.singleWS.mapping, out); } - /** - * @brief Optimized gradient evaluation using pre-computed physical mapping. + /** + * @brief Evaluates the physical gradient tensor at a single integration point using a cached MappingInfo. + * @param cellId Local index of the computational cell. + * @param mapping Single-point metric context containing precomputed physical shape gradients. + * @param out Destination memory span packing the flattened gradient tensor. + * Layout: [component * spatialDimension + spatialDirection] */ void FEField::evaluateGradientWithCachedMapping(CfemInt cellId, const MappingInfo &mapping, std::span out) const { - CfemInt nComp = this->getNumComponents(); - - std::fill_n(out.data(), nComp * 3, 0.0); + const CfemInt nComp = this->getNumComponents(); + const CfemInt spatialDimension = m_space->getMesh()->getSpatialDimension(); - const auto &grads = mapping.physicalShapeGradients; - CfemInt nNodes = static_cast(grads.size()); + // Reset output buffer tracking the exact required tensor memory footprint + std::fill_n(out.data(), nComp * spatialDimension, 0.0); + const auto &gradsX = mapping.dN_dx; + const auto &gradsY = mapping.dN_dy; + const auto &gradsZ = mapping.dN_dz; + const CfemInt nNodes = static_cast(gradsX.size()); + + // Loop over element basis functions (nodes) for (CfemInt i = 0; i < nNodes; ++i) { - const Vector3D &g = grads[i]; + const CfemReal gx = gradsX[i]; + const CfemReal gy = (spatialDimension == 2) ? gradsY[i] : 0.0; + const CfemReal gz = (spatialDimension == 3) ? gradsZ[i] : 0.0; + + // Loop over physical field components for (CfemInt c = 0; c < nComp; ++c) { - CfemInt globalDofIdx = m_space->getGlobalDofIdx(cellId, i, c); - if (globalDofIdx != -1) + const CfemInt globalDofIdx = m_space->getGlobalDofIdx(cellId, i, c); + + CFEM_ASSERT(globalDofIdx >= 0 && globalDofIdx < static_cast(m_localValues.size()), + "FEField::evaluateGradientWithCachedMapping - Resolved global DOF index is out of bounds."); + + const CfemReal val = m_localValues[globalDofIdx]; + const CfemInt rowOffset = c * spatialDimension; + + // Dynamically project derivatives based on the true dimensionality of the mesh + out[rowOffset + 0] += val * gx; + if (spatialDimension > 1) { + out[rowOffset + 1] += val * gy; + if (spatialDimension == 3) { + out[rowOffset + 2] += val * gz; + } + } + } + } + } + + /** + * @brief Interpolates physical spatial derivatives of the field for the entire batch of integration points. + * @param cellId Local index of the computational cell. + * @param mappingBatch Contiguous metric batch structure holding physical derivatives (dN/dx, dN/dy, dN/dz). + * @param outGradientsBatch Destination matrix to store the spatial physical gradients [nQp x (m_dim * spatialDimension)]. + * @param ctx Unified high-performance evaluation context. + */ + void FEField::evaluateGradientBatch(CfemInt cellId, + const MappingInfoBatch& mappingBatch, + DynamicMatrix& outGradientsBatch, + EvalContext& ctx) const + { + // Gather all local cell coefficients into the memory arena scratchpad matrix + this->extractCellDofs(cellId, ctx.scratchCellDofs, ctx); + + const CfemInt nQp = mappingBatch.dN_dx.n_rows(); + const CfemInt nNodes = mappingBatch.dN_dx.n_cols(); + const CfemInt fDim = this->getNumComponents(); + const CfemInt spatialDimension = m_space->getMesh()->getSpatialDimension(); + + // Allocate output footprint: Rows = Quadrature Points, Columns = Combined Gradient Matrix Components + outGradientsBatch.resize(nQp, fDim * spatialDimension); + outGradientsBatch.setZero(); + + // Optimized Multi-Component Contraction Loop + for (CfemInt q = 0; q < nQp; ++q) + { + // Loop over field dimensions (components) first to establish continuous row offsets + for (CfemInt d = 0; d < fDim; ++d) + { + const CfemInt rowOffset = d * spatialDimension; + CfemReal grad_x = 0.0; + CfemReal grad_y = 0.0; + CfemReal grad_z = 0.0; + + // Deepest loop operates over element nodes: perfectly contiguous column memory read + for (CfemInt i = 0; i < nNodes; ++i) { - CfemReal val = m_localValues[globalDofIdx]; - out[c * 3 + 0] += val * g.x; - out[c * 3 + 1] += val * g.y; - out[c * 3 + 2] += val * g.z; + const CfemReal u_nodal = ctx.scratchCellDofs[i][d]; + + grad_x += mappingBatch.dN_dx[q][i] * u_nodal; + grad_y += mappingBatch.dN_dy[q][i] * u_nodal; + if (spatialDimension == 3) { + grad_z += mappingBatch.dN_dz[q][i] * u_nodal; + } + } + + // Continuous sequential store inside the active row segment + outGradientsBatch[q][rowOffset + 0] = grad_x; + outGradientsBatch[q][rowOffset + 1] = grad_y; + if (spatialDimension == 3) { + outGradientsBatch[q][rowOffset + 2] = grad_z; } } } @@ -334,8 +510,8 @@ namespace cfem if (m_boundIndex >= 0) { const auto& data = ctx.getSpaceData(m_boundIndex); - if (data.shape != nullptr) { - this->evaluateWithShape(cellId, *(data.shape), out); + if (data.shapeSingle != nullptr) { + this->evaluateWithShape(cellId, *(data.shapeSingle), out); return; } } @@ -344,101 +520,121 @@ namespace cfem } /** - * @brief Interpolates a symbolic expression onto the FE Space. - * This handles the parallel MPI assembly and OpenMP node-visiting logic. + * @brief Projects a symbolic mathematical expression (recipe) onto the finite element field via nodal interpolation. + * @param recipe The symbolic expression containing the mathematical definition to evaluate. */ void FEField::interpolate(const sym::CfemExpression &recipe) { - CFEM_INFO << "interpolating field into " << getName(); + CFEM_INFO << "Interpolating symbolic expression onto field '" << getName() << "'."; - + // Initialize or bind any symbolic parameters or sub-expressions recipe.prepare(); const auto& mesh = m_space->getMesh(); Vec& petscVec = m_data.getRaw(); - CfemInt dim = this->getNumComponents(); + const CfemInt dim = this->getNumComponents(); + // Query mathematical dependencies to determine which reference derivatives are needed const auto recipeFlags = recipe.requiredShapeFlags(); - - // Determine the local ownership range for MPI - CfemInt low, high; + const bool needsGeoNormals = recipe.dependsOnNormalGeometricNormal(); + // Align evaluation flags to match the exact mathematical types required by the system + const ShapeEvaluationFlags shapeFlags = ShapeEvaluationFlags::Value | recipeFlags | + (needsGeoNormals? ShapeEvaluationFlags::FirstDerivatives : ShapeEvaluationFlags::None); + const MappingEvaluationFlags mappingFlags = MappingEvaluationFlags::PhysicalPosition | transformShapeToMappingFlags(recipeFlags) | + (needsGeoNormals? MappingEvaluationFlags::Normal : MappingEvaluationFlags::None); + + // Determine the local process partition range owned within the distributed global PETSc system + CfemInt low = 0; + CfemInt high = 0; VecGetOwnershipRange(petscVec, &low, &high); - CfemInt localSize = high - low; + const CfemInt localSize = high - low; + // Reset and size the local write buffer matching this process's MPI ownership slice m_localValues.assign(localSize, 0.0); - // Prepare for node visiting (CG: vertex-based, DG: cell-based) - CfemInt numPositions = m_space->getNumUniquePositions(); + // Track visited nodes to guarantee a single evaluation pass for shared entities (CG/DG neutral) + const CfemInt numPositions = m_space->getNumUniquePositions(); std::vector visited(numPositions, 0); #pragma omp parallel { + // Allocation of thread-local unified context. Core structures (Arena, cache) remain local. EvalContext evctx; + std::vector valBuffer(dim); std::span valSpan(valBuffer); #pragma omp for schedule(guided) for (CfemInt cellId = 0; cellId < mesh->getNumCells(); ++cellId) { - auto cell_type = mesh->getCell(cellId).type; + const auto cell_type = mesh->getCell(cellId).type; const auto &refEl = ReferenceElementFactory::getReferenceElement(cell_type, m_space->getOrderInt()); const auto &refElGeo = ReferenceElementFactory::getReferenceElement(cell_type, mesh->getOrderInt()); + // Collect node physical coordinates of the current cell into the context scratchpad mesh->getCellVerticesCoords(cellId, evctx.scratchCoords); - for (CfemInt i = 0; i < (int)refEl.getNumNodes(); ++i) + const CfemInt nNodes = static_cast(refEl.getNumNodes()); + for (CfemInt i = 0; i < nNodes; ++i) { - // Get the position ID for locking/visiting logic - CfemInt posId = m_space->getPositionId(cellId, i); + // Obtain unique global spatial position mapping ID + const CfemInt posId = m_space->getPositionId(cellId, i); if (posId == -1) continue; - // Atomic check: Ensure each unique node is computed only once + // Atomic test-and-set fence to prevent concurrent evaluations of identical shared nodes if (visited[posId]) continue; if (__atomic_test_and_set(&visited[posId], __ATOMIC_RELAXED)) continue; - // Evaluate the recipe at the physical location of the node - Vector3D xi = refEl.getNodeCoords(i); - // ShapeInfo nodeShape; - refElGeo.evaluate(xi, ShapeEvaluationFlags::Value | recipeFlags, evctx.scratchShape); + // Map target reference element node coordinates + const Vector3D xi = refEl.getNodeCoords(i); + + // Compute metric mapping properties at the specific isolated node location refElGeo.computeMapping( xi, evctx.scratchCoords, - ShapeEvaluationFlags::PhysicalPosition | recipeFlags, - static_cast(evctx.scratchShape), - evctx.scratchMapping + shapeFlags, + mappingFlags, + evctx.singleWS.shape, + evctx.singleWS.mapping ); - evctx.physPos = evctx.scratchMapping.physicalPosition; - evctx.currentCellId = cellId; - evctx.currentXi = xi; - evctx.geometricMapping = &evctx.scratchMapping; + // Populate the single-point workspace with current state configurations + evctx.singleWS.physPos = evctx.singleWS.mapping.physicalPosition; + evctx.singleWS.xi = xi; + evctx.geometricMapping = &evctx.singleWS.mapping; + evctx.currentCellId = cellId; + + // Bind the active mapping pointer to our newly established type-safe single slots + // evctx.setSpaceData(m_boundIndex, &evctx.singleWS.shape, &evctx.singleWS.mapping); + // Execute symbolic mathematical projection directly into thread-local buffer recipe.evaluate(cellId, xi, valSpan, evctx); - // Map calculated values to the global algebraic vector + // Map calculated values to the local algebraic partition slice for (CfemInt d = 0; d < dim; ++d) { - CfemInt globalDofIdx = m_space->getGlobalDofIdx(cellId, i, d); + const CfemInt globalDofIdx = m_space->getGlobalDofIdx(cellId, i, d); - // MPI Check: Does this process own this specific DOF? + // MPI ownership range check: store only if it maps onto this local process memory segment if (globalDofIdx >= low && globalDofIdx < high) { - m_localValues[globalDofIdx - low] = valSpan[d]; + const size_t localProcessIdx = static_cast(globalDofIdx - low); + m_localValues[localProcessIdx] = valSpan[d]; } } } } } - // 3. Final PETSc Assembly - PetscScalar *array; + // 3. Complete distributed parallel synchronization into global PETSc architecture + PetscScalar *array = nullptr; VecGetArray(petscVec, &array); std::copy(m_localValues.begin(), m_localValues.end(), array); VecRestoreArray(petscVec, &array); m_data.assemble(); - CFEM_INFO << "Projection of recipe onto field '" << m_name << "' completed."; + CFEM_INFO << "Nodal interpolation of recipe onto field '" << m_name << "' completed successfully."; } } diff --git a/libs/fem/fespace/include/DGSpace.h b/libs/fem/fespace/include/DGSpace.h index c8753b6..0e44ba2 100644 --- a/libs/fem/fespace/include/DGSpace.h +++ b/libs/fem/fespace/include/DGSpace.h @@ -72,7 +72,7 @@ class DGSpace : public FESpace { void getEntityDofs(CfemInt internalEntityId, std::vector& boundaryDofs) const override; void evaluateBasis(CfemInt cellId, const Vector3D& xi, std::vector& values) const override; - void evaluateGradients(CfemInt cellId, const Vector3D& xi, std::vector& grads) const override; + void evaluateBasisGradients(CfemInt cellId, const Vector3D& xi, std::vector& grads) const override; }; } // namespace cfem diff --git a/libs/fem/fespace/include/FESpace.h b/libs/fem/fespace/include/FESpace.h index b1709d6..d4af2e6 100644 --- a/libs/fem/fespace/include/FESpace.h +++ b/libs/fem/fespace/include/FESpace.h @@ -21,7 +21,8 @@ #include "cfemutils.h" #include "cfem_topology.h" -#include "ReferenceElement.h" +#include "ReferenceElementFactory.h" + #include #include @@ -88,7 +89,7 @@ class FESpace : public MeshObserver, public std::enable_shared_from_this& grads) const = 0; + virtual void evaluateBasisGradients(CfemInt cellId, const Vector3D& xi, std::vector& grads) const = 0; // Support for Assembler and PETSc diff --git a/libs/fem/fespace/include/LagrangeSpace.h b/libs/fem/fespace/include/LagrangeSpace.h index 97572b3..17b206a 100644 --- a/libs/fem/fespace/include/LagrangeSpace.h +++ b/libs/fem/fespace/include/LagrangeSpace.h @@ -87,7 +87,7 @@ class LagrangeSpace : public FESpace { // Basis Function Evaluation void evaluateBasis(CfemInt cellId, const Vector3D& xi, std::vector& values) const override; - void evaluateGradients(CfemInt cellId, const Vector3D& xi, std::vector& grads) const override; + void evaluateBasisGradients(CfemInt cellId, const Vector3D& xi, std::vector& grads) const override; // Legacy Accessors (Optional, strictly internal to Lagrange) CfemInt getLocalNodeIdx(CfemInt meshNodeId) const { return m_nodeToLocalIdx[meshNodeId]; } diff --git a/libs/fem/fespace/src/DGSpace.cpp b/libs/fem/fespace/src/DGSpace.cpp index 14feb65..a09c41d 100644 --- a/libs/fem/fespace/src/DGSpace.cpp +++ b/libs/fem/fespace/src/DGSpace.cpp @@ -157,28 +157,66 @@ void DGSpace::computeSparsityPattern(std::vector& nnz) const { } -// Math evaluations (evaluateBasis/evaluateGradients) are identical to Lagrange -// because they both use the ReferenceElement interface! -void DGSpace::evaluateBasis(CfemInt cellId, const Vector3D& xi, std::vector& values) const { - ShapeInfo shape; - const Cell& cell = m_mesh->getCell(cellId); - const ReferenceElement& refEl = ReferenceElementFactory::getReferenceElement( cell.type, getOrderInt() ); - refEl.evaluate(xi, ShapeEvaluationFlags::Value, shape); - values.assign(shape.values.begin(), shape.values.end()); -} + /** + * @brief Evaluates the reference basis function values at a local coordinate xi within a specific cell. + * @param cellId Local index of the computational cell. + * @param xi Reference/Local coordinates inside the cell's geometric space. + * @param values Destination vector container to hold the evaluated scalar basis functions. + */ + void DGSpace::evaluateBasis(CfemInt cellId, const Vector3D& xi, std::vector& values) const + { + ShapeInfo shape; + const Cell& cell = m_mesh->getCell(cellId); + const ReferenceElement& refEl = ReferenceElementFactory::getReferenceElement(cell.type, getOrderInt()); + + // Evaluate reference basis values directly into the ShapeInfo container + shape.values.resize(refEl.getNumNodes()); + refEl.evaluateShapesValues(xi, shape.values.data()); + + // Direct contiguous copy remains valid for scalar values + values.assign(shape.values.begin(), shape.values.end()); + } -void DGSpace::evaluateGradients(CfemInt cellId, const Vector3D& xi, std::vector& grads) const { - static thread_local DynamicVector physCoords; - m_mesh->getCellVerticesCoords(cellId, physCoords); + /** + * @brief Evaluates the physical gradients of the basis functions at a local coordinate xi. + * @param cellId Local index of the computational cell. + * @param xi Reference/Local coordinates inside the cell's geometric space. + * @param grads Destination vector container to pack the calculated physical Vector3D gradients. + */ + void DGSpace::evaluateBasisGradients(CfemInt cellId, const Vector3D& xi, std::vector& grads) const + { + // Thread-local scratchpad to guarantee OpenMP safety and bypass dynamic allocations + static thread_local DynamicVector t_physCoords; + m_mesh->getCellVerticesCoords(cellId, t_physCoords); - const Cell& cell = m_mesh->getCell(cellId); - const ReferenceElement& refEl = ReferenceElementFactory::getReferenceElement( cell.type, getOrderInt() ); - - ShapeInfo shape; - MappingInfo map; - refEl.computeMapping(xi, physCoords, ShapeEvaluationFlags::Gradient, shape, map); - grads.assign(map.physicalShapeGradients.begin(), map.physicalShapeGradients.end()); -} + const Cell& cell = m_mesh->getCell(cellId); + const ReferenceElement& refEl = ReferenceElementFactory::getReferenceElement(cell.type, getOrderInt()); + + ShapeInfo shape; + MappingInfo map; + + refEl.computeMapping( + xi, + t_physCoords, + ShapeEvaluationFlags::FirstDerivatives, + MappingEvaluationFlags::PhysicalFirstDerivatives, + shape, + map + ); + + const CfemInt nNodes = static_cast(map.dN_dx.size()); + const CfemInt spatialDimension = m_mesh->getSpatialDimension(); + + // Resize the destination vector to accommodate the layout without memory spikes + grads.resize(nNodes); + + for (CfemInt i = 0; i < nNodes; ++i) + { + grads[i].x = map.dN_dx[i]; + grads[i].y = (spatialDimension >= 2) ? map.dN_dy[i] : 0.0; + grads[i].z = (spatialDimension == 3) ? map.dN_dz[i] : 0.0; + } + } /** * @brief Récupère les DOFs associés à une entité géométrique (bord ou groupe physique). diff --git a/libs/fem/fespace/src/FESpace.cpp b/libs/fem/fespace/src/FESpace.cpp index 01e95cf..5cf457f 100644 --- a/libs/fem/fespace/src/FESpace.cpp +++ b/libs/fem/fespace/src/FESpace.cpp @@ -18,7 +18,6 @@ #include "FESpace.h" #include "Mesh.h" -#include "ReferenceElements.h" namespace cfem { @@ -89,4 +88,8 @@ namespace cfem } } + const ReferenceElement& FESpace::getReferenceElement(CfemInt cellId) const{ + return ReferenceElementFactory::getReferenceElement(m_mesh->getCell(cellId).type, getOrderInt()); + } + } // namespace cfem \ No newline at end of file diff --git a/libs/fem/fespace/src/LagrangeSpace.cpp b/libs/fem/fespace/src/LagrangeSpace.cpp index f6fab31..99b6a1b 100644 --- a/libs/fem/fespace/src/LagrangeSpace.cpp +++ b/libs/fem/fespace/src/LagrangeSpace.cpp @@ -247,21 +247,23 @@ void LagrangeSpace::getElementDofs(CfemInt cellId, std::vector& globalI } } -// --- MATH & EVALUATION --- +// MATH & EVALUATION void LagrangeSpace::evaluateBasis(CfemInt cellId, const Vector3D& xi, std::vector& values) const { const Cell& cell = m_mesh->getCell(cellId); const ReferenceElement& refEl = ReferenceElementFactory::getReferenceElement( cell.type, getOrderInt() ); ShapeInfo shape; - refEl.evaluate(xi, ShapeEvaluationFlags::Value, shape); + shape.values.resize(refEl.getNumNodes()); + refEl.evaluateShapesValues(xi, shape.values.data()); values.assign(shape.values.begin(), shape.values.end()); } -void LagrangeSpace::evaluateGradients(CfemInt cellId, const Vector3D& xi, std::vector& grads) const { +void LagrangeSpace::evaluateBasisGradients(CfemInt cellId, const Vector3D& xi, std::vector& grads) const +{ const Cell& cell = m_mesh->getCell(cellId); - const ReferenceElement& refEl = ReferenceElementFactory::getReferenceElement( cell.type, getOrderInt() ); + const ReferenceElement& refEl = ReferenceElementFactory::getReferenceElement(cell.type, getOrderInt()); // Thread-local scratchpad to ensure OpenMP safety and avoid allocations static thread_local DynamicVector t_physicalCoords; @@ -269,9 +271,27 @@ void LagrangeSpace::evaluateGradients(CfemInt cellId, const Vector3D& xi, std::v ShapeInfo shape; MappingInfo map; - refEl.computeMapping(xi, t_physicalCoords, ShapeEvaluationFlags::Gradient, shape, map); - grads.assign(map.physicalShapeGradients.begin(), map.physicalShapeGradients.end()); + refEl.computeMapping( + xi, + t_physicalCoords, + ShapeEvaluationFlags::FirstDerivatives, + MappingEvaluationFlags::PhysicalFirstDerivatives, + shape, + map + ); + + const CfemInt nNodes = static_cast(map.dN_dx.size()); + const CfemInt spatialDimension = m_mesh->getSpatialDimension(); + + grads.resize(nNodes); + + for (CfemInt i = 0; i < nNodes; ++i) + { + grads[i].x = map.dN_dx[i]; + grads[i].y = (spatialDimension >= 2) ? map.dN_dy[i] : 0.0; + grads[i].z = (spatialDimension == 3) ? map.dN_dz[i] : 0.0; + } } //PETSc & SYSTEM SUPPORT diff --git a/libs/fem/reference_element/include/EvaluationFlags.h b/libs/fem/reference_element/include/EvaluationFlags.h new file mode 100644 index 0000000..e3f15a6 --- /dev/null +++ b/libs/fem/reference_element/include/EvaluationFlags.h @@ -0,0 +1,171 @@ +//************************************************************************ +// --- CFEM++ Library +// --- +// --- Copyright 2024-2026 Ismaël Tchinda Ngueyong et al. +// --- ALL RIGHTS RESERVED +// --- +// --- This software is protected by international copyright laws. +// --- Use, distribution, or modification of this software in any form, +// --- source or binary, for personal, academic, or non-commercial +// --- purposes is permitted free of charge, provided that this +// --- copyright notice and this permission notice appear in all +// --- copies and supporting documentation. +// --- +// --- The authors and contributors provide this code "as is" without +// --- any express or implied warranty. In no event shall they be +// --- held liable for any damages arising from the use of this software. +//************************************************************************ + +#pragma once + +#include "cfemutils.h" +#include + +namespace cfem +{ + /** + * @brief Bitmask flags to control the evaluation of shape functions on the reference element. + * + * These flags dictate which quantities (values, gradients, etc.) are computed + * purely in the reference coordinate system (xi, eta, zeta). + */ + enum class ShapeEvaluationFlags : CfemIntFlag + { + None = 0, ///< No fields requested. + Value = 1 << 0, ///< Evaluate shape function values (N_i). + FirstDerivatives = 1 << 1, ///< Evaluate first derivatives w.r.t reference coordinates (dN/dxi, dN/deta, dN/dzeta). + SecondDerivatives = 1 << 2, ///< Evaluate second derivatives w.r.t reference coordinates (Hessians). + + All = Value | FirstDerivatives | SecondDerivatives ///< Request all available reference fields. + }; + + /** + * @brief Bitmask flags to control the computation of geometric and physical mapping fields. + * + * These flags utilize a cascading bitwise structure to automatically resolve mathematical + * dependencies. For instance, requesting the InverseJacobian will implicitly flag the + * Determinant and the Jacobian for computation. + */ + enum class MappingEvaluationFlags : CfemIntFlag + { + None = 0, ///< No fields requested. Useful for clearing state. + PhysicalPosition = 1 << 0, ///< Compute the interpolated physical coordinates (x, y, z). + Jacobian = 1 << 1, ///< Compute the Jacobian matrix (dx/dxi). + Determinant = (1 << 2) | Jacobian, ///< Compute the determinant of the Jacobian (requires Jacobian). + InverseJacobian = (1 << 3) | Determinant, ///< Compute the inverse Jacobian matrix (requires Determinant). + PhysicalFirstDerivatives = (1 << 4) | InverseJacobian, ///< Compute shape gradients in the physical domain (dN/dx). + PhysicalSecondDerivatives = (1 << 5) | PhysicalFirstDerivatives, ///< Compute shape Hessians in the physical domain. + Normal = (1 << 6) | Jacobian, ///< Compute the outward unit normal vector (for boundaries/interfaces). + + All = PhysicalPosition | Jacobian | Determinant | InverseJacobian | PhysicalFirstDerivatives | PhysicalSecondDerivatives | Normal ///< Request all available physical mapping fields. + }; + + // Bitwise Operators for Enumerations + // Note: SFINAE is used to restrict these operators to CFEM specific enums only, + // preventing global namespace pollution for other standard enums. + + template || + std::is_same_v>> + constexpr Enum operator|(Enum a, Enum b) + { + return static_cast( static_cast(a) | static_cast(b) ); + } + + template || + std::is_same_v>> + constexpr Enum operator&(Enum a, Enum b) + { + return static_cast( static_cast(a) & static_cast(b) ); + } + + template || + std::is_same_v>> + constexpr Enum& operator|=(Enum& a, Enum b) + { + a = a | b; + return a; + } + + template || + std::is_same_v>> + constexpr Enum& operator&=(Enum& a, Enum b) + { + a = a & b; + return a; + } + + /** + * @brief Checks if a specific flag (or combination of flags) is fully set within a bitmask. + * + * @tparam Enum The enumeration type. + * @param flags The current state bitmask. + * @param test The specific flag(s) to check for. + * @return true if ALL bits of 'test' are present in 'flags', false otherwise. + */ + template || + std::is_same_v>> + [[nodiscard]] constexpr bool hasFlag(Enum flags, Enum test) + { + // STRICT CHECK: Ensure all bits of the 'test' flag are present in 'flags'. + // This is crucial for cascading flags where testing a child should not + // mistakenly return true if only the parent dependency is present. + return (static_cast(flags) & static_cast(test)) == static_cast(test); + } + + + /** + * @brief Explicitly translates reference element evaluation prerequisites into their corresponding + * physical geometric mapping requirements. + * @param shapeFlags Active bitmask representing required reference element quantities. + * @return MappingEvaluationFlags The minimized resolved physical mapping bitmask. + */ + [[nodiscard]] constexpr MappingEvaluationFlags transformShapeToMappingFlags(ShapeEvaluationFlags shapeFlags) + { + MappingEvaluationFlags result = MappingEvaluationFlags::None; + + // Physical positions (x,y,z) are only computed if explicitly requested by a spatial symbolic node + // if (hasFlag(shapeFlags, ShapeEvaluationFlags::Value)) + // { + // } + + if (hasFlag(shapeFlags, ShapeEvaluationFlags::FirstDerivatives)) + { + result |= MappingEvaluationFlags::PhysicalFirstDerivatives; + } + + if (hasFlag(shapeFlags, ShapeEvaluationFlags::SecondDerivatives)) + { + result |= MappingEvaluationFlags::PhysicalSecondDerivatives; + } + + return result; + } + + [[nodiscard]] constexpr ShapeEvaluationFlags transformMappingToShapeFlags(MappingEvaluationFlags mappingFlags) + { + ShapeEvaluationFlags result = ShapeEvaluationFlags::None; + + // Physical positions (x,y,z) are only computed if explicitly requested by a spatial symbolic node + if (hasFlag(mappingFlags, MappingEvaluationFlags::PhysicalPosition)) + { + result |= ShapeEvaluationFlags::Value; + } + + if (hasFlag(mappingFlags, MappingEvaluationFlags::Jacobian) || + hasFlag(mappingFlags, MappingEvaluationFlags::Determinant) || + hasFlag(mappingFlags, MappingEvaluationFlags::InverseJacobian) || + hasFlag(mappingFlags, MappingEvaluationFlags::PhysicalFirstDerivatives) || + hasFlag(mappingFlags, MappingEvaluationFlags::Normal) ) + { + result |= ShapeEvaluationFlags::FirstDerivatives; + } + + if (hasFlag(mappingFlags, MappingEvaluationFlags::PhysicalSecondDerivatives)) + { + result |= ShapeEvaluationFlags::FirstDerivatives | ShapeEvaluationFlags::SecondDerivatives; + } + + return result; + } + +} // namespace cfem diff --git a/libs/fem/reference_element/include/MappingInfo.h b/libs/fem/reference_element/include/MappingInfo.h new file mode 100644 index 0000000..16450e3 --- /dev/null +++ b/libs/fem/reference_element/include/MappingInfo.h @@ -0,0 +1,256 @@ +#pragma once + +#include "cfemutils.h" +#include "EvaluationFlags.h" +#include "DynamicMatrix.h" +#include "DynamicMatrix.h" +#include "Tensor3D.h" +#include "Tensor2D.h" + +#include "ShapeInfo.h" + + +namespace cfem +{ + /** + * @struct MappingInfo + * @brief Stores metric data resulting from the geometric transformation (Reference -> Physical). + */ + struct MappingInfo + { + + CfemInt numNodes = 0; + MappingEvaluationFlags validFlags = MappingEvaluationFlags::None; + + Vector3D physicalPosition; + Tensor3D jacobian; + Tensor3D invJacobian; + CfemReal detJ = 0.0; + Vector3D normal; + + DynamicVector dN_dx; ///< First order derivatives with respect to \$ x \$ + DynamicVector dN_dy; ///< First order derivatives with respect to \$ y \$ + DynamicVector dN_dz; ///< First order derivatives with respect to \$ z \$ + + DynamicVector d2N_dx2; ///< Second order derivatives with respect to \$ x \$ + DynamicVector d2N_dy2; ///< Second order derivatives with respect to \$ y \$ + DynamicVector d2N_dz2; ///< Second order derivatives with respect to \$ z \$ + + DynamicVector d2N_dxdy; ///< Second order derivatives with respect to \$ x and y \$ + DynamicVector d2N_dxdz; ///< Second order derivatives with respect to \$ x and z \$ + DynamicVector d2N_dydz; ///< Second order derivatives with respect to \$ y and z \$ + + CfemReal metric1D = 0.0; + SymTensor2D metric2D; + + void setup(CfemInt nNodes, bool includeSecondDerivatives = false) + { + numNodes = nNodes; + + if (dN_dx.size() != static_cast(numNodes)) + { + dN_dx.resize(numNodes); + dN_dy.resize(numNodes); + dN_dz.resize(numNodes); + } + + if (includeSecondDerivatives && d2N_dx2.size() != static_cast(numNodes)) + { + d2N_dx2.resize(numNodes); + d2N_dy2.resize(numNodes); + d2N_dz2.resize(numNodes); + + d2N_dxdy.resize(numNodes); + d2N_dxdz.resize(numNodes); + d2N_dydz.resize(numNodes); + } + + validFlags = MappingEvaluationFlags::None; + } + + inline Vector3D gradient(CfemInt node) const + { + CFEM_ASSERT(node >= 0 && node < numNodes); + return Vector3D(dN_dx[node], dN_dy[node], dN_dz[node]); + } + + inline SymTensor3D hessian(CfemInt node) const + { + // CFEM_INFO << "Hiiiiiiiiiiiiiiiiiiiiiii"; + // CFEM_INFO << "Size = " << d2N_dx2.size(); + CFEM_ASSERT(node >= 0 && node < numNodes); + + return SymTensor3D(d2N_dx2[node], d2N_dy2[node], d2N_dz2[node], + d2N_dydz[node], d2N_dxdz[node], d2N_dxdy[node]); + } + + inline void invalidate() + { + validFlags = MappingEvaluationFlags::None; + } + + void computePhysicalFirstDerivativesOnly(const ShapeInfo &fieldShape) + { + const CfemInt n = fieldShape.numNodes; + CFEM_ASSERT(n == numNodes); + + const CfemReal i00 = invJacobian(0, 0); + const CfemReal i01 = invJacobian(0, 1); + const CfemReal i02 = invJacobian(0, 2); + + const CfemReal i10 = invJacobian(1, 0); + const CfemReal i11 = invJacobian(1, 1); + const CfemReal i12 = invJacobian(1, 2); + + const CfemReal i20 = invJacobian(2, 0); + const CfemReal i21 = invJacobian(2, 1); + const CfemReal i22 = invJacobian(2, 2); + + const CfemReal *dxi = fieldShape.dN_dxi.data(); + const CfemReal *deta = fieldShape.dN_deta.data(); + const CfemReal *dzeta = fieldShape.dN_dzeta.data(); + // CFEM_INFO << "inv jacobian"; + // CFEM_INFO << invJacobian; + + // CFEM_INFO << "Printing de dN_dzeta"; + // for (CfemInt l = 0; l < fieldShape.dN_dzeta.size(); ++l) + // CFEM_INFO << fieldShape.dN_dzeta[l] << " "; + + CfemReal *dx = dN_dx.data(); + CfemReal *dy = dN_dy.data(); + CfemReal *dz = dN_dz.data(); + + for (CfemInt i = 0; i < n; ++i) + { + dx[i] = i00 * dxi[i] + i10 * deta[i] + i20 * dzeta[i]; + dy[i] = i01 * dxi[i] + i11 * deta[i] + i21 * dzeta[i]; + dz[i] = i02 * dxi[i] + i12 * deta[i] + i22 * dzeta[i]; + } + } + }; + + /** + * @struct MappingInfoBatch + * @brief Stores metric data resulting from the geometric transformation for a batch of points. + */ + struct MappingInfoBatch { + + CfemInt numQuadraturePoints = 0; + CfemInt numNodes = 0; + MappingEvaluationFlags validFlags = MappingEvaluationFlags::None; + + // --- Global quantities per quadrature point [qp] --- + DynamicVector physicalPositions; + DynamicVector jacobians; + DynamicVector invJacobians; + DynamicVector detJs; + DynamicVector normals; + + DynamicVector metric1Ds; + DynamicVector metric2Ds; + + // --- Nodal quantities (SoA) [qp][node] --- + DynamicMatrix dN_dx; + DynamicMatrix dN_dy; + DynamicMatrix dN_dz; + + DynamicMatrix d2N_dx2; + DynamicMatrix d2N_dy2; + DynamicMatrix d2N_dz2; + DynamicMatrix d2N_dxdy; + DynamicMatrix d2N_dxdz; + DynamicMatrix d2N_dydz; + + + + void setup(CfemInt nQp, CfemInt nNodes, CfemInt dim, bool includeSecondDerivatives = false) { + numQuadraturePoints = nQp; + numNodes = nNodes; + + if (physicalPositions.size() != static_cast(nQp)) { + physicalPositions.resize(nQp); + jacobians.resize(nQp); + invJacobians.resize(nQp); + detJs.resize(nQp); + normals.resize(nQp); + + if (dim == 1){ + metric1Ds.resize(nQp); + } + else if (dim == 2) { + metric2Ds.resize(nQp); + } + } + + if (dN_dx.size() != nQp * nNodes) { + dN_dx.resize(nQp, nNodes); + dN_dy.resize(nQp, nNodes); + dN_dz.resize(nQp, nNodes); + } + + if (includeSecondDerivatives && d2N_dx2.size() != nQp * nNodes) { + d2N_dx2.resize(nQp, nNodes); + d2N_dy2.resize(nQp, nNodes); + d2N_dz2.resize(nQp, nNodes); + + d2N_dxdy.resize(nQp, nNodes); + d2N_dxdz.resize(nQp, nNodes); + d2N_dydz.resize(nQp, nNodes); + } + + validFlags = MappingEvaluationFlags::None; + } + + inline Vector3D gradient(CfemInt q, CfemInt node) const { + CFEM_ASSERT(q >= 0 && q < numQuadraturePoints); + CFEM_ASSERT(node >= 0 && node < numNodes); + return Vector3D(dN_dx(q, node), dN_dy(q, node), dN_dz(q, node)); + } + + inline SymTensor3D hessian(CfemInt q, CfemInt node) const { + CFEM_ASSERT(q >= 0 && q < numQuadraturePoints); + CFEM_ASSERT(node >= 0 && node < numNodes); + return SymTensor3D(d2N_dx2(q, node), d2N_dy2(q, node), d2N_dz2(q, node), + d2N_dydz(q, node), d2N_dxdz(q, node), d2N_dxdy(q, node)); + } + + inline void invalidate() { + validFlags = MappingEvaluationFlags::None; + } + + /** + * @brief Computes physical gradients for the entire batch. + * @note Hyper-optimized utilizing SIMD friendly contiguous memory rows. + */ + void computePhysicalFirstDerivativesOnly(const ShapeInfoBatch& fieldShape) { + CFEM_ASSERT(fieldShape.numQuadraturePoints == numQuadraturePoints); + CFEM_ASSERT(fieldShape.numNodes == numNodes); + + for (CfemInt q = 0; q < numQuadraturePoints; ++q) + { + // Cache invariant Jacobian terms in registers for this quadrature point + const CfemReal i00 = invJacobians[q].data[Tensor3D::idx_xx], i01 = invJacobians[q].data[Tensor3D::idx_xy], i02 = invJacobians[q].data[Tensor3D::idx_xz]; + const CfemReal i10 = invJacobians[q].data[Tensor3D::idx_yx], i11 = invJacobians[q].data[Tensor3D::idx_yy], i12 = invJacobians[q].data[Tensor3D::idx_yz]; + const CfemReal i20 = invJacobians[q].data[Tensor3D::idx_zx], i21 = invJacobians[q].data[Tensor3D::idx_zy], i22 = invJacobians[q].data[Tensor3D::idx_zz]; + + // Extract contiguous raw pointers for the current row (quadrature point 'q') + // This utilizes the std::span returned by DynamicMatrix::operator[] + const CfemReal* dxi = fieldShape.dN_dxi[q].data(); + const CfemReal* deta = fieldShape.dN_deta[q].data(); + const CfemReal* dzeta = fieldShape.dN_dzeta[q].data(); + + CfemReal* dx = dN_dx[q].data(); + CfemReal* dy = dN_dy[q].data(); + CfemReal* dz = dN_dz[q].data(); + + // Inner loop over nodes: perfectly contiguous, ripe for auto-vectorization + for (CfemInt i = 0; i < numNodes; ++i) { + dx[i] = i00 * dxi[i] + i10 * deta[i] + i20 * dzeta[i]; + dy[i] = i01 * dxi[i] + i11 * deta[i] + i21 * dzeta[i]; + dz[i] = i02 * dxi[i] + i12 * deta[i] + i22 * dzeta[i]; + } + } + } + }; + +} \ No newline at end of file diff --git a/libs/fem/reference_element/include/ReferenceElement.h b/libs/fem/reference_element/include/ReferenceElement.h index fa607a5..708f904 100644 --- a/libs/fem/reference_element/include/ReferenceElement.h +++ b/libs/fem/reference_element/include/ReferenceElement.h @@ -16,120 +16,29 @@ // --- held liable for any damages arising from the use of this software. //************************************************************************ -#ifndef CFEM_REFERENCE_ELEMENT_H -#define CFEM_REFERENCE_ELEMENT_H +#pragma once #include "cfemutils.h" #include "Vector3D.h" #include "DynamicVector.h" #include "DynamicMatrix.h" #include "Tensor3D.h" -#include "ShapeEvaluationFlags.h" +#include "EvaluationFlags.h" + +#include "ShapeInfo.h" +#include "MappingInfo.h" namespace cfem::reference_elem { - static const std::vector> line_face_nodes = { - {0}, // Face 0 (Nœud gauche) - {1} // Face 1 (Nœud droit) + static const DynamicVector> line_face_nodes = { + {0}, // Face 0 (Left node) + {1} // Face 1 (Right node) }; } namespace cfem { enum class Order { P0 = 0, P1 = 1, P2 = 2 , Mini = 101}; -/** - * @struct ShapeInfo - * @brief Storage buffer for shape function evaluations at a specific quadrature point. - * @note Re-use this object across multiple integration points to avoid frequent heap allocations. - */ -struct ShapeInfo { - DynamicVector values; ///< Shape function values (N_i) - DynamicVector gradients; ///< Local gradients w.r.t reference coordinates (dN_i/dxi) - DynamicVector hessians; ///< Local Hessians w.r.t reference coordinates (d2N_i/dxi2) - bool shapeMustBeUpdated = true; - ShapeEvaluationFlags validFlags = ShapeEvaluationFlags::None; - - /** - * @brief Prepares buffer sizes to prevent runtime reallocations. - * @param numNodes Number of nodes in the element. - * @param includeHessians If true, allocates memory for Hessian tensors. - */ - void setup(CfemInt numNodes, bool includeHessians = false) { - if (values.size() != static_cast(numNodes)) { - values.resize(numNodes); - gradients.resize(numNodes); - } - if (includeHessians && hessians.size() != static_cast(numNodes)) { - hessians.resize(numNodes); - } - validFlags = ShapeEvaluationFlags::None; - } - - /** @brief Checks if a specific field (Value/Gradient/Hessian) is marked as valid. */ - bool has(cfem::ShapeEvaluationFlags field) const { - return hasFlag(validFlags, field); - } -}; - -/** - * @struct MappingInfo - * @brief Stores metric data resulting from the geometric transformation (Reference -> Physical). - */ -struct MappingInfo { - Vector3D physicalPosition; ///< The mapped x, y, z coordinates - Tensor3D jacobian; ///< Jacobian matrix (dx/dxi) - Tensor3D invJacobian; ///< Inverse Jacobian matrix (dxi/dx) - CfemReal detJ; ///< Determinant of the Jacobian - Vector3D normal; - - DynamicVector physicalShapeGradients; ///< dN_i/dx (Gradients in physical space) - DynamicVector physicalShapeHessians; ///< d2N_i/dx2 (Hessians in physical space) - - /** - * @brief Prepares the mapping buffers for a specific element size. - * @param numNodes Number of nodes in the element. - * @param includeHessians Whether to allocate the Hessian buffer. - */ - void setup(CfemInt numNodes, bool includeHessians = false) { - if (physicalShapeGradients.size() != static_cast(numNodes)) { - physicalShapeGradients.resize(numNodes); - } - - if (includeHessians && physicalShapeHessians.size() != static_cast(numNodes)) { - physicalShapeHessians.resize(numNodes); - } - } - - // Quick accessors for physical gradients - inline CfemReal dNdx(CfemInt i) const { return physicalShapeGradients[i].x; } - inline CfemReal dNdy(CfemInt i) const { return physicalShapeGradients[i].y; } - inline CfemReal dNdz(CfemInt i) const { return physicalShapeGradients[i].z; } - - // Quick accessors for physical Hessians (components) - inline CfemReal d2Ndxdx(CfemInt nodeIdx) const { return physicalShapeHessians[nodeIdx].xx(); } - inline CfemReal d2Ndydy(CfemInt nodeIdx) const { return physicalShapeHessians[nodeIdx].yy(); } - inline CfemReal d2Ndzdz(CfemInt nodeIdx) const { return physicalShapeHessians[nodeIdx].zz(); } - inline CfemReal d2Ndydz(CfemInt nodeIdx) const { return physicalShapeHessians[nodeIdx].yz(); } - inline CfemReal d2Ndxdz(CfemInt nodeIdx) const { return physicalShapeHessians[nodeIdx].xz(); } - inline CfemReal d2Ndxdy(CfemInt nodeIdx) const { return physicalShapeHessians[nodeIdx].xy(); } - - void computePhysicalGradientsOnly(const ShapeInfo& fieldShape) { - // Le fait de charger dans des variables locales (registres) - // empêche le CPU de faire des allers-retours mémoire. - const CfemReal i00 = invJacobian(0,0), i01 = invJacobian(0,1), i02 = invJacobian(0,2); - const CfemReal i10 = invJacobian(1,0), i11 = invJacobian(1,1), i12 = invJacobian(1,2); - const CfemReal i20 = invJacobian(2,0), i21 = invJacobian(2,1), i22 = invJacobian(2,2); - - for (size_t i = 0; i < fieldShape.gradients.size(); ++i) { - const auto& g = fieldShape.gradients[i]; - physicalShapeGradients[i].x = i00 * g.x + i10 * g.y + i20 * g.z; - physicalShapeGradients[i].y = i01 * g.x + i11 * g.y + i21 * g.z; - physicalShapeGradients[i].z = i02 * g.x + i12 * g.y + i22 * g.z; - } - } - -}; - /** * @class ReferenceElement * @brief Abstract base class for Finite Element reference definitions. @@ -143,21 +52,73 @@ class ReferenceElement { CfemInt m_numNodes; CfemInt m_dimension; + + public: virtual ~ReferenceElement() = default; CfemInt dimension() const { return m_dimension; }; CfemInt getNumNodes() const { return m_numNodes; }; virtual std::span getFaceNodes(CfemInt iFace) const = 0; + + void evaluateShapes(const Vector3D &xi, ShapeEvaluationFlags requestedShape, ShapeInfo& out) const; + void evaluateShapesBatch(const DynamicVector &xiPoints, ShapeEvaluationFlags requestedShape, ShapeInfoBatch& outBatch) const; /** - * @brief Evaluates shape functions and their local derivatives at point xi. - * @param xi Target point in reference space. - * @param requested Bitmask of fields to calculate (Value, Gradient, Hessian). - * @param out ShapeInfo buffer to be filled. + * @brief Evaluates the shape function values ($N_i$) at a given reference point. + * @param xi The coordinates of the evaluation point in the reference element ($\xi, \eta, \zeta$). + * @param values Raw pointer to a pre-allocated array of size [numNodes] to be populated. */ - virtual void evaluate(const Vector3D& xi, - ShapeEvaluationFlags requested, - ShapeInfo& out) const = 0; + virtual void evaluateShapesValues(const Vector3D &xi, + CfemReal* values) const = 0; + + virtual void evaluateShapesValuesBatch(const DynamicVector &xi, + DynamicMatrix &values) const = 0; + + /** + * @brief Evaluates the first-order derivatives of the shape functions (local gradients) + * with respect to the reference coordinate system. + * @param xi The coordinates of the evaluation point in the reference element ($\xi, \eta, \zeta$). + * @param dN_dxi Raw pointer to the reference coordinate $\xi$ derivative array (size [numNodes]). + * @param dN_deta Raw pointer to the reference coordinate $\eta$ derivative array (size [numNodes]). + * @param dN_dzeta Raw pointer to the reference coordinate $\zeta$ derivative array (size [numNodes]). + */ + virtual void evaluateShapesDerivatives(const Vector3D &xi, + CfemReal* dN_dxi, + CfemReal* dN_deta, + CfemReal* dN_dzeta) const = 0; + + virtual void evaluateShapesDerivativesBatch(const DynamicVector &xi, + DynamicMatrix &dN_dxi, + DynamicMatrix &dN_deta, + DynamicMatrix &dN_dzeta) const = 0; + + /** + * @brief Evaluates the second-order derivatives of the shape functions (local Hessians) + * with respect to the reference coordinate system. + * + * @param xi The coordinates of the evaluation point in the reference element ($\xi, \eta, \zeta$). + * @param d2N_dxi2 Raw pointer to the $\frac{\partial^2 N}{\partial \xi^2}$ array (size [numNodes]). + * @param d2N_deta2 Raw pointer to the $\frac{\partial^2 N}{\partial \eta^2}$ array (size [numNodes]). + * @param d2N_dzeta2 Raw pointer to the $\frac{\partial^2 N}{\partial \zeta^2}$ array (size [numNodes]). + * @param d2N_dxideta Raw pointer to the $\frac{\partial^2 N}{\partial \xi \partial \eta}$ array (size [numNodes]). + * @param d2N_dxidzeta Raw pointer to the $\frac{\partial^2 N}{\partial \xi \partial \zeta}$ array (size [numNodes]). + * @param d2N_detadzeta Raw pointer to the $\frac{\partial^2 N}{\partial \eta \partial \zeta}$ array (size [numNodes]). + */ + virtual void evaluateShapesSecondDerivatives(const Vector3D &xi, + CfemReal* d2N_dxi2, + CfemReal* d2N_deta2, + CfemReal* d2N_dzeta2, + CfemReal* d2N_dxideta, + CfemReal* d2N_dxidzeta, + CfemReal* d2N_detadzeta) const = 0; + + virtual void evaluateShapesSecondDerivativesBatch(const DynamicVector &xi, + DynamicMatrix &d2N_dxi2, + DynamicMatrix &d2N_deta2, + DynamicMatrix &d2N_dzeta2, + DynamicMatrix &d2N_dxideta, + DynamicMatrix &d2N_dxidzeta, + DynamicMatrix &d2N_detadzeta) const = 0; virtual Vector3D projectFacetRefCoordToCellRefCoord(CfemInt localFacetId, const Vector3D& xiFacet) const = 0; /** @@ -176,48 +137,60 @@ class ReferenceElement { * @param shape Work buffer for reference shape functions. * @param out MappingInfo structure to be populated. */ - void computeMapping(const Vector3D& xi, - const DynamicVector& physicalNodesCoords, - ShapeEvaluationFlags requestedFields, - ShapeInfo& shape, - MappingInfo& out) const; - - inline void computeMapping(const Vector3D &xi, - const DynamicVector &physicalNodesCoords, - ShapeEvaluationFlags requestedFields, - const ShapeInfo &shape, - MappingInfo &out) const; - - /** - * @brief Computes the Jacobian, its determinant (metric), and the pseudo-inverse. - * @note This version supports 1D/2D elements embedded in 3D space using - * the Moore-Penrose pseudo-inverse logic. - */ - void computeJacobian(const ShapeInfo& shape, - const DynamicVector& physicalNodesCoords, - MappingInfo& out) const; - + void computeMapping(const Vector3D &xi, + const DynamicVector &physicalNodesCoords, + ShapeEvaluationFlags requestedShape, + MappingEvaluationFlags requestedMapping, + ShapeInfo &shape, + MappingInfo &out) const; + + void computeMapping(const Vector3D &xi, + const DynamicVector &physicalNodesCoords, + MappingEvaluationFlags requestedMapping, + const ShapeInfo &shape, + MappingInfo &out) const; + + void computeMappingBatch(const DynamicVector &xiPoints, + const DynamicVector &physicalNodesCoords, + ShapeEvaluationFlags requestedShape, + MappingEvaluationFlags requestedMapping, + ShapeInfoBatch &shapeBatch, + MappingInfoBatch &outBatch) const; + + void computeMappingBatch(const DynamicVector &xiPoints, + const DynamicVector &physicalNodesCoords, + MappingEvaluationFlags requestedMapping, + const ShapeInfoBatch &shapeBatch, + MappingInfoBatch &outBatch) const; /** * @brief Transforms reference gradients to physical space using J^-T. * @param shape Work buffer for reference shape functions. * @param out MappingInfo structure to be populated. */ - void computePhysicalGradients(const ShapeInfo& shape, - MappingInfo& out) const; + void computePhysicalFirstDerivatives(const ShapeInfo& shape, + MappingInfo& out) const; + + void computePhysicalFirstDerivativesBatch(const ShapeInfoBatch &shape, + MappingInfoBatch &out) const; /** * @brief Computes physical Hessians including the geometric curvature term. * @param physicalNodesCoords Actual physical coordinates of the element's nodes. * @param shape Work buffer for reference shape functions. * @param out MappingInfo structure to be populated. - * @note Reference: H_phys = J^-T * [H_ref - grad_phys(N) * H_geom] * J^-1 + * @note Reference: \$ H_{phys} = J^{-T} * [H_{ref} - \nabla_{phys}(N) * H_{geom}] * J^{-1} \$ */ - void computePhysicalHessians(const DynamicVector& physicalNodesCoords, - const ShapeInfo& shape, - MappingInfo& out) const; + void computePhysicalSecondDerivatives(const DynamicVector& physicalNodesCoords, + const ShapeInfo& shape, + MappingInfo& out) const; + + void computePhysicalSecondDerivativesBatch(const DynamicVector& physicalNodesCoords, + const ShapeInfoBatch& shapeBatch, + MappingInfoBatch& outBatch) const; void computeGeometricNormal(const ShapeInfo &shape, MappingInfo &out) const; + void computeGeometricNormalBatch(const ShapeInfoBatch &shapeBatch, MappingInfoBatch &outBatch) const; /** * @brief Interpolates a field (scalar or vector) at a point evaluated in ShapeInfo. @@ -242,10 +215,40 @@ class ReferenceElement { */ SymTensor3D interpolateHessian(const MappingInfo& map, const DynamicVector& nodalValues) const; + +private: + + /** + * @brief Computes the Jacobian, its determinant (metric), and the pseudo-inverse. + * @note This version supports 1D/2D elements embedded in 3D space using + * the Moore-Penrose pseudo-inverse logic. + */ + inline void computeJacobian(const ShapeInfo& shape, + const DynamicVector& physicalNodesCoords, + MappingInfo& out) const; + + inline void computeJacobianBatch(const ShapeInfoBatch& shape, + const DynamicVector& physicalNodesCoords, + MappingInfoBatch& out) const; + + inline void computeDeterminant(const ShapeInfo &shape, + const DynamicVector &physicalNodesCoords, + MappingInfo &out) const; + + inline void computeDeterminantBatch(const ShapeInfoBatch &shape, + const DynamicVector &physicalNodesCoords, + MappingInfoBatch &out) const; + + inline void computeInverseJacobian(const ShapeInfo &shape, + const DynamicVector &physicalNodesCoords, + MappingInfo &out) const; + + inline void computeInverseJacobianBatch(const ShapeInfoBatch &shape, + const DynamicVector &physicalNodesCoords, + MappingInfoBatch &out) const; + }; } #include "ReferenceElement.tpp" - -#endif \ No newline at end of file diff --git a/libs/fem/reference_element/include/ReferenceElement.tpp b/libs/fem/reference_element/include/ReferenceElement.tpp index 408ab01..1a88266 100644 --- a/libs/fem/reference_element/include/ReferenceElement.tpp +++ b/libs/fem/reference_element/include/ReferenceElement.tpp @@ -16,242 +16,632 @@ // --- held liable for any damages arising from the use of this software. //************************************************************************ -#ifndef CFEM_REFERENCE_ELEMENT_TPP_ -#define CFEM_REFERENCE_ELEMENT_TPP_ +namespace cfem +{ -#include "ReferenceElement.h" + inline void ReferenceElement::evaluateShapes(const Vector3D &xi, ShapeEvaluationFlags requestedShape, ShapeInfo& shape) const + { + const bool includeSecondDerivatives = hasFlag(requestedShape, ShapeEvaluationFlags::SecondDerivatives); + shape.setup(m_numNodes, includeSecondDerivatives); -#endif + if (hasFlag(requestedShape, ShapeEvaluationFlags::Value)) { + evaluateShapesValues(xi, shape.values.data()); + } + if (hasFlag(requestedShape, ShapeEvaluationFlags::FirstDerivatives)) { + evaluateShapesDerivatives(xi, shape.dN_dxi.data(), shape.dN_deta.data(), shape.dN_dzeta.data()); + } + if (includeSecondDerivatives) { + evaluateShapesSecondDerivatives(xi, + shape.d2N_dxi2.data(), shape.d2N_deta2.data(), shape.d2N_dzeta2.data(), + shape.d2N_dxideta.data(), shape.d2N_dxidzeta.data(), shape.d2N_detadzeta.data()); + } + shape.validFlags = requestedShape; -namespace cfem -{ + } + + inline void ReferenceElement::evaluateShapesBatch(const DynamicVector &xiPoints, ShapeEvaluationFlags requestedShape, ShapeInfoBatch& shapeBatch) const + { + const bool includeSecondDerivatives = hasFlag(requestedShape, ShapeEvaluationFlags::SecondDerivatives); + if (hasFlag(requestedShape, ShapeEvaluationFlags::Value)) { + evaluateShapesValuesBatch(xiPoints, shapeBatch.values); + } + if (hasFlag(requestedShape, ShapeEvaluationFlags::FirstDerivatives)) { + evaluateShapesDerivativesBatch(xiPoints, + shapeBatch.dN_dxi, + shapeBatch.dN_deta, + shapeBatch.dN_dzeta); + } + if (includeSecondDerivatives) { + evaluateShapesSecondDerivativesBatch(xiPoints, + shapeBatch.d2N_dxi2, shapeBatch.d2N_deta2, shapeBatch.d2N_dzeta2, + shapeBatch.d2N_dxideta, shapeBatch.d2N_dxidzeta, shapeBatch.d2N_detadzeta); + } + + shapeBatch.validFlags = requestedShape; + + } + // ========================================================================= + // ÉVALUATIONS PONCTUELLES (SINGLE POINT) + // ========================================================================= inline void ReferenceElement::computeMapping(const Vector3D &xi, const DynamicVector &physicalNodesCoords, - ShapeEvaluationFlags requestedFields, + ShapeEvaluationFlags requestedShape, + MappingEvaluationFlags requestedMapping, ShapeInfo &shape, MappingInfo &out) const { - const bool wantHessians = hasFlag(requestedFields, ShapeEvaluationFlags::Hessian); - - // On prépare uniquement l'objet shape (qui est non-const ici) - shape.setup(m_numNodes, wantHessians); - - if (shape.shapeMustBeUpdated) { - evaluate(xi, requestedFields, shape); + // Évaluation polymorphe des fonctions de forme sur l'élément de référence + if (shape.shapeMustBeUpdated){ + evaluateShapes(xi, requestedShape, shape); } - - // Force the use of the optimized const_cast version - computeMapping(xi, physicalNodesCoords, requestedFields, static_cast(shape), out); + + // Passage direct à la variante géométrique passive via const ShapeInfo& + computeMapping(xi, physicalNodesCoords, requestedMapping, shape, out); } inline void ReferenceElement::computeMapping(const Vector3D &xi, const DynamicVector &physicalNodesCoords, - ShapeEvaluationFlags requestedFields, + MappingEvaluationFlags requestedMapping, const ShapeInfo &shape, MappingInfo &out) const { - const CfemInt n = m_numNodes; - const bool wantPhysicalPosition = hasFlag(requestedFields, ShapeEvaluationFlags::PhysicalPosition); - const bool wantJacobian = hasFlag(requestedFields, ShapeEvaluationFlags::Jacobian); - const bool wantGradients = hasFlag(requestedFields, ShapeEvaluationFlags::Gradient); - const bool wantHessians = hasFlag(requestedFields, ShapeEvaluationFlags::Hessian); - const bool wantNormal = hasFlag(requestedFields, ShapeEvaluationFlags::Normal); + // Préparation des buffers physiques (gère l'allocation ou la réutilisation) + out.setup(m_numNodes, hasFlag(requestedMapping, MappingEvaluationFlags::PhysicalSecondDerivatives)); + + // Position physique interpolée + if (hasFlag(requestedMapping, MappingEvaluationFlags::PhysicalPosition)) { + out.physicalPosition.setZero(); + const CfemReal* N = shape.values.data(); + for (CfemInt i = 0; i < m_numNodes; ++i) { + out.physicalPosition += physicalNodesCoords[i] * N[i]; + } + } + + // Base locale / Jacobien + if (hasFlag(requestedMapping, MappingEvaluationFlags::Jacobian)) { + computeJacobian(shape, physicalNodesCoords, out); + } - out.setup(n, wantHessians); + // Déterminant (Calcule et stocke le tenseur métrique SymTensor2D/3D pour éviter le double calcul) + if (hasFlag(requestedMapping, MappingEvaluationFlags::Determinant)) { + computeDeterminant(shape, physicalNodesCoords, out); + } - CFEM_ASSERT(shape.values.size() >= m_numNodes, "Shape Info is not initialized?"); + // Inverse / Pseudo-inverse du Jacobien (Réutilise le tenseur métrique mis en cache) + if (hasFlag(requestedMapping, MappingEvaluationFlags::InverseJacobian)) { + computeInverseJacobian(shape, physicalNodesCoords, out); + } - if (wantPhysicalPosition){ - out.physicalPosition = {0, 0, 0}; - for (CfemInt i = 0; i < n; ++i){ - out.physicalPosition += physicalNodesCoords[i] * shape.values[i]; - } + // Gradients physiques SoA (dN/dx, dN/dy, dN/dz) + if (hasFlag(requestedMapping, MappingEvaluationFlags::PhysicalFirstDerivatives)) { + computePhysicalFirstDerivatives(shape, out); + } + + // Hessiens physiques translatés (avec prise en compte de la courbure géométrique) + if (hasFlag(requestedMapping, MappingEvaluationFlags::PhysicalSecondDerivatives)) { + computePhysicalSecondDerivatives(physicalNodesCoords, shape, out); } + + if (hasFlag(requestedMapping, MappingEvaluationFlags::Normal)){ + computeGeometricNormal(shape, out); + } + + out.validFlags = requestedMapping; + } + + // ========================================================================= + // ÉVALUATIONS PAR LOT (BATCH) + // ========================================================================= + + inline void ReferenceElement::computeMappingBatch(const DynamicVector &xiPoints, + const DynamicVector &physicalNodesCoords, + ShapeEvaluationFlags requestedShape, + MappingEvaluationFlags requestedMapping, + ShapeInfoBatch &shapeBatch, + MappingInfoBatch &outBatch) const + { + // Évaluation par lignes contiguës dans les DynamicMatrix + if (shapeBatch.shapeMustBeUpdated){ + evaluateShapesBatch(xiPoints, requestedShape, shapeBatch); + } + + // Redirection stricte vers la variante const pour les transformations géométriques par lot + computeMappingBatch(xiPoints, physicalNodesCoords, requestedMapping, shapeBatch, outBatch); + } + + inline void ReferenceElement::computeMappingBatch(const DynamicVector &xiPoints, + const DynamicVector &physicalNodesCoords, + MappingEvaluationFlags requestedMapping, + const ShapeInfoBatch &shapeBatch, + MappingInfoBatch &outBatch) const + { + const CfemInt nQp = static_cast(xiPoints.size()); - if (wantJacobian || wantGradients || wantHessians) { - CFEM_ASSERT(n > 1, "Jacobian/Gradients requested for P0 element!"); - computeJacobian(shape, physicalNodesCoords, out); + // Initialisation du lot physique + outBatch.setup(nQp, m_numNodes, m_dimension, hasFlag(requestedMapping, MappingEvaluationFlags::PhysicalSecondDerivatives)); + + // Interpolation des positions physiques par point de quadrature + if (hasFlag(requestedMapping, MappingEvaluationFlags::PhysicalPosition)) { + for (CfemInt q = 0; q < nQp; ++q) { + outBatch.physicalPositions[q].setZero(); + const auto rowValues = shapeBatch.values[q]; + for (CfemInt i = 0; i < m_numNodes; ++i) { + outBatch.physicalPositions[q] += physicalNodesCoords[i] * rowValues[i]; + } + } } - if (wantGradients){ - computePhysicalGradients(shape, out); + // Évaluation des opérateurs physiques par lot (Chaque fonction traite l'ensemble du vecteur de points qp) + if (hasFlag(requestedMapping, MappingEvaluationFlags::Jacobian)) { + computeJacobianBatch(shapeBatch, physicalNodesCoords, outBatch); } - if (wantHessians) { - computePhysicalHessians(physicalNodesCoords, shape, out); + if (hasFlag(requestedMapping, MappingEvaluationFlags::Determinant)) { + computeDeterminantBatch(shapeBatch, physicalNodesCoords, outBatch); } - if (wantNormal) { - computeGeometricNormal(shape, out); + if (hasFlag(requestedMapping, MappingEvaluationFlags::InverseJacobian)) { + computeInverseJacobianBatch(shapeBatch, physicalNodesCoords, outBatch); + } + + if (hasFlag(requestedMapping, MappingEvaluationFlags::PhysicalFirstDerivatives)) { + computePhysicalFirstDerivativesBatch(shapeBatch, outBatch); + } + + if (hasFlag(requestedMapping, MappingEvaluationFlags::PhysicalSecondDerivatives)) { + computePhysicalSecondDerivativesBatch(physicalNodesCoords, shapeBatch, outBatch); + } + + if (hasFlag(requestedMapping, MappingEvaluationFlags::Normal)){ + computeGeometricNormalBatch(shapeBatch, outBatch); } + + outBatch.validFlags = requestedMapping; } - inline void ReferenceElement::computeJacobian(const ShapeInfo &shape, - const DynamicVector &physicalNodesCoords, - MappingInfo &out) const + inline void ReferenceElement::computeJacobian(const ShapeInfo& shape, + const DynamicVector& physicalNodesCoords, + MappingInfo& out) const { - const CfemInt n = static_cast(shape.gradients.size()); - CFEM_ASSERT(n>0, "Shape Gradient Info must be ready prior to calling computeJacobian" ); - const CfemInt dim = m_dimension; + const CfemInt n = shape.numNodes; - // OPTIMISATION MAJEURE : Accumulation dans les registres CPU. - // On initialise 9 doubles locaux. Le compilateur les placera dans les registres xmm/ymm. CfemReal j00 = 0.0, j01 = 0.0, j02 = 0.0; CfemReal j10 = 0.0, j11 = 0.0, j12 = 0.0; CfemReal j20 = 0.0, j21 = 0.0, j22 = 0.0; + // Pointeurs SoA pour ce point de quadrature précis + const CfemReal* dxi = shape.dN_dxi.data(); + const CfemReal* deta = shape.dN_deta.data(); + const CfemReal* dzeta = shape.dN_dzeta.data(); + for (CfemInt i = 0; i < n; ++i) { const Vector3D &X = physicalNodesCoords[i]; - const Vector3D &gradN = shape.gradients[i]; - - j00 += X.x * gradN.x; j10 += X.y * gradN.x; j20 += X.z * gradN.x; - j01 += X.x * gradN.y; j11 += X.y * gradN.y; j21 += X.z * gradN.y; - j02 += X.x * gradN.z; j12 += X.y * gradN.z; j22 += X.z * gradN.z; + j00 += X.x * dxi[i]; j10 += X.y * dxi[i]; j20 += X.z * dxi[i]; + j01 += X.x * deta[i]; j11 += X.y * deta[i]; j21 += X.z * deta[i]; + j02 += X.x * dzeta[i]; j12 += X.y * dzeta[i]; j22 += X.z * dzeta[i]; } - // On écrit le résultat dans la mémoire structure une seule fois à la fin. - out.jacobian(0, 0) = j00; out.jacobian(0, 1) = j01; out.jacobian(0, 2) = j02; - out.jacobian(1, 0) = j10; out.jacobian(1, 1) = j11; out.jacobian(1, 2) = j12; - out.jacobian(2, 0) = j20; out.jacobian(2, 1) = j21; out.jacobian(2, 2) = j22; + out.jacobian.data[Tensor3D::idx_xx] = j00; out.jacobian.data[Tensor3D::idx_xy] = j01; out.jacobian.data[Tensor3D::idx_xz] = j02; + out.jacobian.data[Tensor3D::idx_yx] = j10; out.jacobian.data[Tensor3D::idx_yy] = j11; out.jacobian.data[Tensor3D::idx_yz] = j12; + out.jacobian.data[Tensor3D::idx_zx] = j20; out.jacobian.data[Tensor3D::idx_zy] = j21; out.jacobian.data[Tensor3D::idx_zz] = j22; + } + - if (dim == 3) + inline void ReferenceElement::computeJacobianBatch(const ShapeInfoBatch& shape, + const DynamicVector& physicalNodesCoords, + MappingInfoBatch& out) const + { + const CfemInt nQp = shape.numQuadraturePoints; + const CfemInt n = shape.numNodes; + + for (CfemInt q = 0; q < nQp; ++q) { - out.detJ = det(out.jacobian); - CFEM_ASSERT(std::abs(out.detJ) > ZERO_TOLERANCE, "Degenerate Jacobian detected"); - out.invJacobian = inverse(out.jacobian, out.detJ); + CfemReal j00 = 0.0, j01 = 0.0, j02 = 0.0; + CfemReal j10 = 0.0, j11 = 0.0, j12 = 0.0; + CfemReal j20 = 0.0, j21 = 0.0, j22 = 0.0; + + // Pointeurs SoA pour ce point de quadrature précis + const CfemReal* dxi = shape.dN_dxi[q].data(); + const CfemReal* deta = shape.dN_deta[q].data(); + const CfemReal* dzeta = shape.dN_dzeta[q].data(); + + for (CfemInt i = 0; i < n; ++i) + { + const Vector3D &X = physicalNodesCoords[i]; + j00 += X.x * dxi[i]; j10 += X.y * dxi[i]; j20 += X.z * dxi[i]; + j01 += X.x * deta[i]; j11 += X.y * deta[i]; j21 += X.z * deta[i]; + j02 += X.x * dzeta[i]; j12 += X.y * dzeta[i]; j22 += X.z * dzeta[i]; + } + + out.jacobians[q].data[Tensor3D::idx_xx] = j00; out.jacobians[q].data[Tensor3D::idx_xy] = j01; out.jacobians[q].data[Tensor3D::idx_xz] = j02; + out.jacobians[q].data[Tensor3D::idx_yx] = j10; out.jacobians[q].data[Tensor3D::idx_yy] = j11; out.jacobians[q].data[Tensor3D::idx_yz] = j12; + out.jacobians[q].data[Tensor3D::idx_zx] = j20; out.jacobians[q].data[Tensor3D::idx_zy] = j21; out.jacobians[q].data[Tensor3D::idx_zz] = j22; } - else if (dim == 2) - { - // tangent vectors + } + + inline void ReferenceElement::computeDeterminant(const ShapeInfo &shape, + const DynamicVector &physicalNodesCoords, + MappingInfo &out) const + { + if (m_dimension == 3) { + out.detJ =det(out.jacobian); + } + else if (m_dimension == 2) { + // Métrique de surface + const CfemReal j00 = out.jacobian.data[Tensor3D::idx_xx], j01 = out.jacobian.data[Tensor3D::idx_xy]; + const CfemReal j10 = out.jacobian.data[Tensor3D::idx_yx], j11 = out.jacobian.data[Tensor3D::idx_yy]; + const CfemReal j20 = out.jacobian.data[Tensor3D::idx_zx], j21 = out.jacobian.data[Tensor3D::idx_zy]; + CfemReal g11 = j00*j00 + j10*j10 + j20*j20; CfemReal g22 = j01*j01 + j11*j11 + j21*j21; CfemReal g12 = j00*j01 + j10*j11 + j20*j21; - CfemReal detG = g11 * g22 - g12 * g12; - out.detJ = std::sqrt(std::max(0.0, detG)); + out.metric2D = SymTensor2D(g11, g22, g12); + out.detJ = std::sqrt(std::max(0.0, g11 * g22 - g12 * g12)); + } + else if (m_dimension == 1) { + const CfemReal j00 = out.jacobian.data[Tensor3D::idx_xx]; + const CfemReal j10 = out.jacobian.data[Tensor3D::idx_yx]; + const CfemReal j20 = out.jacobian.data[Tensor3D::idx_zx]; + out.metric1D = j00*j00 + j10*j10 + j20*j20; + out.detJ = std::sqrt(out.metric1D); + } + } + + inline void ReferenceElement::computeDeterminantBatch(const ShapeInfoBatch &shape, + const DynamicVector &physicalNodesCoords, + MappingInfoBatch &out) const + { + const CfemInt nQp = shape.numQuadraturePoints; - CFEM_ASSERT(std::abs(out.detJ) > ZERO_TOLERANCE, "Degenerate Jacobian detected"); - CfemReal invDetG = 1.0 / detG; - for (CfemInt i = 0; i < 3; ++i) - { + if (m_dimension == 3) { + for (CfemInt q = 0; q < nQp; ++q){ + out.detJs[q] = det(out.jacobians[q]); + } + } + else if (m_dimension == 2) { + // Métrique de surface + for (CfemInt q = 0; q < nQp; ++q){ + const CfemReal j00 = out.jacobians[q].data[Tensor3D::idx_xx], j01 = out.jacobians[q].data[Tensor3D::idx_xy]; + const CfemReal j10 = out.jacobians[q].data[Tensor3D::idx_yx], j11 = out.jacobians[q].data[Tensor3D::idx_yy]; + const CfemReal j20 = out.jacobians[q].data[Tensor3D::idx_zx], j21 = out.jacobians[q].data[Tensor3D::idx_zy]; + + CfemReal g11 = j00*j00 + j10*j10 + j20*j20; + CfemReal g22 = j01*j01 + j11*j11 + j21*j21; + CfemReal g12 = j00*j01 + j10*j11 + j20*j21; + + out.metric2Ds[q] = SymTensor2D{g11, g22, g12}; + out.detJs[q] = std::sqrt(std::max(0.0, g11 * g22 - g12 * g12)); + } + } + else if (m_dimension == 1) { + for (CfemInt q = 0; q < nQp; ++q){ + const CfemReal j00 = out.jacobians[q].data[Tensor3D::idx_xx]; + const CfemReal j10 = out.jacobians[q].data[Tensor3D::idx_yx]; + const CfemReal j20 = out.jacobians[q].data[Tensor3D::idx_zx]; + CfemReal g11 = j00*j00 + j10*j10 + j20*j20; + + out.metric1Ds[q] = g11; + out.detJs[q] = std::sqrt(g11); + } + } + + } + + inline void ReferenceElement::computeInverseJacobian(const ShapeInfo &shape, + const DynamicVector &physicalNodesCoords, + MappingInfo &out) const + { + CFEM_ASSERT(std::abs(out.detJ) > cfem::ZERO_TOLERANCE, "Degenerate Jacobian detected"); + + if (m_dimension == 3) { + out.invJacobian = inverse(out.jacobian, out.detJ); + } + else if (m_dimension == 2) { + const CfemReal g11 = out.metric2D.xx(); + const CfemReal g22 = out.metric2D.yy(); + const CfemReal g12 = out.metric2D.xy(); + + CfemReal invDetG = 1.0 / (g11 * g22 - g12 * g12); + + for (CfemInt i = 0; i < 3; ++i) { out.invJacobian(0, i) = (g22 * out.jacobian(i, 0) - g12 * out.jacobian(i, 1)) * invDetG; out.invJacobian(1, i) = (g11 * out.jacobian(i, 1) - g12 * out.jacobian(i, 0)) * invDetG; out.invJacobian(2, i) = 0.0; } } - else if (dim == 1) - { - CfemReal g11 = j00*j00 + j10*j10 + j20*j20; - out.detJ = std::sqrt(g11); - CfemReal invG11 = 1.0 / g11; - for (CfemInt i = 0; i < 3; ++i) - { + else if (m_dimension == 1) { + CfemReal invG11 = 1.0 / out.metric1D; + for (CfemInt i = 0; i < 3; ++i) { out.invJacobian(i, 0) = out.jacobian(i, 0) * invG11; out.invJacobian(i, 1) = 0.0; out.invJacobian(i, 2) = 0.0; } } + } + inline void ReferenceElement::computeInverseJacobianBatch(const ShapeInfoBatch &shape, + const DynamicVector &physicalNodesCoords, + MappingInfoBatch &out) const + { + const CfemInt nQp = shape.numQuadraturePoints; + CFEM_ASSERT(std::abs(out.detJs[0]) > cfem::ZERO_TOLERANCE, "Degenerate Jacobian detected"); + + + if (m_dimension == 3) { + for (CfemInt q = 0; q < nQp; ++q){ + out.invJacobians[q] = inverse(out.jacobians[q], out.detJs[q]); + } + } + else if (m_dimension == 2) { + for (CfemInt q = 0; q < nQp; ++q){ + const CfemReal g11 = out.metric2Ds[q].xx(); + const CfemReal g22 = out.metric2Ds[q].yy(); + const CfemReal g12 = out.metric2Ds[q].xy(); + + CfemReal invDetG = 1.0 / (g11 * g22 - g12 * g12); + + for (CfemInt i = 0; i < 3; ++i) { + out.invJacobians[q](0, i) = (g22 * out.jacobians[q](i, 0) - g12 * out.jacobians[q](i, 1)) * invDetG; + out.invJacobians[q](1, i) = (g11 * out.jacobians[q](i, 1) - g12 * out.jacobians[q](i, 0)) * invDetG; + out.invJacobians[q](2, i) = 0.0; + } + } + } + else if (m_dimension == 1) { + for (CfemInt q = 0; q < nQp; ++q){ + CfemReal invG11 = 1.0 / out.metric1Ds[q]; + for (CfemInt i = 0; i < 3; ++i) { + out.invJacobians[q](i, 0) = out.jacobians[q](i, 0) * invG11; + out.invJacobians[q](i, 1) = 0.0; + out.invJacobians[q](i, 2) = 0.0; + } + } + } + } - inline void ReferenceElement::computePhysicalGradients(const ShapeInfo &shape, - MappingInfo &out) const + inline void ReferenceElement::computePhysicalFirstDerivatives(const ShapeInfo &shape, + MappingInfo &out) const { - const CfemInt n = static_cast(shape.gradients.size()); + const CfemInt n = shape.numNodes; - // Cache de la transposée de l'inverse du Jacobien dans des variables locales. // J^{-T} appliqué au vecteur local. - const CfemReal i00 = out.invJacobian(0,0), i01 = out.invJacobian(0,1), i02 = out.invJacobian(0,2); - const CfemReal i10 = out.invJacobian(1,0), i11 = out.invJacobian(1,1), i12 = out.invJacobian(1,2); - const CfemReal i20 = out.invJacobian(2,0), i21 = out.invJacobian(2,1), i22 = out.invJacobian(2,2); + const CfemReal i00 = out.invJacobian.xx(), i01 = out.invJacobian.xy(), i02 = out.invJacobian.xz(); + const CfemReal i10 = out.invJacobian.yx(), i11 = out.invJacobian.yy(), i12 = out.invJacobian.yz(); + const CfemReal i20 = out.invJacobian.zx(), i21 = out.invJacobian.zy(), i22 = out.invJacobian.zz(); + + // Pointeurs SoA (lecture) + const CfemReal* dxi = shape.dN_dxi.data(); + const CfemReal* deta = shape.dN_deta.data(); + const CfemReal* dzeta = shape.dN_dzeta.data(); + // Pointeurs SoA (écriture) + CfemReal* dx = out.dN_dx.data(); + CfemReal* dy = out.dN_dy.data(); + CfemReal* dz = out.dN_dz.data(); + + // Boucle SIMD ultra-rapide for (CfemInt a = 0; a < n; ++a) { - const Vector3D& gRef = shape.gradients[a]; - - out.physicalShapeGradients[a].x = i00 * gRef.x + i10 * gRef.y + i20 * gRef.z; - out.physicalShapeGradients[a].y = i01 * gRef.x + i11 * gRef.y + i21 * gRef.z; - out.physicalShapeGradients[a].z = i02 * gRef.x + i12 * gRef.y + i22 * gRef.z; + dx[a] = i00 * dxi[a] + i10 * deta[a] + i20 * dzeta[a]; + dy[a] = i01 * dxi[a] + i11 * deta[a] + i21 * dzeta[a]; + dz[a] = i02 * dxi[a] + i12 * deta[a] + i22 * dzeta[a]; + } + } + + inline void ReferenceElement::computePhysicalFirstDerivativesBatch(const ShapeInfoBatch &shape, + MappingInfoBatch &out) const + { + const CfemInt nQp = shape.numQuadraturePoints; + const CfemInt n = shape.numNodes; + + for (CfemInt q = 0; q < nQp; ++q) + { + // Cache des composantes de l'inverse du Jacobien dans les registres pour le point q + const CfemReal i00 = out.invJacobians[q](0,0), i01 = out.invJacobians[q](0,1), i02 = out.invJacobians[q](0,2); + const CfemReal i10 = out.invJacobians[q](1,0), i11 = out.invJacobians[q](1,1), i12 = out.invJacobians[q](1,2); + const CfemReal i20 = out.invJacobians[q](2,0), i21 = out.invJacobians[q](2,1), i22 = out.invJacobians[q](2,2); + + // Pointeurs SoA vers les lignes de la matrice de référence (Lecture) + const CfemReal* dxi = shape.dN_dxi[q].data(); + const CfemReal* deta = shape.dN_deta[q].data(); + const CfemReal* dzeta = shape.dN_dzeta[q].data(); + + // Pointeurs SoA vers les lignes de la matrice physique (Écriture) + CfemReal* dx = out.dN_dx[q].data(); + CfemReal* dy = out.dN_dy[q].data(); + CfemReal* dz = out.dN_dz[q].data(); + + // Boucle sur les nœuds interne : vectorisation SIMD maximale + for (CfemInt a = 0; a < n; ++a) + { + dx[a] = i00 * dxi[a] + i10 * deta[a] + i20 * dzeta[a]; + dy[a] = i01 * dxi[a] + i11 * deta[a] + i21 * dzeta[a]; + dz[a] = i02 * dxi[a] + i12 * deta[a] + i22 * dzeta[a]; + } } } - inline void ReferenceElement::computePhysicalHessians(const DynamicVector &physicalNodesCoords, - const ShapeInfo &shape, - MappingInfo &out) const + inline void ReferenceElement::computePhysicalSecondDerivatives(const DynamicVector &physicalNodesCoords, + const ShapeInfo &shape, + MappingInfo &out) const { - const CfemInt n = static_cast(shape.hessians.size()); + const CfemInt n = shape.numNodes; - // Calcul de la courbure géométrique (Hessienne de la transformation) - // On utilise des variables locales pour accumuler les 6 composantes indépendantes (xx, yy, zz, xy, xz, yz) - // pour chaque direction (X, Y, Z). + // Étape 1 : Calcul de la courbure géométrique (H_geom) CfemReal hX_xx = 0, hX_yy = 0, hX_zz = 0, hX_xy = 0, hX_xz = 0, hX_yz = 0; CfemReal hY_xx = 0, hY_yy = 0, hY_zz = 0, hY_xy = 0, hY_xz = 0, hY_yz = 0; CfemReal hZ_xx = 0, hZ_yy = 0, hZ_zz = 0, hZ_xy = 0, hZ_xz = 0, hZ_yz = 0; + // Pointeurs SoA pour les Hessiens de référence + const CfemReal* d2xi2 = shape.d2N_dxi2.data(); + const CfemReal* d2eta2 = shape.d2N_deta2.data(); + const CfemReal* d2zeta2 = shape.d2N_dzeta2.data(); + const CfemReal* d2xideta = shape.d2N_dxideta.data(); + const CfemReal* d2xidzeta = shape.d2N_dxidzeta.data(); + const CfemReal* d2etadzeta = shape.d2N_detadzeta.data(); + for (CfemInt a = 0; a < n; ++a) { - const auto &hRef = shape.hessians[a]; - const auto &pos = physicalNodesCoords[a]; - - hX_xx += hRef.xx() * pos.x; hX_yy += hRef.yy() * pos.x; hX_zz += hRef.zz() * pos.x; - hX_xy += hRef.xy() * pos.x; hX_xz += hRef.xz() * pos.x; hX_yz += hRef.yz() * pos.x; - - hY_xx += hRef.xx() * pos.y; hY_yy += hRef.yy() * pos.y; hY_zz += hRef.zz() * pos.y; - hY_xy += hRef.xy() * pos.y; hY_xz += hRef.xz() * pos.y; hY_yz += hRef.yz() * pos.y; - - hZ_xx += hRef.xx() * pos.z; hZ_yy += hRef.yy() * pos.z; hZ_zz += hRef.zz() * pos.z; - hZ_xy += hRef.xy() * pos.z; hZ_xz += hRef.xz() * pos.z; hZ_yz += hRef.yz() * pos.z; + const Vector3D &pos = physicalNodesCoords[a]; + + // Chargement local depuis les tableaux + CfemReal hxx = d2xi2[a]; + CfemReal hyy = d2eta2[a]; + CfemReal hzz = d2zeta2[a]; + CfemReal hxy = d2xideta[a]; + CfemReal hxz = d2xidzeta[a]; + CfemReal hyz = d2etadzeta[a]; + + hX_xx += hxx * pos.x; hX_yy += hyy * pos.x; hX_zz += hzz * pos.x; + hX_xy += hxy * pos.x; hX_xz += hxz * pos.x; hX_yz += hyz * pos.x; + + hY_xx += hxx * pos.y; hY_yy += hyy * pos.y; hY_zz += hzz * pos.y; + hY_xy += hxy * pos.y; hY_xz += hxz * pos.y; hY_yz += hyz * pos.y; + + hZ_xx += hxx * pos.z; hZ_yy += hyy * pos.z; hZ_zz += hzz * pos.z; + hZ_xy += hxy * pos.z; hZ_xz += hxz * pos.z; hZ_yz += hyz * pos.z; } const CfemReal i00 = out.invJacobian(0,0), i01 = out.invJacobian(0,1), i02 = out.invJacobian(0,2); const CfemReal i10 = out.invJacobian(1,0), i11 = out.invJacobian(1,1), i12 = out.invJacobian(1,2); const CfemReal i20 = out.invJacobian(2,0), i21 = out.invJacobian(2,1), i22 = out.invJacobian(2,2); - // Helper lambda pour le calcul de chaque composante p,q + // Helper lambda pour la transformation de congruence auto computeComp = [&](CfemInt p, CfemInt q, CfemReal r_xx, CfemReal r_yy, CfemReal r_zz, - CfemReal r_xy, CfemReal r_xz, CfemReal r_yz) { - // Puisque R est symétrique : R01=R10, R02=R20, R12=R21 - // Termes diagonaux de R : i=j + CfemReal r_xy, CfemReal r_xz, CfemReal r_yz) { CfemReal res = out.invJacobian(0,p)*r_xx*out.invJacobian(0,q) + out.invJacobian(1,p)*r_yy*out.invJacobian(1,q) + out.invJacobian(2,p)*r_zz*out.invJacobian(2,q); - // Termes croisés de R : i != j (facteur 2 car R_ij = R_ji) res += (out.invJacobian(0,p)*r_xy*out.invJacobian(1,q) + out.invJacobian(1,p)*r_xy*out.invJacobian(0,q)) + (out.invJacobian(0,p)*r_xz*out.invJacobian(2,q) + out.invJacobian(2,p)*r_xz*out.invJacobian(0,q)) + (out.invJacobian(1,p)*r_yz*out.invJacobian(2,q) + out.invJacobian(2,p)*r_yz*out.invJacobian(1,q)); return res; }; - // Transformation vers l'espace physique + // Pointeurs SoA pour la lecture des gradients physiques + const CfemReal* dNdx = out.dN_dx.data(); + const CfemReal* dNdy = out.dN_dy.data(); + const CfemReal* dNdz = out.dN_dz.data(); + + // Pointeurs SoA pour l'écriture des Hessiens physiques + CfemReal* d2x2 = out.d2N_dx2.data(); + CfemReal* d2y2 = out.d2N_dy2.data(); + CfemReal* d2z2 = out.d2N_dz2.data(); + CfemReal* dxdy = out.d2N_dxdy.data(); + CfemReal* dxdz = out.d2N_dxdz.data(); + CfemReal* dydz = out.d2N_dydz.data(); + + // --- Étape 2 : Matrice R et Transformation vers l'espace physique --- for (CfemInt a = 0; a < n; ++a) { - const auto &hRef = shape.hessians[a]; - const CfemReal dNdx = out.dNdx(a); - const CfemReal dNdy = out.dNdy(a); - const CfemReal dNdz = out.dNdz(a); - // R = H_ref - sum( dN/dx_k * H_geom_k ) - // On calcule les 6 composantes de R directement - CfemReal r_xx = hRef.xx() - (dNdx * hX_xx + dNdy * hY_xx + dNdz * hZ_xx); - CfemReal r_yy = hRef.yy() - (dNdx * hX_yy + dNdy * hY_yy + dNdz * hZ_yy); - CfemReal r_zz = hRef.zz() - (dNdx * hX_zz + dNdy * hY_zz + dNdz * hZ_zz); - CfemReal r_xy = hRef.xy() - (dNdx * hX_xy + dNdy * hY_xy + dNdz * hZ_xy); - CfemReal r_xz = hRef.xz() - (dNdx * hX_xz + dNdy * hY_xz + dNdz * hZ_xz); - CfemReal r_yz = hRef.yz() - (dNdx * hX_yz + dNdy * hY_yz + dNdz * hZ_yz); - - // Transformation de congruence (équivalent à J^-T * R * J^-1) - // Pour une matrice symétrique R, la composante (p,q) de J^-T R J^-1 est: - // H_phys_pq = sum_i sum_j (invJ_ip * R_ij * invJ_jq) - auto& hPhys = out.physicalShapeHessians[a]; + CfemReal r_xx = d2xi2[a] - (dNdx[a] * hX_xx + dNdy[a] * hY_xx + dNdz[a] * hZ_xx); + CfemReal r_yy = d2eta2[a] - (dNdx[a] * hX_yy + dNdy[a] * hY_yy + dNdz[a] * hZ_yy); + CfemReal r_zz = d2zeta2[a] - (dNdx[a] * hX_zz + dNdy[a] * hY_zz + dNdz[a] * hZ_zz); + CfemReal r_xy = d2xideta[a] - (dNdx[a] * hX_xy + dNdy[a] * hY_xy + dNdz[a] * hZ_xy); + CfemReal r_xz = d2xidzeta[a] - (dNdx[a] * hX_xz + dNdy[a] * hY_xz + dNdz[a] * hZ_xz); + CfemReal r_yz = d2etadzeta[a] - (dNdx[a] * hX_yz + dNdy[a] * hY_yz + dNdz[a] * hZ_yz); - hPhys.xx() = computeComp(0, 0, r_xx, r_yy, r_zz, r_xy, r_xz, r_yz); - hPhys.yy() = computeComp(1, 1, r_xx, r_yy, r_zz, r_xy, r_xz, r_yz); - hPhys.zz() = computeComp(2, 2, r_xx, r_yy, r_zz, r_xy, r_xz, r_yz); - hPhys.xy() = computeComp(0, 1, r_xx, r_yy, r_zz, r_xy, r_xz, r_yz); - hPhys.xz() = computeComp(0, 2, r_xx, r_yy, r_zz, r_xy, r_xz, r_yz); - hPhys.yz() = computeComp(1, 2, r_xx, r_yy, r_zz, r_xy, r_xz, r_yz); + // Écriture directe et contiguë des 6 composantes (Congruence) + d2x2[a] = computeComp(0, 0, r_xx, r_yy, r_zz, r_xy, r_xz, r_yz); + d2y2[a] = computeComp(1, 1, r_xx, r_yy, r_zz, r_xy, r_xz, r_yz); + d2z2[a] = computeComp(2, 2, r_xx, r_yy, r_zz, r_xy, r_xz, r_yz); + dxdy[a] = computeComp(0, 1, r_xx, r_yy, r_zz, r_xy, r_xz, r_yz); + dxdz[a] = computeComp(0, 2, r_xx, r_yy, r_zz, r_xy, r_xz, r_yz); + dydz[a] = computeComp(1, 2, r_xx, r_yy, r_zz, r_xy, r_xz, r_yz); + } + } + + inline void ReferenceElement::computePhysicalSecondDerivativesBatch(const DynamicVector &physicalNodesCoords, + const ShapeInfoBatch &shape, + MappingInfoBatch &out) const + { + const CfemInt nQp = shape.numQuadraturePoints; + const CfemInt n = shape.numNodes; + + for (CfemInt q = 0; q < nQp; ++q) + { + // --- Étape 1 : Calcul de la courbure géométrique pour le point q --- + CfemReal hX_xx = 0, hX_yy = 0, hX_zz = 0, hX_xy = 0, hX_xz = 0, hX_yz = 0; + CfemReal hY_xx = 0, hY_yy = 0, hY_zz = 0, hY_xy = 0, hY_xz = 0, hY_yz = 0; + CfemReal hZ_xx = 0, hZ_yy = 0, hZ_zz = 0, hZ_xy = 0, hZ_xz = 0, hZ_yz = 0; + + const CfemReal* d2xi2 = shape.d2N_dxi2[q].data(); + const CfemReal* d2eta2 = shape.d2N_deta2[q].data(); + const CfemReal* d2zeta2 = shape.d2N_dzeta2[q].data(); + const CfemReal* d2xideta = shape.d2N_dxideta[q].data(); + const CfemReal* d2xidzeta = shape.d2N_dxidzeta[q].data(); + const CfemReal* d2etadzeta = shape.d2N_detadzeta[q].data(); + + for (CfemInt a = 0; a < n; ++a) + { + const Vector3D &pos = physicalNodesCoords[a]; + CfemReal hxx = d2xi2[a]; + CfemReal hyy = d2eta2[a]; + CfemReal hzz = d2zeta2[a]; + CfemReal hxy = d2xideta[a]; + CfemReal hxz = d2xidzeta[a]; + CfemReal hyz = d2etadzeta[a]; + + hX_xx += hxx * pos.x; hX_yy += hyy * pos.x; hX_zz += hzz * pos.x; + hX_xy += hxy * pos.x; hX_xz += hxz * pos.x; hX_yz += hyz * pos.x; + + hY_xx += hxx * pos.y; hY_yy += hyy * pos.y; hY_zz += hzz * pos.y; + hY_xy += hxy * pos.y; hY_xz += hxz * pos.y; hY_yz += hyz * pos.y; + + hZ_xx += hxx * pos.z; hZ_yy += hyy * pos.z; hZ_zz += hzz * pos.z; + hZ_xy += hxy * pos.z; hZ_xz += hxz * pos.z; hZ_yz += hyz * pos.z; + } + + // Récupération de l'inverse du Jacobien pour le point q + const auto& invJ = out.invJacobians[q]; + + // Lambda helper local pour la transformation de congruence au point q + auto computeComp = [&](CfemInt p, CfemInt qIndex, CfemReal r_xx, CfemReal r_yy, CfemReal r_zz, + CfemReal r_xy, CfemReal r_xz, CfemReal r_yz) { + CfemReal res = invJ(0,p)*r_xx*invJ(0,qIndex) + + invJ(1,p)*r_yy*invJ(1,qIndex) + + invJ(2,p)*r_zz*invJ(2,qIndex); + res += (invJ(0,p)*r_xy*invJ(1,qIndex) + invJ(1,p)*r_xy*invJ(0,qIndex)) + + (invJ(0,p)*r_xz*invJ(2,qIndex) + invJ(2,p)*r_xz*invJ(0,qIndex)) + + (invJ(1,p)*r_yz*invJ(2,qIndex) + invJ(2,p)*r_yz*invJ(0,qIndex)); + return res; + }; + + // Recouvrement des pointeurs de lignes physiques (Lecture des gradients, Écriture des Hessiens) + const CfemReal* dNdx = out.dN_dx[q].data(); + const CfemReal* dNdy = out.dN_dy[q].data(); + const CfemReal* dNdz = out.dN_dz[q].data(); + + CfemReal* d2x2 = out.d2N_dx2[q].data(); + CfemReal* d2y2 = out.d2N_dy2[q].data(); + CfemReal* d2z2 = out.d2N_dz2[q].data(); + CfemReal* dxdy = out.d2N_dxdy[q].data(); + CfemReal* dxdz = out.d2N_dxdz[q].data(); + CfemReal* dydz = out.d2N_dydz[q].data(); + + // Étape 2 : Calcul de R et application de la congruence pour chaque nœud + for (CfemInt a = 0; a < n; ++a) + { + CfemReal r_xx = d2xi2[a] - (dNdx[a] * hX_xx + dNdy[a] * hY_xx + dNdz[a] * hZ_xx); + CfemReal r_yy = d2eta2[a] - (dNdx[a] * hX_yy + dNdy[a] * hY_yy + dNdz[a] * hZ_yy); + CfemReal r_zz = d2zeta2[a] - (dNdx[a] * hX_zz + dNdy[a] * hY_zz + dNdz[a] * hZ_zz); + CfemReal r_xy = d2xideta[a] - (dNdx[a] * hX_xy + dNdy[a] * hY_xy + dNdz[a] * hZ_xy); + CfemReal r_xz = d2xidzeta[a] - (dNdx[a] * hX_xz + dNdy[a] * hY_xz + dNdz[a] * hZ_xz); + CfemReal r_yz = d2etadzeta[a] - (dNdx[a] * hX_yz + dNdy[a] * hY_yz + dNdz[a] * hZ_yz); + + // Enregistrement direct dans les structures matricielles contigues du Batch + d2x2[a] = computeComp(0, 0, r_xx, r_yy, r_zz, r_xy, r_xz, r_yz); + d2y2[a] = computeComp(1, 1, r_xx, r_yy, r_zz, r_xy, r_xz, r_yz); + d2z2[a] = computeComp(2, 2, r_xx, r_yy, r_zz, r_xy, r_xz, r_yz); + dxdy[a] = computeComp(0, 1, r_xx, r_yy, r_zz, r_xy, r_xz, r_yz); + dxdz[a] = computeComp(0, 2, r_xx, r_yy, r_zz, r_xy, r_xz, r_yz); + dydz[a] = computeComp(1, 2, r_xx, r_yy, r_zz, r_xy, r_xz, r_yz); + } } } @@ -289,20 +679,64 @@ namespace cfem out.normal = Vector3D(0.0, 0.0, 0.0); } } + + inline void ReferenceElement::computeGeometricNormalBatch(const ShapeInfoBatch &shapeBatch, + MappingInfoBatch &outBatch) const + { + const CfemInt nQp = shapeBatch.numQuadraturePoints; + CFEM_ASSERT(outBatch.jacobians.size() == nQp, "ReferenceElement::computeGeometricNormalBatch outBatch.jacobians not pre-computed?"); + if (m_dimension == 2) + { + // Variété 2D (Surface, Triangle/Quad) dans l'espace 3D + // Les deux vecteurs tangents sont les deux premières colonnes du Jacobien + for (CfemInt q = 0; q < nQp; ++q){ + Vector3D t1(outBatch.jacobians[q].xx(), outBatch.jacobians[q].yx(), outBatch.jacobians[q].zx()); + Vector3D t2(outBatch.jacobians[q].xy(), outBatch.jacobians[q].yy(), outBatch.jacobians[q].zy()); + + outBatch.normals[q] = (t1.cross(t2)).normalized(); + } + + } + else if (m_dimension == 1) + { + for (CfemInt q = 0; q < nQp; ++q){ + // Variété 1D (Ligne) dans un espace 2D (ou 3D z=0) + CfemReal dx = outBatch.jacobians[q].xx(); + CfemReal dy = outBatch.jacobians[q].yx(); + CfemReal norm = std::sqrt(dx * dx + dy * dy); + + if (norm > cfem::ZERO_TOLERANCE) { + // Règle de la main droite (rotation de -90 degrés du vecteur tangent) + // L'orientation extérieure dépend de la convention de rotation de ton maillage + outBatch.normals[q] = Vector3D(dy / norm, -dx / norm, 0.0); + } else { + outBatch.normals[q] = Vector3D(0.0, 0.0, 0.0); + } + } + } + else + { + // Dimension 3 (Volume). Geometric normal undefined inside a 3D cell. + for (CfemInt q = 0; q < nQp; ++q){ + outBatch.normals[q] = Vector3D(0.0, 0.0, 0.0); + } + } + } // ---------------------------------------------------------------------- // ------------------------------- interpolation ------------------------ - template - inline T ReferenceElement::interpolate(const ShapeInfo &shape, const DynamicVector &nodalValues) const + template + inline T ReferenceElement::interpolate(const ShapeInfo& shape, + const DynamicVector& nodalValues) const { - const CfemInt n = m_numNodes; - CFEM_ASSERT(shape.values.size() >= n, "ShapeInfo not evaluated for this element type"); + const CfemInt n = shape.numNodes; + CFEM_ASSERT(shape.has(ShapeEvaluationFlags::Value), "Shape values not computed"); CFEM_ASSERT(nodalValues.size() == n, "Nodal values size mismatch"); - T result{}; // T must have a default constructor that initialize the field with 0. + T result{}; + const CfemReal* N = shape.values.data(); - for (CfemInt i = 0; i < n; ++i) - { - result += nodalValues[i] * shape.values[i]; + for (CfemInt i = 0; i < n; ++i){ + result += nodalValues[i] * N[i]; } return result; } @@ -310,32 +744,61 @@ namespace cfem inline Vector3D ReferenceElement::interpolateGradient(const MappingInfo &map, const DynamicVector &nodalValues) const { - const CfemInt n = m_numNodes; - CFEM_ASSERT(map.physicalShapeGradients.size() >= n, "MappingInfo gradients not computed"); + const CfemInt n = map.numNodes; + CFEM_ASSERT(hasFlag(map.validFlags, MappingEvaluationFlags::PhysicalFirstDerivatives), + "Physical gradients not computed in MappingInfo"); CFEM_ASSERT(nodalValues.size() == n, "Nodal values size mismatch"); - Vector3D grad{0, 0, 0}; - for (CfemInt i = 0; i < n; ++i) - { - grad += map.physicalShapeGradients[i] * nodalValues[i]; + // Accumulateurs dans les registres CPU (ultra rapide) + CfemReal gradX = 0.0; + CfemReal gradY = 0.0; + CfemReal gradZ = 0.0; + + // Pointeurs vers la mémoire SoA + const CfemReal* dx = map.dN_dx.data(); + const CfemReal* dy = map.dN_dy.data(); + const CfemReal* dz = map.dN_dz.data(); + + for (CfemInt i = 0; i < n; ++i) { + const CfemReal u = nodalValues[i]; + gradX += dx[i] * u; + gradY += dy[i] * u; + gradZ += dz[i] * u; } - return grad; + + return Vector3D(gradX, gradY, gradZ); } inline SymTensor3D ReferenceElement::interpolateHessian(const MappingInfo &map, const DynamicVector &nodalValues) const { - const CfemInt n = m_numNodes; - CFEM_ASSERT(map.physicalShapeHessians.size() >= n, "MappingInfo hessians not computed"); + const CfemInt n = map.numNodes; + CFEM_ASSERT(hasFlag(map.validFlags, MappingEvaluationFlags::PhysicalSecondDerivatives), + "Physical hessians not computed in MappingInfo"); CFEM_ASSERT(nodalValues.size() == n, "Nodal values size mismatch"); - SymTensor3D hess; - hess.setZero(); + CfemReal h11 = 0.0, h22 = 0.0, h33 = 0.0; + CfemReal h12 = 0.0, h13 = 0.0, h23 = 0.0; + + const CfemReal* d2x = map.d2N_dx2.data(); + const CfemReal* d2y = map.d2N_dy2.data(); + const CfemReal* d2z = map.d2N_dz2.data(); + const CfemReal* dxdy = map.d2N_dxdy.data(); + const CfemReal* dxdz = map.d2N_dxdz.data(); + const CfemReal* dydz = map.d2N_dydz.data(); + for (CfemInt i = 0; i < n; ++i) { - hess += map.physicalShapeHessians[i] * nodalValues[i]; + const CfemReal u = nodalValues[i]; + h11 += d2x[i] * u; + h22 += d2y[i] * u; + h33 += d2z[i] * u; + h12 += dxdy[i] * u; + h13 += dxdz[i] * u; + h23 += dydz[i] * u; } - return hess; + + return SymTensor3D(h11, h22, h33, h23, h13, h12); } } // namespace cfem \ No newline at end of file diff --git a/libs/fem/reference_element/include/ReferenceElement2.h b/libs/fem/reference_element/include/ReferenceElement2.h deleted file mode 100644 index be1cb37..0000000 --- a/libs/fem/reference_element/include/ReferenceElement2.h +++ /dev/null @@ -1,373 +0,0 @@ -//************************************************************************ -// --- CFEM++ Library -// --- -// --- Copyright 2024-2026 Ismaël Tchinda Ngueyong et al. -// --- ALL RIGHTS RESERVED -// --- -// --- This software is protected by international copyright laws. -// --- Use, distribution, or modification of this software in any form, -// --- source or binary, for personal, academic, or non-commercial -// --- purposes is permitted free of charge, provided that this -// --- copyright notice and this permission notice appear in all -// --- copies and supporting documentation. -// --- -// --- The authors and contributors provide this code "as is" without -// --- any express or implied warranty. In no event shall they be -// --- held liable for any damages arising from the use of this software. -//************************************************************************ - -#ifndef CFEM_REFERENCE_ELEMENT_H -#define CFEM_REFERENCE_ELEMENT_H - -#include "cfemutils.h" -#include "Vector3D.h" -#include "DynamicVector.h" -#include "DynamicMatrix.h" -#include "Tensor3D.h" -#include "ShapeEvaluationFlags.h" - -namespace cfem::reference_elem -{ - static const std::vector> line_face_nodes = { - {0}, // Face 0 (Nœud gauche) - {1} // Face 1 (Nœud droit) - }; -} - -namespace cfem { - enum class Order { P0 = 0, P1 = 1, P2 = 2 , Mini = 101}; - -/** - * @struct ShapeInfo - * @brief Storage buffer for shape function evaluations at a specific quadrature point. - * @note Re-use this object across multiple integration points to avoid frequent heap allocations. - */ -struct ShapeInfo { - CfemInt numNodes = 0; - bool shapeMustBeUpdated = true; - ShapeEvaluationFlags validFlags = ShapeEvaluationFlags::None; - - // Valeurs (N_i) - DynamicVector values; - - // Gradients locaux éclatés (dN_i/dxi) - DynamicVector dN_dxi; - DynamicVector dN_deta; - DynamicVector dN_dzeta; - - // Hessiens locaux (d2N_i/dxi2) - DynamicVector hessians; - - /** - * @brief Prepares buffer sizes to prevent runtime reallocations. - * @param nNodes Number of nodes in the element. - * @param includeHessians If true, allocates memory for Hessian tensors. - */ - void setup(CfemInt nNodes, bool includeHessians = false) { - numNodes = nNodes; - - if (values.size() != static_cast(numNodes)) { - values.resize(numNodes); - dN_dxi.resize(numNodes); - dN_deta.resize(numNodes); - dN_dzeta.resize(numNodes); - } - - if (includeHessians && hessians.size() != static_cast(numNodes)) { - hessians.resize(numNodes); - } - - validFlags = ShapeEvaluationFlags::None; - } - - /** @brief Checks if a specific field (Value/Gradient/Hessian) is marked as valid. */ - bool has(ShapeEvaluationFlags field) const { - return hasFlag(validFlags, field); - } -}; - -// struct ShapeInfo { -// DynamicVector values; ///< Shape function values (N_i) -// DynamicVector gradients; ///< Local gradients w.r.t reference coordinates (dN_i/dxi) -// DynamicVector hessians; ///< Local Hessians w.r.t reference coordinates (d2N_i/dxi2) -// bool shapeMustBeUpdated = true; -// ShapeEvaluationFlags validFlags = ShapeEvaluationFlags::None; - -// /** -// * @brief Prepares buffer sizes to prevent runtime reallocations. -// * @param numNodes Number of nodes in the element. -// * @param includeHessians If true, allocates memory for Hessian tensors. -// */ -// void setup(CfemInt numNodes, bool includeHessians = false) { -// if (values.size() != static_cast(numNodes)) { -// values.resize(numNodes); -// gradients.resize(numNodes); -// } -// if (includeHessians && hessians.size() != static_cast(numNodes)) { -// hessians.resize(numNodes); -// } -// validFlags = ShapeEvaluationFlags::None; -// } - -// /** -// * @brief Checks if a specific field (Value/Gradient/Hessian) is marked as valid. -// */ -// bool has(cfem::ShapeEvaluationFlags field) const { -// return hasFlag(validFlags, field); -// } -// }; - -// d2Ndxdx, d2Ndxdy, d2Ndxdz -// d2Ndydy, d2Ndydz, d2Ndzdz -/** - * @struct MappingInfo - * @brief Stores metric data resulting from the geometric transformation (Reference -> Physical). - */ -struct MappingInfo { - Vector3D physicalPosition; ///< The mapped x, y, z coordinates - Tensor3D jacobian; ///< Jacobian matrix (dx/dxi) - Tensor3D invJacobian; ///< Inverse Jacobian matrix (dxi/dx) - CfemReal detJ; ///< Determinant of the Jacobian - Vector3D normal; - - DynamicVector physicalShapeGradients; ///< dN_i/dx (Gradients in physical space) - DynamicVector physicalShapeHessians; ///< d2N_i/dx2 (Hessians in physical space) - - /** - * @brief Prepares the mapping buffers for a specific element size. - * @param numNodes Number of nodes in the element. - * @param includeHessians Whether to allocate the Hessian buffer. - */ - void setup(CfemInt numNodes, bool includeHessians = false) { - if (physicalShapeGradients.size() != static_cast(numNodes)) { - physicalShapeGradients.resize(numNodes); - } - - if (includeHessians && physicalShapeHessians.size() != static_cast(numNodes)) { - physicalShapeHessians.resize(numNodes); - } - } - - // Quick accessors for physical gradients - inline CfemReal dNdx(CfemInt i) const { return physicalShapeGradients[i].x; } - inline CfemReal dNdy(CfemInt i) const { return physicalShapeGradients[i].y; } - inline CfemReal dNdz(CfemInt i) const { return physicalShapeGradients[i].z; } - - // Quick accessors for physical Hessians (components) - inline CfemReal d2Ndxdx(CfemInt nodeIdx) const { return physicalShapeHessians[nodeIdx].xx(); } - inline CfemReal d2Ndydy(CfemInt nodeIdx) const { return physicalShapeHessians[nodeIdx].yy(); } - inline CfemReal d2Ndzdz(CfemInt nodeIdx) const { return physicalShapeHessians[nodeIdx].zz(); } - inline CfemReal d2Ndydz(CfemInt nodeIdx) const { return physicalShapeHessians[nodeIdx].yz(); } - inline CfemReal d2Ndxdz(CfemInt nodeIdx) const { return physicalShapeHessians[nodeIdx].xz(); } - inline CfemReal d2Ndxdy(CfemInt nodeIdx) const { return physicalShapeHessians[nodeIdx].xy(); } - - void computePhysicalGradientsOnly(const ShapeInfo& fieldShape) { - // Le fait de charger dans des variables locales (registres) - // empêche le CPU de faire des allers-retours mémoire. - const CfemReal i00 = invJacobian(0,0), i01 = invJacobian(0,1), i02 = invJacobian(0,2); - const CfemReal i10 = invJacobian(1,0), i11 = invJacobian(1,1), i12 = invJacobian(1,2); - const CfemReal i20 = invJacobian(2,0), i21 = invJacobian(2,1), i22 = invJacobian(2,2); - - for (size_t i = 0; i < fieldShape.gradients.size(); ++i) { - const auto& g = fieldShape.gradients[i]; - physicalShapeGradients[i].x = i00 * g.x + i10 * g.y + i20 * g.z; - physicalShapeGradients[i].y = i01 * g.x + i11 * g.y + i21 * g.z; - physicalShapeGradients[i].z = i02 * g.x + i12 * g.y + i22 * g.z; - } - } - -}; - -struct ShapeInfoBatch { - CfemInt numQuadraturePoints = 0; - CfemInt numNodes = 0; - ShapeEvaluationFlags validFlags = ShapeEvaluationFlags::None; - - // [qp][node] - 1D contiguous memory ! - DynamicMatrix values; - - DynamicMatrix dN_dxi; - DynamicMatrix dN_deta; - DynamicMatrix dN_dzeta; - - // Hessiens (gardé tel quel pour l'instant comme compromis lisibilité/perf) - DynamicMatrix hessians; - - void setup(CfemInt nQp, CfemInt nNodes, bool withHessians = false) { - numQuadraturePoints = nQp; - numNodes = nNodes; - - // Resize préserve la capacité sous-jacente si la taille ne change pas - // (ex: passage d'un hexaèdre à un autre avec le même nb de points de Gauss) - values.resize(nQp, nNodes); - dN_dxi.resize(nQp, nNodes); - dN_deta.resize(nQp, nNodes); - dN_dzeta.resize(nQp, nNodes); - - if (withHessians) { - hessians.resize(nQp, nNodes); - } - validFlags = ShapeEvaluationFlags::None; - } -}; - -struct MappingInfoBatch { - CfemInt numQuadraturePoints = 0; - CfemInt numNodes = 0; - - // Données par point de quadrature [qp] - DynamicVector physicalPositions; - DynamicVector jacobians; - DynamicVector invJacobians; - DynamicVector detJs; - DynamicVector normals; - - // Gradients physiques éclatés [qp][node] - DynamicMatrix dNdx; - DynamicMatrix dNdy; - DynamicMatrix dNdz; - - DynamicMatrix physicalHessians; - - void setup(CfemInt nQp, CfemInt nNodes, bool withHessians = false) { - numQuadraturePoints = nQp; - numNodes = nNodes; - - physicalPositions.resize(nQp); - jacobians.resize(nQp); - invJacobians.resize(nQp); - detJs.resize(nQp); - normals.resize(nQp); - - dNdx.resize(nQp, nNodes); - dNdy.resize(nQp, nNodes); - dNdz.resize(nQp, nNodes); - - if (withHessians) { - physicalHessians.resize(nQp, nNodes); - } - } -}; - - - - -/** - * @class ReferenceElement - * @brief Abstract base class for Finite Element reference definitions. - * Provides the machinery to compute geometric mappings, Jacobians, and - * physical derivatives from local shape functions. - */ -class ReferenceElement { -protected: - DynamicVector m_nodes; ///< Node coordinates in reference space (xi) - - CfemInt m_numNodes; - CfemInt m_dimension; - -public: - virtual ~ReferenceElement() = default; - - CfemInt dimension() const { return m_dimension; }; - CfemInt getNumNodes() const { return m_numNodes; }; - virtual std::span getFaceNodes(CfemInt iFace) const = 0; - /** - * @brief Evaluates shape functions and their local derivatives at point xi. - * @param xi Target point in reference space. - * @param requested Bitmask of fields to calculate (Value, Gradient, Hessian). - * @param out ShapeInfo buffer to be filled. - */ - virtual void evaluate(const Vector3D& xi, - ShapeEvaluationFlags requested, - ShapeInfo& out) const = 0; - - virtual Vector3D projectFacetRefCoordToCellRefCoord(CfemInt localFacetId, const Vector3D& xiFacet) const = 0; - /** - * @brief Returns the coordinates of the nodes in the reference space. - */ - const DynamicVector& referenceNodes() const { return m_nodes; } - const Vector3D& getNodeCoords( CfemInt i) const { CFEM_ASSERT( i < m_nodes.size() && i >= 0 ); return m_nodes[i]; } - - - - /** - * @brief Computes the full geometric mapping at a given point xi. - * @param xi Target poCfemInt in reference space. - * @param physicalNodesCoords Actual physical coordinates of the element's nodes. - * @param requestedFields Bitmask of physical fields required (Gradients, Hessians). - * @param shape Work buffer for reference shape functions. - * @param out MappingInfo structure to be populated. - */ - void computeMapping(const Vector3D& xi, - const DynamicVector& physicalNodesCoords, - ShapeEvaluationFlags requestedFields, - ShapeInfo& shape, - MappingInfo& out) const; - - inline void computeMapping(const Vector3D &xi, - const DynamicVector &physicalNodesCoords, - ShapeEvaluationFlags requestedFields, - const ShapeInfo &shape, - MappingInfo &out) const; - - /** - * @brief Computes the Jacobian, its determinant (metric), and the pseudo-inverse. - * @note This version supports 1D/2D elements embedded in 3D space using - * the Moore-Penrose pseudo-inverse logic. - */ - void computeJacobian(const ShapeInfo& shape, - const DynamicVector& physicalNodesCoords, - MappingInfo& out) const; - - - /** - * @brief Transforms reference gradients to physical space using J^-T. - * @param shape Work buffer for reference shape functions. - * @param out MappingInfo structure to be populated. - */ - void computePhysicalGradients(const ShapeInfo& shape, - MappingInfo& out) const; - - /** - * @brief Computes physical Hessians including the geometric curvature term. - * @param physicalNodesCoords Actual physical coordinates of the element's nodes. - * @param shape Work buffer for reference shape functions. - * @param out MappingInfo structure to be populated. - * @note Reference: H_phys = J^-T * [H_ref - grad_phys(N) * H_geom] * J^-1 - */ - void computePhysicalHessians(const DynamicVector& physicalNodesCoords, - const ShapeInfo& shape, - MappingInfo& out) const; - - void computeGeometricNormal(const ShapeInfo &shape, MappingInfo &out) const; - - /** - * @brief Interpolates a field (scalar or vector) at a point evaluated in ShapeInfo. - * @tparam T The field type (e.g., CfemReal, Vector3D, or even Tensor types). - * @param shape Pre-evaluated shape functions at point xi. - * @param nodalValues The values of the field at the element nodes. - */ - template - T interpolate(const ShapeInfo& shape, const DynamicVector& nodalValues) const; - - /** - * @brief Interpolates the physical gradient of a scalar field at a point. - * @return Vector3D containing [du/dx, du/dy, du/dz] - */ - Vector3D interpolateGradient(const MappingInfo& map, - const DynamicVector& nodalValues) const; - - - /** - * @brief Interpolates the physical Hessian of a scalar field. - * @return SymTensor3D representing the 2nd derivative matrix. - */ - SymTensor3D interpolateHessian(const MappingInfo& map, - const DynamicVector& nodalValues) const; -}; - -} - -#include "ReferenceElement.tpp" - -#endif \ No newline at end of file diff --git a/libs/fem/reference_element/include/ReferenceElement2.tpp b/libs/fem/reference_element/include/ReferenceElement2.tpp deleted file mode 100644 index 408ab01..0000000 --- a/libs/fem/reference_element/include/ReferenceElement2.tpp +++ /dev/null @@ -1,341 +0,0 @@ -//************************************************************************ -// --- CFEM++ Library -// --- -// --- Copyright 2024-2026 Ismaël Tchinda Ngueyong et al. -// --- ALL RIGHTS RESERVED -// --- -// --- This software is protected by international copyright laws. -// --- Use, distribution, or modification of this software in any form, -// --- source or binary, for personal, academic, or non-commercial -// --- purposes is permitted free of charge, provided that this -// --- copyright notice and this permission notice appear in all -// --- copies and supporting documentation. -// --- -// --- The authors and contributors provide this code "as is" without -// --- any express or implied warranty. In no event shall they be -// --- held liable for any damages arising from the use of this software. -//************************************************************************ - -#ifndef CFEM_REFERENCE_ELEMENT_TPP_ -#define CFEM_REFERENCE_ELEMENT_TPP_ - -#include "ReferenceElement.h" - -#endif - -namespace cfem -{ - - inline void ReferenceElement::computeMapping(const Vector3D &xi, - const DynamicVector &physicalNodesCoords, - ShapeEvaluationFlags requestedFields, - ShapeInfo &shape, - MappingInfo &out) const - { - const bool wantHessians = hasFlag(requestedFields, ShapeEvaluationFlags::Hessian); - - // On prépare uniquement l'objet shape (qui est non-const ici) - shape.setup(m_numNodes, wantHessians); - - if (shape.shapeMustBeUpdated) { - evaluate(xi, requestedFields, shape); - } - - // Force the use of the optimized const_cast version - computeMapping(xi, physicalNodesCoords, requestedFields, static_cast(shape), out); - } - - inline void ReferenceElement::computeMapping(const Vector3D &xi, - const DynamicVector &physicalNodesCoords, - ShapeEvaluationFlags requestedFields, - const ShapeInfo &shape, - MappingInfo &out) const - { - const CfemInt n = m_numNodes; - const bool wantPhysicalPosition = hasFlag(requestedFields, ShapeEvaluationFlags::PhysicalPosition); - const bool wantJacobian = hasFlag(requestedFields, ShapeEvaluationFlags::Jacobian); - const bool wantGradients = hasFlag(requestedFields, ShapeEvaluationFlags::Gradient); - const bool wantHessians = hasFlag(requestedFields, ShapeEvaluationFlags::Hessian); - const bool wantNormal = hasFlag(requestedFields, ShapeEvaluationFlags::Normal); - - out.setup(n, wantHessians); - - CFEM_ASSERT(shape.values.size() >= m_numNodes, "Shape Info is not initialized?"); - - if (wantPhysicalPosition){ - out.physicalPosition = {0, 0, 0}; - for (CfemInt i = 0; i < n; ++i){ - out.physicalPosition += physicalNodesCoords[i] * shape.values[i]; - } - } - - if (wantJacobian || wantGradients || wantHessians) { - CFEM_ASSERT(n > 1, "Jacobian/Gradients requested for P0 element!"); - computeJacobian(shape, physicalNodesCoords, out); - } - - if (wantGradients){ - computePhysicalGradients(shape, out); - } - - if (wantHessians) { - computePhysicalHessians(physicalNodesCoords, shape, out); - } - - if (wantNormal) { - computeGeometricNormal(shape, out); - } - } - - inline void ReferenceElement::computeJacobian(const ShapeInfo &shape, - const DynamicVector &physicalNodesCoords, - MappingInfo &out) const - { - const CfemInt n = static_cast(shape.gradients.size()); - CFEM_ASSERT(n>0, "Shape Gradient Info must be ready prior to calling computeJacobian" ); - const CfemInt dim = m_dimension; - - // OPTIMISATION MAJEURE : Accumulation dans les registres CPU. - // On initialise 9 doubles locaux. Le compilateur les placera dans les registres xmm/ymm. - CfemReal j00 = 0.0, j01 = 0.0, j02 = 0.0; - CfemReal j10 = 0.0, j11 = 0.0, j12 = 0.0; - CfemReal j20 = 0.0, j21 = 0.0, j22 = 0.0; - - for (CfemInt i = 0; i < n; ++i) - { - const Vector3D &X = physicalNodesCoords[i]; - const Vector3D &gradN = shape.gradients[i]; - - j00 += X.x * gradN.x; j10 += X.y * gradN.x; j20 += X.z * gradN.x; - j01 += X.x * gradN.y; j11 += X.y * gradN.y; j21 += X.z * gradN.y; - j02 += X.x * gradN.z; j12 += X.y * gradN.z; j22 += X.z * gradN.z; - } - - // On écrit le résultat dans la mémoire structure une seule fois à la fin. - out.jacobian(0, 0) = j00; out.jacobian(0, 1) = j01; out.jacobian(0, 2) = j02; - out.jacobian(1, 0) = j10; out.jacobian(1, 1) = j11; out.jacobian(1, 2) = j12; - out.jacobian(2, 0) = j20; out.jacobian(2, 1) = j21; out.jacobian(2, 2) = j22; - - if (dim == 3) - { - out.detJ = det(out.jacobian); - CFEM_ASSERT(std::abs(out.detJ) > ZERO_TOLERANCE, "Degenerate Jacobian detected"); - out.invJacobian = inverse(out.jacobian, out.detJ); - } - else if (dim == 2) - { - // tangent vectors - CfemReal g11 = j00*j00 + j10*j10 + j20*j20; - CfemReal g22 = j01*j01 + j11*j11 + j21*j21; - CfemReal g12 = j00*j01 + j10*j11 + j20*j21; - - CfemReal detG = g11 * g22 - g12 * g12; - out.detJ = std::sqrt(std::max(0.0, detG)); - - CFEM_ASSERT(std::abs(out.detJ) > ZERO_TOLERANCE, "Degenerate Jacobian detected"); - CfemReal invDetG = 1.0 / detG; - for (CfemInt i = 0; i < 3; ++i) - { - out.invJacobian(0, i) = (g22 * out.jacobian(i, 0) - g12 * out.jacobian(i, 1)) * invDetG; - out.invJacobian(1, i) = (g11 * out.jacobian(i, 1) - g12 * out.jacobian(i, 0)) * invDetG; - out.invJacobian(2, i) = 0.0; - } - } - else if (dim == 1) - { - CfemReal g11 = j00*j00 + j10*j10 + j20*j20; - out.detJ = std::sqrt(g11); - CfemReal invG11 = 1.0 / g11; - for (CfemInt i = 0; i < 3; ++i) - { - out.invJacobian(i, 0) = out.jacobian(i, 0) * invG11; - out.invJacobian(i, 1) = 0.0; - out.invJacobian(i, 2) = 0.0; - } - } - - } - - inline void ReferenceElement::computePhysicalGradients(const ShapeInfo &shape, - MappingInfo &out) const - { - const CfemInt n = static_cast(shape.gradients.size()); - - // Cache de la transposée de l'inverse du Jacobien dans des variables locales. - // J^{-T} appliqué au vecteur local. - const CfemReal i00 = out.invJacobian(0,0), i01 = out.invJacobian(0,1), i02 = out.invJacobian(0,2); - const CfemReal i10 = out.invJacobian(1,0), i11 = out.invJacobian(1,1), i12 = out.invJacobian(1,2); - const CfemReal i20 = out.invJacobian(2,0), i21 = out.invJacobian(2,1), i22 = out.invJacobian(2,2); - - for (CfemInt a = 0; a < n; ++a) - { - const Vector3D& gRef = shape.gradients[a]; - - out.physicalShapeGradients[a].x = i00 * gRef.x + i10 * gRef.y + i20 * gRef.z; - out.physicalShapeGradients[a].y = i01 * gRef.x + i11 * gRef.y + i21 * gRef.z; - out.physicalShapeGradients[a].z = i02 * gRef.x + i12 * gRef.y + i22 * gRef.z; - } - } - - inline void ReferenceElement::computePhysicalHessians(const DynamicVector &physicalNodesCoords, - const ShapeInfo &shape, - MappingInfo &out) const - { - const CfemInt n = static_cast(shape.hessians.size()); - - // Calcul de la courbure géométrique (Hessienne de la transformation) - // On utilise des variables locales pour accumuler les 6 composantes indépendantes (xx, yy, zz, xy, xz, yz) - // pour chaque direction (X, Y, Z). - CfemReal hX_xx = 0, hX_yy = 0, hX_zz = 0, hX_xy = 0, hX_xz = 0, hX_yz = 0; - CfemReal hY_xx = 0, hY_yy = 0, hY_zz = 0, hY_xy = 0, hY_xz = 0, hY_yz = 0; - CfemReal hZ_xx = 0, hZ_yy = 0, hZ_zz = 0, hZ_xy = 0, hZ_xz = 0, hZ_yz = 0; - - for (CfemInt a = 0; a < n; ++a) - { - const auto &hRef = shape.hessians[a]; - const auto &pos = physicalNodesCoords[a]; - - hX_xx += hRef.xx() * pos.x; hX_yy += hRef.yy() * pos.x; hX_zz += hRef.zz() * pos.x; - hX_xy += hRef.xy() * pos.x; hX_xz += hRef.xz() * pos.x; hX_yz += hRef.yz() * pos.x; - - hY_xx += hRef.xx() * pos.y; hY_yy += hRef.yy() * pos.y; hY_zz += hRef.zz() * pos.y; - hY_xy += hRef.xy() * pos.y; hY_xz += hRef.xz() * pos.y; hY_yz += hRef.yz() * pos.y; - - hZ_xx += hRef.xx() * pos.z; hZ_yy += hRef.yy() * pos.z; hZ_zz += hRef.zz() * pos.z; - hZ_xy += hRef.xy() * pos.z; hZ_xz += hRef.xz() * pos.z; hZ_yz += hRef.yz() * pos.z; - } - - const CfemReal i00 = out.invJacobian(0,0), i01 = out.invJacobian(0,1), i02 = out.invJacobian(0,2); - const CfemReal i10 = out.invJacobian(1,0), i11 = out.invJacobian(1,1), i12 = out.invJacobian(1,2); - const CfemReal i20 = out.invJacobian(2,0), i21 = out.invJacobian(2,1), i22 = out.invJacobian(2,2); - - // Helper lambda pour le calcul de chaque composante p,q - auto computeComp = [&](CfemInt p, CfemInt q, CfemReal r_xx, CfemReal r_yy, CfemReal r_zz, - CfemReal r_xy, CfemReal r_xz, CfemReal r_yz) { - // Puisque R est symétrique : R01=R10, R02=R20, R12=R21 - // Termes diagonaux de R : i=j - CfemReal res = out.invJacobian(0,p)*r_xx*out.invJacobian(0,q) + - out.invJacobian(1,p)*r_yy*out.invJacobian(1,q) + - out.invJacobian(2,p)*r_zz*out.invJacobian(2,q); - // Termes croisés de R : i != j (facteur 2 car R_ij = R_ji) - res += (out.invJacobian(0,p)*r_xy*out.invJacobian(1,q) + out.invJacobian(1,p)*r_xy*out.invJacobian(0,q)) + - (out.invJacobian(0,p)*r_xz*out.invJacobian(2,q) + out.invJacobian(2,p)*r_xz*out.invJacobian(0,q)) + - (out.invJacobian(1,p)*r_yz*out.invJacobian(2,q) + out.invJacobian(2,p)*r_yz*out.invJacobian(1,q)); - return res; - }; - - // Transformation vers l'espace physique - for (CfemInt a = 0; a < n; ++a) - { - const auto &hRef = shape.hessians[a]; - const CfemReal dNdx = out.dNdx(a); - const CfemReal dNdy = out.dNdy(a); - const CfemReal dNdz = out.dNdz(a); - - // R = H_ref - sum( dN/dx_k * H_geom_k ) - // On calcule les 6 composantes de R directement - CfemReal r_xx = hRef.xx() - (dNdx * hX_xx + dNdy * hY_xx + dNdz * hZ_xx); - CfemReal r_yy = hRef.yy() - (dNdx * hX_yy + dNdy * hY_yy + dNdz * hZ_yy); - CfemReal r_zz = hRef.zz() - (dNdx * hX_zz + dNdy * hY_zz + dNdz * hZ_zz); - CfemReal r_xy = hRef.xy() - (dNdx * hX_xy + dNdy * hY_xy + dNdz * hZ_xy); - CfemReal r_xz = hRef.xz() - (dNdx * hX_xz + dNdy * hY_xz + dNdz * hZ_xz); - CfemReal r_yz = hRef.yz() - (dNdx * hX_yz + dNdy * hY_yz + dNdz * hZ_yz); - - // Transformation de congruence (équivalent à J^-T * R * J^-1) - // Pour une matrice symétrique R, la composante (p,q) de J^-T R J^-1 est: - // H_phys_pq = sum_i sum_j (invJ_ip * R_ij * invJ_jq) - auto& hPhys = out.physicalShapeHessians[a]; - - hPhys.xx() = computeComp(0, 0, r_xx, r_yy, r_zz, r_xy, r_xz, r_yz); - hPhys.yy() = computeComp(1, 1, r_xx, r_yy, r_zz, r_xy, r_xz, r_yz); - hPhys.zz() = computeComp(2, 2, r_xx, r_yy, r_zz, r_xy, r_xz, r_yz); - hPhys.xy() = computeComp(0, 1, r_xx, r_yy, r_zz, r_xy, r_xz, r_yz); - hPhys.xz() = computeComp(0, 2, r_xx, r_yy, r_zz, r_xy, r_xz, r_yz); - hPhys.yz() = computeComp(1, 2, r_xx, r_yy, r_zz, r_xy, r_xz, r_yz); - } - } - - - inline void ReferenceElement::computeGeometricNormal(const ShapeInfo &shape, MappingInfo &out) const - { - if (m_dimension == 2) - { - // Variété 2D (Surface, Triangle/Quad) dans l'espace 3D - // Les deux vecteurs tangents sont les deux premières colonnes du Jacobien - Vector3D t1(out.jacobian(0, 0), out.jacobian(1, 0), out.jacobian(2, 0)); - Vector3D t2(out.jacobian(0, 1), out.jacobian(1, 1), out.jacobian(2, 1)); - - out.normal = (t1.cross(t2)).normalized(); - - } - else if (m_dimension == 1) - { - // Variété 1D (Ligne) dans un espace 2D (ou 3D z=0) - CfemReal dx = out.jacobian(0, 0); - CfemReal dy = out.jacobian(1, 0); - CfemReal norm = std::sqrt(dx * dx + dy * dy); - - if (norm > cfem::ZERO_TOLERANCE) { - // Règle de la main droite (rotation de -90 degrés du vecteur tangent) - // L'orientation extérieure dépend de la convention de rotation de ton maillage - out.normal = Vector3D(dy / norm, -dx / norm, 0.0); - } else { - out.normal = Vector3D(0.0, 0.0, 0.0); - } - } - else - { - // Dimension 3 (Volume). Geometric normal undefined inside a 3D cell. - out.normal = Vector3D(0.0, 0.0, 0.0); - } - } - // ---------------------------------------------------------------------- - // ------------------------------- interpolation ------------------------ - template - inline T ReferenceElement::interpolate(const ShapeInfo &shape, const DynamicVector &nodalValues) const - { - const CfemInt n = m_numNodes; - CFEM_ASSERT(shape.values.size() >= n, "ShapeInfo not evaluated for this element type"); - CFEM_ASSERT(nodalValues.size() == n, "Nodal values size mismatch"); - - T result{}; // T must have a default constructor that initialize the field with 0. - - for (CfemInt i = 0; i < n; ++i) - { - result += nodalValues[i] * shape.values[i]; - } - return result; - } - - inline Vector3D ReferenceElement::interpolateGradient(const MappingInfo &map, - const DynamicVector &nodalValues) const - { - const CfemInt n = m_numNodes; - CFEM_ASSERT(map.physicalShapeGradients.size() >= n, "MappingInfo gradients not computed"); - CFEM_ASSERT(nodalValues.size() == n, "Nodal values size mismatch"); - - Vector3D grad{0, 0, 0}; - for (CfemInt i = 0; i < n; ++i) - { - grad += map.physicalShapeGradients[i] * nodalValues[i]; - } - return grad; - } - - inline SymTensor3D ReferenceElement::interpolateHessian(const MappingInfo &map, - const DynamicVector &nodalValues) const - { - const CfemInt n = m_numNodes; - CFEM_ASSERT(map.physicalShapeHessians.size() >= n, "MappingInfo hessians not computed"); - CFEM_ASSERT(nodalValues.size() == n, "Nodal values size mismatch"); - - SymTensor3D hess; - hess.setZero(); - for (CfemInt i = 0; i < n; ++i) - { - hess += map.physicalShapeHessians[i] * nodalValues[i]; - } - return hess; - } - -} // namespace cfem \ No newline at end of file diff --git a/libs/fem/reference_element/include/ReferenceHexahedron.h b/libs/fem/reference_element/include/ReferenceHexahedron.h index 3316c2c..9bf4855 100644 --- a/libs/fem/reference_element/include/ReferenceHexahedron.h +++ b/libs/fem/reference_element/include/ReferenceHexahedron.h @@ -6,13 +6,13 @@ // --- // --- This software is protected by international copyright laws. // --- Use, distribution, or modification of this software in any form, -// --- source or binary, for personal, academic, or non-commercial -// --- purposes is permitted free of charge, provided that this -// --- copyright notice and this permission notice appear in all +// --- source or binary, for personal, academic, or non-commercial +// --- purposes is permitted free of charge, provided that this +// --- copyright notice and this permission notice appear in all // --- copies and supporting documentation. // --- -// --- The authors and contributors provide this code "as is" without -// --- any express or implied warranty. In no event shall they be +// --- The authors and contributors provide this code "as is" without +// --- any express or implied warranty. In no event shall they be // --- held liable for any damages arising from the use of this software. //************************************************************************ @@ -21,22 +21,31 @@ #include "ReferenceElement.h" -namespace cfem::reference_elem::utils{ +namespace cfem::reference_elem::utils +{ - inline Vector3D projectFacetRefCoordToHexaRefCoord(CfemInt localFacetId, - const Vector3D& xiFacet) + inline Vector3D projectFacetRefCoordToHexaRefCoord(CfemInt localFacetId, + const Vector3D &xiFacet) { const CfemReal u = xiFacet.x; const CfemReal v = xiFacet.y; - switch (localFacetId) { - case 0: return Vector3D(v, u, -1.0); // Face {0,3,2,1} : z=-1 - case 1: return Vector3D(u, v, 1.0); // Face {4,5,6,7} : z=1 - case 2: return Vector3D(u, -1.0, v); // Face {0,1,5,4} : y=-1 - case 3: return Vector3D(1.0, u, v); // Face {1,2,6,5} : x=1 - case 4: return Vector3D(-u, 1.0, v); // Face {2,3,7,6} : y=1 - case 5: return Vector3D(-1.0, -u, v); // Face {3,0,4,7} : x=-1 - default: CFEM_ERROR << "Invalid facet ID for Hexa"; + switch (localFacetId) + { + case 0: + return Vector3D(v, u, -1.0); // Face {0,3,2,1} : z=-1 + case 1: + return Vector3D(u, v, 1.0); // Face {4,5,6,7} : z=1 + case 2: + return Vector3D(u, -1.0, v); // Face {0,1,5,4} : y=-1 + case 3: + return Vector3D(1.0, u, v); // Face {1,2,6,5} : x=1 + case 4: + return Vector3D(-u, 1.0, v); // Face {2,3,7,6} : y=1 + case 5: + return Vector3D(-1.0, -u, v); // Face {3,0,4,7} : x=-1 + default: + CFEM_ERROR << "Invalid facet ID for Hexa"; } return Vector3D{}; // Make the compiler happy } @@ -51,310 +60,915 @@ namespace cfem::reference_elem::utils{ }; static const std::vector> hex27_face_nodes = { - {0, 3, 2, 1, 11, 10, 9, 8, 24}, // Face 0 - {4, 5, 6, 7, 12, 13, 14, 15, 25}, // Face 1 - {0, 1, 5, 4, 8, 17, 12, 16, 22}, // Face 2 - {1, 2, 6, 5, 9, 18, 13, 17, 21}, // Face 3 - {2, 3, 7, 6, 10, 19, 14, 18, 23}, // Face 4 - {3, 0, 4, 7, 11, 16, 15, 19, 20} // Face 5 + {0, 3, 2, 1, 11, 10, 9, 8, 24}, // Face 0 + {4, 5, 6, 7, 12, 13, 14, 15, 25}, // Face 1 + {0, 1, 5, 4, 8, 17, 12, 16, 22}, // Face 2 + {1, 2, 6, 5, 9, 18, 13, 17, 21}, // Face 3 + {2, 3, 7, 6, 10, 19, 14, 18, 23}, // Face 4 + {3, 0, 4, 7, 11, 16, 15, 19, 20} // Face 5 }; static const std::vector> hex1_face_nodes = { - {0}, {0}, {0}, {0}, {0}, {0} - }; + {0}, {0}, {0}, {0}, {0}, {0}}; } -namespace cfem{ +namespace cfem +{ - class Q1ReferenceHexahedron : public ReferenceElement { + class Q1ReferenceHexahedron : public ReferenceElement + { public: - Q1ReferenceHexahedron() { + Q1ReferenceHexahedron() + { m_numNodes = 8; m_dimension = 3; m_nodes.resize(m_numNodes); // Ordre VTK : Face Z=-1 (0,1,2,3), puis Face Z=1 (4,5,6,7) - m_nodes[0] = {-1,-1,-1}; m_nodes[1] = { 1,-1,-1}; m_nodes[2] = { 1, 1,-1}; m_nodes[3] = {-1, 1,-1}; - m_nodes[4] = {-1,-1, 1}; m_nodes[5] = { 1,-1, 1}; m_nodes[6] = { 1, 1, 1}; m_nodes[7] = {-1, 1, 1}; + m_nodes[0] = {-1, -1, -1}; + m_nodes[1] = {1, -1, -1}; + m_nodes[2] = {1, 1, -1}; + m_nodes[3] = {-1, 1, -1}; + m_nodes[4] = {-1, -1, 1}; + m_nodes[5] = {1, -1, 1}; + m_nodes[6] = {1, 1, 1}; + m_nodes[7] = {-1, 1, 1}; + } + + void evaluateShapesValues(const Vector3D &xi, CfemReal *values) const override + { + const CfemReal xm = 1.0 - xi.x; + const CfemReal xp = 1.0 + xi.x; + const CfemReal ym = 1.0 - xi.y; + const CfemReal yp = 1.0 + xi.y; + const CfemReal zm = 1.0 - xi.z; + const CfemReal zp = 1.0 + xi.z; + + constexpr CfemReal o8 = 0.125; // 1.0 / 8.0 + + values[0] = o8 * xm * ym * zm; + values[1] = o8 * xp * ym * zm; + values[2] = o8 * xp * yp * zm; + values[3] = o8 * xm * yp * zm; + values[4] = o8 * xm * ym * zp; + values[5] = o8 * xp * ym * zp; + values[6] = o8 * xp * yp * zp; + values[7] = o8 * xm * yp * zp; } - void evaluate(const Vector3D& xi, ShapeEvaluationFlags requested, ShapeInfo& out) const override { - const CfemInt n = m_numNodes; - if (out.values.size() != n) out.setup(n); - if ((hasFlag(requested, ShapeEvaluationFlags::Hessian)) && out.hessians.size() != n) - out.hessians.assign(n, SymTensor3D()); + void evaluateShapesDerivatives(const Vector3D &xi, + CfemReal *dN_dxi, + CfemReal *dN_deta, + CfemReal *dN_dzeta) const override + { + const CfemReal xm = 1.0 - xi.x; + const CfemReal xp = 1.0 + xi.x; + const CfemReal ym = 1.0 - xi.y; + const CfemReal yp = 1.0 + xi.y; + const CfemReal zm = 1.0 - xi.z; + const CfemReal zp = 1.0 + xi.z; + + constexpr CfemReal o8 = 0.125; + + // First derivatives with respect to xi (x) + dN_dxi[0] = -o8 * ym * zm; + dN_dxi[1] = o8 * ym * zm; + dN_dxi[2] = o8 * yp * zm; + dN_dxi[3] = -o8 * yp * zm; + dN_dxi[4] = -o8 * ym * zp; + dN_dxi[5] = o8 * ym * zp; + dN_dxi[6] = o8 * yp * zp; + dN_dxi[7] = -o8 * yp * zp; + + // First derivatives with respect to eta (y) + dN_deta[0] = -o8 * xm * zm; + dN_deta[1] = -o8 * xp * zm; + dN_deta[2] = o8 * xp * zm; + dN_deta[3] = o8 * xm * zm; + dN_deta[4] = -o8 * xm * zp; + dN_deta[5] = -o8 * xp * zp; + dN_deta[6] = o8 * xp * zp; + dN_deta[7] = o8 * xm * zp; + + // First derivatives with respect to zeta (z) + dN_dzeta[0] = -o8 * xm * ym; + dN_dzeta[1] = -o8 * xp * ym; + dN_dzeta[2] = -o8 * xp * yp; + dN_dzeta[3] = -o8 * xm * yp; + dN_dzeta[4] = o8 * xm * ym; + dN_dzeta[5] = o8 * xp * ym; + dN_dzeta[6] = o8 * xp * yp; + dN_dzeta[7] = o8 * xm * yp; + } - const CfemReal r = xi.x, s = xi.y, t = xi.z; - const CfemReal xr[8] = {-1, 1, 1, -1, -1, 1, 1, -1}; - const CfemReal xs[8] = {-1, -1, 1, 1, -1, -1, 1, 1}; - const CfemReal xt[8] = {-1, -1, -1, -1, 1, 1, 1, 1}; + void evaluateShapesSecondDerivatives(const Vector3D &xi, + CfemReal *d2N_dxi2, + CfemReal *d2N_deta2, + CfemReal *d2N_dzeta2, + CfemReal *d2N_dxideta, + CfemReal *d2N_dxidzeta, + CfemReal *d2N_detadzeta) const override + { + const CfemReal xm = 1.0 - xi.x; + const CfemReal xp = 1.0 + xi.x; + const CfemReal ym = 1.0 - xi.y; + const CfemReal yp = 1.0 + xi.y; + const CfemReal zm = 1.0 - xi.z; + const CfemReal zp = 1.0 + xi.z; + + constexpr CfemReal o8 = 0.125; + + // Pure second derivatives are strictly zero for trilinear fields + for (CfemInt i = 0; i < 8; ++i) + { + d2N_dxi2[i] = 0.0; + d2N_deta2[i] = 0.0; + d2N_dzeta2[i] = 0.0; + } - for (CfemInt i = 0; i < n; ++i) { - CfemReal pr = 1.0 + xr[i]*r; - CfemReal ps = 1.0 + xs[i]*s; - CfemReal pt = 1.0 + xt[i]*t; + // Cross derivatives: d2N / (dxi * deta) + d2N_dxideta[0] = o8 * zm; + d2N_dxideta[1] = -o8 * zm; + d2N_dxideta[2] = o8 * zm; + d2N_dxideta[3] = -o8 * zm; + d2N_dxideta[4] = o8 * zp; + d2N_dxideta[5] = -o8 * zp; + d2N_dxideta[6] = o8 * zp; + d2N_dxideta[7] = -o8 * zp; + + // Cross derivatives: d2N / (dxi * dzeta) + d2N_dxidzeta[0] = o8 * ym; + d2N_dxidzeta[1] = -o8 * ym; + d2N_dxidzeta[2] = -o8 * yp; + d2N_dxidzeta[3] = o8 * yp; + d2N_dxidzeta[4] = -o8 * ym; + d2N_dxidzeta[5] = o8 * ym; + d2N_dxidzeta[6] = o8 * yp; + d2N_dxidzeta[7] = -o8 * yp; + + // Cross derivatives: d2N / (deta * dzeta) + d2N_detadzeta[0] = o8 * xm; + d2N_detadzeta[1] = o8 * xp; + d2N_detadzeta[2] = -o8 * xp; + d2N_detadzeta[3] = -o8 * xm; + d2N_detadzeta[4] = -o8 * xm; + d2N_detadzeta[5] = -o8 * xp; + d2N_detadzeta[6] = o8 * xp; + d2N_detadzeta[7] = o8 * xm; + } - if (hasFlag(requested, ShapeEvaluationFlags::Value)) - out.values[i] = 0.125 * pr * ps * pt; + void evaluateShapesValuesBatch(const DynamicVector &xiPoints, + DynamicMatrix &valuesBatch) const override + { + constexpr CfemReal o8 = 0.125; + + const CfemInt nQp = static_cast(xiPoints.size()); + for (CfemInt q = 0; q < nQp; ++q) + { + const CfemReal xm = 1.0 - xiPoints[q].x; + const CfemReal xp = 1.0 + xiPoints[q].x; + const CfemReal ym = 1.0 - xiPoints[q].y; + const CfemReal yp = 1.0 + xiPoints[q].y; + const CfemReal zm = 1.0 - xiPoints[q].z; + const CfemReal zp = 1.0 + xiPoints[q].z; + + valuesBatch[q][0] = o8 * xm * ym * zm; + valuesBatch[q][1] = o8 * xp * ym * zm; + valuesBatch[q][2] = o8 * xp * yp * zm; + valuesBatch[q][3] = o8 * xm * yp * zm; + valuesBatch[q][4] = o8 * xm * ym * zp; + valuesBatch[q][5] = o8 * xp * ym * zp; + valuesBatch[q][6] = o8 * xp * yp * zp; + valuesBatch[q][7] = o8 * xm * yp * zp; + } + } - if (hasFlag(requested, ShapeEvaluationFlags::Gradient)) { - out.gradients[i].x = 0.125 * xr[i] * ps * pt; - out.gradients[i].y = 0.125 * xs[i] * pr * pt; - out.gradients[i].z = 0.125 * xt[i] * pr * ps; - } + void evaluateShapesDerivativesBatch(const DynamicVector &xiPoints, + DynamicMatrix &dN_dxi, + DynamicMatrix &dN_deta, + DynamicMatrix &dN_dzeta) const override + { + constexpr CfemReal o8 = 0.125; + + const CfemInt nQp = static_cast(xiPoints.size()); + for (CfemInt q = 0; q < nQp; ++q) + { + const Vector3D &xi = xiPoints[q]; + const CfemReal xm = 1.0 - xi.x; + const CfemReal xp = 1.0 + xi.x; + const CfemReal ym = 1.0 - xi.y; + const CfemReal yp = 1.0 + xi.y; + const CfemReal zm = 1.0 - xi.z; + const CfemReal zp = 1.0 + xi.z; + + // d/dxi + dN_dxi[q][0] = -o8 * ym * zm; + dN_dxi[q][1] = o8 * ym * zm; + dN_dxi[q][2] = o8 * yp * zm; + dN_dxi[q][3] = -o8 * yp * zm; + dN_dxi[q][4] = -o8 * ym * zp; + dN_dxi[q][5] = o8 * ym * zp; + dN_dxi[q][6] = o8 * yp * zp; + dN_dxi[q][7] = -o8 * yp * zp; + + // d/deta + dN_deta[q][0] = -o8 * xm * zm; + dN_deta[q][1] = -o8 * xp * zm; + dN_deta[q][2] = o8 * xp * zm; + dN_deta[q][3] = o8 * xm * zm; + dN_deta[q][4] = -o8 * xm * zp; + dN_deta[q][5] = -o8 * xp * zp; + dN_deta[q][6] = o8 * xp * zp; + dN_deta[q][7] = o8 * xm * zp; + + // d/dzeta + dN_dzeta[q][0] = -o8 * xm * ym; + dN_dzeta[q][1] = -o8 * xp * ym; + dN_dzeta[q][2] = -o8 * xp * yp; + dN_dzeta[q][3] = -o8 * xm * yp; + dN_dzeta[q][4] = o8 * xm * ym; + dN_dzeta[q][5] = o8 * xp * ym; + dN_dzeta[q][6] = o8 * xp * yp; + dN_dzeta[q][7] = o8 * xm * yp; + } + } - if (hasFlag(requested, ShapeEvaluationFlags::Hessian)) { - out.hessians[i].setZero(); - out.hessians[i].xy() = 0.125 * xr[i] * xs[i] * pt; - out.hessians[i].xz() = 0.125 * xr[i] * ps * xt[i]; - out.hessians[i].yz() = 0.125 * pr * xs[i] * xt[i]; + void evaluateShapesSecondDerivativesBatch(const DynamicVector &xiPoints, + DynamicMatrix &d2N_dxi2, + DynamicMatrix &d2N_deta2, + DynamicMatrix &d2N_dzeta2, + DynamicMatrix &d2N_dxideta, + DynamicMatrix &d2N_dxidzeta, + DynamicMatrix &d2N_detadzeta) const override + { + constexpr CfemReal o8 = 0.125; + + const CfemInt nQp = static_cast(xiPoints.size()); + for (CfemInt q = 0; q < nQp; ++q) + { + const Vector3D &xi = xiPoints[q]; + const CfemReal xm = 1.0 - xi.x; + const CfemReal xp = 1.0 + xi.x; + const CfemReal ym = 1.0 - xi.y; + const CfemReal yp = 1.0 + xi.y; + const CfemReal zm = 1.0 - xi.z; + const CfemReal zp = 1.0 + xi.z; + + for (CfemInt i = 0; i < 8; ++i) + { + d2N_dxi2[q][i] = 0.0; + d2N_deta2[q][i] = 0.0; + d2N_dzeta2[q][i] = 0.0; } + + // d2N / (dxi * deta) + d2N_dxideta[q][0] = o8 * zm; + d2N_dxideta[q][1] = -o8 * zm; + d2N_dxideta[q][2] = o8 * zm; + d2N_dxideta[q][3] = -o8 * zm; + d2N_dxideta[q][4] = o8 * zp; + d2N_dxideta[q][5] = -o8 * zp; + d2N_dxideta[q][6] = o8 * zp; + d2N_dxideta[q][7] = -o8 * zp; + + // d2N / (dxi * dzeta) + d2N_dxidzeta[q][0] = o8 * ym; + d2N_dxidzeta[q][1] = -o8 * ym; + d2N_dxidzeta[q][2] = -o8 * yp; + d2N_dxidzeta[q][3] = o8 * yp; + d2N_dxidzeta[q][4] = -o8 * ym; + d2N_dxidzeta[q][5] = o8 * ym; + d2N_dxidzeta[q][6] = o8 * yp; + d2N_dxidzeta[q][7] = -o8 * yp; + + // d2N / (deta * dzeta) + d2N_detadzeta[q][0] = o8 * xm; + d2N_detadzeta[q][1] = o8 * xp; + d2N_detadzeta[q][2] = -o8 * xp; + d2N_detadzeta[q][3] = -o8 * xm; + d2N_detadzeta[q][4] = -o8 * xm; + d2N_detadzeta[q][5] = -o8 * xp; + d2N_detadzeta[q][6] = o8 * xp; + d2N_detadzeta[q][7] = o8 * xm; } - out.validFlags = requested; } - Vector3D projectFacetRefCoordToCellRefCoord(CfemInt localFacetId, const Vector3D& xiFacet) const override{ + Vector3D projectFacetRefCoordToCellRefCoord(CfemInt localFacetId, const Vector3D &xiFacet) const override + { return cfem::reference_elem::utils::projectFacetRefCoordToHexaRefCoord(localFacetId, xiFacet); // Make the compiler happy } - std::span getFaceNodes(CfemInt iFace) const override { + std::span getFaceNodes(CfemInt iFace) const override + { return cfem::reference_elem::utils::hex8_face_nodes[iFace]; } }; + class Q2ReferenceHexahedron : public ReferenceElement + { + private: + // N[0] correspond à -1.0 | N[1] correspond à 0.0 | N[2] correspond à +1.0 + static inline void eval1D_shape(CfemReal u, CfemReal *N) + { + const CfemReal u2 = u * u; + N[0] = 0.5 * (u2 - u); + N[1] = 1.0 - u2; + N[2] = 0.5 * (u2 + u); + } + + static inline void eval1D_deriv(CfemReal u, CfemReal *N, CfemReal *dN) + { + const CfemReal u2 = u * u; + N[0] = 0.5 * (u2 - u); + dN[0] = u - 0.5; + N[1] = 1.0 - u2; + dN[1] = -2.0 * u; + N[2] = 0.5 * (u2 + u); + dN[2] = u + 0.5; + } + + static inline void eval1D_hessian(CfemReal u, CfemReal *N, CfemReal *dN, CfemReal *d2N) + { + const CfemReal u2 = u * u; + N[0] = 0.5 * (u2 - u); + dN[0] = u - 0.5; + d2N[0] = 1.0; + N[1] = 1.0 - u2; + dN[1] = -2.0 * u; + d2N[1] = -2.0; + N[2] = 0.5 * (u2 + u); + dN[2] = u + 0.5; + d2N[2] = 1.0; + } + + // i (X) parcourt : 0 (-1), 1 (0), 2 (+1) + // j (Y) parcourt : 0 (-1), 1 (0), 2 (+1) + // k (Z) parcourt : 0 (-1), 1 (0), 2 (+1) + static constexpr CfemInt node_map[3][3][3] = { + // i = 0 (X = -1.0) + { + {0, 16, 4}, // j=0 (Y=-1) | k=0,1,2 (Z=-1,0,1) + {11, 20, 15}, // j=1 (Y= 0) | k=0,1,2 (Z=-1,0,1) + {3, 19, 7} // j=2 (Y=+1) | k=0,1,2 (Z=-1,0,1) + }, + // i = 1 (X = 0.0) + { + {8, 22, 12}, // j=0 (Y=-1) | k=0,1,2 (Z=-1,0,1) + {24, 26, 25}, // j=1 (Y= 0) | k=0,1,2 (Z=-1,0,1) + {10, 23, 14} // j=2 (Y=+1) | k=0,1,2 (Z=-1,0,1) + }, + // i = 2 (X = +1.0) + { + {1, 17, 5}, // j=0 (Y=-1) | k=0,1,2 (Z=-1,0,1) + {9, 21, 13}, // j=1 (Y= 0) | k=0,1,2 (Z=-1,0,1) + {2, 18, 6} // j=2 (Y=+1) | k=0,1,2 (Z=-1,0,1) + }}; - class Q2ReferenceHexahedron : public ReferenceElement { public: - Q2ReferenceHexahedron() { + Q2ReferenceHexahedron() + { m_numNodes = 27; m_dimension = 3; m_nodes.resize(m_numNodes); // On définit les positions 1D pour r, s, t : -1.0, 0.0, 1.0 CfemReal pos[3] = {-1.0, 0.0, 1.0}; - // Mapping VTK pour HEX27 (i, j, k correspondent aux indices de pos[3]) - // Cet ordre est crucial pour passer le test de Kronecker - CfemInt vtk_map[3][3][3]; - // Sommets - vtk_map[0][0][0] = 0; vtk_map[2][0][0] = 1; vtk_map[2][2][0] = 2; vtk_map[0][2][0] = 3; - vtk_map[0][0][2] = 4; vtk_map[2][0][2] = 5; vtk_map[2][2][2] = 6; vtk_map[0][2][2] = 7; - // Milieux d'arêtes - vtk_map[1][0][0] = 8; vtk_map[2][1][0] = 9; vtk_map[1][2][0] = 10; vtk_map[0][1][0] = 11; - vtk_map[1][0][2] = 12; vtk_map[2][1][2] = 13; vtk_map[1][2][2] = 14; vtk_map[0][1][2] = 15; - vtk_map[0][0][1] = 16; vtk_map[2][0][1] = 17; vtk_map[2][2][1] = 18; vtk_map[0][2][1] = 19; - // Centres de faces - vtk_map[0][1][1] = 20; vtk_map[2][1][1] = 21; vtk_map[1][0][1] = 22; - vtk_map[1][2][1] = 23; vtk_map[1][1][0] = 24; vtk_map[1][1][2] = 25; - // Centre volume - vtk_map[1][1][1] = 26; + for (CfemInt i = 0; i < 3; ++i) + for (CfemInt j = 0; j < 3; ++j) + for (CfemInt k = 0; k < 3; ++k) + m_nodes[node_map[i][j][k]] = {pos[i], pos[j], pos[k]}; + } + + void evaluateShapesValues(const Vector3D &xi, + CfemReal *values) const override + { + CfemReal Nx[3], Ny[3], Nz[3]; + eval1D_shape(xi.x, Nx); + eval1D_shape(xi.y, Ny); + eval1D_shape(xi.z, Nz); + + for (CfemInt i = 0; i < 3; ++i) + { + for (CfemInt j = 0; j < 3; ++j) + { + for (CfemInt k = 0; k < 3; ++k) + { + values[node_map[i][j][k]] = Nx[i] * Ny[j] * Nz[k]; + } + } + } + } + + void evaluateShapesDerivatives(const Vector3D &xi, + CfemReal *dN_dxi, + CfemReal *dN_deta, + CfemReal *dN_dzeta) const override + { + + CfemReal Nx[3], Ny[3], Nz[3], dNx[3], dNy[3], dNz[3]; + eval1D_deriv(xi.x, Nx, dNx); + eval1D_deriv(xi.y, Ny, dNy); + eval1D_deriv(xi.z, Nz, dNz); for (CfemInt i = 0; i < 3; ++i) + { for (CfemInt j = 0; j < 3; ++j) + { for (CfemInt k = 0; k < 3; ++k) - m_nodes[vtk_map[i][j][k]] = {pos[i], pos[j], pos[k]}; - } - - void evaluate(const Vector3D& xi, ShapeEvaluationFlags requested, ShapeInfo& out) const override { - const CfemInt n = m_numNodes; - if (out.values.size() != n) out.setup(n); - if ((hasFlag(requested, ShapeEvaluationFlags::Hessian)) && out.hessians.size() != n) - out.hessians.assign(n, SymTensor3D()); - - const CfemReal r = xi.x, s = xi.y, t = xi.z; - - // Fonctions 1D P2 et leurs dérivées - CfemReal fr[3] = {0.5*r*(r-1.0), 1.0-r*r, 0.5*r*(r+1.0)}; - CfemReal fs[3] = {0.5*s*(s-1.0), 1.0-s*s, 0.5*s*(s+1.0)}; - CfemReal ft[3] = {0.5*t*(t-1.0), 1.0-t*t, 0.5*t*(t+1.0)}; - - CfemReal dfr[3] = {r-0.5, -2.0*r, r+0.5}; - CfemReal dfs[3] = {s-0.5, -2.0*s, s+0.5}; - CfemReal dft[3] = {t-0.5, -2.0*t, t+0.5}; - - CfemReal ddfr[3] = {1.0, -2.0, 1.0}; - CfemReal ddfs[3] = {1.0, -2.0, 1.0}; - CfemReal ddft[3] = {1.0, -2.0, 1.0}; - - // Re-déclaration locale du map pour la boucle (ou utilisation d'un membre static) - static const CfemInt map[3][3][3] = { - {{0, 16, 4}, {11, 20, 15}, {3, 19, 7}}, - {{8, 22, 12}, {24, 26, 25}, {10, 23, 14}}, - {{1, 17, 5}, {9, 21, 13}, {2, 18, 6}} - }; - - for (CfemInt i = 0; i < 3; ++i) { // r - for (CfemInt j = 0; j < 3; ++j) { // s - for (CfemInt k = 0; k < 3; ++k) { // t - CfemInt idx = map[i][j][k]; - - // if (hasFlag(requested, ShapeEvaluationFlags::Value)) - // out.values[idx] = fr[i] * fs[j] * ft[idx < 0 ? 0 : k]; // Correction logique simple - - // Plus proprement : - CfemReal val_i = fr[i], val_j = fs[j], val_k = ft[k]; - - if (hasFlag(requested, ShapeEvaluationFlags::Value)) - out.values[idx] = val_i * val_j * val_k; - - if (hasFlag(requested, ShapeEvaluationFlags::Gradient)) { - out.gradients[idx].x = dfr[i] * val_j * val_k; - out.gradients[idx].y = val_i * dfs[j] * val_k; - out.gradients[idx].z = val_i * val_j * dft[k]; + { + const CfemInt node = node_map[i][j][k]; + dN_dxi[node] = dNx[i] * Ny[j] * Nz[k]; + dN_deta[node] = Nx[i] * dNy[j] * Nz[k]; + dN_dzeta[node] = Nx[i] * Ny[j] * dNz[k]; + } + } + } + } + + void evaluateShapesSecondDerivatives(const Vector3D &xi, + CfemReal *d2N_dxi2, + CfemReal *d2N_deta2, + CfemReal *d2N_dzeta2, + CfemReal *d2N_dxideta, + CfemReal *d2N_dxidzeta, + CfemReal *d2N_detadzeta) const override + { + CfemReal Nx[3], Ny[3], Nz[3], dNx[3], dNy[3], dNz[3], d2Nx[3], d2Ny[3], d2Nz[3]; + eval1D_hessian(xi.x, Nx, dNx, d2Nx); + eval1D_hessian(xi.y, Ny, dNy, d2Ny); + eval1D_hessian(xi.z, Nz, dNz, d2Nz); + + for (CfemInt i = 0; i < 3; ++i) + { + for (CfemInt j = 0; j < 3; ++j) + { + for (CfemInt k = 0; k < 3; ++k) + { + const CfemInt node = node_map[i][j][k]; + d2N_dxi2[node] = d2Nx[i] * Ny[j] * Nz[k]; + d2N_deta2[node] = Nx[i] * d2Ny[j] * Nz[k]; + d2N_dzeta2[node] = Nx[i] * Ny[j] * d2Nz[k]; + + d2N_dxideta[node] = dNx[i] * dNy[j] * Nz[k]; + d2N_dxidzeta[node] = dNx[i] * Ny[j] * dNz[k]; + d2N_detadzeta[node] = Nx[i] * dNy[j] * dNz[k]; + } + } + } + } + + void evaluateShapesValuesBatch(const DynamicVector &xiPoints, + DynamicMatrix &valuesBatch) const override + { + const CfemInt nQp = static_cast(xiPoints.size()); + for (CfemInt q = 0; q < nQp; ++q) + { + CfemReal Nx[3], Ny[3], Nz[3]; + eval1D_shape(xiPoints[q].x, Nx); + eval1D_shape(xiPoints[q].y, Ny); + eval1D_shape(xiPoints[q].z, Nz); + + for (CfemInt i = 0; i < 3; ++i) + { + for (CfemInt j = 0; j < 3; ++j) + { + for (CfemInt k = 0; k < 3; ++k) + { + valuesBatch[q][node_map[i][j][k]] = Nx[i] * Ny[j] * Nz[k]; } + } + } + } + } + + void evaluateShapesDerivativesBatch(const DynamicVector &xiPoints, + DynamicMatrix &dN_dxi, + DynamicMatrix &dN_deta, + DynamicMatrix &dN_dzeta) const override + { + const CfemInt nQp = static_cast(xiPoints.size()); + for (CfemInt q = 0; q < nQp; ++q) + { + CfemReal Nx[3], Ny[3], Nz[3], dNx[3], dNy[3], dNz[3]; + eval1D_deriv(xiPoints[q].x, Nx, dNx); + eval1D_deriv(xiPoints[q].y, Ny, dNy); + eval1D_deriv(xiPoints[q].z, Nz, dNz); + + for (CfemInt i = 0; i < 3; ++i) + { + for (CfemInt j = 0; j < 3; ++j) + { + for (CfemInt k = 0; k < 3; ++k) + { + const CfemInt node = node_map[i][j][k]; + dN_dxi[q][node] = dNx[i] * Ny[j] * Nz[k]; + dN_deta[q][node] = Nx[i] * dNy[j] * Nz[k]; + dN_dzeta[q][node] = Nx[i] * Ny[j] * dNz[k]; + } + } + } + } + } - if (hasFlag(requested, ShapeEvaluationFlags::Hessian)) { - out.hessians[idx].setZero(); - // Diagonales - out.hessians[idx].xx() = ddfr[i] * val_j * val_k; - out.hessians[idx].yy() = val_i * ddfs[j] * val_k; - out.hessians[idx].zz() = val_i * val_j * ddft[k]; - // Croisées - out.hessians[idx].xy() = dfr[i] * dfs[j] * val_k; - out.hessians[idx].xz() = dfr[i] * val_j * dft[k]; - out.hessians[idx].yz() = val_i * dfs[j] * dft[k]; + void evaluateShapesSecondDerivativesBatch(const DynamicVector &xiPoints, + DynamicMatrix &d2N_dxi2, + DynamicMatrix &d2N_deta2, + DynamicMatrix &d2N_dzeta2, + DynamicMatrix &d2N_dxideta, + DynamicMatrix &d2N_dxidzeta, + DynamicMatrix &d2N_detadzeta) const override + { + const CfemInt nQp = static_cast(xiPoints.size()); + for (CfemInt q = 0; q < nQp; ++q) + { + CfemReal Nx[3], Ny[3], Nz[3], dNx[3], dNy[3], dNz[3], d2Nx[3], d2Ny[3], d2Nz[3]; + eval1D_hessian(xiPoints[q].x, Nx, dNx, d2Nx); + eval1D_hessian(xiPoints[q].y, Ny, dNy, d2Ny); + eval1D_hessian(xiPoints[q].z, Nz, dNz, d2Nz); + + for (CfemInt i = 0; i < 3; ++i) + { + for (CfemInt j = 0; j < 3; ++j) + { + for (CfemInt k = 0; k < 3; ++k) + { + const CfemInt node = node_map[i][j][k]; + d2N_dxi2[q][node] = d2Nx[i] * Ny[j] * Nz[k]; + d2N_deta2[q][node] = Nx[i] * d2Ny[j] * Nz[k]; + d2N_dzeta2[q][node] = Nx[i] * Ny[j] * d2Nz[k]; + + d2N_dxideta[q][node] = dNx[i] * dNy[j] * Nz[k]; + d2N_dxidzeta[q][node] = dNx[i] * Ny[j] * dNz[k]; + d2N_detadzeta[q][node] = Nx[i] * dNy[j] * dNz[k]; } } } } - out.validFlags = requested; } - Vector3D projectFacetRefCoordToCellRefCoord(CfemInt localFacetId, const Vector3D& xiFacet) const override{ + Vector3D projectFacetRefCoordToCellRefCoord(CfemInt localFacetId, const Vector3D &xiFacet) const override + { return cfem::reference_elem::utils::projectFacetRefCoordToHexaRefCoord(localFacetId, xiFacet); // Make the compiler happy } - std::span getFaceNodes(CfemInt iFace) const override { + std::span getFaceNodes(CfemInt iFace) const override + { return cfem::reference_elem::utils::hex27_face_nodes[iFace]; } }; - class MiniReferenceHexahedron : public ReferenceElement { + class MiniReferenceHexahedron : public ReferenceElement + { + protected: + static constexpr CfemReal xr[8] = {-1.0, 1.0, 1.0, -1.0, -1.0, 1.0, 1.0, -1.0}; + static constexpr CfemReal ys[8] = {-1.0, -1.0, 1.0, 1.0, -1.0, -1.0, 1.0, 1.0}; + static constexpr CfemReal zt[8] = {-1.0, -1.0, -1.0, -1.0, 1.0, 1.0, 1.0, 1.0}; + static constexpr CfemReal o8 = 0.125; + public: - MiniReferenceHexahedron() { + MiniReferenceHexahedron() + { m_numNodes = 9; // 8 sommets + 1 bulle m_dimension = 3; m_nodes.resize(m_numNodes); - + // Ordre VTK Q1 - m_nodes[0] = {-1,-1,-1}; m_nodes[1] = { 1,-1,-1}; m_nodes[2] = { 1, 1,-1}; m_nodes[3] = {-1, 1,-1}; - m_nodes[4] = {-1,-1, 1}; m_nodes[5] = { 1,-1, 1}; m_nodes[6] = { 1, 1, 1}; m_nodes[7] = {-1, 1, 1}; + m_nodes[0] = {-1, -1, -1}; + m_nodes[1] = {1, -1, -1}; + m_nodes[2] = {1, 1, -1}; + m_nodes[3] = {-1, 1, -1}; + m_nodes[4] = {-1, -1, 1}; + m_nodes[5] = {1, -1, 1}; + m_nodes[6] = {1, 1, 1}; + m_nodes[7] = {-1, 1, 1}; // Nœud Bulle au centre m_nodes[8] = {0, 0, 0}; } - void evaluate(const Vector3D& xi, ShapeEvaluationFlags requested, ShapeInfo& out) const override { - const CfemInt n = m_numNodes; - if (out.values.size() != n) out.setup(n); - if ((hasFlag(requested, ShapeEvaluationFlags::Hessian)) && out.hessians.size() != n) - out.hessians.assign(n, SymTensor3D()); - + void evaluateShapesValues(const Vector3D &xi, CfemReal *values) const override + { const CfemReal x = xi.x, y = xi.y, z = xi.z; - const CfemReal xr[8] = {-1, 1, 1, -1, -1, 1, 1, -1}; - const CfemReal ys[8] = {-1, -1, 1, 1, -1, -1, 1, 1}; - const CfemReal zt[8] = {-1, -1, -1, -1, 1, 1, 1, 1}; - - // Calcul de la Bulle - CfemReal b = (1.0 - x*x) * (1.0 - y*y) * (1.0 - z*z); - CfemReal db_dx = -2.0 * x * (1.0 - y*y) * (1.0 - z*z); - CfemReal db_dy = -2.0 * y * (1.0 - x*x) * (1.0 - z*z); - CfemReal db_dz = -2.0 * z * (1.0 - x*x) * (1.0 - y*y); - - if (hasFlag(requested, ShapeEvaluationFlags::Value)) { - out.values[8] = b; - } - if (hasFlag(requested, ShapeEvaluationFlags::Gradient)) { - out.gradients[8] = Vector3D{db_dx, db_dy, db_dz}; + + // Calcul de la bulle + const CfemReal b = (1.0 - x * x) * (1.0 - y * y) * (1.0 - z * z); + values[8] = b; + + // Calcul des sommets Q1 avec correction + const CfemReal correction = b * o8; + for (CfemInt i = 0; i < 8; ++i) + { + const CfemReal px = 1.0 + xr[i] * x; + const CfemReal py = 1.0 + ys[i] * y; + const CfemReal pz = 1.0 + zt[i] * z; + values[i] = (o8 * px * py * pz) - correction; } - if (hasFlag(requested, ShapeEvaluationFlags::Hessian)) { - out.hessians[8].setZero(); - out.hessians[8].xx() = -2.0 * (1.0 - y*y) * (1.0 - z*z); - out.hessians[8].yy() = -2.0 * (1.0 - x*x) * (1.0 - z*z); - out.hessians[8].zz() = -2.0 * (1.0 - x*x) * (1.0 - y*y); - out.hessians[8].xy() = 4.0 * x * y * (1.0 - z*z); - out.hessians[8].xz() = 4.0 * x * z * (1.0 - y*y); - out.hessians[8].yz() = 4.0 * y * z * (1.0 - x*x); + } + + void evaluateShapesDerivatives(const Vector3D &xi, + CfemReal *dN_dxi, + CfemReal *dN_deta, + CfemReal *dN_dzeta) const override + { + const CfemReal x = xi.x, y = xi.y, z = xi.z; + const CfemReal mx2 = 1.0 - x * x; + const CfemReal my2 = 1.0 - y * y; + const CfemReal mz2 = 1.0 - z * z; + + // Gradients de la bulle + const CfemReal db_dx = -2.0 * x * my2 * mz2; + const CfemReal db_dy = -2.0 * y * mx2 * mz2; + const CfemReal db_dz = -2.0 * z * mx2 * my2; + + dN_dxi[8] = db_dx; + dN_deta[8] = db_dy; + dN_dzeta[8] = db_dz; + + // Gradients Q1 corrigés + const CfemReal corr_dx = db_dx * o8; + const CfemReal corr_dy = db_dy * o8; + const CfemReal corr_dz = db_dz * o8; + + for (CfemInt i = 0; i < 8; ++i) + { + const CfemReal px = 1.0 + xr[i] * x; + const CfemReal py = 1.0 + ys[i] * y; + const CfemReal pz = 1.0 + zt[i] * z; + + dN_dxi[i] = (o8 * xr[i] * py * pz) - corr_dx; + dN_deta[i] = (o8 * ys[i] * px * pz) - corr_dy; + dN_dzeta[i] = (o8 * zt[i] * px * py) - corr_dz; } + } - // Calcul des fonctions Q1 corrigées - for (CfemInt i = 0; i < 8; ++i) { - CfemReal px = 1.0 + xr[i]*x; - CfemReal py = 1.0 + ys[i]*y; - CfemReal pz = 1.0 + zt[i]*z; + void evaluateShapesSecondDerivatives(const Vector3D &xi, + CfemReal *d2N_dxi2, CfemReal *d2N_deta2, CfemReal *d2N_dzeta2, + CfemReal *d2N_dxideta, CfemReal *d2N_dxidzeta, CfemReal *d2N_detadzeta) const override + { + const CfemReal x = xi.x, y = xi.y, z = xi.z; + const CfemReal mx2 = 1.0 - x * x; + const CfemReal my2 = 1.0 - y * y; + const CfemReal mz2 = 1.0 - z * z; + + // Hessiens de la bulle + const CfemReal d2b_dx2 = -2.0 * my2 * mz2; + const CfemReal d2b_dy2 = -2.0 * mx2 * mz2; + const CfemReal d2b_dz2 = -2.0 * mx2 * my2; + const CfemReal d2b_dxy = 4.0 * x * y * mz2; + const CfemReal d2b_dxz = 4.0 * x * z * my2; + const CfemReal d2b_dyz = 4.0 * y * z * mx2; + + d2N_dxi2[8] = d2b_dx2; + d2N_deta2[8] = d2b_dy2; + d2N_dzeta2[8] = d2b_dz2; + d2N_dxideta[8] = d2b_dxy; + d2N_dxidzeta[8] = d2b_dxz; + d2N_detadzeta[8] = d2b_dyz; + + // Hessiens Q1 corrigés + const CfemReal corr_dx2 = d2b_dx2 * o8; + const CfemReal corr_dy2 = d2b_dy2 * o8; + const CfemReal corr_dz2 = d2b_dz2 * o8; + const CfemReal corr_dxy = d2b_dxy * o8; + const CfemReal corr_dxz = d2b_dxz * o8; + const CfemReal corr_dyz = d2b_dyz * o8; + + for (CfemInt i = 0; i < 8; ++i) + { + const CfemReal px = 1.0 + xr[i] * x; + const CfemReal py = 1.0 + ys[i] * y; + const CfemReal pz = 1.0 + zt[i] * z; + + d2N_dxi2[i] = -corr_dx2; // La composante pure Q1 est nulle + d2N_deta2[i] = -corr_dy2; + d2N_dzeta2[i] = -corr_dz2; + d2N_dxideta[i] = (o8 * xr[i] * ys[i] * pz) - corr_dxy; + d2N_dxidzeta[i] = (o8 * xr[i] * py * zt[i]) - corr_dxz; + d2N_detadzeta[i] = (o8 * px * ys[i] * zt[i]) - corr_dyz; + } + } - if (hasFlag(requested, ShapeEvaluationFlags::Value)) { - out.values[i] = (0.125 * px * py * pz) - (b / 8.0); + void evaluateShapesValuesBatch(const DynamicVector &xiPoints, + DynamicMatrix &valuesBatch) const override + { + const CfemInt nQp = static_cast(xiPoints.size()); + for (CfemInt q = 0; q < nQp; ++q) + { + const CfemReal x = xiPoints[q].x, y = xiPoints[q].y, z = xiPoints[q].z; + const CfemReal b = (1.0 - x * x) * (1.0 - y * y) * (1.0 - z * z); + + valuesBatch[q][8] = b; + const CfemReal correction = b * o8; + + for (CfemInt i = 0; i < 8; ++i) + { + const CfemReal px = 1.0 + xr[i] * x; + const CfemReal py = 1.0 + ys[i] * y; + const CfemReal pz = 1.0 + zt[i] * z; + valuesBatch[q][i] = (o8 * px * py * pz) - correction; } + } + } - if (hasFlag(requested, ShapeEvaluationFlags::Gradient)) { - out.gradients[i].x = (0.125 * xr[i] * py * pz) - (db_dx / 8.0); - out.gradients[i].y = (0.125 * ys[i] * px * pz) - (db_dy / 8.0); - out.gradients[i].z = (0.125 * zt[i] * px * py) - (db_dz / 8.0); + void evaluateShapesDerivativesBatch(const DynamicVector &xiPoints, + DynamicMatrix &dN_dxi, + DynamicMatrix &dN_deta, + DynamicMatrix &dN_dzeta) const override + { + const CfemInt nQp = static_cast(xiPoints.size()); + for (CfemInt q = 0; q < nQp; ++q) + { + const CfemReal x = xiPoints[q].x, y = xiPoints[q].y, z = xiPoints[q].z; + const CfemReal mx2 = 1.0 - x * x; + const CfemReal my2 = 1.0 - y * y; + const CfemReal mz2 = 1.0 - z * z; + + const CfemReal db_dx = -2.0 * x * my2 * mz2; + const CfemReal db_dy = -2.0 * y * mx2 * mz2; + const CfemReal db_dz = -2.0 * z * mx2 * my2; + + dN_dxi[q][8] = db_dx; + dN_deta[q][8] = db_dy; + dN_dzeta[q][8] = db_dz; + + const CfemReal corr_dx = db_dx * o8; + const CfemReal corr_dy = db_dy * o8; + const CfemReal corr_dz = db_dz * o8; + + for (CfemInt i = 0; i < 8; ++i) + { + const CfemReal px = 1.0 + xr[i] * x; + const CfemReal py = 1.0 + ys[i] * y; + const CfemReal pz = 1.0 + zt[i] * z; + + dN_dxi[q][i] = (o8 * xr[i] * py * pz) - corr_dx; + dN_deta[q][i] = (o8 * ys[i] * px * pz) - corr_dy; + dN_dzeta[q][i] = (o8 * zt[i] * px * py) - corr_dz; } + } + } - if (hasFlag(requested, ShapeEvaluationFlags::Hessian)) { - out.hessians[i].setZero(); - out.hessians[i].xx() = -out.hessians[8].xx() / 8.0; - out.hessians[i].yy() = -out.hessians[8].yy() / 8.0; - out.hessians[i].zz() = -out.hessians[8].zz() / 8.0; - // Les croisées ont une partie Q1 + la correction bulle - out.hessians[i].xy() = (0.125 * xr[i] * ys[i] * pz) - (out.hessians[8].xy() / 8.0); - out.hessians[i].xz() = (0.125 * xr[i] * py * zt[i]) - (out.hessians[8].xz() / 8.0); - out.hessians[i].yz() = (0.125 * px * ys[i] * zt[i]) - (out.hessians[8].yz() / 8.0); + void evaluateShapesSecondDerivativesBatch(const DynamicVector &xiPoints, + DynamicMatrix &d2N_dxi2, + DynamicMatrix &d2N_deta2, + DynamicMatrix &d2N_dzeta2, + DynamicMatrix &d2N_dxideta, + DynamicMatrix &d2N_dxidzeta, + DynamicMatrix &d2N_detadzeta) const override + { + const CfemInt nQp = static_cast(xiPoints.size()); + for (CfemInt q = 0; q < nQp; ++q) + { + const CfemReal x = xiPoints[q].x, y = xiPoints[q].y, z = xiPoints[q].z; + const CfemReal mx2 = 1.0 - x * x; + const CfemReal my2 = 1.0 - y * y; + const CfemReal mz2 = 1.0 - z * z; + + const CfemReal d2b_dx2 = -2.0 * my2 * mz2; + const CfemReal d2b_dy2 = -2.0 * mx2 * mz2; + const CfemReal d2b_dz2 = -2.0 * mx2 * my2; + const CfemReal d2b_dxy = 4.0 * x * y * mz2; + const CfemReal d2b_dxz = 4.0 * x * z * my2; + const CfemReal d2b_dyz = 4.0 * y * z * mx2; + + d2N_dxi2[q][8] = d2b_dx2; + d2N_deta2[q][8] = d2b_dy2; + d2N_dzeta2[q][8] = d2b_dz2; + d2N_dxideta[q][8] = d2b_dxy; + d2N_dxidzeta[q][8] = d2b_dxz; + d2N_detadzeta[q][8] = d2b_dyz; + + const CfemReal corr_dx2 = d2b_dx2 * o8; + const CfemReal corr_dy2 = d2b_dy2 * o8; + const CfemReal corr_dz2 = d2b_dz2 * o8; + const CfemReal corr_dxy = d2b_dxy * o8; + const CfemReal corr_dxz = d2b_dxz * o8; + const CfemReal corr_dyz = d2b_dyz * o8; + + for (CfemInt i = 0; i < 8; ++i) + { + const CfemReal px = 1.0 + xr[i] * x; + const CfemReal py = 1.0 + ys[i] * y; + const CfemReal pz = 1.0 + zt[i] * z; + + d2N_dxi2[q][i] = -corr_dx2; + d2N_deta2[q][i] = -corr_dy2; + d2N_dzeta2[q][i] = -corr_dz2; + d2N_dxideta[q][i] = (o8 * xr[i] * ys[i] * pz) - corr_dxy; + d2N_dxidzeta[q][i] = (o8 * xr[i] * py * zt[i]) - corr_dxz; + d2N_detadzeta[q][i] = (o8 * px * ys[i] * zt[i]) - corr_dyz; } } - out.validFlags = requested; } - Vector3D projectFacetRefCoordToCellRefCoord(CfemInt localFacetId, const Vector3D& xiFacet) const override { + Vector3D projectFacetRefCoordToCellRefCoord(CfemInt localFacetId, const Vector3D &xiFacet) const override + { return cfem::reference_elem::utils::projectFacetRefCoordToHexaRefCoord(localFacetId, xiFacet); } - std::span getFaceNodes(CfemInt iFace) const override { + std::span getFaceNodes(CfemInt iFace) const override + { return cfem::reference_elem::utils::hex8_face_nodes[iFace]; } }; - class Q0ReferenceHexahedron : public ReferenceElement { + class Q0ReferenceHexahedron : public ReferenceElement + { public: - Q0ReferenceHexahedron() { + Q0ReferenceHexahedron() + { m_numNodes = 1; m_dimension = 3; - m_nodes.resize(m_numNodes); // Un seul nœud au centre du cube de référence [-1, 1]^3 - m_nodes = { {0.0, 0.0, 0.0} }; + m_nodes = {Vector3D{0.0, 0.0, 0.0}}; } - void evaluate(const Vector3D& xi, ShapeEvaluationFlags requested, ShapeInfo& out) const override { - const CfemInt n = m_numNodes; - if (out.values.size() != n) out.setup(n); + void evaluateShapesValues(const Vector3D &, CfemReal *values) const override + { + values[0] = 1.0; + } - if (hasFlag(requested, ShapeEvaluationFlags::Value)) { - out.values[0] = 1.0; - } + void evaluateShapesDerivatives(const Vector3D &, + CfemReal *dN_dxi, + CfemReal *dN_deta, + CfemReal *dN_dzeta) const override + { + dN_dxi[0] = 0.0; + dN_deta[0] = 0.0; + dN_dzeta[0] = 0.0; + } - if (hasFlag(requested, ShapeEvaluationFlags::Gradient)) { - out.gradients[0] = Vector3D{0.0, 0.0, 0.0}; + void evaluateShapesSecondDerivatives(const Vector3D &, + CfemReal *d2N_dxi2, + CfemReal *d2N_deta2, + CfemReal *d2N_dzeta2, + CfemReal *d2N_dxideta, + CfemReal *d2N_dxidzeta, + CfemReal *d2N_detadzeta) const override + { + d2N_dxi2[0] = 0.0; + d2N_deta2[0] = 0.0; + d2N_dzeta2[0] = 0.0; + d2N_dxideta[0] = 0.0; + d2N_dxidzeta[0] = 0.0; + d2N_detadzeta[0] = 0.0; + } + + void evaluateShapesValuesBatch(const DynamicVector &xiPoints, DynamicMatrix &values) const override + { + // Constant piecewise field value + const CfemInt nQp = static_cast(xiPoints.size()); + for (CfemInt q = 0; q < nQp; ++q) + { + values[q][0] = 1.0; } + } - if (hasFlag(requested, ShapeEvaluationFlags::Hessian)) { - if (out.hessians.size() != n) out.hessians.assign(n, SymTensor3D()); - out.hessians[0].setZero(); + void evaluateShapesDerivativesBatch(const DynamicVector &xiPoints, + DynamicMatrix &dN_dxi, + DynamicMatrix &dN_deta, + DynamicMatrix &dN_dzeta) const override + { + // Constant function results in flat zero gradients everywhere + const CfemInt nQp = static_cast(xiPoints.size()); + for (CfemInt q = 0; q < nQp; ++q) + { + dN_dxi[q][0] = 0.0; + dN_deta[q][0] = 0.0; + dN_dzeta[q][0] = 0.0; } + } - out.validFlags = requested; + void evaluateShapesSecondDerivativesBatch(const DynamicVector &xiPoints, + DynamicMatrix &d2N_dxi2, + DynamicMatrix &d2N_deta2, + DynamicMatrix &d2N_dzeta2, + DynamicMatrix &d2N_dxideta, + DynamicMatrix &d2N_dxidzeta, + DynamicMatrix &d2N_detadzeta) const override + { + // Flat null second derivatives + const CfemInt nQp = static_cast(xiPoints.size()); + for (CfemInt q = 0; q < nQp; ++q) + { + d2N_dxi2[q][0] = 0.0; + d2N_deta2[q][0] = 0.0; + d2N_dzeta2[q][0] = 0.0; + d2N_dxideta[q][0] = 0.0; + d2N_dxidzeta[q][0] = 0.0; + d2N_detadzeta[q][0] = 0.0; + } } - Vector3D projectFacetRefCoordToCellRefCoord(CfemInt localFacetId, const Vector3D& xiFacet) const override { + Vector3D projectFacetRefCoordToCellRefCoord(CfemInt localFacetId, const Vector3D &xiFacet) const override + { return cfem::reference_elem::utils::projectFacetRefCoordToHexaRefCoord(localFacetId, xiFacet); } - std::span getFaceNodes(CfemInt iFace) const override { + std::span getFaceNodes(CfemInt iFace) const override + { return cfem::reference_elem::utils::hex1_face_nodes[iFace]; } }; - } - - #endif \ No newline at end of file diff --git a/libs/fem/reference_element/include/ReferencePrism.h b/libs/fem/reference_element/include/ReferencePrism.h index 581b833..b293da7 100644 --- a/libs/fem/reference_element/include/ReferencePrism.h +++ b/libs/fem/reference_element/include/ReferencePrism.h @@ -67,6 +67,12 @@ namespace cfem::reference_elem::utils{ namespace cfem{ class P1ReferencePrism : public ReferenceElement { + private: + static constexpr CfemInt node_map[3][2] = { + {0, 3}, // Nœuds du Triangle 0 + {1, 4}, // Nœuds du Triangle 1 + {2, 5} // Nœuds du Triangle 2 + }; public: P1ReferencePrism() { m_numNodes = 6; @@ -77,45 +83,174 @@ namespace cfem{ m_nodes[3] = {0,0, 1}; m_nodes[4] = {1,0, 1}; m_nodes[5] = {0,1, 1}; } - void evaluate(const Vector3D& xi, ShapeEvaluationFlags requested, ShapeInfo& out) const override { - const CfemInt n = 6; - if (out.values.size() != n) out.setup(n); - if (hasFlag(requested, ShapeEvaluationFlags::Hessian) && out.hessians.size() != n) - out.hessians.assign(n, SymTensor3D()); + void evaluateShapesValues(const Vector3D& xi, + CfemReal* values) const override + { + constexpr CfemReal o2 = 0.5; + const CfemReal x = xi.x; + const CfemReal y = xi.y; + const CfemReal z = xi.z; + + // Fonctions du Triangle P1 + const CfemReal Nt[3] = {1.0 - x - y, x, y}; + // Fonctions de la Ligne P1 + const CfemReal Nl[2] = {o2 * (1.0 - z), o2 * (1.0 + z)}; + + for (CfemInt t = 0; t < 3; ++t) { + for (CfemInt l = 0; l < 2; ++l) { + values[node_map[t][l]] = Nt[t] * Nl[l]; + } + } + } - const CfemReal r = xi.x, s = xi.y, t = xi.z; - - // Fonctions Triangle P1 - CfemReal LT[3] = { 1.0 - r - s, r, s }; - CfemReal dLT_r[3] = { -1.0, 1.0, 0.0 }; - CfemReal dLT_s[3] = { -1.0, 0.0, 1.0 }; + void evaluateShapesDerivatives(const Vector3D& xi, + CfemReal* dN_dxi, + CfemReal* dN_deta, + CfemReal* dN_dzeta) const override + { - // Fonctions Segment P1 (sur t de -1 à 1) - CfemReal LZ[2] = { 0.5 * (1.0 - t), 0.5 * (1.0 + t) }; - CfemReal dLZ_t[2] = { -0.5, 0.5 }; + constexpr CfemReal o2 = 0.5; - for (CfemInt i = 0; i < 6; ++i) { - CfemInt triIdx = i % 3; // 0, 1, 2 - CfemInt zIdx = i / 3; // 0 pour Z=-1, 1 pour Z=1 + const CfemReal x = xi.x; + const CfemReal y = xi.y; + const CfemReal z = xi.z; + + const CfemReal Nt[3] = {1.0 - x - y, x, y}; + const CfemReal dNt_dx[3] = {-1.0, 1.0, 0.0}; + const CfemReal dNt_dy[3] = {-1.0, 0.0, 1.0}; + + const CfemReal Nl[2] = {o2 * (1.0 - z), o2 * (1.0 + z)}; + const CfemReal dNl_dz[2] = {-o2, o2}; + + for (CfemInt t = 0; t < 3; ++t) { + for (CfemInt l = 0; l < 2; ++l) { + const CfemInt node = node_map[t][l]; + dN_dxi[node] = dNt_dx[t] * Nl[l]; + dN_deta[node] = dNt_dy[t] * Nl[l]; + dN_dzeta[node] = Nt[t] * dNl_dz[l]; + } + } + } + + void evaluateShapesSecondDerivatives(const Vector3D& xiPoints, + CfemReal* d2N_dxi2, + CfemReal* d2N_deta2, + CfemReal* d2N_dzeta2, + CfemReal* d2N_dxideta, + CfemReal* d2N_dxidzeta, + CfemReal* d2N_detadzeta) const override + { + + constexpr CfemReal o2 = 0.5; + + const CfemReal dNt_dx[3] = {-1.0, 1.0, 0.0}; + const CfemReal dNt_dy[3] = {-1.0, 0.0, 1.0}; + const CfemReal dNl_dz[2] = {-o2, o2}; + + for (CfemInt t = 0; t < 3; ++t) { + for (CfemInt l = 0; l < 2; ++l) { + const CfemInt node = node_map[t][l]; + + // Les dérivées secondes pures sont nulles pour un champ linéaire (P1xP1) + d2N_dxi2[node] = 0.0; + d2N_deta2[node] = 0.0; + d2N_dzeta2[node] = 0.0; + d2N_dxideta[node] = 0.0; + + // Les dérivées croisées impliquant 'z' survivent au produit tensoriel ! + d2N_dxidzeta[node] = dNt_dx[t] * dNl_dz[l]; + d2N_detadzeta[node] = dNt_dy[t] * dNl_dz[l]; + } + } + } - if (hasFlag(requested, ShapeEvaluationFlags::Value)) - out.values[i] = LT[triIdx] * LZ[zIdx]; + void evaluateShapesValuesBatch(const DynamicVector& xiPoints, + DynamicMatrix& valuesBatch) const override + { + const CfemInt nQp = static_cast(xiPoints.size()); + constexpr CfemReal o2 = 0.5; + + for (CfemInt q = 0; q < nQp; ++q) { + const CfemReal x = xiPoints[q].x; + const CfemReal y = xiPoints[q].y; + const CfemReal z = xiPoints[q].z; + + // Fonctions du Triangle P1 + const CfemReal Nt[3] = {1.0 - x - y, x, y}; + // Fonctions de la Ligne P1 + const CfemReal Nl[2] = {o2 * (1.0 - z), o2 * (1.0 + z)}; + + for (CfemInt t = 0; t < 3; ++t) { + for (CfemInt l = 0; l < 2; ++l) { + valuesBatch[q][node_map[t][l]] = Nt[t] * Nl[l]; + } + } + } + } - if (hasFlag(requested, ShapeEvaluationFlags::Gradient)) { - out.gradients[i].x = dLT_r[triIdx] * LZ[zIdx]; - out.gradients[i].y = dLT_s[triIdx] * LZ[zIdx]; - out.gradients[i].z = LT[triIdx] * dLZ_t[zIdx]; + void evaluateShapesDerivativesBatch(const DynamicVector& xiPoints, + DynamicMatrix& dN_dxi, + DynamicMatrix& dN_deta, + DynamicMatrix& dN_dzeta) const override + { + const CfemInt nQp = static_cast(xiPoints.size()); + constexpr CfemReal o2 = 0.5; + + for (CfemInt q = 0; q < nQp; ++q) { + const CfemReal x = xiPoints[q].x; + const CfemReal y = xiPoints[q].y; + const CfemReal z = xiPoints[q].z; + + const CfemReal Nt[3] = {1.0 - x - y, x, y}; + const CfemReal dNt_dx[3] = {-1.0, 1.0, 0.0}; + const CfemReal dNt_dy[3] = {-1.0, 0.0, 1.0}; + + const CfemReal Nl[2] = {o2 * (1.0 - z), o2 * (1.0 + z)}; + const CfemReal dNl_dz[2] = {-o2, o2}; + + for (CfemInt t = 0; t < 3; ++t) { + for (CfemInt l = 0; l < 2; ++l) { + const CfemInt node = node_map[t][l]; + dN_dxi[q][node] = dNt_dx[t] * Nl[l]; + dN_deta[q][node] = dNt_dy[t] * Nl[l]; + dN_dzeta[q][node] = Nt[t] * dNl_dz[l]; + } } + } + } - if (hasFlag(requested, ShapeEvaluationFlags::Hessian)) { - out.hessians[i].setZero(); - // Dérivées croisées (Plan x Hauteur) - out.hessians[i].xz() = dLT_r[triIdx] * dLZ_t[zIdx]; - out.hessians[i].yz() = dLT_s[triIdx] * dLZ_t[zIdx]; - // Les dérivées d2N/drds sont nulles ici car Triangle P1 est linéaire. + void evaluateShapesSecondDerivativesBatch(const DynamicVector& xiPoints, + DynamicMatrix& d2N_dxi2, + DynamicMatrix& d2N_deta2, + DynamicMatrix& d2N_dzeta2, + DynamicMatrix& d2N_dxideta, + DynamicMatrix& d2N_dxidzeta, + DynamicMatrix& d2N_detadzeta) const override + { + const CfemInt nQp = static_cast(xiPoints.size()); + constexpr CfemReal o2 = 0.5; + + for (CfemInt q = 0; q < nQp; ++q) { + const CfemReal dNt_dx[3] = {-1.0, 1.0, 0.0}; + const CfemReal dNt_dy[3] = {-1.0, 0.0, 1.0}; + const CfemReal dNl_dz[2] = {-o2, o2}; + + for (CfemInt t = 0; t < 3; ++t) { + for (CfemInt l = 0; l < 2; ++l) { + const CfemInt node = node_map[t][l]; + + // Les dérivées secondes pures sont nulles pour un champ linéaire (P1xP1) + d2N_dxi2[q][node] = 0.0; + d2N_deta2[q][node] = 0.0; + d2N_dzeta2[q][node] = 0.0; + d2N_dxideta[q][node] = 0.0; + + // Les dérivées croisées impliquant 'z' survivent au produit tensoriel ! + d2N_dxidzeta[q][node] = dNt_dx[t] * dNl_dz[l]; + d2N_detadzeta[q][node] = dNt_dy[t] * dNl_dz[l]; + } } } - out.validFlags = requested; } Vector3D projectFacetRefCoordToCellRefCoord(CfemInt localFacetId, const Vector3D& xiFacet) const override{ @@ -129,6 +264,53 @@ namespace cfem{ class P2ReferencePrism : public ReferenceElement { + protected: + // Mapping strict selon la connectivité de P2ReferencePrism + // t (Triangle P2) : 0..5 + // l (Ligne P2) : 0 (-1), 1 (0), 2 (+1) + static constexpr CfemInt node_map[6][3] = { + {0, 12, 3}, // Sommet 0 (0,0) + {1, 13, 4}, // Sommet 1 (1,0) + {2, 14, 5}, // Sommet 2 (0,1) + {6, 15, 9}, // Milieu 3 (0.5,0) + {7, 16, 10},// Milieu 4 (0.5,0.5) + {8, 17, 11} // Milieu 5 (0,0.5) + }; + + // Helper: Triangle P2 + static inline void eval2D_tri_all(CfemReal x, CfemReal y, + CfemReal* N, CfemReal* dNx, CfemReal* dNy, + CfemReal* d2Nx, CfemReal* d2Ny, CfemReal* d2Nxy) + { + const CfemReal L1 = x, L2 = y, L0 = 1.0 - x - y; + + N[0] = L0 * (2.0*L0 - 1.0); N[1] = L1 * (2.0*L1 - 1.0); N[2] = L2 * (2.0*L2 - 1.0); + N[3] = 4.0 * L0 * L1; N[4] = 4.0 * L1 * L2; N[5] = 4.0 * L2 * L0; + + dNx[0] = -3.0 + 4.0*L1 + 4.0*L2; dNy[0] = -3.0 + 4.0*L1 + 4.0*L2; + dNx[1] = 4.0*L1 - 1.0; dNy[1] = 0.0; + dNx[2] = 0.0; dNy[2] = 4.0*L2 - 1.0; + dNx[3] = 4.0 - 8.0*L1 - 4.0*L2; dNy[3] = -4.0*L1; + dNx[4] = 4.0*L2; dNy[4] = 4.0*L1; + dNx[5] = -4.0*L2; dNy[5] = 4.0 - 4.0*L1 - 8.0*L2; + + d2Nx[0] = 4.0; d2Ny[0] = 4.0; d2Nxy[0] = 4.0; + d2Nx[1] = 4.0; d2Ny[1] = 0.0; d2Nxy[1] = 0.0; + d2Nx[2] = 0.0; d2Ny[2] = 4.0; d2Nxy[2] = 0.0; + d2Nx[3] = -8.0; d2Ny[3] = 0.0; d2Nxy[3] = -4.0; + d2Nx[4] = 0.0; d2Ny[4] = 0.0; d2Nxy[4] = 4.0; + d2Nx[5] = 0.0; d2Ny[5] = -8.0; d2Nxy[5] = -4.0; + } + + // Helper: Ligne P2 (Ordre -1.0, 0.0, 1.0) + static inline void eval1D_line_all(CfemReal z, CfemReal* N, CfemReal* dNz, CfemReal* d2Nz) + { + const CfemReal z2 = z * z; + N[0] = 0.5 * (z2 - z); dNz[0] = z - 0.5; d2Nz[0] = 1.0; + N[1] = 1.0 - z2; dNz[1] = -2.0 * z; d2Nz[1] = -2.0; + N[2] = 0.5 * (z2 + z); dNz[2] = z + 0.5; d2Nz[2] = 1.0; + } + public: P2ReferencePrism() { m_numNodes = 18; @@ -150,65 +332,147 @@ namespace cfem{ m_nodes[15]= {0.5, 0.0, 0.0}; m_nodes[16]= {0.5, 0.5, 0.0}; m_nodes[17]= {0.0, 0.5, 0.0}; } - void evaluate(const Vector3D& xi, ShapeEvaluationFlags requested, ShapeInfo& out) const override { - const CfemInt n = 18; - if (out.values.size() != n) out.setup(n); - if (hasFlag(requested, ShapeEvaluationFlags::Hessian) && out.hessians.size() != n) { - out.hessians.assign(n, SymTensor3D()); + void evaluateShapesValues(const Vector3D& xi, + CfemReal* values) const override + { + CfemReal Nt[6], dNtx[6], dNty[6], d2Ntx[6], d2Nty[6], d2Ntxy[6]; + CfemReal Nl[3], dNlz[3], d2Nlz[3]; + + eval2D_tri_all(xi.x, xi.y, Nt, dNtx, dNty, d2Ntx, d2Nty, d2Ntxy); + eval1D_line_all(xi.z, Nl, dNlz, d2Nlz); + + for (CfemInt t = 0; t < 6; ++t) { + for (CfemInt l = 0; l < 3; ++l) { + values[node_map[t][l]] = Nt[t] * Nl[l]; + } } + } - const CfemReal r = xi.x; - const CfemReal s = xi.y; - const CfemReal t = xi.z; - const CfemReal L0 = 1.0 - r - s; + void evaluateShapesDerivatives(const Vector3D& xi, + CfemReal* dN_dxi, + CfemReal* dN_deta, + CfemReal* dN_dzeta) const override + { + CfemReal Nt[6], dNtx[6], dNty[6], d2Ntx[6], d2Nty[6], d2Ntxy[6]; + CfemReal Nl[3], dNlz[3], d2Nlz[3]; + + eval2D_tri_all(xi.x, xi.y, Nt, dNtx, dNty, d2Ntx, d2Nty, d2Ntxy); + eval1D_line_all(xi.z, Nl, dNlz, d2Nlz); + + for (CfemInt t = 0; t < 6; ++t) { + for (CfemInt l = 0; l < 3; ++l) { + const CfemInt node = node_map[t][l]; + dN_dxi[node] = dNtx[t] * Nl[l]; + dN_deta[node] = dNty[t] * Nl[l]; + dN_dzeta[node] = Nt[t] * dNlz[l]; + } + } + } - // Fonctions 2D Triangle P2 - CfemReal fT[6] = { L0*(2*L0-1), r*(2*r-1), s*(2*s-1), 4*r*L0, 4*r*s, 4*s*L0 }; - CfemReal dfTr[6] = { -3.0+4*r+4*s, 4*r-1, 0, 4-8*r-4*s, 4*s, -4*s }; - CfemReal dfTs[6] = { -3.0+4*r+4*s, 0, 4*s-1, -4*r, 4*r, 4-4*r-8*s }; - - // Hessiennes constantes du triangle P2 (d2N/dr2, d2N/ds2, d2N/drds) - static const CfemReal hT[6][3] = { - {4, 4, 4}, {4, 0, 0}, {0, 4, 0}, {-8, 0, -4}, {0, 0, 4}, {0, -8, -4} - }; - - // Fonctions 1D Segment P2 (sur t) - CfemReal fZ[3] = { 0.5*t*(t-1), 0.5*t*(t+1), 1-t*t }; // Bas, Haut, Milieu - CfemReal dfZt[3] = { t-0.5, t+0.5, -2.0*t }; - CfemReal ddfZtt[3]= { 1.0, 1.0, -2.0 }; - - // Mapping Topologique VTK (Index Triangle, Index Segment) - static const CfemInt mapT[18] = {0,1,2, 0,1,2, 3,4,5, 3,4,5, 0,1,2, 3,4,5}; - static const CfemInt mapZ[18] = {0,0,0, 1,1,1, 0,0,0, 1,1,1, 2,2,2, 2,2,2}; - - for (CfemInt i = 0; i < 18; ++i) { - CfemInt iT = mapT[i]; - CfemInt iZ = mapZ[i]; - - if (hasFlag(requested, ShapeEvaluationFlags::Value)) { - out.values[i] = fT[iT] * fZ[iZ]; + void evaluateShapesSecondDerivatives(const Vector3D& xi, + CfemReal* d2N_dxi2, + CfemReal* d2N_deta2, + CfemReal* d2N_dzeta2, + CfemReal* d2N_dxideta, + CfemReal* d2N_dxidzeta, + CfemReal* d2N_detadzeta) const override + { + CfemReal Nt[6], dNtx[6], dNty[6], d2Ntx[6], d2Nty[6], d2Ntxy[6]; + CfemReal Nl[3], dNlz[3], d2Nlz[3]; + + eval2D_tri_all(xi.x, xi.y, Nt, dNtx, dNty, d2Ntx, d2Nty, d2Ntxy); + eval1D_line_all(xi.z, Nl, dNlz, d2Nlz); + + for (CfemInt t = 0; t < 6; ++t) { + for (CfemInt l = 0; l < 3; ++l) { + const CfemInt node = node_map[t][l]; + + // Règle du produit tensoriel pour les dérivées secondes + d2N_dxi2[node] = d2Ntx[t] * Nl[l]; + d2N_deta2[node] = d2Nty[t] * Nl[l]; + d2N_dzeta2[node] = Nt[t] * d2Nlz[l]; + + d2N_dxideta[node] = d2Ntxy[t] * Nl[l]; + d2N_dxidzeta[node] = dNtx[t] * dNlz[l]; + d2N_detadzeta[node] = dNty[t] * dNlz[l]; + } + } + } + + void evaluateShapesValuesBatch(const DynamicVector& xiPoints, + DynamicMatrix& valuesBatch) const override + { + const CfemInt nQp = static_cast(xiPoints.size()); + for (CfemInt q = 0; q < nQp; ++q) { + CfemReal Nt[6], dNtx[6], dNty[6], d2Ntx[6], d2Nty[6], d2Ntxy[6]; + CfemReal Nl[3], dNlz[3], d2Nlz[3]; + + eval2D_tri_all(xiPoints[q].x, xiPoints[q].y, Nt, dNtx, dNty, d2Ntx, d2Nty, d2Ntxy); + eval1D_line_all(xiPoints[q].z, Nl, dNlz, d2Nlz); + + for (CfemInt t = 0; t < 6; ++t) { + for (CfemInt l = 0; l < 3; ++l) { + valuesBatch[q][node_map[t][l]] = Nt[t] * Nl[l]; + } } + } + } - if (hasFlag(requested, ShapeEvaluationFlags::Gradient)) { - out.gradients[i].x = dfTr[iT] * fZ[iZ]; - out.gradients[i].y = dfTs[iT] * fZ[iZ]; - out.gradients[i].z = fT[iT] * dfZt[iZ]; + void evaluateShapesDerivativesBatch(const DynamicVector& xiPoints, + DynamicMatrix& dN_dxi, + DynamicMatrix& dN_deta, + DynamicMatrix& dN_dzeta) const override + { + const CfemInt nQp = static_cast(xiPoints.size()); + for (CfemInt q = 0; q < nQp; ++q) { + CfemReal Nt[6], dNtx[6], dNty[6], d2Ntx[6], d2Nty[6], d2Ntxy[6]; + CfemReal Nl[3], dNlz[3], d2Nlz[3]; + + eval2D_tri_all(xiPoints[q].x, xiPoints[q].y, Nt, dNtx, dNty, d2Ntx, d2Nty, d2Ntxy); + eval1D_line_all(xiPoints[q].z, Nl, dNlz, d2Nlz); + + for (CfemInt t = 0; t < 6; ++t) { + for (CfemInt l = 0; l < 3; ++l) { + const CfemInt node = node_map[t][l]; + dN_dxi[q][node] = dNtx[t] * Nl[l]; + dN_deta[q][node] = dNty[t] * Nl[l]; + dN_dzeta[q][node] = Nt[t] * dNlz[l]; + } } + } + } - if (hasFlag(requested, ShapeEvaluationFlags::Hessian)) { - out.hessians[i].setZero(); - // d2N/dr2, d2N/ds2, d2N/drds - out.hessians[i].xx() = hT[iT][0] * fZ[iZ]; - out.hessians[i].yy() = hT[iT][1] * fZ[iZ]; - out.hessians[i].xy() = hT[iT][2] * fZ[iZ]; - // d2N/dt2 - out.hessians[i].zz() = fT[iT] * ddfZtt[iZ]; - // Croisées (Plan x t) - out.hessians[i].xz() = dfTr[iT] * dfZt[iZ]; - out.hessians[i].yz() = dfTs[iT] * dfZt[iZ]; + void evaluateShapesSecondDerivativesBatch(const DynamicVector& xiPoints, + DynamicMatrix& d2N_dxi2, + DynamicMatrix& d2N_deta2, + DynamicMatrix& d2N_dzeta2, + DynamicMatrix& d2N_dxideta, + DynamicMatrix& d2N_dxidzeta, + DynamicMatrix& d2N_detadzeta) const override + { + const CfemInt nQp = static_cast(xiPoints.size()); + for (CfemInt q = 0; q < nQp; ++q) { + CfemReal Nt[6], dNtx[6], dNty[6], d2Ntx[6], d2Nty[6], d2Ntxy[6]; + CfemReal Nl[3], dNlz[3], d2Nlz[3]; + + eval2D_tri_all(xiPoints[q].x, xiPoints[q].y, Nt, dNtx, dNty, d2Ntx, d2Nty, d2Ntxy); + eval1D_line_all(xiPoints[q].z, Nl, dNlz, d2Nlz); + + for (CfemInt t = 0; t < 6; ++t) { + for (CfemInt l = 0; l < 3; ++l) { + const CfemInt node = node_map[t][l]; + + // Règle du produit tensoriel pour les dérivées secondes + d2N_dxi2[q][node] = d2Ntx[t] * Nl[l]; + d2N_deta2[q][node] = d2Nty[t] * Nl[l]; + d2N_dzeta2[q][node] = Nt[t] * d2Nlz[l]; + + d2N_dxideta[q][node] = d2Ntxy[t] * Nl[l]; + d2N_dxidzeta[q][node] = dNtx[t] * dNlz[l]; + d2N_detadzeta[q][node] = dNty[t] * dNlz[l]; + } } } - out.validFlags = requested; } Vector3D projectFacetRefCoordToCellRefCoord(CfemInt localFacetId, const Vector3D& xiFacet) const override{ @@ -231,66 +495,275 @@ namespace cfem{ m_nodes[6]={1.0/3.0, 1.0/3.0, 0.0}; // Bulle } - void evaluate(const Vector3D& xi, ShapeEvaluationFlags requested, ShapeInfo& out) const override { - const CfemInt n = 7; - if (out.values.size() != n) out.setup(n); - if (hasFlag(requested, ShapeEvaluationFlags::Hessian) && out.hessians.size() != n) - out.hessians.assign(n, SymTensor3D()); + void evaluateShapesValues(const Vector3D& xi, + CfemReal* values) const override + { + constexpr CfemReal o6 = 1.0 / 6.0; + + const CfemReal r = xi.x; + const CfemReal s = xi.y; + const CfemReal t = xi.z; - const CfemReal r = xi.x, s = xi.y, t = xi.z; const CfemReal L[3] = {1.0 - r - s, r, s}; - const CfemReal triB = 27.0 * L[0] * L[1] * L[2]; - const CfemReal zB = 1.0 - t*t; + const CfemReal M[2] = {0.5 * (1.0 - t), 0.5 * (1.0 + t)}; - // 1. Calcul de la Bulle (Nœud 6) + // Calcul de la Bulle (Nœud 6) + const CfemReal triB = 27.0 * L[0] * L[1] * L[2]; + const CfemReal zB = 1.0 - t * t; const CfemReal B = triB * zB; - Vector3D gB = { 27.0 * s * (1.0 - 2.0*r - s) * zB, - 27.0 * r * (1.0 - r - 2.0*s) * zB, - triB * (-2.0 * t) }; - - SymTensor3D hB; hB.setZero(); - if (hasFlag(requested, ShapeEvaluationFlags::Hessian)) { - hB.xx() = -54.0 * s * zB; - hB.yy() = -54.0 * r * zB; - hB.zz() = -2.0 * triB; - hB.xy() = 27.0 * (1.0 - 2.0*r - 2.0*s) * zB; - hB.xz() = gB.x / zB * (-2.0 * t); - hB.yz() = gB.y / zB * (-2.0 * t); + + values[6] = B; + + // 2. Fonctions des Sommets (0-5) avec correction + const CfemReal correction = B * o6; + for (CfemInt i = 0; i < 6; ++i) { + const CfemInt iT = i % 3; + const CfemInt iZ = i / 3; + values[i] = L[iT] * M[iZ] - correction; } + } - // 2. Fonctions des Sommets (0-5) corrigées par -B/6 - const CfemReal M[2] = {0.5*(1.0-t), 0.5*(1.0+t)}; + void evaluateShapesDerivatives(const Vector3D& xi, + CfemReal* dN_dxi, + CfemReal* dN_deta, + CfemReal* dN_dzeta) const override + { + constexpr CfemReal o6 = 1.0 / 6.0; + + const CfemReal r = xi.x; + const CfemReal s = xi.y; + const CfemReal t = xi.z; + + const CfemReal L[3] = {1.0 - r - s, r, s}; + const CfemReal M[2] = {0.5 * (1.0 - t), 0.5 * (1.0 + t)}; const CfemReal dM[2] = {-0.5, 0.5}; + // Gradients de la bulle + const CfemReal triB = 27.0 * L[0] * L[1] * L[2]; + const CfemReal zB = 1.0 - t * t; + + const CfemReal gBx = 27.0 * s * (1.0 - 2.0*r - s) * zB; + const CfemReal gBy = 27.0 * r * (1.0 - r - 2.0*s) * zB; + const CfemReal gBz = triB * (-2.0 * t); + + dN_dxi[6] = gBx; + dN_deta[6] = gBy; + dN_dzeta[6] = gBz; + + // Gradients des sommets avec correction + const CfemReal c_dx = gBx * o6; + const CfemReal c_dy = gBy * o6; + const CfemReal c_dz = gBz * o6; + for (CfemInt i = 0; i < 6; ++i) { - CfemInt iT = i % 3, iZ = i / 3; - CfemReal drL = (iT==0?-1:(iT==1?1:0)), dsL = (iT==0?-1:(iT==2?1:0)); + const CfemInt iT = i % 3; + const CfemInt iZ = i / 3; + const CfemReal drL = (iT == 0 ? -1.0 : (iT == 1 ? 1.0 : 0.0)); + const CfemReal dsL = (iT == 0 ? -1.0 : (iT == 2 ? 1.0 : 0.0)); + + dN_dxi[i] = drL * M[iZ] - c_dx; + dN_deta[i] = dsL * M[iZ] - c_dy; + dN_dzeta[i] = L[iT] * dM[iZ] - c_dz; + } + } - if (hasFlag(requested, ShapeEvaluationFlags::Value)) - out.values[i] = L[iT] * M[iZ] - B / 6.0; + void evaluateShapesSecondDerivatives(const Vector3D& xi, + CfemReal* d2N_dxi2, + CfemReal* d2N_deta2, + CfemReal* d2N_dzeta2, + CfemReal* d2N_dxideta, + CfemReal* d2N_dxidzeta, + CfemReal* d2N_detadzeta) const override + { + constexpr CfemReal o6 = 1.0 / 6.0; - if (hasFlag(requested, ShapeEvaluationFlags::Gradient)) { - out.gradients[i].x = drL * M[iZ] - gB.x / 6.0; - out.gradients[i].y = dsL * M[iZ] - gB.y / 6.0; - out.gradients[i].z = L[iT] * dM[iZ] - gB.z / 6.0; - } + const CfemReal r = xi.x; + const CfemReal s = xi.y; + const CfemReal t = xi.z; + + const CfemReal L[3] = {1.0 - r - s, r, s}; + const CfemReal dM[2] = {-0.5, 0.5}; + + const CfemReal triB = 27.0 * L[0] * L[1] * L[2]; + const CfemReal zB = 1.0 - t * t; + + // Hessiens de la bulle (calculs simplifiés sans divisions par zéro) + const CfemReal hBxx = -54.0 * s * zB; + const CfemReal hByy = -54.0 * r * zB; + const CfemReal hBzz = -2.0 * triB; + const CfemReal hBxy = 27.0 * (1.0 - 2.0*r - 2.0*s) * zB; + const CfemReal hBxz = -54.0 * s * t * (1.0 - 2.0*r - s); // CORRECTION: evite (gBx / zB) * (-2t) + const CfemReal hByz = -54.0 * r * t * (1.0 - r - 2.0*s); // CORRECTION: evite (gBy / zB) * (-2t) + + d2N_dxi2[6] = hBxx; + d2N_deta2[6] = hByy; + d2N_dzeta2[6] = hBzz; + d2N_dxideta[6] = hBxy; + d2N_dxidzeta[6] = hBxz; + d2N_detadzeta[6] = hByz; + + // Hessiens des sommets avec correction + const CfemReal c_xx = hBxx * o6; + const CfemReal c_yy = hByy * o6; + const CfemReal c_zz = hBzz * o6; + const CfemReal c_xy = hBxy * o6; + const CfemReal c_xz = hBxz * o6; + const CfemReal c_yz = hByz * o6; + + for (CfemInt i = 0; i < 6; ++i) { + const CfemInt iT = i % 3; + const CfemInt iZ = i / 3; + const CfemReal drL = (iT == 0 ? -1.0 : (iT == 1 ? 1.0 : 0.0)); + const CfemReal dsL = (iT == 0 ? -1.0 : (iT == 2 ? 1.0 : 0.0)); + + d2N_dxi2[i] = -c_xx; + d2N_deta2[i] = -c_yy; + d2N_dzeta2[i] = -c_zz; + d2N_dxideta[i] = -c_xy; + // La partie croisée linéaire-linéaire n'est non nulle que pour les couples impliquant z + d2N_dxidzeta[i] = drL * dM[iZ] - c_xz; + d2N_detadzeta[i] = dsL * dM[iZ] - c_yz; + } + } - if (hasFlag(requested, ShapeEvaluationFlags::Hessian)) { - out.hessians[i].setZero(); - out.hessians[i].xz() = drL * dM[iZ] - hB.xz() / 6.0; - out.hessians[i].yz() = dsL * dM[iZ] - hB.yz() / 6.0; - out.hessians[i].xx() = -hB.xx() / 6.0; - out.hessians[i].yy() = -hB.yy() / 6.0; - out.hessians[i].zz() = -hB.zz() / 6.0; - out.hessians[i].xy() = -hB.xy() / 6.0; + void evaluateShapesValuesBatch(const DynamicVector& xiPoints, + DynamicMatrix& valuesBatch) const override + { + const CfemInt nQp = static_cast(xiPoints.size()); + constexpr CfemReal o6 = 1.0 / 6.0; + + for (CfemInt q = 0; q < nQp; ++q) { + const CfemReal r = xiPoints[q].x; + const CfemReal s = xiPoints[q].y; + const CfemReal t = xiPoints[q].z; + + const CfemReal L[3] = {1.0 - r - s, r, s}; + const CfemReal M[2] = {0.5 * (1.0 - t), 0.5 * (1.0 + t)}; + + // 1. Calcul de la Bulle (Nœud 6) + const CfemReal triB = 27.0 * L[0] * L[1] * L[2]; + const CfemReal zB = 1.0 - t * t; + const CfemReal B = triB * zB; + + valuesBatch[q][6] = B; + + // 2. Fonctions des Sommets (0-5) avec correction + const CfemReal correction = B * o6; + for (CfemInt i = 0; i < 6; ++i) { + const CfemInt iT = i % 3; + const CfemInt iZ = i / 3; + valuesBatch[q][i] = L[iT] * M[iZ] - correction; } } + } - if (hasFlag(requested, ShapeEvaluationFlags::Value)) out.values[6] = B; - if (hasFlag(requested, ShapeEvaluationFlags::Gradient)) out.gradients[6] = gB; - if (hasFlag(requested, ShapeEvaluationFlags::Hessian)) out.hessians[6] = hB; + void evaluateShapesDerivativesBatch(const DynamicVector& xiPoints, + DynamicMatrix& dN_dxi, + DynamicMatrix& dN_deta, + DynamicMatrix& dN_dzeta) const override + { + const CfemInt nQp = static_cast(xiPoints.size()); + constexpr CfemReal o6 = 1.0 / 6.0; + + for (CfemInt q = 0; q < nQp; ++q) { + const CfemReal r = xiPoints[q].x; + const CfemReal s = xiPoints[q].y; + const CfemReal t = xiPoints[q].z; + + const CfemReal L[3] = {1.0 - r - s, r, s}; + const CfemReal M[2] = {0.5 * (1.0 - t), 0.5 * (1.0 + t)}; + const CfemReal dM[2] = {-0.5, 0.5}; + + // Gradients de la bulle + const CfemReal triB = 27.0 * L[0] * L[1] * L[2]; + const CfemReal zB = 1.0 - t * t; + + const CfemReal gBx = 27.0 * s * (1.0 - 2.0*r - s) * zB; + const CfemReal gBy = 27.0 * r * (1.0 - r - 2.0*s) * zB; + const CfemReal gBz = triB * (-2.0 * t); + + dN_dxi[q][6] = gBx; + dN_deta[q][6] = gBy; + dN_dzeta[q][6] = gBz; + + // Gradients des sommets avec correction + const CfemReal c_dx = gBx * o6; + const CfemReal c_dy = gBy * o6; + const CfemReal c_dz = gBz * o6; + + for (CfemInt i = 0; i < 6; ++i) { + const CfemInt iT = i % 3; + const CfemInt iZ = i / 3; + const CfemReal drL = (iT == 0 ? -1.0 : (iT == 1 ? 1.0 : 0.0)); + const CfemReal dsL = (iT == 0 ? -1.0 : (iT == 2 ? 1.0 : 0.0)); + + dN_dxi[q][i] = drL * M[iZ] - c_dx; + dN_deta[q][i] = dsL * M[iZ] - c_dy; + dN_dzeta[q][i] = L[iT] * dM[iZ] - c_dz; + } + } + } - out.validFlags = requested; + void evaluateShapesSecondDerivativesBatch(const DynamicVector& xiPoints, + DynamicMatrix& d2N_dxi2, + DynamicMatrix& d2N_deta2, + DynamicMatrix& d2N_dzeta2, + DynamicMatrix& d2N_dxideta, + DynamicMatrix& d2N_dxidzeta, + DynamicMatrix& d2N_detadzeta) const override + { + const CfemInt nQp = static_cast(xiPoints.size()); + constexpr CfemReal o6 = 1.0 / 6.0; + + for (CfemInt q = 0; q < nQp; ++q) { + const CfemReal r = xiPoints[q].x; + const CfemReal s = xiPoints[q].y; + const CfemReal t = xiPoints[q].z; + + const CfemReal L[3] = {1.0 - r - s, r, s}; + const CfemReal dM[2] = {-0.5, 0.5}; + + const CfemReal triB = 27.0 * L[0] * L[1] * L[2]; + const CfemReal zB = 1.0 - t * t; + + // Hessiens de la bulle (calculs simplifiés sans divisions par zéro) + const CfemReal hBxx = -54.0 * s * zB; + const CfemReal hByy = -54.0 * r * zB; + const CfemReal hBzz = -2.0 * triB; + const CfemReal hBxy = 27.0 * (1.0 - 2.0*r - 2.0*s) * zB; + const CfemReal hBxz = -54.0 * s * t * (1.0 - 2.0*r - s); // CORRECTION: evite (gBx / zB) * (-2t) + const CfemReal hByz = -54.0 * r * t * (1.0 - r - 2.0*s); // CORRECTION: evite (gBy / zB) * (-2t) + + d2N_dxi2[q][6] = hBxx; + d2N_deta2[q][6] = hByy; + d2N_dzeta2[q][6] = hBzz; + d2N_dxideta[q][6] = hBxy; + d2N_dxidzeta[q][6] = hBxz; + d2N_detadzeta[q][6] = hByz; + + // Hessiens des sommets avec correction + const CfemReal c_xx = hBxx * o6; + const CfemReal c_yy = hByy * o6; + const CfemReal c_zz = hBzz * o6; + const CfemReal c_xy = hBxy * o6; + const CfemReal c_xz = hBxz * o6; + const CfemReal c_yz = hByz * o6; + + for (CfemInt i = 0; i < 6; ++i) { + const CfemInt iT = i % 3; + const CfemInt iZ = i / 3; + const CfemReal drL = (iT == 0 ? -1.0 : (iT == 1 ? 1.0 : 0.0)); + const CfemReal dsL = (iT == 0 ? -1.0 : (iT == 2 ? 1.0 : 0.0)); + + d2N_dxi2[q][i] = -c_xx; + d2N_deta2[q][i] = -c_yy; + d2N_dzeta2[q][i] = -c_zz; + d2N_dxideta[q][i] = -c_xy; + // La partie croisée linéaire-linéaire n'est non nulle que pour les couples impliquant z + d2N_dxidzeta[q][i] = drL * dM[iZ] - c_xz; + d2N_detadzeta[q][i] = dsL * dM[iZ] - c_yz; + } + } } Vector3D projectFacetRefCoordToCellRefCoord(CfemInt localFacetId, const Vector3D& xiFacet) const override { @@ -306,31 +779,85 @@ namespace cfem{ P0ReferencePrism() { m_numNodes = 1; m_dimension = 3; - m_nodes.resize(m_numNodes); + // Un seul nœud au centre de gravité du prisme de référence // Triangle base (0,0), (1,0), (0,1) -> (1/3, 1/3) // Segment Z de -1 à 1 -> 0 - m_nodes = { {1.0/3.0, 1.0/3.0, 0.0} }; + m_nodes = { Vector3D{1.0/3.0, 1.0/3.0, 0.0} }; } - void evaluate(const Vector3D& xi, ShapeEvaluationFlags requested, ShapeInfo& out) const override { - const CfemInt n = 1; - if (out.values.size() != n) out.setup(n); + void evaluateShapesValues(const Vector3D&, CfemReal* values) const override + { + values[0] = 1.0; + } - if (hasFlag(requested, ShapeEvaluationFlags::Value)) { - out.values[0] = 1.0; - } + void evaluateShapesDerivatives(const Vector3D&, + CfemReal* dN_dxi, + CfemReal* dN_deta, + CfemReal* dN_dzeta) const override + { + dN_dxi[0] = 0.0; + dN_deta[0] = 0.0; + dN_dzeta[0] = 0.0; + } + + void evaluateShapesSecondDerivatives(const Vector3D&, + CfemReal* d2N_dxi2, + CfemReal* d2N_deta2, + CfemReal* d2N_dzeta2, + CfemReal* d2N_dxideta, + CfemReal* d2N_dxidzeta, + CfemReal* d2N_detadzeta) const override + { + d2N_dxi2[0] = 0.0; + d2N_deta2[0] = 0.0; + d2N_dzeta2[0] = 0.0; + d2N_dxideta[0] = 0.0; + d2N_dxidzeta[0] = 0.0; + d2N_detadzeta[0] = 0.0; + } - if (hasFlag(requested, ShapeEvaluationFlags::Gradient)) { - out.gradients[0] = Vector3D{0.0, 0.0, 0.0}; + void evaluateShapesValuesBatch(const DynamicVector &xiPoints, DynamicMatrix& values) const override + { + // Constant piecewise field value + const CfemInt nQp = static_cast(xiPoints.size()); + for (CfemInt q = 0; q < nQp; ++q) { + values[q][0] = 1.0; } + } - if (hasFlag(requested, ShapeEvaluationFlags::Hessian)) { - if (out.hessians.size() != n) out.hessians.assign(n, SymTensor3D()); - out.hessians[0].setZero(); + void evaluateShapesDerivativesBatch(const DynamicVector & xiPoints, + DynamicMatrix &dN_dxi, + DynamicMatrix &dN_deta, + DynamicMatrix &dN_dzeta) const override + { + // Constant function results in flat zero gradients everywhere + const CfemInt nQp = static_cast(xiPoints.size()); + for (CfemInt q = 0; q < nQp; ++q) { + dN_dxi[q][0] = 0.0; + dN_deta[q][0] = 0.0; + dN_dzeta[q][0] = 0.0; } + } - out.validFlags = requested; + void evaluateShapesSecondDerivativesBatch(const DynamicVector &xiPoints, + DynamicMatrix &d2N_dxi2, + DynamicMatrix &d2N_deta2, + DynamicMatrix &d2N_dzeta2, + DynamicMatrix &d2N_dxideta, + DynamicMatrix &d2N_dxidzeta, + DynamicMatrix &d2N_detadzeta) const override + { + // Flat null second derivatives + const CfemInt nQp = static_cast(xiPoints.size()); + for (CfemInt q = 0; q < nQp; ++q) { + d2N_dxi2[q][0] = 0.0; + d2N_deta2[q][0] = 0.0; + d2N_dzeta2[q][0] = 0.0; + d2N_dxideta[q][0] = 0.0; + d2N_dxidzeta[q][0] = 0.0; + d2N_detadzeta[q][0] = 0.0; + } } Vector3D projectFacetRefCoordToCellRefCoord(CfemInt localFacetId, const Vector3D& xiFacet) const override { diff --git a/libs/fem/reference_element/include/ReferenceQuad.h b/libs/fem/reference_element/include/ReferenceQuad.h index 5653e28..e08482d 100644 --- a/libs/fem/reference_element/include/ReferenceQuad.h +++ b/libs/fem/reference_element/include/ReferenceQuad.h @@ -6,13 +6,13 @@ // --- // --- This software is protected by international copyright laws. // --- Use, distribution, or modification of this software in any form, -// --- source or binary, for personal, academic, or non-commercial -// --- purposes is permitted free of charge, provided that this -// --- copyright notice and this permission notice appear in all +// --- source or binary, for personal, academic, or non-commercial +// --- purposes is permitted free of charge, provided that this +// --- copyright notice and this permission notice appear in all // --- copies and supporting documentation. // --- -// --- The authors and contributors provide this code "as is" without -// --- any express or implied warranty. In no event shall they be +// --- The authors and contributors provide this code "as is" without +// --- any express or implied warranty. In no event shall they be // --- held liable for any damages arising from the use of this software. //************************************************************************ @@ -21,287 +21,732 @@ #include "ReferenceElement.h" -namespace cfem::reference_elem::utils{ +namespace cfem::reference_elem::utils +{ - inline Vector3D projectFacetRefCoordToQuadRefCoord(CfemInt localFacetId, - const Vector3D& xiFacet) + inline Vector3D projectFacetRefCoordToQuadRefCoord(CfemInt localFacetId, + const Vector3D &xiFacet) { const CfemReal u = xiFacet.x; - switch (localFacetId) { - case 0: return Vector3D(u, -1.0, 0.0); // Face {0,1} : y=-1 - case 1: return Vector3D(1.0, u, 0.0); // Face {1,2} : x=1 - case 2: return Vector3D(-u, 1.0, 0.0); // Face {2,3} : y=1 - case 3: return Vector3D(-1.0, -u, 0.0); // Face {3,0} : x=-1 - default: CFEM_ERROR << "Invalid facet ID for Quad cell"; + switch (localFacetId) + { + case 0: + return Vector3D(u, -1.0, 0.0); // Face {0,1} : y=-1 + case 1: + return Vector3D(1.0, u, 0.0); // Face {1,2} : x=1 + case 2: + return Vector3D(-u, 1.0, 0.0); // Face {2,3} : y=1 + case 3: + return Vector3D(-1.0, -u, 0.0); // Face {3,0} : x=-1 + default: + CFEM_ERROR << "Invalid facet ID for Quad cell"; } return Vector3D{}; // Make the compiler happy } static const std::vector> quad4_face_nodes = { - {0, 1}, {1, 2}, {2, 3}, {3, 0} - }; + {0, 1}, {1, 2}, {2, 3}, {3, 0}}; static const std::vector> quad9_face_nodes = { - {0, 1, 4}, {1, 2, 5}, {2, 3, 6}, {3, 0, 7} - }; + {0, 1, 4}, {1, 2, 5}, {2, 3, 6}, {3, 0, 7}}; static const std::vector> quad1_face_nodes = { - {0}, {0}, {0}, {0} - }; + {0}, {0}, {0}, {0}}; } -namespace cfem { +namespace cfem +{ - class Q1ReferenceQuad : public ReferenceElement { + class Q1ReferenceQuad : public ReferenceElement + { public: - Q1ReferenceQuad() { + Q1ReferenceQuad() + { m_numNodes = 4; m_dimension = 2; m_nodes.resize(m_numNodes); m_nodes[0] = Vector3D{-1.0, -1.0, 0.0}; - m_nodes[1] = Vector3D{ 1.0, -1.0, 0.0}; - m_nodes[2] = Vector3D{ 1.0, 1.0, 0.0}; - m_nodes[3] = Vector3D{-1.0, 1.0, 0.0}; + m_nodes[1] = Vector3D{1.0, -1.0, 0.0}; + m_nodes[2] = Vector3D{1.0, 1.0, 0.0}; + m_nodes[3] = Vector3D{-1.0, 1.0, 0.0}; } - void evaluate(const Vector3D& xi, ShapeEvaluationFlags requested, ShapeInfo& out) const override { - const CfemInt n = 4; - if (out.values.size() != n) out.setup(n); - - if (hasFlag(requested, ShapeEvaluationFlags::Hessian) && out.hessians.size() != n) { - out.hessians.assign(n, SymTensor3D()); + void evaluateShapesValues(const Vector3D &xi, + CfemReal *values) const override + { + constexpr CfemReal o4 = 0.25; + + const CfemReal xm = 1.0 - xi.x; + const CfemReal xp = 1.0 + xi.x; + const CfemReal ym = 1.0 - xi.y; + const CfemReal yp = 1.0 + xi.y; + + values[0] = o4 * xm * ym; + values[1] = o4 * xp * ym; + values[2] = o4 * xp * yp; + values[3] = o4 * xm * yp; + } + + void evaluateShapesDerivatives(const Vector3D &xi, + CfemReal *dN_dxi, + CfemReal *dN_deta, + CfemReal *dN_dzeta) const override + { + constexpr CfemReal o4 = 0.25; + + const CfemReal xm = 1.0 - xi.x; + const CfemReal xp = 1.0 + xi.x; + const CfemReal ym = 1.0 - xi.y; + const CfemReal yp = 1.0 + xi.y; + + // Dérivées par rapport à x (xi) + dN_dxi[0] = -o4 * ym; + dN_dxi[1] = o4 * ym; + dN_dxi[2] = o4 * yp; + dN_dxi[3] = -o4 * yp; + + // Dérivées par rapport à y (eta) + dN_deta[0] = -o4 * xm; + dN_deta[1] = -o4 * xp; + dN_deta[2] = o4 * xp; + dN_deta[3] = o4 * xm; + + // 2D : Z est nul + for (CfemInt i = 0; i < 4; ++i) + dN_dzeta[i] = 0.0; + } + + void evaluateShapesSecondDerivatives(const Vector3D &xi, + CfemReal *d2N_dxi2, + CfemReal *d2N_deta2, + CfemReal *d2N_dzeta2, + CfemReal *d2N_dxideta, + CfemReal *d2N_dxidzeta, + CfemReal *d2N_detadzeta) const override + { + constexpr CfemReal o4 = 0.25; + + for (CfemInt i = 0; i < 4; ++i) + { + d2N_dxi2[i] = 0.0; + d2N_deta2[i] = 0.0; + d2N_dzeta2[i] = 0.0; + d2N_dxidzeta[i] = 0.0; + d2N_detadzeta[i] = 0.0; } - const CfemReal x = xi.x; - const CfemReal y = xi.y; + // Seule la dérivée croisée xy survit dans un bilinéaire + d2N_dxideta[0] = o4; + d2N_dxideta[1] = -o4; + d2N_dxideta[2] = o4; + d2N_dxideta[3] = -o4; + } - // Facteurs de signes pour les 4 nœuds - const CfemReal sx[4] = {-1, 1, 1, -1}; - const CfemReal sy[4] = {-1, -1, 1, 1}; + void evaluateShapesValuesBatch(const DynamicVector &xiPoints, + DynamicMatrix &valuesBatch) const override + { + const CfemInt nQp = static_cast(xiPoints.size()); + constexpr CfemReal o4 = 0.25; + + for (CfemInt q = 0; q < nQp; ++q) + { + const CfemReal xm = 1.0 - xiPoints[q].x; + const CfemReal xp = 1.0 + xiPoints[q].x; + const CfemReal ym = 1.0 - xiPoints[q].y; + const CfemReal yp = 1.0 + xiPoints[q].y; + + valuesBatch[q][0] = o4 * xm * ym; + valuesBatch[q][1] = o4 * xp * ym; + valuesBatch[q][2] = o4 * xp * yp; + valuesBatch[q][3] = o4 * xm * yp; + } + } - for (CfemInt i = 0; i < n; ++i) { - if ( hasFlag(requested, ShapeEvaluationFlags::Value)) { - out.values[i] = 0.25 * (1.0 + sx[i] * x) * (1.0 + sy[i] * y); - } + void evaluateShapesDerivativesBatch(const DynamicVector &xiPoints, + DynamicMatrix &dN_dxi, + DynamicMatrix &dN_deta, + DynamicMatrix &dN_dzeta) const override + { + const CfemInt nQp = static_cast(xiPoints.size()); + constexpr CfemReal o4 = 0.25; + + for (CfemInt q = 0; q < nQp; ++q) + { + const CfemReal xm = 1.0 - xiPoints[q].x; + const CfemReal xp = 1.0 + xiPoints[q].x; + const CfemReal ym = 1.0 - xiPoints[q].y; + const CfemReal yp = 1.0 + xiPoints[q].y; + + // Dérivées par rapport à x (xi) + dN_dxi[q][0] = -o4 * ym; + dN_dxi[q][1] = o4 * ym; + dN_dxi[q][2] = o4 * yp; + dN_dxi[q][3] = -o4 * yp; + + // Dérivées par rapport à y (eta) + dN_deta[q][0] = -o4 * xm; + dN_deta[q][1] = -o4 * xp; + dN_deta[q][2] = o4 * xp; + dN_deta[q][3] = o4 * xm; + + // 2D : Z est nul + for (CfemInt i = 0; i < 4; ++i) + dN_dzeta[q][i] = 0.0; + } + } - if ( hasFlag(requested, ShapeEvaluationFlags::Gradient) ) { - out.gradients[i].x = 0.25 * sx[i] * (1.0 + sy[i] * y); - out.gradients[i].y = 0.25 * sy[i] * (1.0 + sx[i] * x); - out.gradients[i].z = 0.0; + void evaluateShapesSecondDerivativesBatch(const DynamicVector &xiPoints, + DynamicMatrix &d2N_dxi2, + DynamicMatrix &d2N_deta2, + DynamicMatrix &d2N_dzeta2, + DynamicMatrix &d2N_dxideta, + DynamicMatrix &d2N_dxidzeta, + DynamicMatrix &d2N_detadzeta) const override + { + const CfemInt nQp = static_cast(xiPoints.size()); + constexpr CfemReal o4 = 0.25; + + for (CfemInt q = 0; q < nQp; ++q) + { + for (CfemInt i = 0; i < 4; ++i) + { + d2N_dxi2[q][i] = 0.0; + d2N_deta2[q][i] = 0.0; + d2N_dzeta2[q][i] = 0.0; + d2N_dxidzeta[q][i] = 0.0; + d2N_detadzeta[q][i] = 0.0; } - if ( hasFlag(requested, ShapeEvaluationFlags::Hessian)) { - out.hessians[i].setZero(); - // Seule la dérivée croisée est non nulle : d2N / dx dy - CfemReal d2N_dxy = 0.25 * sx[i] * sy[i]; - out.hessians[i].xy() = d2N_dxy; - } + // Seule la dérivée croisée xy survit dans un bilinéaire + d2N_dxideta[q][0] = o4; + d2N_dxideta[q][1] = -o4; + d2N_dxideta[q][2] = o4; + d2N_dxideta[q][3] = -o4; } - out.validFlags = requested; } - Vector3D projectFacetRefCoordToCellRefCoord(CfemInt localFacetId, const Vector3D& xiFacet) const override{ + Vector3D projectFacetRefCoordToCellRefCoord(CfemInt localFacetId, const Vector3D &xiFacet) const override + { return cfem::reference_elem::utils::projectFacetRefCoordToQuadRefCoord(localFacetId, xiFacet); // Make the compiler happy } - std::span getFaceNodes(CfemInt iFace) const override { + std::span getFaceNodes(CfemInt iFace) const override + { return cfem::reference_elem::utils::quad4_face_nodes[iFace]; } }; + class Q2ReferenceQuad : public ReferenceElement + { + protected: + // Helpers 1D P2 (-1.0, 0.0, 1.0) + static inline void eval1D_hessian(CfemReal u, CfemReal *N, CfemReal *dN, CfemReal *d2N) + { + const CfemReal u2 = u * u; + N[0] = 0.5 * (u2 - u); + dN[0] = u - 0.5; + d2N[0] = 1.0; + N[1] = 1.0 - u2; + dN[1] = -2.0 * u; + d2N[1] = -2.0; + N[2] = 0.5 * (u2 + u); + dN[2] = u + 0.5; + d2N[2] = 1.0; + } + + // Connectivité Q2 stricte (X, Y) + // i (X) parcourt: 0 (-1), 1 (0), 2 (1) + // j (Y) parcourt: 0 (-1), 1 (0), 2 (1) + static constexpr CfemInt node_map[3][3] = { + {0, 7, 3}, // X = -1 | Y = -1, 0, 1 + {4, 8, 6}, // X = 0 | Y = -1, 0, 1 + {1, 5, 2} // X = +1 | Y = -1, 0, 1 + }; -class Q2ReferenceQuad : public ReferenceElement { public: - Q2ReferenceQuad() { + Q2ReferenceQuad() + { m_numNodes = 9; m_dimension = 2; m_nodes.resize(m_numNodes); // Coordonnées de référence sur [-1, 1] // Sommets m_nodes[0] = Vector3D{-1.0, -1.0, 0.0}; // i=0, j=0 - m_nodes[1] = Vector3D{ 1.0, -1.0, 0.0}; // i=2, j=0 - m_nodes[2] = Vector3D{ 1.0, 1.0, 0.0}; // i=2, j=2 - m_nodes[3] = Vector3D{-1.0, 1.0, 0.0}; // i=0, j=2 + m_nodes[1] = Vector3D{1.0, -1.0, 0.0}; // i=2, j=0 + m_nodes[2] = Vector3D{1.0, 1.0, 0.0}; // i=2, j=2 + m_nodes[3] = Vector3D{-1.0, 1.0, 0.0}; // i=0, j=2 // Milieux d'arêtes - m_nodes[4] = Vector3D{ 0.0, -1.0, 0.0}; // i=1, j=0 - m_nodes[5] = Vector3D{ 1.0, 0.0, 0.0}; // i=2, j=1 - m_nodes[6] = Vector3D{ 0.0, 1.0, 0.0}; // i=1, j=2 - m_nodes[7] = Vector3D{-1.0, 0.0, 0.0}; // i=0, j=1 + m_nodes[4] = Vector3D{0.0, -1.0, 0.0}; // i=1, j=0 + m_nodes[5] = Vector3D{1.0, 0.0, 0.0}; // i=2, j=1 + m_nodes[6] = Vector3D{0.0, 1.0, 0.0}; // i=1, j=2 + m_nodes[7] = Vector3D{-1.0, 0.0, 0.0}; // i=0, j=1 // Centre - m_nodes[8] = Vector3D{ 0.0, 0.0, 0.0}; // i=1, j=1 + m_nodes[8] = Vector3D{0.0, 0.0, 0.0}; // i=1, j=1 } - void evaluate(const Vector3D& xi, ShapeEvaluationFlags requested, ShapeInfo& out) const override { - const CfemInt n = 9; - if (out.values.size() != n) out.setup(n); - - if (hasFlag(requested, ShapeEvaluationFlags::Hessian) && out.hessians.size() != n) { - out.hessians.assign(n, SymTensor3D()); + void evaluateShapesValues(const Vector3D &xi, + CfemReal *values) const override + { + CfemReal Nx[3], Ny[3], dNx[3], dNy[3], d2Nx[3], d2Ny[3]; + eval1D_hessian(xi.x, Nx, dNx, d2Nx); + eval1D_hessian(xi.y, Ny, dNy, d2Ny); + + for (CfemInt i = 0; i < 3; ++i) + { + for (CfemInt j = 0; j < 3; ++j) + { + values[node_map[i][j]] = Nx[i] * Ny[j]; + } } + } - const CfemReal r = xi.x; - const CfemReal s = xi.y; + void evaluateShapesDerivatives(const Vector3D &xi, + CfemReal *dN_dxi, + CfemReal *dN_deta, + CfemReal *dN_dzeta) const override + { + CfemReal Nx[3], Ny[3], dNx[3], dNy[3], d2Nx[3], d2Ny[3]; + eval1D_hessian(xi.x, Nx, dNx, d2Nx); + eval1D_hessian(xi.y, Ny, dNy, d2Ny); + + for (CfemInt i = 0; i < 3; ++i) + { + for (CfemInt j = 0; j < 3; ++j) + { + const CfemInt node = node_map[i][j]; + dN_dxi[node] = dNx[i] * Ny[j]; + dN_deta[node] = Nx[i] * dNy[j]; + dN_dzeta[node] = 0.0; + } + } + } - // Fonctions de base Lagrange 1D (P2) - CfemReal fr[3] = { 0.5 * r * (r - 1.0), 1.0 - r * r, 0.5 * r * (r + 1.0) }; - CfemReal fs[3] = { 0.5 * s * (s - 1.0), 1.0 - s * s, 0.5 * s * (s + 1.0) }; - - // Dérivées premières 1D - CfemReal dfr[3] = { r - 0.5, -2.0 * r, r + 0.5 }; - CfemReal dfs[3] = { s - 0.5, -2.0 * s, s + 0.5 }; - - // Dérivées secondes 1D - CfemReal ddfr[3] = { 1.0, -2.0, 1.0 }; - CfemReal ddfs[3] = { 1.0, -2.0, 1.0 }; - - // Mapping (i,j) -> index global m_nodes - // i=r index (0:-1, 1:0, 2:1), j=s index (0:-1, 1:0, 2:1) - static const CfemInt map[3][3] = { - {0, 7, 3}, // r = -1.0 - {4, 8, 6}, // r = 0.0 - {1, 5, 2} // r = 1.0 - }; - - for (CfemInt i = 0; i < 3; ++i) { - for (CfemInt j = 0; j < 3; ++j) { - CfemInt idx = map[i][j]; - - if (hasFlag(requested, ShapeEvaluationFlags::Value)) { - out.values[idx] = fr[i] * fs[j]; - } + void evaluateShapesSecondDerivatives(const Vector3D &xi, + CfemReal *d2N_dxi2, + CfemReal *d2N_deta2, + CfemReal *d2N_dzeta2, + CfemReal *d2N_dxideta, + CfemReal *d2N_dxidzeta, + CfemReal *d2N_detadzeta) const override + { + + CfemReal Nx[3], Ny[3], dNx[3], dNy[3], d2Nx[3], d2Ny[3]; + eval1D_hessian(xi.x, Nx, dNx, d2Nx); + eval1D_hessian(xi.y, Ny, dNy, d2Ny); + + for (CfemInt i = 0; i < 3; ++i) + { + for (CfemInt j = 0; j < 3; ++j) + { + const CfemInt node = node_map[i][j]; + d2N_dxi2[node] = d2Nx[i] * Ny[j]; + d2N_deta2[node] = Nx[i] * d2Ny[j]; + d2N_dzeta2[node] = 0.0; + + d2N_dxideta[node] = dNx[i] * dNy[j]; + d2N_dxidzeta[node] = 0.0; + d2N_detadzeta[node] = 0.0; + } + } + } - if (hasFlag(requested, ShapeEvaluationFlags::Gradient)) { - out.gradients[idx].x = dfr[i] * fs[j]; - out.gradients[idx].y = fr[i] * dfs[j]; - out.gradients[idx].z = 0.0; + void evaluateShapesValuesBatch(const DynamicVector &xiPoints, + DynamicMatrix &valuesBatch) const override + { + const CfemInt nQp = static_cast(xiPoints.size()); + for (CfemInt q = 0; q < nQp; ++q) + { + CfemReal Nx[3], Ny[3], dNx[3], dNy[3], d2Nx[3], d2Ny[3]; + eval1D_hessian(xiPoints[q].x, Nx, dNx, d2Nx); + eval1D_hessian(xiPoints[q].y, Ny, dNy, d2Ny); + + for (CfemInt i = 0; i < 3; ++i) + { + for (CfemInt j = 0; j < 3; ++j) + { + valuesBatch[q][node_map[i][j]] = Nx[i] * Ny[j]; } + } + } + } - if (hasFlag(requested, ShapeEvaluationFlags::Hessian)) { - out.hessians[idx].setZero(); - out.hessians[idx].xx() = ddfr[i] * fs[j]; // d2N/dr2 - out.hessians[idx].yy() = fr[i] * ddfs[j]; // d2N/ds2 - out.hessians[idx].xy() = dfr[i] * dfs[j]; // d2N/drds + void evaluateShapesDerivativesBatch(const DynamicVector &xiPoints, + DynamicMatrix &dN_dxi, + DynamicMatrix &dN_deta, + DynamicMatrix &dN_dzeta) const override + { + const CfemInt nQp = static_cast(xiPoints.size()); + for (CfemInt q = 0; q < nQp; ++q) + { + CfemReal Nx[3], Ny[3], dNx[3], dNy[3], d2Nx[3], d2Ny[3]; + eval1D_hessian(xiPoints[q].x, Nx, dNx, d2Nx); + eval1D_hessian(xiPoints[q].y, Ny, dNy, d2Ny); + + for (CfemInt i = 0; i < 3; ++i) + { + for (CfemInt j = 0; j < 3; ++j) + { + const CfemInt node = node_map[i][j]; + dN_dxi[q][node] = dNx[i] * Ny[j]; + dN_deta[q][node] = Nx[i] * dNy[j]; + dN_dzeta[q][node] = 0.0; } } } - out.validFlags = requested; } - Vector3D projectFacetRefCoordToCellRefCoord(CfemInt localFacetId, const Vector3D& xiFacet) const override{ + void evaluateShapesSecondDerivativesBatch(const DynamicVector &xiPoints, + DynamicMatrix &d2N_dxi2, + DynamicMatrix &d2N_deta2, + DynamicMatrix &d2N_dzeta2, + DynamicMatrix &d2N_dxideta, + DynamicMatrix &d2N_dxidzeta, + DynamicMatrix &d2N_detadzeta) const override + { + const CfemInt nQp = static_cast(xiPoints.size()); + for (CfemInt q = 0; q < nQp; ++q) + { + CfemReal Nx[3], Ny[3], dNx[3], dNy[3], d2Nx[3], d2Ny[3]; + eval1D_hessian(xiPoints[q].x, Nx, dNx, d2Nx); + eval1D_hessian(xiPoints[q].y, Ny, dNy, d2Ny); + + for (CfemInt i = 0; i < 3; ++i) + { + for (CfemInt j = 0; j < 3; ++j) + { + const CfemInt node = node_map[i][j]; + d2N_dxi2[q][node] = d2Nx[i] * Ny[j]; + d2N_deta2[q][node] = Nx[i] * d2Ny[j]; + d2N_dzeta2[q][node] = 0.0; + + d2N_dxideta[q][node] = dNx[i] * dNy[j]; + d2N_dxidzeta[q][node] = 0.0; + d2N_detadzeta[q][node] = 0.0; + } + } + } + } + Vector3D projectFacetRefCoordToCellRefCoord(CfemInt localFacetId, const Vector3D &xiFacet) const override + { return cfem::reference_elem::utils::projectFacetRefCoordToQuadRefCoord(localFacetId, xiFacet); // Make the compiler happy } - std::span getFaceNodes(CfemInt iFace) const override { + std::span getFaceNodes(CfemInt iFace) const override + { return cfem::reference_elem::utils::quad9_face_nodes[iFace]; } }; - class MiniReferenceQuad : public ReferenceElement { + class MiniReferenceQuad : public ReferenceElement + { public: - MiniReferenceQuad() { + MiniReferenceQuad() + { m_numNodes = 5; m_dimension = 2; m_nodes.resize(5); - m_nodes[0] = {-1,-1,0}; m_nodes[1] = {1,-1,0}; - m_nodes[2] = { 1, 1, 0}; m_nodes[3] = {-1, 1,0}; - m_nodes[4] = { 0, 0, 0}; // Bulle + m_nodes[0] = {-1, -1, 0}; + m_nodes[1] = {1, -1, 0}; + m_nodes[2] = {1, 1, 0}; + m_nodes[3] = {-1, 1, 0}; + m_nodes[4] = {0, 0, 0}; // Bulle + } + + void evaluateShapesValues(const Vector3D& xi, + CfemReal* values) const override + { + constexpr CfemReal o4 = 0.25; + + const CfemReal x = xi.x; + const CfemReal y = xi.y; + + // Bulle Centrale + const CfemReal b = (1.0 - x*x) * (1.0 - y*y); + values[4] = b; + + // Sommets + const CfemReal correction = b * o4; + values[0] = (o4 * (1.0 - x) * (1.0 - y)) - correction; + values[1] = (o4 * (1.0 + x) * (1.0 - y)) - correction; + values[2] = (o4 * (1.0 + x) * (1.0 + y)) - correction; + values[3] = (o4 * (1.0 - x) * (1.0 + y)) - correction; } - void evaluate(const Vector3D& xi, ShapeEvaluationFlags requested, ShapeInfo& out) const override { - const CfemInt n = 5; - if (out.values.size() != n) out.setup(n); - if (hasFlag(requested, ShapeEvaluationFlags::Hessian) && out.hessians.size() != n) - out.hessians.assign(n, SymTensor3D()); + void evaluateShapesDerivatives(const Vector3D& xi, + CfemReal* dN_dxi, + CfemReal* dN_deta, + CfemReal* dN_dzeta) const override + { + constexpr CfemReal o4 = 0.25; + + const CfemReal x = xi.x; + const CfemReal y = xi.y; + + const CfemReal db_dx = -2.0 * x * (1.0 - y*y); + const CfemReal db_dy = -2.0 * y * (1.0 - x*x); + + dN_dxi[4] = db_dx; + dN_deta[4] = db_dy; + dN_dzeta[4] = 0.0; + + const CfemReal c_dx = db_dx * o4; + const CfemReal c_dy = db_dy * o4; - const CfemReal x = xi.x, y = xi.y; + dN_dxi[0] = (-o4 * (1.0 - y)) - c_dx; dN_deta[0] = (-o4 * (1.0 - x)) - c_dy; dN_dzeta[0] = 0.0; + dN_dxi[1] = ( o4 * (1.0 - y)) - c_dx; dN_deta[1] = (-o4 * (1.0 + x)) - c_dy; dN_dzeta[1] = 0.0; + dN_dxi[2] = ( o4 * (1.0 + y)) - c_dx; dN_deta[2] = ( o4 * (1.0 + x)) - c_dy; dN_dzeta[2] = 0.0; + dN_dxi[3] = (-o4 * (1.0 + y)) - c_dx; dN_deta[3] = ( o4 * (1.0 - x)) - c_dy; dN_dzeta[3] = 0.0; - // 1. Calcul de la Bulle (Nœud 4) - const CfemReal bx = 1.0 - x*x, by = 1.0 - y*y; - const CfemReal B = bx * by; - const Vector3D gradB = {-2.0*x*by, -2.0*y*bx, 0.0}; - SymTensor3D hessB; - if (hasFlag(requested, ShapeEvaluationFlags::Hessian)) { - hessB.setZero(); - hessB.xx() = -2.0 * by; - hessB.yy() = -2.0 * bx; - hessB.xy() = 4.0 * x * y; - } + } - // 2. Fonctions des Sommets (0-3) corrigées par -B/4 - const CfemReal sx[4] = {-1, 1, 1, -1}, sy[4] = {-1, -1, 1, 1}; - for (CfemInt i = 0; i < 4; ++i) { - if (hasFlag(requested, ShapeEvaluationFlags::Value)) { - CfemReal Q1 = 0.25 * (1.0 + sx[i]*x) * (1.0 + sy[i]*y); - out.values[i] = Q1 - 0.25 * B; - } - if (hasFlag(requested, ShapeEvaluationFlags::Gradient)) { - out.gradients[i].x = 0.25 * sx[i] * (1.0 + sy[i]*y) - 0.25 * gradB.x; - out.gradients[i].y = 0.25 * sy[i] * (1.0 + sx[i]*x) - 0.25 * gradB.y; - } - if (hasFlag(requested, ShapeEvaluationFlags::Hessian)) { - out.hessians[i].setZero(); - out.hessians[i].xy() = 0.25 * sx[i] * sy[i] - 0.25 * hessB.xy(); - out.hessians[i].xx() = -0.25 * hessB.xx(); - out.hessians[i].yy() = -0.25 * hessB.yy(); + void evaluateShapesSecondDerivatives(const Vector3D& xi, + CfemReal* d2N_dxi2, + CfemReal* d2N_deta2, + CfemReal* d2N_dzeta2, + CfemReal* d2N_dxideta, + CfemReal* d2N_dxidzeta, + CfemReal* d2N_detadzeta) const override + { + + constexpr CfemReal o4 = 0.25; + + const CfemReal x = xi.x; + const CfemReal y = xi.y; + + // Hessiens de la bulle + const CfemReal d2b_dx2 = -2.0 * (1.0 - y*y); + const CfemReal d2b_dy2 = -2.0 * (1.0 - x*x); + const CfemReal d2b_dxy = 4.0 * x * y; + + d2N_dxi2[4] = d2b_dx2; + d2N_deta2[4] = d2b_dy2; + d2N_dxideta[4] = d2b_dxy; + d2N_dzeta2[4] = 0.0; d2N_dxidzeta[4] = 0.0; d2N_detadzeta[4] = 0.0; + + // Sommets (Correction de la composante purement nulle pour xx et yy du Q1) + const CfemReal c_dx2 = d2b_dx2 * o4; + const CfemReal c_dy2 = d2b_dy2 * o4; + const CfemReal c_dxy = d2b_dxy * o4; + + for(CfemInt i=0; i<4; ++i) { + d2N_dxi2[i] = -c_dx2; + d2N_deta2[i] = -c_dy2; + d2N_dzeta2[i] = 0.0; + d2N_dxidzeta[i] = 0.0; + d2N_detadzeta[i] = 0.0; } + + d2N_dxideta[0] = o4 - c_dxy; + d2N_dxideta[1] = -o4 - c_dxy; + d2N_dxideta[2] = o4 - c_dxy; + d2N_dxideta[3] = -o4 - c_dxy; + } + + void evaluateShapesValuesBatch(const DynamicVector& xiPoints, + DynamicMatrix& valuesBatch) const override + { + const CfemInt nQp = static_cast(xiPoints.size()); + constexpr CfemReal o4 = 0.25; + + for (CfemInt q = 0; q < nQp; ++q) { + const CfemReal x = xiPoints[q].x; + const CfemReal y = xiPoints[q].y; + + // Bulle Centrale + const CfemReal b = (1.0 - x*x) * (1.0 - y*y); + valuesBatch[q][4] = b; + + // Sommets + const CfemReal correction = b * o4; + valuesBatch[q][0] = (o4 * (1.0 - x) * (1.0 - y)) - correction; + valuesBatch[q][1] = (o4 * (1.0 + x) * (1.0 - y)) - correction; + valuesBatch[q][2] = (o4 * (1.0 + x) * (1.0 + y)) - correction; + valuesBatch[q][3] = (o4 * (1.0 - x) * (1.0 + y)) - correction; } + } + + void evaluateShapesDerivativesBatch(const DynamicVector& xiPoints, + DynamicMatrix& dN_dxi, + DynamicMatrix& dN_deta, + DynamicMatrix& dN_dzeta) const override + { + const CfemInt nQp = static_cast(xiPoints.size()); + constexpr CfemReal o4 = 0.25; + + for (CfemInt q = 0; q < nQp; ++q) { + const CfemReal x = xiPoints[q].x; + const CfemReal y = xiPoints[q].y; - // 3. Stockage final de la Bulle - if (hasFlag(requested, ShapeEvaluationFlags::Value)) out.values[4] = B; - if (hasFlag(requested, ShapeEvaluationFlags::Gradient)) out.gradients[4] = gradB; - if (hasFlag(requested, ShapeEvaluationFlags::Hessian)) out.hessians[4] = hessB; + const CfemReal db_dx = -2.0 * x * (1.0 - y*y); + const CfemReal db_dy = -2.0 * y * (1.0 - x*x); - out.validFlags = requested; + dN_dxi[q][4] = db_dx; + dN_deta[q][4] = db_dy; + dN_dzeta[q][4] = 0.0; + + const CfemReal c_dx = db_dx * o4; + const CfemReal c_dy = db_dy * o4; + + dN_dxi[q][0] = (-o4 * (1.0 - y)) - c_dx; dN_deta[q][0] = (-o4 * (1.0 - x)) - c_dy; dN_dzeta[q][0] = 0.0; + dN_dxi[q][1] = ( o4 * (1.0 - y)) - c_dx; dN_deta[q][1] = (-o4 * (1.0 + x)) - c_dy; dN_dzeta[q][1] = 0.0; + dN_dxi[q][2] = ( o4 * (1.0 + y)) - c_dx; dN_deta[q][2] = ( o4 * (1.0 + x)) - c_dy; dN_dzeta[q][2] = 0.0; + dN_dxi[q][3] = (-o4 * (1.0 + y)) - c_dx; dN_deta[q][3] = ( o4 * (1.0 - x)) - c_dy; dN_dzeta[q][3] = 0.0; + } + } + + void evaluateShapesSecondDerivativesBatch(const DynamicVector& xiPoints, + DynamicMatrix& d2N_dxi2, + DynamicMatrix& d2N_deta2, + DynamicMatrix& d2N_dzeta2, + DynamicMatrix& d2N_dxideta, + DynamicMatrix& d2N_dxidzeta, + DynamicMatrix& d2N_detadzeta) const override + { + const CfemInt nQp = static_cast(xiPoints.size()); + constexpr CfemReal o4 = 0.25; + + for (CfemInt q = 0; q < nQp; ++q) { + const CfemReal x = xiPoints[q].x; + const CfemReal y = xiPoints[q].y; + + // Hessiens de la bulle + const CfemReal d2b_dx2 = -2.0 * (1.0 - y*y); + const CfemReal d2b_dy2 = -2.0 * (1.0 - x*x); + const CfemReal d2b_dxy = 4.0 * x * y; + + d2N_dxi2[q][4] = d2b_dx2; + d2N_deta2[q][4] = d2b_dy2; + d2N_dxideta[q][4] = d2b_dxy; + d2N_dzeta2[q][4] = 0.0; d2N_dxidzeta[q][4] = 0.0; d2N_detadzeta[q][4] = 0.0; + + // Sommets (Correction de la composante purement nulle pour xx et yy du Q1) + const CfemReal c_dx2 = d2b_dx2 * o4; + const CfemReal c_dy2 = d2b_dy2 * o4; + const CfemReal c_dxy = d2b_dxy * o4; + + for(CfemInt i=0; i<4; ++i) { + d2N_dxi2[q][i] = -c_dx2; + d2N_deta2[q][i] = -c_dy2; + d2N_dzeta2[q][i] = 0.0; + d2N_dxidzeta[q][i] = 0.0; + d2N_detadzeta[q][i] = 0.0; + } + + d2N_dxideta[q][0] = o4 - c_dxy; + d2N_dxideta[q][1] = -o4 - c_dxy; + d2N_dxideta[q][2] = o4 - c_dxy; + d2N_dxideta[q][3] = -o4 - c_dxy; + } } - Vector3D projectFacetRefCoordToCellRefCoord(CfemInt localFacetId, const Vector3D& xiFacet) const override { + Vector3D projectFacetRefCoordToCellRefCoord(CfemInt localFacetId, const Vector3D &xiFacet) const override + { return reference_elem::utils::projectFacetRefCoordToQuadRefCoord(localFacetId, xiFacet); } - std::span getFaceNodes(CfemInt iFace) const override { + std::span getFaceNodes(CfemInt iFace) const override + { return reference_elem::utils::quad4_face_nodes[iFace]; } }; -class Q0ReferenceQuad : public ReferenceElement { + class Q0ReferenceQuad : public ReferenceElement + { public: - Q0ReferenceQuad() { + Q0ReferenceQuad() + { m_numNodes = 1; m_dimension = 2; - m_nodes.resize(m_numNodes); + // Un seul nœud au centre du carré de référence [-1, 1]x[-1, 1] - m_nodes = { {0.0, 0.0, 0.0} }; + m_nodes = {Vector3D{0.0, 0.0, 0.0}}; } - void evaluate(const Vector3D& xi, ShapeEvaluationFlags requested, ShapeInfo& out) const override { - const CfemInt n = 1; - if (out.values.size() != n) out.setup(n); + void evaluateShapesValues(const Vector3D &, CfemReal *values) const override + { + values[0] = 1.0; + } - // Valeur constante N0 = 1.0 - if (hasFlag(requested, ShapeEvaluationFlags::Value)) { - out.values[0] = 1.0; - } + void evaluateShapesDerivatives(const Vector3D &, + CfemReal *dN_dxi, + CfemReal *dN_deta, + CfemReal *dN_dzeta) const override + { + dN_dxi[0] = 0.0; + dN_deta[0] = 0.0; + dN_dzeta[0] = 0.0; + } - // Gradient nul - if (hasFlag(requested, ShapeEvaluationFlags::Gradient)) { - out.gradients[0] = Vector3D{0.0, 0.0, 0.0}; + void evaluateShapesSecondDerivatives(const Vector3D &, + CfemReal *d2N_dxi2, + CfemReal *d2N_deta2, + CfemReal *d2N_dzeta2, + CfemReal *d2N_dxideta, + CfemReal *d2N_dxidzeta, + CfemReal *d2N_detadzeta) const override + { + d2N_dxi2[0] = 0.0; + d2N_deta2[0] = 0.0; + d2N_dzeta2[0] = 0.0; + d2N_dxideta[0] = 0.0; + d2N_dxidzeta[0] = 0.0; + d2N_detadzeta[0] = 0.0; + } + + void evaluateShapesValuesBatch(const DynamicVector &xiPoints, DynamicMatrix &values) const override + { + // Constant piecewise field value + const CfemInt nQp = static_cast(xiPoints.size()); + for (CfemInt q = 0; q < nQp; ++q) + { + values[q][0] = 1.0; } + } - // Hessien nul - if (hasFlag(requested, ShapeEvaluationFlags::Hessian)) { - if (out.hessians.size() != n) out.hessians.assign(n, SymTensor3D()); - out.hessians[0].setZero(); + void evaluateShapesDerivativesBatch(const DynamicVector &xiPoints, + DynamicMatrix &dN_dxi, + DynamicMatrix &dN_deta, + DynamicMatrix &dN_dzeta) const override + { + // Constant function results in flat zero gradients everywhere + const CfemInt nQp = static_cast(xiPoints.size()); + for (CfemInt q = 0; q < nQp; ++q) + { + dN_dxi[q][0] = 0.0; + dN_deta[q][0] = 0.0; + dN_dzeta[q][0] = 0.0; } + } - out.validFlags = requested; + void evaluateShapesSecondDerivativesBatch(const DynamicVector &xiPoints, + DynamicMatrix &d2N_dxi2, + DynamicMatrix &d2N_deta2, + DynamicMatrix &d2N_dzeta2, + DynamicMatrix &d2N_dxideta, + DynamicMatrix &d2N_dxidzeta, + DynamicMatrix &d2N_detadzeta) const override + { + // Flat null second derivatives + const CfemInt nQp = static_cast(xiPoints.size()); + for (CfemInt q = 0; q < nQp; ++q) + { + d2N_dxi2[q][0] = 0.0; + d2N_deta2[q][0] = 0.0; + d2N_dzeta2[q][0] = 0.0; + d2N_dxideta[q][0] = 0.0; + d2N_dxidzeta[q][0] = 0.0; + d2N_detadzeta[q][0] = 0.0; + } } - Vector3D projectFacetRefCoordToCellRefCoord(CfemInt localFacetId, const Vector3D& xiFacet) const override { + Vector3D projectFacetRefCoordToCellRefCoord(CfemInt localFacetId, const Vector3D &xiFacet) const override + { return cfem::reference_elem::utils::projectFacetRefCoordToQuadRefCoord(localFacetId, xiFacet); } - std::span getFaceNodes(CfemInt iFace) const override { + std::span getFaceNodes(CfemInt iFace) const override + { return cfem::reference_elem::utils::quad1_face_nodes[iFace]; } }; diff --git a/libs/fem/reference_element/include/ReferenceSegment.h b/libs/fem/reference_element/include/ReferenceSegment.h index 423006b..effadb8 100644 --- a/libs/fem/reference_element/include/ReferenceSegment.h +++ b/libs/fem/reference_element/include/ReferenceSegment.h @@ -6,13 +6,13 @@ // --- // --- This software is protected by international copyright laws. // --- Use, distribution, or modification of this software in any form, -// --- source or binary, for personal, academic, or non-commercial -// --- purposes is permitted free of charge, provided that this -// --- copyright notice and this permission notice appear in all +// --- source or binary, for personal, academic, or non-commercial +// --- purposes is permitted free of charge, provided that this +// --- copyright notice and this permission notice appear in all // --- copies and supporting documentation. // --- -// --- The authors and contributors provide this code "as is" without -// --- any express or implied warranty. In no event shall they be +// --- The authors and contributors provide this code "as is" without +// --- any express or implied warranty. In no event shall they be // --- held liable for any damages arising from the use of this software. //************************************************************************ @@ -21,15 +21,16 @@ #include "ReferenceElement.h" -namespace cfem::reference_elem::utils{ +namespace cfem::reference_elem::utils +{ - inline Vector3D projectFacetRefCoordToSegmentRefCoord(CfemInt localFacetId, - const Vector3D& xiFacet) + inline Vector3D projectFacetRefCoordToSegmentRefCoord(CfemInt localFacetId, + const Vector3D &xiFacet) { // Une facette d'une ligne est un point. xiFacet est ignoré. // Selon ReferenceSegment.h (P2), le segment de référence est [-1, 1] // Face 0 = Noeud 0 (-1.0), Face 1 = Noeud 1 (1.0) - return Vector3D( (localFacetId == 0) ? -1.0 : 1.0, 0.0, 0.0 ); + return Vector3D((localFacetId == 0) ? -1.0 : 1.0, 0.0, 0.0); } static const std::vector> line_face_nodes = { @@ -43,149 +44,367 @@ namespace cfem::reference_elem::utils{ }; } +namespace cfem +{ -namespace cfem { - - class P1ReferenceSegment : public ReferenceElement { + class P1ReferenceSegment : public ReferenceElement + { public: - P1ReferenceSegment() { + P1ReferenceSegment() + { m_numNodes = 2; m_dimension = 1; m_nodes.resize(m_numNodes); // Domaine de référence [-1, 1] m_nodes[0] = Vector3D{-1.0, 0.0, 0.0}; - m_nodes[1] = Vector3D{ 1.0, 0.0, 0.0}; + m_nodes[1] = Vector3D{1.0, 0.0, 0.0}; } - void evaluate(const Vector3D& xi, ShapeEvaluationFlags requested, ShapeInfo& out) const override { - const CfemInt n = 2; - if (out.values.size() != n) out.setup(n); + void evaluateShapesValues(const Vector3D &xi, + CfemReal *values) const override + { + const CfemReal x = xi.x; + values[0] = 0.5 * (1.0 - x); + values[1] = 0.5 * (1.0 + x); + } - if (hasFlag(requested, ShapeEvaluationFlags::Hessian) && out.hessians.size() != n) { - out.hessians.assign(n, SymTensor3D()); - } + void evaluateShapesDerivatives(const Vector3D &xi, + CfemReal *dN_dxi, + CfemReal *dN_deta, + CfemReal *dN_dzeta) const override + { + // Dérivées / x + dN_dxi[0] = -0.5; + dN_dxi[1] = 0.5; + + // L'élément est 1D : dérivées y et z sont nulles + dN_deta[0] = 0.0; + dN_deta[1] = 0.0; + dN_dzeta[0] = 0.0; + dN_dzeta[1] = 0.0; + } - const CfemReal x = xi.x; + void evaluateShapesSecondDerivatives(const Vector3D &xi, + CfemReal *d2N_dxi2, + CfemReal *d2N_deta2, + CfemReal *d2N_dzeta2, + CfemReal *d2N_dxideta, + CfemReal *d2N_dxidzeta, + CfemReal *d2N_detadzeta) const override + { + for (CfemInt i = 0; i < 2; ++i) + { + d2N_dxi2[i] = 0.0; + d2N_deta2[i] = 0.0; + d2N_dzeta2[i] = 0.0; + d2N_dxideta[i] = 0.0; + d2N_dxidzeta[i] = 0.0; + d2N_detadzeta[i] = 0.0; + } + } - if (hasFlag(requested, ShapeEvaluationFlags::Value)) { - out.values[0] = 0.5 * (1.0 - x); - out.values[1] = 0.5 * (1.0 + x); + void evaluateShapesValuesBatch(const DynamicVector &xiPoints, + DynamicMatrix &valuesBatch) const override + { + const CfemInt nQp = static_cast(xiPoints.size()); + for (CfemInt q = 0; q < nQp; ++q) + { + const CfemReal x = xiPoints[q].x; + valuesBatch[q][0] = 0.5 * (1.0 - x); + valuesBatch[q][1] = 0.5 * (1.0 + x); } + } - if (hasFlag(requested, ShapeEvaluationFlags::Gradient)) { - out.gradients[0] = Vector3D{-0.5, 0.0, 0.0}; - out.gradients[1] = Vector3D{ 0.5, 0.0, 0.0}; + void evaluateShapesDerivativesBatch(const DynamicVector &xiPoints, + DynamicMatrix &dN_dxi, + DynamicMatrix &dN_deta, + DynamicMatrix &dN_dzeta) const override + { + const CfemInt nQp = static_cast(xiPoints.size()); + for (CfemInt q = 0; q < nQp; ++q) + { + // Dérivées / x + dN_dxi[q][0] = -0.5; + dN_dxi[q][1] = 0.5; + + // L'élément est 1D : dérivées y et z sont nulles + dN_deta[q][0] = 0.0; + dN_deta[q][1] = 0.0; + dN_dzeta[q][0] = 0.0; + dN_dzeta[q][1] = 0.0; } + } - if (hasFlag(requested, ShapeEvaluationFlags::Hessian)) { - for (auto& H : out.hessians) H.setZero(); + void evaluateShapesSecondDerivativesBatch(const DynamicVector &xiPoints, + DynamicMatrix &d2N_dxi2, + DynamicMatrix &d2N_deta2, + DynamicMatrix &d2N_dzeta2, + DynamicMatrix &d2N_dxideta, + DynamicMatrix &d2N_dxidzeta, + DynamicMatrix &d2N_detadzeta) const override + { + const CfemInt nQp = static_cast(xiPoints.size()); + for (CfemInt q = 0; q < nQp; ++q) + { + for (CfemInt i = 0; i < 2; ++i) + { + d2N_dxi2[q][i] = 0.0; + d2N_deta2[q][i] = 0.0; + d2N_dzeta2[q][i] = 0.0; + d2N_dxideta[q][i] = 0.0; + d2N_dxidzeta[q][i] = 0.0; + d2N_detadzeta[q][i] = 0.0; + } } - out.validFlags = requested; } - Vector3D projectFacetRefCoordToCellRefCoord(CfemInt localFacetId, const Vector3D& xiFacet) const override{ + Vector3D projectFacetRefCoordToCellRefCoord(CfemInt localFacetId, const Vector3D &xiFacet) const override + { return cfem::reference_elem::utils::projectFacetRefCoordToSegmentRefCoord(localFacetId, xiFacet); // Make the compiler happy } - std::span getFaceNodes(CfemInt iFace) const override { + std::span getFaceNodes(CfemInt iFace) const override + { return cfem::reference_elem::utils::line_face_nodes[iFace]; } }; - class P2ReferenceSegment : public ReferenceElement { + class P2ReferenceSegment : public ReferenceElement + { public: - P2ReferenceSegment() { + P2ReferenceSegment() + { m_numNodes = 3; m_dimension = 1; m_nodes.resize(m_numNodes); // Domaine de référence [-1, 1], ordre VTK : extrémités puis milieu m_nodes[0] = Vector3D{-1.0, 0.0, 0.0}; - m_nodes[1] = Vector3D{ 1.0, 0.0, 0.0}; - m_nodes[2] = Vector3D{ 0.0, 0.0, 0.0}; // Nœud central en x=0 + m_nodes[1] = Vector3D{1.0, 0.0, 0.0}; + m_nodes[2] = Vector3D{0.0, 0.0, 0.0}; // Nœud central en x=0 } - void evaluate(const Vector3D& xi, ShapeEvaluationFlags requested, ShapeInfo& out) const override { - const CfemInt n = 3; - if (out.values.size() != n) out.setup(n); - if (hasFlag(requested, ShapeEvaluationFlags::Hessian) && out.hessians.size() != n) { - out.hessians.assign(n, SymTensor3D()); - } + void evaluateShapesValues(const Vector3D &xi, + CfemReal *values) const override + { + const CfemReal x = xi.x; + const CfemReal x2 = x * x; + values[0] = 0.5 * (x2 - x); + values[1] = 0.5 * (x2 + x); + values[2] = 1.0 - x2; + } + + void evaluateShapesDerivatives(const Vector3D &xi, + CfemReal *dN_dxi, + CfemReal *dN_deta, + CfemReal *dN_dzeta) const override + { const CfemReal x = xi.x; - if (hasFlag(requested, ShapeEvaluationFlags::Value)) { - out.values[0] = 0.5 * x * (x - 1.0); - out.values[1] = 0.5 * x * (x + 1.0); - out.values[2] = 1.0 - x * x; + dN_dxi[0] = x - 0.5; + dN_dxi[1] = x + 0.5; + dN_dxi[2] = -2.0 * x; + + for (CfemInt i = 0; i < 3; ++i) + { + dN_deta[i] = 0.0; + dN_dzeta[i] = 0.0; + } + } + + void evaluateShapesSecondDerivatives(const Vector3D &xiPoints, + CfemReal *d2N_dxi2, + CfemReal *d2N_deta2, + CfemReal *d2N_dzeta2, + CfemReal *d2N_dxideta, + CfemReal *d2N_dxidzeta, + CfemReal *d2N_detadzeta) const override + { + + d2N_dxi2[0] = 1.0; + d2N_dxi2[1] = 1.0; + d2N_dxi2[2] = -2.0; + + for (CfemInt i = 0; i < 3; ++i) + { + d2N_deta2[i] = 0.0; + d2N_dzeta2[i] = 0.0; + d2N_dxideta[i] = 0.0; + d2N_dxidzeta[i] = 0.0; + d2N_detadzeta[i] = 0.0; } + } - if (hasFlag(requested, ShapeEvaluationFlags::Gradient)) { - out.gradients[0] = Vector3D{x - 0.5, 0.0, 0.0}; - out.gradients[1] = Vector3D{x + 0.5, 0.0, 0.0}; - out.gradients[2] = Vector3D{-2.0 * x, 0.0, 0.0}; + void evaluateShapesValuesBatch(const DynamicVector &xiPoints, + DynamicMatrix &valuesBatch) const override + { + const CfemInt nQp = static_cast(xiPoints.size()); + for (CfemInt q = 0; q < nQp; ++q) + { + const CfemReal x = xiPoints[q].x; + const CfemReal x2 = x * x; + + valuesBatch[q][0] = 0.5 * (x2 - x); + valuesBatch[q][1] = 0.5 * (x2 + x); + valuesBatch[q][2] = 1.0 - x2; } + } - if (hasFlag(requested, ShapeEvaluationFlags::Hessian)) { - for (auto& H : out.hessians) H.setZero(); - out.hessians[0].xx() = 1.0; - out.hessians[1].xx() = 1.0; - out.hessians[2].xx() = -2.0; + void evaluateShapesDerivativesBatch(const DynamicVector &xiPoints, + DynamicMatrix &dN_dxi, + DynamicMatrix &dN_deta, + DynamicMatrix &dN_dzeta) const override + { + const CfemInt nQp = static_cast(xiPoints.size()); + for (CfemInt q = 0; q < nQp; ++q) + { + const CfemReal x = xiPoints[q].x; + + dN_dxi[q][0] = x - 0.5; + dN_dxi[q][1] = x + 0.5; + dN_dxi[q][2] = -2.0 * x; + + for (CfemInt i = 0; i < 3; ++i) + { + dN_deta[q][i] = 0.0; + dN_dzeta[q][i] = 0.0; + } } - out.validFlags = requested; } - Vector3D projectFacetRefCoordToCellRefCoord(CfemInt localFacetId, const Vector3D& xiFacet) const override{ + void evaluateShapesSecondDerivativesBatch(const DynamicVector &xiPoints, + DynamicMatrix &d2N_dxi2, DynamicMatrix &d2N_deta2, DynamicMatrix &d2N_dzeta2, + DynamicMatrix &d2N_dxideta, DynamicMatrix &d2N_dxidzeta, DynamicMatrix &d2N_detadzeta) const override + { + const CfemInt nQp = static_cast(xiPoints.size()); + for (CfemInt q = 0; q < nQp; ++q) + { + + d2N_dxi2[q][0] = 1.0; + d2N_dxi2[q][1] = 1.0; + d2N_dxi2[q][2] = -2.0; + + for (CfemInt i = 0; i < 3; ++i) + { + d2N_deta2[q][i] = 0.0; + d2N_dzeta2[q][i] = 0.0; + d2N_dxideta[q][i] = 0.0; + d2N_dxidzeta[q][i] = 0.0; + d2N_detadzeta[q][i] = 0.0; + } + } + } + + Vector3D projectFacetRefCoordToCellRefCoord(CfemInt localFacetId, const Vector3D &xiFacet) const override + { return cfem::reference_elem::utils::projectFacetRefCoordToSegmentRefCoord(localFacetId, xiFacet); // Make the compiler happy } - std::span getFaceNodes(CfemInt iFace) const override { + std::span getFaceNodes(CfemInt iFace) const override + { return cfem::reference_elem::utils::line_face_nodes[iFace]; } }; - - class P0ReferenceSegment : public ReferenceElement { + class P0ReferenceSegment : public ReferenceElement + { public: - P0ReferenceSegment() { + P0ReferenceSegment() + { m_numNodes = 1; m_dimension = 1; m_nodes.resize(m_numNodes); // Un seul nœud au centre du segment de référence [-1, 1] - m_nodes = { {0.0, 0.0, 0.0} }; + m_nodes = {Vector3D{0.0, 0.0, 0.0}}; } - void evaluate(const Vector3D& xi, ShapeEvaluationFlags requested, ShapeInfo& out) const override { - const CfemInt n = 1; - if (out.values.size() != n) out.setup(n); + void evaluateShapesValues(const Vector3D &, CfemReal *values) const override + { + values[0] = 1.0; + } - // Valeur constante N0 = 1 - if (hasFlag(requested, ShapeEvaluationFlags::Value)) { - out.values[0] = 1.0; - } + void evaluateShapesDerivatives(const Vector3D &, + CfemReal *dN_dxi, + CfemReal *dN_deta, + CfemReal *dN_dzeta) const override + { + dN_dxi[0] = 0.0; + dN_deta[0] = 0.0; + dN_dzeta[0] = 0.0; + } + + void evaluateShapesSecondDerivatives(const Vector3D &, + CfemReal *d2N_dxi2, + CfemReal *d2N_deta2, + CfemReal *d2N_dzeta2, + CfemReal *d2N_dxideta, + CfemReal *d2N_dxidzeta, + CfemReal *d2N_detadzeta) const override + { + d2N_dxi2[0] = 0.0; + d2N_deta2[0] = 0.0; + d2N_dzeta2[0] = 0.0; + d2N_dxideta[0] = 0.0; + d2N_dxidzeta[0] = 0.0; + d2N_detadzeta[0] = 0.0; + } - // Gradient nul - if (hasFlag(requested, ShapeEvaluationFlags::Gradient)) { - out.gradients[0] = Vector3D{0.0, 0.0, 0.0}; + void evaluateShapesValuesBatch(const DynamicVector &xiPoints, DynamicMatrix &values) const override + { + // Constant piecewise field value + const CfemInt nQp = static_cast(xiPoints.size()); + for (CfemInt q = 0; q < nQp; ++q) + { + values[q][0] = 1.0; } + } - // Hessien nul - if (hasFlag(requested, ShapeEvaluationFlags::Hessian)) { - if (out.hessians.size() != n) out.hessians.assign(n, SymTensor3D()); - out.hessians[0].setZero(); + void evaluateShapesDerivativesBatch(const DynamicVector &xiPoints, + DynamicMatrix &dN_dxi, + DynamicMatrix &dN_deta, + DynamicMatrix &dN_dzeta) const override + { + // Constant function results in flat zero gradients everywhere + const CfemInt nQp = static_cast(xiPoints.size()); + for (CfemInt q = 0; q < nQp; ++q) + { + dN_dxi[q][0] = 0.0; + dN_deta[q][0] = 0.0; + dN_dzeta[q][0] = 0.0; } + } - out.validFlags = requested; + void evaluateShapesSecondDerivativesBatch(const DynamicVector &xiPoints, + DynamicMatrix &d2N_dxi2, + DynamicMatrix &d2N_deta2, + DynamicMatrix &d2N_dzeta2, + DynamicMatrix &d2N_dxideta, + DynamicMatrix &d2N_dxidzeta, + DynamicMatrix &d2N_detadzeta) const override + { + // Flat null second derivatives + const CfemInt nQp = static_cast(xiPoints.size()); + for (CfemInt q = 0; q < nQp; ++q) + { + d2N_dxi2[q][0] = 0.0; + d2N_deta2[q][0] = 0.0; + d2N_dzeta2[q][0] = 0.0; + d2N_dxideta[q][0] = 0.0; + d2N_dxidzeta[q][0] = 0.0; + d2N_detadzeta[q][0] = 0.0; + } } - Vector3D projectFacetRefCoordToCellRefCoord(CfemInt localFacetId, const Vector3D& xiFacet) const override { + Vector3D projectFacetRefCoordToCellRefCoord(CfemInt localFacetId, const Vector3D &xiFacet) const override + { return cfem::reference_elem::utils::projectFacetRefCoordToSegmentRefCoord(localFacetId, xiFacet); } - std::span getFaceNodes(CfemInt iFace) const override { + std::span getFaceNodes(CfemInt iFace) const override + { return cfem::reference_elem::utils::line1_face_nodes[iFace]; } }; } - #endif \ No newline at end of file diff --git a/libs/fem/reference_element/include/ReferenceTetrahedron.h b/libs/fem/reference_element/include/ReferenceTetrahedron.h index 9a1fb85..cb31267 100644 --- a/libs/fem/reference_element/include/ReferenceTetrahedron.h +++ b/libs/fem/reference_element/include/ReferenceTetrahedron.h @@ -69,38 +69,102 @@ namespace cfem { P1ReferenceTetrahedron() { m_numNodes = 4; m_dimension = 3; - m_nodes.resize(m_numNodes); - m_nodes = { {0,0,0}, {1,0,0}, {0,1,0}, {0,0,1} }; + + m_nodes = { + Vector3D{0.0, 0.0, 0.0}, + Vector3D{1.0, 0.0, 0.0}, + Vector3D{0.0, 1.0, 0.0}, + Vector3D{0.0, 0.0, 1.0} + }; } - void evaluate(const Vector3D& xi, ShapeEvaluationFlags requested, ShapeInfo& out) const override { - const CfemInt n = 4; - if (out.values.size() != n) out.setup(n); - if (hasFlag(requested, ShapeEvaluationFlags::Hessian) && out.hessians.size() != n) { - out.hessians.assign(n, SymTensor3D()); - } + void evaluateShapesValues(const Vector3D& xi, CfemReal* values) const override + { + values[0] = 1.0 - xi.x - xi.y - xi.z; + values[1] = xi.x; + values[2] = xi.y; + values[3] = xi.z; + } - const CfemReal L1 = xi.x; - const CfemReal L2 = xi.y; - const CfemReal L3 = xi.z; - const CfemReal L0 = 1.0 - L1 - L2 - L3; + void evaluateShapesDerivatives(const Vector3D&, + CfemReal* dN_dxi, + CfemReal* dN_deta, + CfemReal* dN_dzeta) const override + { + // d/dxi + dN_dxi[0] = -1.0; dN_dxi[1] = 1.0; dN_dxi[2] = 0.0; dN_dxi[3] = 0.0; + // d/deta + dN_deta[0] = -1.0; dN_deta[1] = 0.0; dN_deta[2] = 1.0; dN_deta[3] = 0.0; + // d/dzeta + dN_dzeta[0] = -1.0; dN_dzeta[1] = 0.0; dN_dzeta[2] = 0.0; dN_dzeta[3] = 1.0; + } + + void evaluateShapesSecondDerivatives(const Vector3D&, + CfemReal* d2N_dxi2, + CfemReal* d2N_deta2, + CfemReal* d2N_dzeta2, + CfemReal* d2N_dxideta, + CfemReal* d2N_dxidzeta, + CfemReal* d2N_detadzeta) const override + { + for (CfemInt i = 0; i < 4; ++i) { + d2N_dxi2[i] = 0.0; + d2N_deta2[i] = 0.0; + d2N_dzeta2[i] = 0.0; + d2N_dxideta[i] = 0.0; + d2N_dxidzeta[i] = 0.0; + d2N_detadzeta[i] = 0.0; + } + } - if (hasFlag(requested, ShapeEvaluationFlags::Value)) { - out.values[0] = L0; out.values[1] = L1; - out.values[2] = L2; out.values[3] = L3; + void evaluateShapesValuesBatch(const DynamicVector &xiPoints, DynamicMatrix& values) const override + { + // Constant piecewise field value + const CfemInt nQp = static_cast(xiPoints.size()); + for (CfemInt q = 0; q < nQp; ++q) { + const auto& xi = xiPoints[q]; + values[q][0] = 1.0 - xi.x - xi.y - xi.z; + values[q][1] = xi.x; + values[q][2] = xi.y; + values[q][3] = xi.z; } + } - if (hasFlag(requested, ShapeEvaluationFlags::Gradient)) { - out.gradients[0] = {-1, -1, -1}; - out.gradients[1] = { 1, 0, 0}; - out.gradients[2] = { 0, 1, 0}; - out.gradients[3] = { 0, 0, 1}; + void evaluateShapesDerivativesBatch(const DynamicVector & xiPoints, + DynamicMatrix &dN_dxi, + DynamicMatrix &dN_deta, + DynamicMatrix &dN_dzeta) const override + { + // Constant function results in flat zero gradients everywhere + const CfemInt nQp = static_cast(xiPoints.size()); + for (CfemInt q = 0; q < nQp; ++q) { + // d/dxi + dN_dxi[q][0] = -1.0; dN_dxi[q][1] = 1.0; dN_dxi[q][2] = 0.0; dN_dxi[q][3] = 0.0; + // d/deta + dN_deta[q][0] = -1.0; dN_deta[q][1] = 0.0; dN_deta[q][2] = 1.0; dN_deta[q][3] = 0.0; + // d/dzeta + dN_dzeta[q][0] = -1.0; dN_dzeta[q][1] = 0.0; dN_dzeta[q][2] = 0.0; dN_dzeta[q][3] = 1.0; } + } - if (hasFlag(requested, ShapeEvaluationFlags::Hessian)) { - for (auto& H : out.hessians) H.setZero(); + void evaluateShapesSecondDerivativesBatch(const DynamicVector &xiPoints, + DynamicMatrix &d2N_dxi2, + DynamicMatrix &d2N_deta2, + DynamicMatrix &d2N_dzeta2, + DynamicMatrix &d2N_dxideta, + DynamicMatrix &d2N_dxidzeta, + DynamicMatrix &d2N_detadzeta) const override + { + // Flat null second derivatives + const CfemInt nQp = static_cast(xiPoints.size()); + for (CfemInt q = 0; q < nQp; ++q) { + d2N_dxi2[q][0] = 0.0; + d2N_deta2[q][0] = 0.0; + d2N_dzeta2[q][0] = 0.0; + d2N_dxideta[q][0] = 0.0; + d2N_dxidzeta[q][0] = 0.0; + d2N_detadzeta[q][0] = 0.0; } - out.validFlags = requested; } Vector3D projectFacetRefCoordToCellRefCoord(CfemInt localFacetId, const Vector3D& xiFacet) const override{ @@ -120,87 +184,272 @@ namespace cfem { P2ReferenceTetrahedron() { m_numNodes = 10; m_dimension = 3; - m_nodes.resize(m_numNodes); - // Sommets - m_nodes[0]={0,0,0}; m_nodes[1]={1,0,0}; m_nodes[2]={0,1,0}; m_nodes[3]={0,0,1}; - // Milieux d'arêtes (Standard convention: 01, 12, 20, 03, 13, 23) - m_nodes[4]={0.5, 0.0, 0.0}; m_nodes[5]={0.5, 0.5, 0.0}; m_nodes[6]={0.0, 0.5, 0.0}; - m_nodes[7]={0.0, 0.0, 0.5}; m_nodes[8]={0.5, 0.0, 0.5}; m_nodes[9]={0.0, 0.5, 0.5}; + + m_nodes = { + Vector3D{0.0, 0.0 ,0.0}, + Vector3D{1.0, 0.0 ,0.0}, + Vector3D{0.0, 1.0 ,0.0}, + Vector3D{0.0, 0.0 ,1.0}, + // Milieux d'arêtes (Standard convention: 01, 12, 20, 03, 13, 23) + Vector3D{0.5, 0.0, 0.0}, + Vector3D{0.5, 0.5, 0.0}, + Vector3D{0.0, 0.5, 0.0}, + Vector3D{0.0, 0.0, 0.5}, + Vector3D{0.5, 0.0, 0.5}, + Vector3D{0.0, 0.5, 0.5} + }; } - void evaluate(const Vector3D& xi, ShapeEvaluationFlags requested, ShapeInfo& out) const override { - const CfemInt n = 10; - if (out.values.size() != n) out.setup(n); - if (hasFlag(requested, ShapeEvaluationFlags::Hessian) && out.hessians.size() != n) { - out.hessians.assign(n, SymTensor3D()); - } - + void evaluateShapesValues(const Vector3D& xi, CfemReal* values) const override + { const CfemReal L1 = xi.x; const CfemReal L2 = xi.y; const CfemReal L3 = xi.z; const CfemReal L0 = 1.0 - L1 - L2 - L3; - if (hasFlag(requested, ShapeEvaluationFlags::Value)) { - out.values[0] = L0*(2*L0-1); out.values[1] = L1*(2*L1-1); - out.values[2] = L2*(2*L2-1); out.values[3] = L3*(2*L3-1); - out.values[4] = 4*L0*L1; out.values[5] = 4*L1*L2; - out.values[6] = 4*L2*L0; out.values[7] = 4*L0*L3; - out.values[8] = 4*L1*L3; out.values[9] = 4*L2*L3; - } + // Sommets + values[0] = L0 * (2.0 * L0 - 1.0); + values[1] = L1 * (2.0 * L1 - 1.0); + values[2] = L2 * (2.0 * L2 - 1.0); + values[3] = L3 * (2.0 * L3 - 1.0); + + // Arêtes horizontales (base) + values[4] = 4.0 * L0 * L1; + values[5] = 4.0 * L1 * L2; + values[6] = 4.0 * L2 * L0; + + // Arêtes verticales (vers L3) + values[7] = 4.0 * L0 * L3; + values[8] = 4.0 * L1 * L3; + values[9] = 4.0 * L2 * L3; + } - if (hasFlag(requested, ShapeEvaluationFlags::Gradient)) { - // Exemple pour N0: Grad(L0(2L0-1)) = (4L0-1)*Grad(L0) = (4L0-1)*(-1,-1,-1) - auto gL0 = Vector3D{-1,-1,-1}; - out.gradients[0] = gL0 * (4*L0-1); - out.gradients[1] = {4*L1-1, 0, 0}; - out.gradients[2] = {0, 4*L2-1, 0}; - out.gradients[3] = {0, 0, 4*L3-1}; - // Milieux (ex N4 = 4L0L1 -> Grad = 4(L1*GradL0 + L0*GradL1)) - out.gradients[4] = {4*(L0-L1), -4*L1, -4*L1}; - out.gradients[5] = {4*L2, 4*L1, 0}; - out.gradients[6] = {-4*L2, 4*(L0-L2), -4*L2}; - out.gradients[7] = {-4*L3, -4*L3, 4*(L0-L3)}; - out.gradients[8] = {4*L3, 0, 4*L1}; - out.gradients[9] = {0, 4*L3, 4*L2}; - } + void evaluateShapesDerivatives(const Vector3D& xi, + CfemReal* dN_dxi, + CfemReal* dN_deta, + CfemReal* dN_dzeta) const override + { + const CfemReal L1 = xi.x; + const CfemReal L2 = xi.y; + const CfemReal L3 = xi.z; + + // Dérivées par rapport à xi (x) + dN_dxi[0] = -3.0 + 4.0*L1 + 4.0*L2 + 4.0*L3; + dN_dxi[1] = 4.0*L1 - 1.0; + dN_dxi[2] = 0.0; + dN_dxi[3] = 0.0; + dN_dxi[4] = 4.0 - 8.0*L1 - 4.0*L2 - 4.0*L3; + dN_dxi[5] = 4.0*L2; + dN_dxi[6] = -4.0*L2; + dN_dxi[7] = -4.0*L3; + dN_dxi[8] = 4.0*L3; + dN_dxi[9] = 0.0; + + // Dérivées par rapport à eta (y) + dN_deta[0] = -3.0 + 4.0*L1 + 4.0*L2 + 4.0*L3; + dN_deta[1] = 0.0; + dN_deta[2] = 4.0*L2 - 1.0; + dN_deta[3] = 0.0; + dN_deta[4] = -4.0*L1; + dN_deta[5] = 4.0*L1; + dN_deta[6] = 4.0 - 4.0*L1 - 8.0*L2 - 4.0*L3; + dN_deta[7] = -4.0*L3; + dN_deta[8] = 0.0; + dN_deta[9] = 4.0*L3; + + // Dérivées par rapport à zeta (z) + dN_dzeta[0] = -3.0 + 4.0*L1 + 4.0*L2 + 4.0*L3; + dN_dzeta[1] = 0.0; + dN_dzeta[2] = 0.0; + dN_dzeta[3] = 4.0*L3 - 1.0; + dN_dzeta[4] = -4.0*L1; + dN_dzeta[5] = 0.0; + dN_dzeta[6] = -4.0*L2; + dN_dzeta[7] = 4.0 - 4.0*L1 - 4.0*L2 - 8.0*L3; + dN_dzeta[8] = 4.0*L1; + dN_dzeta[9] = 4.0*L2; + } - if (hasFlag(requested, ShapeEvaluationFlags::Hessian)) { - for (CfemInt i = 0; i < n; ++i) out.hessians[i].setZero(); + void evaluateShapesSecondDerivatives(const Vector3D&, + CfemReal* d2N_dxi2, + CfemReal* d2N_deta2, + CfemReal* d2N_dzeta2, + CfemReal* d2N_dxideta, + CfemReal* d2N_dxidzeta, + CfemReal* d2N_detadzeta) const override + { + // Nœud 0 + d2N_dxi2[0] = 4.0; d2N_deta2[0] = 4.0; d2N_dzeta2[0] = 4.0; + d2N_dxideta[0] = 4.0; d2N_dxidzeta[0] = 4.0; d2N_detadzeta[0] = 4.0; + + // Nœud 1 + d2N_dxi2[1] = 4.0; d2N_deta2[1] = 0.0; d2N_dzeta2[1] = 0.0; + d2N_dxideta[1] = 0.0; d2N_dxidzeta[1] = 0.0; d2N_detadzeta[1] = 0.0; + + // Nœud 2 + d2N_dxi2[2] = 0.0; d2N_deta2[2] = 4.0; d2N_dzeta2[2] = 0.0; + d2N_dxideta[2] = 0.0; d2N_dxidzeta[2] = 0.0; d2N_detadzeta[2] = 0.0; + + // Nœud 3 + d2N_dxi2[3] = 0.0; d2N_deta2[3] = 0.0; d2N_dzeta2[3] = 4.0; + d2N_dxideta[3] = 0.0; d2N_dxidzeta[3] = 0.0; d2N_detadzeta[3] = 0.0; + + // Nœud 4 + d2N_dxi2[4] =-8.0; d2N_deta2[4] = 0.0; d2N_dzeta2[4] = 0.0; + d2N_dxideta[4] =-4.0; d2N_dxidzeta[4] =-4.0; d2N_detadzeta[4] = 0.0; + + // Nœud 5 + d2N_dxi2[5] = 0.0; d2N_deta2[5] = 0.0; d2N_dzeta2[5] = 0.0; + d2N_dxideta[5] = 4.0; d2N_dxidzeta[5] = 0.0; d2N_detadzeta[5] = 0.0; + + // Nœud 6 + d2N_dxi2[6] = 0.0; d2N_deta2[6] =-8.0; d2N_dzeta2[6] = 0.0; + d2N_dxideta[6] =-4.0; d2N_dxidzeta[6] = 0.0; d2N_detadzeta[6] =-4.0; + + // Nœud 7 + d2N_dxi2[7] = 0.0; d2N_deta2[7] = 0.0; d2N_dzeta2[7] =-8.0; + d2N_dxideta[7] = 0.0; d2N_dxidzeta[7] =-4.0; d2N_detadzeta[7] =-4.0; + + // Nœud 8 + d2N_dxi2[8] = 0.0; d2N_deta2[8] = 0.0; d2N_dzeta2[8] = 0.0; + d2N_dxideta[8] = 0.0; d2N_dxidzeta[8] = 4.0; d2N_detadzeta[8] = 0.0; + + // Nœud 9 + d2N_dxi2[9] = 0.0; d2N_deta2[9] = 0.0; d2N_dzeta2[9] = 0.0; + d2N_dxideta[9] = 0.0; d2N_dxidzeta[9] = 0.0; d2N_detadzeta[9] = 4.0; + } + + void evaluateShapesValuesBatch(const DynamicVector &xiPoints, DynamicMatrix& values) const override + { + // Constant piecewise field value + const CfemInt nQp = static_cast(xiPoints.size()); + for (CfemInt q = 0; q < nQp; ++q) { + const Vector3D& xi = xiPoints[q]; + const CfemReal L1 = xi.x; + const CfemReal L2 = xi.y; + const CfemReal L3 = xi.z; + const CfemReal L0 = 1.0 - L1 - L2 - L3; // Sommets - // N0 = L0(2L0-1) avec L0 = 1-x-y-z. - // d2N0/dx2 = d2N0/dy2 = d2N0/dz2 = 4 | d2N0/dxdy = d2N0/dxdz = d2N0/dydz = 4 - out.hessians[0].setTo(4.0); - out.hessians[1].xx() = 4.0; // Ni = Li(2Li-1) - out.hessians[2].yy() = 4.0; - out.hessians[3].zz() = 4.0; - - // Milieux d'arêtes - // N4 = 4*L0*L1 = 4*(1-x-y-z)*x = 4x - 4x^2 - 4xy - 4xz - out.hessians[4].xx() = -8.0; - out.hessians[4].xy() = -4.0; - out.hessians[4].xz() = -4.0; - - // N5 = 4*L1*L2 = 4*x*y - out.hessians[5].xy() = 4.0; - - // N6 = 4*L2*L0 = 4*y*(1-x-y-z) = 4y - 4xy - 4y^2 - 4yz - out.hessians[6].yy() = -8.0; - out.hessians[6].xy() = -4.0; - out.hessians[6].yz() = -4.0; - - // N7 = 4*L0*L3 = 4*(1-x-y-z)*z = 4z - 4xz - 4yz - 4z^2 - out.hessians[7].zz() = -8.0; - out.hessians[7].xz() = -4.0; - out.hessians[7].yz() = -4.0; - - // N8 = 4*L1*L3 = 4*x*z - out.hessians[8].xz() = 4.0; - - // N9 = 4*L2*L3 = 4*y*z - out.hessians[9].yz() = 4.0; + values[q][0] = L0 * (2.0 * L0 - 1.0); + values[q][1] = L1 * (2.0 * L1 - 1.0); + values[q][2] = L2 * (2.0 * L2 - 1.0); + values[q][3] = L3 * (2.0 * L3 - 1.0); + + // Arêtes horizontales (base) + values[q][4] = 4.0 * L0 * L1; + values[q][5] = 4.0 * L1 * L2; + values[q][6] = 4.0 * L2 * L0; + + // Arêtes verticales (vers L3) + values[q][7] = 4.0 * L0 * L3; + values[q][8] = 4.0 * L1 * L3; + values[q][9] = 4.0 * L2 * L3; + } + } + + void evaluateShapesDerivativesBatch(const DynamicVector & xiPoints, + DynamicMatrix &dN_dxi, + DynamicMatrix &dN_deta, + DynamicMatrix &dN_dzeta) const override + { + // Constant function results in flat zero gradients everywhere + const CfemInt nQp = static_cast(xiPoints.size()); + for (CfemInt q = 0; q < nQp; ++q) { + const Vector3D& xi = xiPoints[q]; + const CfemReal L1 = xi.x; + const CfemReal L2 = xi.y; + const CfemReal L3 = xi.z; + + // Dérivées par rapport à xi (x) + dN_dxi[q][0] = -3.0 + 4.0*L1 + 4.0*L2 + 4.0*L3; + dN_dxi[q][1] = 4.0*L1 - 1.0; + dN_dxi[q][2] = 0.0; + dN_dxi[q][3] = 0.0; + dN_dxi[q][4] = 4.0 - 8.0*L1 - 4.0*L2 - 4.0*L3; + dN_dxi[q][5] = 4.0*L2; + dN_dxi[q][6] = -4.0*L2; + dN_dxi[q][7] = -4.0*L3; + dN_dxi[q][8] = 4.0*L3; + dN_dxi[q][9] = 0.0; + + // Dérivées par rapport à eta (y) + dN_deta[q][0] = -3.0 + 4.0*L1 + 4.0*L2 + 4.0*L3; + dN_deta[q][1] = 0.0; + dN_deta[q][2] = 4.0*L2 - 1.0; + dN_deta[q][3] = 0.0; + dN_deta[q][4] = -4.0*L1; + dN_deta[q][5] = 4.0*L1; + dN_deta[q][6] = 4.0 - 4.0*L1 - 8.0*L2 - 4.0*L3; + dN_deta[q][7] = -4.0*L3; + dN_deta[q][8] = 0.0; + dN_deta[q][9] = 4.0*L3; + + // Dérivées par rapport à zeta (z) + dN_dzeta[q][0] = -3.0 + 4.0*L1 + 4.0*L2 + 4.0*L3; + dN_dzeta[q][1] = 0.0; + dN_dzeta[q][2] = 0.0; + dN_dzeta[q][3] = 4.0*L3 - 1.0; + dN_dzeta[q][4] = -4.0*L1; + dN_dzeta[q][5] = 0.0; + dN_dzeta[q][6] = -4.0*L2; + dN_dzeta[q][7] = 4.0 - 4.0*L1 - 4.0*L2 - 8.0*L3; + dN_dzeta[q][8] = 4.0*L1; + dN_dzeta[q][9] = 4.0*L2; + } + } + + void evaluateShapesSecondDerivativesBatch(const DynamicVector &xiPoints, + DynamicMatrix &d2N_dxi2, + DynamicMatrix &d2N_deta2, + DynamicMatrix &d2N_dzeta2, + DynamicMatrix &d2N_dxideta, + DynamicMatrix &d2N_dxidzeta, + DynamicMatrix &d2N_detadzeta) const override + { + // Flat null second derivatives + const CfemInt nQp = static_cast(xiPoints.size()); + for (CfemInt q = 0; q < nQp; ++q) { + // Nœud 0 + d2N_dxi2[q][0] = 4.0; d2N_deta2[q][0] = 4.0; d2N_dzeta2[q][0] = 4.0; + d2N_dxideta[q][0] = 4.0; d2N_dxidzeta[q][0] = 4.0; d2N_detadzeta[q][0] = 4.0; + + // Nœud 1 + d2N_dxi2[q][1] = 4.0; d2N_deta2[q][1] = 0.0; d2N_dzeta2[q][1] = 0.0; + d2N_dxideta[q][1] = 0.0; d2N_dxidzeta[q][1] = 0.0; d2N_detadzeta[q][1] = 0.0; + + // Nœud 2 + d2N_dxi2[q][2] = 0.0; d2N_deta2[q][2] = 4.0; d2N_dzeta2[q][2] = 0.0; + d2N_dxideta[q][2] = 0.0; d2N_dxidzeta[q][2] = 0.0; d2N_detadzeta[q][2] = 0.0; + + // Nœud 3 + d2N_dxi2[q][3] = 0.0; d2N_deta2[q][3] = 0.0; d2N_dzeta2[q][3] = 4.0; + d2N_dxideta[q][3] = 0.0; d2N_dxidzeta[q][3] = 0.0; d2N_detadzeta[q][3] = 0.0; + + // Nœud 4 + d2N_dxi2[q][4] =-8.0; d2N_deta2[q][4] = 0.0; d2N_dzeta2[q][4] = 0.0; + d2N_dxideta[q][4] =-4.0; d2N_dxidzeta[q][4] =-4.0; d2N_detadzeta[q][4] = 0.0; + + // Nœud 5 + d2N_dxi2[q][5] = 0.0; d2N_deta2[q][5] = 0.0; d2N_dzeta2[q][5] = 0.0; + d2N_dxideta[q][5] = 4.0; d2N_dxidzeta[q][5] = 0.0; d2N_detadzeta[q][5] = 0.0; + + // Nœud 6 + d2N_dxi2[q][6] = 0.0; d2N_deta2[q][6] =-8.0; d2N_dzeta2[q][6] = 0.0; + d2N_dxideta[q][6] =-4.0; d2N_dxidzeta[q][6] = 0.0; d2N_detadzeta[q][6] =-4.0; + + // Nœud 7 + d2N_dxi2[q][7] = 0.0; d2N_deta2[q][7] = 0.0; d2N_dzeta2[q][7] =-8.0; + d2N_dxideta[q][7] = 0.0; d2N_dxidzeta[q][7] =-4.0; d2N_detadzeta[q][7] =-4.0; + + // Nœud 8 + d2N_dxi2[q][8] = 0.0; d2N_deta2[q][8] = 0.0; d2N_dzeta2[q][8] = 0.0; + d2N_dxideta[q][8] = 0.0; d2N_dxidzeta[q][8] = 4.0; d2N_detadzeta[q][8] = 0.0; + + // Nœud 9 + d2N_dxi2[q][9] = 0.0; d2N_deta2[q][9] = 0.0; d2N_dzeta2[q][9] = 0.0; + d2N_dxideta[q][9] = 0.0; d2N_dxidzeta[q][9] = 0.0; d2N_detadzeta[q][9] = 4.0; } - out.validFlags = requested; } Vector3D projectFacetRefCoordToCellRefCoord(CfemInt localFacetId, const Vector3D& xiFacet) const override{ @@ -217,63 +466,199 @@ namespace cfem { MiniReferenceTetrahedron() { m_numNodes = 5; // 4 sommets + 1 bulle m_dimension = 3; - m_nodes.resize(m_numNodes); - m_nodes = { {0,0,0}, {1,0,0}, {0,1,0}, {0,0,1}, {0.25, 0.25, 0.25} }; + + m_nodes = { + Vector3D{0.0, 0.0 ,0.0}, + Vector3D{1.0, 0.0 ,0.0}, + Vector3D{0.0, 1.0 ,0.0}, + Vector3D{0.0, 0.0 ,1.0}, + Vector3D{0.25, 0.25, 0.25} + }; } - void evaluate(const Vector3D& xi, ShapeEvaluationFlags requested, ShapeInfo& out) const override { - const CfemInt n = 5; - if (out.values.size() != n) out.setup(n); - if (hasFlag(requested, ShapeEvaluationFlags::Hessian) && out.hessians.size() != n) { - out.hessians.assign(n, SymTensor3D()); - } - - const CfemReal x = xi.x, y = xi.y, z = xi.z; - const CfemReal L0 = 1.0 - x - y - z; - const CfemReal L1 = x, L2 = y, L3 = z; + void evaluateShapesValues(const Vector3D& xi, CfemReal* values) const override + { + const CfemReal L1 = xi.x; + const CfemReal L2 = xi.y; + const CfemReal L3 = xi.z; + const CfemReal L0 = 1.0 - L1 - L2 - L3; - // Fonction Bulle : 256 * L0 * L1 * L2 * L3 + // Bubble function: 256 * L0 * L1 * L2 * L3 const CfemReal b = 256.0 * L0 * L1 * L2 * L3; - if (hasFlag(requested, ShapeEvaluationFlags::Value)) { - out.values[4] = b; - out.values[0] = L0 - b / 4.0; - out.values[1] = L1 - b / 4.0; - out.values[2] = L2 - b / 4.0; - out.values[3] = L3 - b / 4.0; + values[4] = b; + values[0] = L0 - b * 0.25; + values[1] = L1 - b * 0.25; + values[2] = L2 - b * 0.25; + values[3] = L3 - b * 0.25; + } + + void evaluateShapesDerivatives(const Vector3D& xi, + CfemReal* dN_dxi, + CfemReal* dN_deta, + CfemReal* dN_dzeta) const override + { + const CfemReal L1 = xi.x; + const CfemReal L2 = xi.y; + const CfemReal L3 = xi.z; + const CfemReal L0 = 1.0 - L1 - L2 - L3; + + // Bubble gradients + const CfemReal db_dxi = 256.0 * L2 * L3 * (L0 - L1); + const CfemReal db_deta = 256.0 * L1 * L3 * (L0 - L2); + const CfemReal db_dzeta = 256.0 * L1 * L2 * (L0 - L3); + + // Node 4 (Bubble) + dN_dxi[4] = db_dxi; + dN_deta[4] = db_deta; + dN_dzeta[4] = db_dzeta; + + // Node 0 + dN_dxi[0] = -1.0 - db_dxi * 0.25; + dN_deta[0] = -1.0 - db_deta * 0.25; + dN_dzeta[0] = -1.0 - db_dzeta * 0.25; + + // Node 1 + dN_dxi[1] = 1.0 - db_dxi * 0.25; + dN_deta[1] = 0.0 - db_deta * 0.25; + dN_dzeta[1] = 0.0 - db_dzeta * 0.25; + + // Node 2 + dN_dxi[2] = 0.0 - db_dxi * 0.25; + dN_deta[2] = 1.0 - db_deta * 0.25; + dN_dzeta[2] = 0.0 - db_dzeta * 0.25; + + // Node 3 + dN_dxi[3] = 0.0 - db_dxi * 0.25; + dN_deta[3] = 0.0 - db_deta * 0.25; + dN_dzeta[3] = 1.0 - db_dzeta * 0.25; + } + + void evaluateShapesSecondDerivatives(const Vector3D& xi, + CfemReal* d2N_dxi2, + CfemReal* d2N_deta2, + CfemReal* d2N_dzeta2, + CfemReal* d2N_dxideta, + CfemReal* d2N_dxidzeta, + CfemReal* d2N_detadzeta) const override + { + const CfemReal L1 = xi.x; + const CfemReal L2 = xi.y; + const CfemReal L3 = xi.z; + const CfemReal L0 = 1.0 - L1 - L2 - L3; + + // Second derivatives of the bubble function + const CfemReal d2b_dxi2 = -512.0 * L2 * L3; + const CfemReal d2b_deta2 = -512.0 * L1 * L3; + const CfemReal d2b_dzeta2 = -512.0 * L1 * L2; + const CfemReal d2b_dxideta = 256.0 * L3 * (L0 - L1 - L2); + const CfemReal d2b_dxidzeta = 256.0 * L2 * (L0 - L1 - L3); + const CfemReal d2b_detadzeta= 256.0 * L1 * (L0 - L2 - L3); + + // Node 4 (Bubble) + d2N_dxi2[4] = d2b_dxi2; + d2N_deta2[4] = d2b_deta2; + d2N_dzeta2[4] = d2b_dzeta2; + d2N_dxideta[4] = d2b_dxideta; + d2N_dxidzeta[4] = d2b_dxidzeta; + d2N_detadzeta[4] = d2b_detadzeta; + + // Nodes 0 to 3 inherit the bubble's correction divided by -4.0 + for (CfemInt i = 0; i < 4; ++i) { + d2N_dxi2[i] = -d2b_dxi2 * 0.25; + d2N_deta2[i] = -d2b_deta2 * 0.25; + d2N_dzeta2[i] = -d2b_dzeta2 * 0.25; + d2N_dxideta[i] = -d2b_dxideta * 0.25; + d2N_dxidzeta[i] = -d2b_dxidzeta * 0.25; + d2N_detadzeta[i] = -d2b_detadzeta * 0.25; } + } - if (hasFlag(requested, ShapeEvaluationFlags::Gradient)) { - CfemReal db_dx = 256.0 * L2 * L3 * (L0 - L1); - CfemReal db_dy = 256.0 * L1 * L3 * (L0 - L2); - CfemReal db_dz = 256.0 * L1 * L2 * (L0 - L3); + void evaluateShapesValuesBatch(const DynamicVector &xiPoints, DynamicMatrix& values) const override + { + // Constant piecewise field value + const CfemInt nQp = static_cast(xiPoints.size()); + for (CfemInt q = 0; q < nQp; ++q) { + const CfemReal L1 = xiPoints[q].x; + const CfemReal L2 = xiPoints[q].y; + const CfemReal L3 = xiPoints[q].z; + const CfemReal L0 = 1.0 - L1 - L2 - L3; + + // Bubble function: 256 * L0 * L1 * L2 * L3 + const CfemReal b = 256.0 * L0 * L1 * L2 * L3; + + values[q][4] = b; + values[q][0] = L0 - b * 0.25; + values[q][1] = L1 - b * 0.25; + values[q][2] = L2 - b * 0.25; + values[q][3] = L3 - b * 0.25; + } + } - out.gradients[4] = Vector3D{db_dx, db_dy, db_dz}; - out.gradients[0] = Vector3D{-1.0 - db_dx/4.0, -1.0 - db_dy/4.0, -1.0 - db_dz/4.0}; - out.gradients[1] = Vector3D{ 1.0 - db_dx/4.0, 0.0 - db_dy/4.0, 0.0 - db_dz/4.0}; - out.gradients[2] = Vector3D{ 0.0 - db_dx/4.0, 1.0 - db_dy/4.0, 0.0 - db_dz/4.0}; - out.gradients[3] = Vector3D{ 0.0 - db_dx/4.0, 0.0 - db_dy/4.0, 1.0 - db_dz/4.0}; + void evaluateShapesDerivativesBatch(const DynamicVector & xiPoints, + DynamicMatrix &dN_dxi, + DynamicMatrix &dN_deta, + DynamicMatrix &dN_dzeta) const override + { + // Constant function results in flat zero gradients everywhere + const CfemInt nQp = static_cast(xiPoints.size()); + for (CfemInt q = 0; q < nQp; ++q) { + const Vector3D& xi = xiPoints[q]; + const CfemReal L1 = xi.x; + const CfemReal L2 = xi.y; + const CfemReal L3 = xi.z; + const CfemReal L0 = 1.0 - L1 - L2 - L3; + + // Bubble gradients + const CfemReal db_dxi = 256.0 * L2 * L3 * (L0 - L1); + const CfemReal db_deta = 256.0 * L1 * L3 * (L0 - L2); + const CfemReal db_dzeta = 256.0 * L1 * L2 * (L0 - L3); + + // Node 4 (Bubble) + dN_dxi[q][4] = db_dxi; + dN_deta[q][4] = db_deta; + dN_dzeta[q][4] = db_dzeta; + + // Node 0 + dN_dxi[q][0] = -1.0 - db_dxi * 0.25; + dN_deta[q][0] = -1.0 - db_deta * 0.25; + dN_dzeta[q][0] = -1.0 - db_dzeta * 0.25; + + // Node 1 + dN_dxi[q][1] = 1.0 - db_dxi * 0.25; + dN_deta[q][1] = 0.0 - db_deta * 0.25; + dN_dzeta[q][1] = 0.0 - db_dzeta * 0.25; + + // Node 2 + dN_dxi[q][2] = 0.0 - db_dxi * 0.25; + dN_deta[q][2] = 1.0 - db_deta * 0.25; + dN_dzeta[q][2] = 0.0 - db_dzeta * 0.25; + + // Node 3 + dN_dxi[q][3] = 0.0 - db_dxi * 0.25; + dN_deta[q][3] = 0.0 - db_deta * 0.25; + dN_dzeta[q][3] = 1.0 - db_dzeta * 0.25; } + } - if (hasFlag(requested, ShapeEvaluationFlags::Hessian)) { - for (auto& H : out.hessians) H.setZero(); - - CfemReal d2b_dxx = -512.0 * L2 * L3; - CfemReal d2b_dyy = -512.0 * L1 * L3; - CfemReal d2b_dzz = -512.0 * L1 * L2; - CfemReal d2b_dxy = 256.0 * L3 * (L0 - L1 - L2); - CfemReal d2b_dxz = 256.0 * L2 * (L0 - L1 - L3); - CfemReal d2b_dyz = 256.0 * L1 * (L0 - L2 - L3); - - out.hessians[4].xx() = d2b_dxx; out.hessians[4].yy() = d2b_dyy; out.hessians[4].zz() = d2b_dzz; - out.hessians[4].xy() = d2b_dxy; out.hessians[4].xz() = d2b_dxz; out.hessians[4].yz() = d2b_dyz; - - for (CfemInt i = 0; i < 4; ++i) { - out.hessians[i].xx() = -d2b_dxx / 4.0; out.hessians[i].yy() = -d2b_dyy / 4.0; out.hessians[i].zz() = -d2b_dzz / 4.0; - out.hessians[i].xy() = -d2b_dxy / 4.0; out.hessians[i].xz() = -d2b_dxz / 4.0; out.hessians[i].yz() = -d2b_dyz / 4.0; - } + void evaluateShapesSecondDerivativesBatch(const DynamicVector &xiPoints, + DynamicMatrix &d2N_dxi2, + DynamicMatrix &d2N_deta2, + DynamicMatrix &d2N_dzeta2, + DynamicMatrix &d2N_dxideta, + DynamicMatrix &d2N_dxidzeta, + DynamicMatrix &d2N_detadzeta) const override + { + // Flat null second derivatives + const CfemInt nQp = static_cast(xiPoints.size()); + for (CfemInt q = 0; q < nQp; ++q) { + d2N_dxi2[q][0] = 0.0; + d2N_deta2[q][0] = 0.0; + d2N_dzeta2[q][0] = 0.0; + d2N_dxideta[q][0] = 0.0; + d2N_dxidzeta[q][0] = 0.0; + d2N_detadzeta[q][0] = 0.0; } - out.validFlags = requested; } Vector3D projectFacetRefCoordToCellRefCoord(CfemInt localFacetId, const Vector3D& xiFacet) const override { @@ -290,33 +675,82 @@ class P0ReferenceTetrahedron : public ReferenceElement { P0ReferenceTetrahedron() { m_numNodes = 1; m_dimension = 3; - m_nodes.resize(m_numNodes); - // Un seul nœud au centre de gravité du tétraèdre de référence - // Sommets: (0,0,0), (1,0,0), (0,1,0), (0,0,1) -> Barycentre: (1/4, 1/4, 1/4) - m_nodes = { {0.25, 0.25, 0.25} }; + + m_nodes = { Vector3D{0.25, 0.25, 0.25} }; } - void evaluate(const Vector3D& xi, ShapeEvaluationFlags requested, ShapeInfo& out) const override { - const CfemInt n = 1; - if (out.values.size() != n) out.setup(n); + void evaluateShapesValues(const Vector3D&, CfemReal* values) const override + { + values[0] = 1.0; + } - // Valeur constante N0 = 1.0 - if (hasFlag(requested, ShapeEvaluationFlags::Value)) { - out.values[0] = 1.0; - } + void evaluateShapesDerivatives(const Vector3D&, + CfemReal* dN_dxi, + CfemReal* dN_deta, + CfemReal* dN_dzeta) const override + { + dN_dxi[0] = 0.0; + dN_deta[0] = 0.0; + dN_dzeta[0] = 0.0; + } + + void evaluateShapesSecondDerivatives(const Vector3D&, + CfemReal* d2N_dxi2, + CfemReal* d2N_deta2, + CfemReal* d2N_dzeta2, + CfemReal* d2N_dxideta, + CfemReal* d2N_dxidzeta, + CfemReal* d2N_detadzeta) const override + { + d2N_dxi2[0] = 0.0; + d2N_deta2[0] = 0.0; + d2N_dzeta2[0] = 0.0; + d2N_dxideta[0] = 0.0; + d2N_dxidzeta[0] = 0.0; + d2N_detadzeta[0] = 0.0; + } - // Gradient nul dans les 3 directions - if (hasFlag(requested, ShapeEvaluationFlags::Gradient)) { - out.gradients[0] = Vector3D{0.0, 0.0, 0.0}; + void evaluateShapesValuesBatch(const DynamicVector &xiPoints, DynamicMatrix& values) const override + { + // Constant piecewise field value + const CfemInt nQp = static_cast(xiPoints.size()); + for (CfemInt q = 0; q < nQp; ++q) { + values[q][0] = 1.0; } + } - // Hessien nul - if (hasFlag(requested, ShapeEvaluationFlags::Hessian)) { - if (out.hessians.size() != n) out.hessians.assign(n, SymTensor3D()); - out.hessians[0].setZero(); + void evaluateShapesDerivativesBatch(const DynamicVector & xiPoints, + DynamicMatrix &dN_dxi, + DynamicMatrix &dN_deta, + DynamicMatrix &dN_dzeta) const override + { + // Constant function results in flat zero gradients everywhere + const CfemInt nQp = static_cast(xiPoints.size()); + for (CfemInt q = 0; q < nQp; ++q) { + dN_dxi[q][0] = 0.0; + dN_deta[q][0] = 0.0; + dN_dzeta[q][0] = 0.0; } + } - out.validFlags = requested; + void evaluateShapesSecondDerivativesBatch(const DynamicVector &xiPoints, + DynamicMatrix &d2N_dxi2, + DynamicMatrix &d2N_deta2, + DynamicMatrix &d2N_dzeta2, + DynamicMatrix &d2N_dxideta, + DynamicMatrix &d2N_dxidzeta, + DynamicMatrix &d2N_detadzeta) const override + { + // Flat null second derivatives + const CfemInt nQp = static_cast(xiPoints.size()); + for (CfemInt q = 0; q < nQp; ++q) { + d2N_dxi2[q][0] = 0.0; + d2N_deta2[q][0] = 0.0; + d2N_dzeta2[q][0] = 0.0; + d2N_dxideta[q][0] = 0.0; + d2N_dxidzeta[q][0] = 0.0; + d2N_detadzeta[q][0] = 0.0; + } } Vector3D projectFacetRefCoordToCellRefCoord(CfemInt localFacetId, const Vector3D& xiFacet) const override { diff --git a/libs/fem/reference_element/include/ReferenceTriangle.h b/libs/fem/reference_element/include/ReferenceTriangle.h index a9aad56..4945569 100644 --- a/libs/fem/reference_element/include/ReferenceTriangle.h +++ b/libs/fem/reference_element/include/ReferenceTriangle.h @@ -55,6 +55,11 @@ namespace cfem::reference_elem::utils{ } namespace cfem { + + /** + * @class P1ReferenceTriangle + * @brief Linear 3-node reference triangle. + */ class P1ReferenceTriangle : public ReferenceElement { public: @@ -62,43 +67,96 @@ namespace cfem { P1ReferenceTriangle() { m_numNodes = 3; m_dimension = 2; - m_nodes.resize(m_numNodes); - m_nodes = { {0.0, 0.0, 0.0}, {1.0, 0.0, 0.0}, {0.0, 1.0, 0.0} }; + m_nodes = { Vector3D{0.0, 0.0, 0.0}, + Vector3D{1.0, 0.0, 0.0}, + Vector3D{0.0, 1.0, 0.0} }; } - void evaluate(const Vector3D& xi, ShapeEvaluationFlags requested, ShapeInfo& out) const override { - const CfemInt n = getNumNodes(); - - if (out.values.size() != n) { - out.setup(n); - } + void evaluateShapesValues(const Vector3D &xi, CfemReal* values) const override + { + values[0] = 1.0 - xi.x - xi.y; + values[1] = xi.x; + values[2] = xi.y; + } - if (hasFlag(requested, ShapeEvaluationFlags::Hessian) && out.hessians.size() != n) { - out.hessians.assign(n, SymTensor3D()); - } + void evaluateShapesDerivatives(const Vector3D &, + CfemReal* dN_dxi, + CfemReal* dN_deta, + CfemReal* dN_dzeta) const override + { + dN_dxi[0] = -1.0; dN_deta[0] = -1.0; dN_dzeta[0] = 0.0; + dN_dxi[1] = 1.0; dN_deta[1] = 0.0; dN_dzeta[1] = 0.0; + dN_dxi[2] = 0.0; dN_deta[2] = 1.0; dN_dzeta[2] = 0.0; + } - // N0 = 1 - xi - eta, N1 = xi, N2 = eta - if (hasFlag(requested, ShapeEvaluationFlags::Value)) { - out.values[0] = 1.0 - xi.x - xi.y; - out.values[1] = xi.x; - out.values[2] = xi.y; + void evaluateShapesSecondDerivatives(const Vector3D &, + CfemReal* d2N_dxi2, + CfemReal* d2N_deta2, + CfemReal* d2N_dzeta2, + CfemReal* d2N_dxideta, + CfemReal* d2N_dxidzeta, + CfemReal* d2N_detadzeta) const override + { + for (CfemInt i = 0; i < m_numNodes; ++i) { + d2N_dxi2[i] = 0.0; + d2N_deta2[i] = 0.0; + d2N_dzeta2[i] = 0.0; + d2N_dxideta[i] = 0.0; + d2N_dxidzeta[i] = 0.0; + d2N_detadzeta[i] = 0.0; } + } - if (hasFlag(requested, ShapeEvaluationFlags::Gradient) || hasFlag(requested, ShapeEvaluationFlags::Jacobian) || hasFlag(requested, ShapeEvaluationFlags::Normal)) { - out.gradients[0] = Vector3D{-1.0, -1.0, 0.0}; - out.gradients[1] = Vector3D{ 1.0, 0.0, 0.0}; - out.gradients[2] = Vector3D{ 0.0, 1.0, 0.0}; + void evaluateShapesValuesBatch(const DynamicVector &xiPoints, DynamicMatrix& values) const override + { + // Constant piecewise field value + const CfemInt nQp = static_cast(xiPoints.size()); + for (CfemInt q = 0; q < nQp; ++q) { + const Vector3D& xi = xiPoints[q]; + values[q][0] = 1.0 - xi.x - xi.y; + values[q][1] = xi.x; + values[q][2] = xi.y; } + } - // Hessiens par rapport à (xi, eta) - if (hasFlag(requested, ShapeEvaluationFlags::Hessian)) { - for (auto& H : out.hessians) H.setZero(); + void evaluateShapesDerivativesBatch(const DynamicVector & xiPoints, + DynamicMatrix &dN_dxi, + DynamicMatrix &dN_deta, + DynamicMatrix &dN_dzeta) const override + { + // Constant function results in flat zero gradients everywhere + const CfemInt nQp = static_cast(xiPoints.size()); + for (CfemInt q = 0; q < nQp; ++q) { + const Vector3D& xi = xiPoints[q]; + dN_dxi[q][0] = -1.0; dN_deta[q][0] = -1.0; dN_dzeta[q][0] = 0.0; + dN_dxi[q][1] = 1.0; dN_deta[q][1] = 0.0; dN_dzeta[q][1] = 0.0; + dN_dxi[q][2] = 0.0; dN_deta[q][2] = 1.0; dN_dzeta[q][2] = 0.0; } - - out.validFlags = requested; } + void evaluateShapesSecondDerivativesBatch(const DynamicVector &xiPoints, + DynamicMatrix &d2N_dxi2, + DynamicMatrix &d2N_deta2, + DynamicMatrix &d2N_dzeta2, + DynamicMatrix &d2N_dxideta, + DynamicMatrix &d2N_dxidzeta, + DynamicMatrix &d2N_detadzeta) const override + { + // Flat null second derivatives + const CfemInt nQp = static_cast(xiPoints.size()); + for (CfemInt q = 0; q < nQp; ++q) { + for (CfemInt i = 0; i < m_numNodes; ++i) { + d2N_dxi2[q][i] = 0.0; + d2N_deta2[q][i] = 0.0; + d2N_dzeta2[q][i] = 0.0; + d2N_dxideta[q][i] = 0.0; + d2N_dxidzeta[q][i] = 0.0; + d2N_detadzeta[q][i] = 0.0; + } + } + } + Vector3D projectFacetRefCoordToCellRefCoord(CfemInt localFacetId, const Vector3D& xiFacet) const override{ return cfem::reference_elem::utils::projectFacetRefCoordToTriRefCoord(localFacetId, xiFacet); // Make the compiler happy } @@ -110,67 +168,174 @@ namespace cfem { }; + /** + * @class P2ReferenceTriangle + * @brief Quadratic 6-node reference triangle. + */ class P2ReferenceTriangle : public ReferenceElement { public: P2ReferenceTriangle() { m_numNodes = 6; m_dimension = 2; - m_nodes.resize(m_numNodes); - // Main nodes - m_nodes[0] = Vector3D{0.0, 0.0, 0.0}; - m_nodes[1] = Vector3D{1.0, 0.0, 0.0}; - m_nodes[2] = Vector3D{0.0, 1.0, 0.0}; - // Edges Middles - m_nodes[3] = Vector3D{0.5, 0.0, 0.0}; - m_nodes[4] = Vector3D{0.5, 0.5, 0.0}; - m_nodes[5] = Vector3D{0.0, 0.5, 0.0}; + + m_nodes = { + Vector3D{0.0, 0.0, 0.0}, + Vector3D{1.0, 0.0, 0.0}, + Vector3D{0.0, 1.0, 0.0}, + Vector3D{0.5, 0.0, 0.0}, + Vector3D{0.5, 0.5, 0.0}, + Vector3D{0.0, 0.5, 0.0} + }; } - - void evaluate(const Vector3D& xi, ShapeEvaluationFlags requested, ShapeInfo& out) const override { - const CfemInt n = 6; - if (out.values.size() != n) out.setup(n); - - if (hasFlag(requested, ShapeEvaluationFlags::Hessian) && out.hessians.size() != n) { - out.hessians.assign(n, SymTensor3D()); - } + void evaluateShapesValues(const Vector3D &xi, CfemReal* values) const override + { const CfemReal L1 = xi.x; const CfemReal L2 = xi.y; const CfemReal L0 = 1.0 - L1 - L2; - if (hasFlag(requested, ShapeEvaluationFlags::Value)) { - out.values[0] = L0 * (2.0 * L0 - 1.0); - out.values[1] = L1 * (2.0 * L1 - 1.0); - out.values[2] = L2 * (2.0 * L2 - 1.0); - out.values[3] = 4.0 * L0 * L1; - out.values[4] = 4.0 * L1 * L2; - out.values[5] = 4.0 * L2 * L0; + values[0] = L0 * (2.0 * L0 - 1.0); + values[1] = L1 * (2.0 * L1 - 1.0); + values[2] = L2 * (2.0 * L2 - 1.0); + values[3] = 4.0 * L0 * L1; + values[4] = 4.0 * L1 * L2; + values[5] = 4.0 * L2 * L0; + } + + void evaluateShapesDerivatives(const Vector3D &xi, + CfemReal* dN_dxi, + CfemReal* dN_deta, + CfemReal* dN_dzeta) const override + { + const CfemReal L1 = xi.x; + const CfemReal L2 = xi.y; + + // Derivatives with respect to xi (Local x direction) + dN_dxi[0] =-3.0 + 4.0 * L1 + 4.0 * L2; + dN_dxi[1] = 4.0 * L1 - 1.0; + dN_dxi[2] = 0.0; + dN_dxi[3] = 4.0 - 8.0 * L1 - 4.0 * L2; + dN_dxi[4] = 4.0 * L2; + dN_dxi[5] =-4.0 * L2; + + // Derivatives with respect to eta (Local y direction) + dN_deta[0] = -3.0 + 4.0 * L1 + 4.0 * L2; + dN_deta[1] = 0.0; + dN_deta[2] = 4.0 * L2 - 1.0; + dN_deta[3] = -4.0 * L1; + dN_deta[4] = 4.0 * L1; + dN_deta[5] = 4.0 - 4.0 * L1 - 8.0 * L2; + + // Derivatives with respect to zeta (Z-direction is zero for a 2D planar element) + for (CfemInt i = 0; i < 6; ++i) { + dN_dzeta[i] = 0.0; } + } - if (hasFlag(requested, ShapeEvaluationFlags::Gradient)) { - out.gradients[0] = Vector3D{-3.0 + 4.0*L1 + 4.0*L2, -3.0 + 4.0*L1 + 4.0*L2, 0.0}; - out.gradients[1] = Vector3D{4.0*L1 - 1.0, 0.0, 0.0}; - out.gradients[2] = Vector3D{0.0, 4.0*L2 - 1.0, 0.0}; - out.gradients[3] = Vector3D{4.0 - 8.0*L1 - 4.0*L2, -4.0*L1, 0.0}; - out.gradients[4] = Vector3D{4.0*L2, 4.0*L1, 0.0}; - out.gradients[5] = Vector3D{-4.0*L2, 4.0 - 4.0*L1 - 8.0*L2, 0.0}; + void evaluateShapesSecondDerivatives(const Vector3D &, + CfemReal* d2N_dxi2, + CfemReal* d2N_deta2, + CfemReal* d2N_dzeta2, + CfemReal* d2N_dxideta, + CfemReal* d2N_dxidzeta, + CfemReal* d2N_detadzeta) const override + { + // Quadratic shape function second derivatives are constants. + d2N_dxi2[0] = 4.0; d2N_deta2[0] = 4.0; d2N_dxideta[0] = 4.0; + d2N_dxi2[1] = 4.0; d2N_deta2[1] = 0.0; d2N_dxideta[1] = 0.0; + d2N_dxi2[2] = 0.0; d2N_deta2[2] = 4.0; d2N_dxideta[2] = 0.0; + d2N_dxi2[3] =-8.0; d2N_deta2[3] = 0.0; d2N_dxideta[3] =-4.0; + d2N_dxi2[4] = 0.0; d2N_deta2[4] = 0.0; d2N_dxideta[4] = 4.0; + d2N_dxi2[5] = 0.0; d2N_deta2[5] =-8.0; d2N_dxideta[5] =-4.0; + + // All derivatives involving the out-of-plane reference coordinate zeta are strictly zero + for (CfemInt i = 0; i < 6; ++i) { + d2N_dzeta2[i] = 0.0; + d2N_dxidzeta[i] = 0.0; + d2N_detadzeta[i] = 0.0; } + } - // Hessiennes (Dérivées secondes constantes) - if (hasFlag(requested, ShapeEvaluationFlags::Hessian)) { - for (CfemInt i = 0; i < n; ++i) { - out.hessians[i].setZero(); - } + void evaluateShapesValuesBatch(const DynamicVector &xiPoints, DynamicMatrix& values) const override + { + // Constant piecewise field value + const CfemInt nQp = static_cast(xiPoints.size()); + for (CfemInt q = 0; q < nQp; ++q) { + const Vector3D& xi = xiPoints[q]; + const CfemReal L1 = xi.x; + const CfemReal L2 = xi.y; + const CfemReal L0 = 1.0 - L1 - L2; + + values[q][0] = L0 * (2.0 * L0 - 1.0); + values[q][1] = L1 * (2.0 * L1 - 1.0); + values[q][2] = L2 * (2.0 * L2 - 1.0); + values[q][3] = 4.0 * L0 * L1; + values[q][4] = 4.0 * L1 * L2; + values[q][5] = 4.0 * L2 * L0; + } + } - out.hessians[0].xx() = 4.0; out.hessians[0].yy() = 4.0; out.hessians[0].xy()=4.0; - out.hessians[1].xx() = 4.0; - out.hessians[2].yy() = 4.0; - out.hessians[3].xx() =-8.0; out.hessians[3].xy() = -4.0; - out.hessians[4].xy() = 4.0; out.hessians[4].xy() = 4.0; - out.hessians[5].yy() =-8.0; out.hessians[5].xy() = -4.0; + void evaluateShapesDerivativesBatch(const DynamicVector & xiPoints, + DynamicMatrix &dN_dxi, + DynamicMatrix &dN_deta, + DynamicMatrix &dN_dzeta) const override + { + // Constant function results in flat zero gradients everywhere + const CfemInt nQp = static_cast(xiPoints.size()); + for (CfemInt q = 0; q < nQp; ++q) { + const Vector3D& xi = xiPoints[q]; + const CfemReal L1 = xi.x; + const CfemReal L2 = xi.y; + + // Derivatives with respect to xi (Local x direction) + dN_dxi[q][0] =-3.0 + 4.0 * L1 + 4.0 * L2; + dN_dxi[q][1] = 4.0 * L1 - 1.0; + dN_dxi[q][2] = 0.0; + dN_dxi[q][3] = 4.0 - 8.0 * L1 - 4.0 * L2; + dN_dxi[q][4] = 4.0 * L2; + dN_dxi[q][5] =-4.0 * L2; + + // Derivatives with respect to eta (Local y direction) + dN_deta[q][0] = -3.0 + 4.0 * L1 + 4.0 * L2; + dN_deta[q][1] = 0.0; + dN_deta[q][2] = 4.0 * L2 - 1.0; + dN_deta[q][3] = -4.0 * L1; + dN_deta[q][4] = 4.0 * L1; + dN_deta[q][5] = 4.0 - 4.0 * L1 - 8.0 * L2; + + // Derivatives with respect to zeta (Z-direction is zero for a 2D planar element) + for (CfemInt i = 0; i < 6; ++i) { + dN_dzeta[q][i] = 0.0; + } } + } - out.validFlags = requested; + void evaluateShapesSecondDerivativesBatch(const DynamicVector &xiPoints, + DynamicMatrix &d2N_dxi2, + DynamicMatrix &d2N_deta2, + DynamicMatrix &d2N_dzeta2, + DynamicMatrix &d2N_dxideta, + DynamicMatrix &d2N_dxidzeta, + DynamicMatrix &d2N_detadzeta) const override + { + // Flat null second derivatives + const CfemInt nQp = static_cast(xiPoints.size()); + for (CfemInt q = 0; q < nQp; ++q) { + // Quadratic shape function second derivatives are constants. + d2N_dxi2[q][0] = 4.0; d2N_deta2[q][0] = 4.0; d2N_dxideta[q][0] = 4.0; + d2N_dxi2[q][1] = 4.0; d2N_deta2[q][1] = 0.0; d2N_dxideta[q][1] = 0.0; + d2N_dxi2[q][2] = 0.0; d2N_deta2[q][2] = 4.0; d2N_dxideta[q][2] = 0.0; + d2N_dxi2[q][3] =-8.0; d2N_deta2[q][3] = 0.0; d2N_dxideta[q][3] =-4.0; + d2N_dxi2[q][4] = 0.0; d2N_deta2[q][4] = 0.0; d2N_dxideta[q][4] = 4.0; + d2N_dxi2[q][5] = 0.0; d2N_deta2[q][5] =-8.0; d2N_dxideta[q][5] =-4.0; + + // All derivatives involving the out-of-plane reference coordinate zeta are strictly zero + for (CfemInt i = 0; i < 6; ++i) { + d2N_dzeta2[q][i] = 0.0; + d2N_dxidzeta[q][i] = 0.0; + d2N_detadzeta[q][i] = 0.0; + } + } } Vector3D projectFacetRefCoordToCellRefCoord(CfemInt localFacetId, const Vector3D& xiFacet) const override{ @@ -183,72 +348,195 @@ namespace cfem { }; class MiniReferenceTriangle : public ReferenceElement { + private: + static constexpr inline CfemReal m_1_3 {1.0 / 3.0}; public: MiniReferenceTriangle() { m_numNodes = 4; // 3 sommets + 1 bulle m_dimension = 2; - m_nodes.resize(m_numNodes); - m_nodes[0] = {0.0, 0.0, 0.0}; - m_nodes[1] = {1.0, 0.0, 0.0}; - m_nodes[2] = {0.0, 1.0, 0.0}; - m_nodes[3] = {1.0/3.0, 1.0/3.0, 0.0}; + m_nodes = { + Vector3D{0.0, 0.0, 0.0}, + Vector3D{1.0, 0.0, 0.0}, + Vector3D{0.0, 1.0, 0.0}, + Vector3D{m_1_3, m_1_3, 0.0} + }; } - void evaluate(const Vector3D& xi, ShapeEvaluationFlags requested, ShapeInfo& out) const override { - const CfemInt n = 4; - if (out.values.size() != n) out.setup(n); - if (hasFlag(requested, ShapeEvaluationFlags::Hessian) && out.hessians.size() != n) { - out.hessians.assign(n, SymTensor3D()); - } - - const CfemReal x = xi.x; - const CfemReal y = xi.y; - const CfemReal L0 = 1.0 - x - y; - const CfemReal L1 = x; - const CfemReal L2 = y; + void evaluateShapesValues(const Vector3D &xi, CfemReal* values) const override + { + const CfemReal L1 = xi.x; + const CfemReal L2 = xi.y; + const CfemReal L0 = 1.0 - L1 - L2; - // Fonction Bulle (Normalisée pour valoir 1.0 au centre) const CfemReal b = 27.0 * L0 * L1 * L2; + const CfemReal b_x_m_1_3 = b * m_1_3; - if (hasFlag(requested, ShapeEvaluationFlags::Value)) { - out.values[3] = b; - out.values[0] = L0 - b / 3.0; - out.values[1] = L1 - b / 3.0; - out.values[2] = L2 - b / 3.0; - } + values[3] = b; + values[0] = L0 - b_x_m_1_3; + values[1] = L1 - b_x_m_1_3; + values[2] = L2 - b_x_m_1_3; + } - if (hasFlag(requested, ShapeEvaluationFlags::Gradient)) { - // Gradient de la bulle - CfemReal db_dx = 27.0 * L2 * (1.0 - 2.0*L1 - L2); - CfemReal db_dy = 27.0 * L1 * (1.0 - L1 - 2.0*L2); + void evaluateShapesDerivatives(const Vector3D &xi, + CfemReal* dN_dxi, + CfemReal* dN_deta, + CfemReal* dN_dzeta) const override + { + const CfemReal L1 = xi.x; + const CfemReal L2 = xi.y; - out.gradients[3] = Vector3D{db_dx, db_dy, 0.0}; - out.gradients[0] = Vector3D{-1.0 - db_dx / 3.0, -1.0 - db_dy / 3.0, 0.0}; - out.gradients[1] = Vector3D{ 1.0 - db_dx / 3.0, 0.0 - db_dy / 3.0, 0.0}; - out.gradients[2] = Vector3D{ 0.0 - db_dx / 3.0, 1.0 - db_dy / 3.0, 0.0}; + // Evaluate bubble gradients + const CfemReal db_dxi = 27.0 * L2 * (1.0 - 2.0 * L1 - L2); + const CfemReal db_deta = 27.0 * L1 * (1.0 - L1 - 2.0 * L2); + + // Derivatives with respect to xi + dN_dxi[3] = db_dxi; + dN_dxi[0] = -1.0 - db_dxi * m_1_3; + dN_dxi[1] = 1.0 - db_dxi * m_1_3; + dN_dxi[2] = 0.0 - db_dxi * m_1_3; + + // Derivatives with respect to eta + dN_deta[3] = db_deta; + dN_deta[0] = -1.0 - db_deta * m_1_3; + dN_deta[1] = 0.0 - db_deta * m_1_3; + dN_deta[2] = 1.0 - db_deta * m_1_3; + + // Out-of-plane derivatives are zero + for (CfemInt i = 0; i < 4; ++i) { + dN_dzeta[i] = 0.0; } + } - if (hasFlag(requested, ShapeEvaluationFlags::Hessian)) { - for (auto& H : out.hessians) H.setZero(); + void evaluateShapesSecondDerivatives(const Vector3D &xi, + CfemReal* d2N_dxi2, + CfemReal* d2N_deta2, + CfemReal* d2N_dzeta2, + CfemReal* d2N_dxideta, + CfemReal* d2N_dxidzeta, + CfemReal* d2N_detadzeta) const override + { + const CfemReal L1 = xi.x; + const CfemReal L2 = xi.y; - // Hessienne de la bulle - CfemReal d2b_dxx = -54.0 * L2; - CfemReal d2b_dyy = -54.0 * L1; - CfemReal d2b_dxy = 27.0 * (1.0 - 2.0*L1 - 2.0*L2); + // Second derivatives of the bubble function + const CfemReal d2b_dxi2 = -54.0 * L2; + const CfemReal d2b_deta2 = -54.0 * L1; + const CfemReal d2b_dxideta = 27.0 * (1.0 - 2.0 * L1 - 2.0 * L2); + + // Node 3 (Bubble) + d2N_dxi2[3] = d2b_dxi2; + d2N_deta2[3] = d2b_deta2; + d2N_dxideta[3] = d2b_dxideta; + + // Nodes 0, 1, 2 inherit the bubble's linear correction term + for (CfemInt i = 0; i < 3; ++i) { + d2N_dxi2[i] = -d2b_dxi2 * m_1_3; + d2N_deta2[i] = -d2b_deta2 * m_1_3; + d2N_dxideta[i] = -d2b_dxideta * m_1_3; + } - out.hessians[3].xx() = d2b_dxx; - out.hessians[3].yy() = d2b_dyy; - out.hessians[3].xy() = d2b_dxy; + // Initialize all remaining out-of-plane and zeta cross-derivatives to zero + for (CfemInt i = 0; i < 4; ++i) { + d2N_dzeta2[i] = 0.0; + d2N_dxidzeta[i] = 0.0; + d2N_detadzeta[i] = 0.0; + } + } - // Les fonctions P1 n'ont pas de Hessienne propre, on hérite juste de la correction bulle + void evaluateShapesValuesBatch(const DynamicVector &xiPoints, DynamicMatrix& values) const override + { + // Constant piecewise field value + const CfemInt nQp = static_cast(xiPoints.size()); + for (CfemInt q = 0; q < nQp; ++q) { + const Vector3D& xi = xiPoints[q]; + const CfemReal L1 = xi.x; + const CfemReal L2 = xi.y; + const CfemReal L0 = 1.0 - L1 - L2; + + const CfemReal b = 27.0 * L0 * L1 * L2; + + values[q][3] = b; + values[q][0] = L0 - b * m_1_3; + values[q][1] = L1 - b * m_1_3; + values[q][2] = L2 - b * m_1_3; + } + } + + void evaluateShapesDerivativesBatch(const DynamicVector & xiPoints, + DynamicMatrix &dN_dxi, + DynamicMatrix &dN_deta, + DynamicMatrix &dN_dzeta) const override + { + // Constant function results in flat zero gradients everywhere + const CfemInt nQp = static_cast(xiPoints.size()); + for (CfemInt q = 0; q < nQp; ++q) { + const Vector3D& xi = xiPoints[q]; + const CfemReal L1 = xi.x; + const CfemReal L2 = xi.y; + + // Evaluate bubble gradients + const CfemReal db_dxi = 27.0 * L2 * (1.0 - 2.0 * L1 - L2); + const CfemReal db_deta = 27.0 * L1 * (1.0 - L1 - 2.0 * L2); + + // Derivatives with respect to xi + dN_dxi[q][3] = db_dxi; + dN_dxi[q][0] = -1.0 - db_dxi * m_1_3; + dN_dxi[q][1] = 1.0 - db_dxi * m_1_3; + dN_dxi[q][2] = 0.0 - db_dxi * m_1_3; + + // Derivatives with respect to eta + dN_deta[q][3] = db_deta; + dN_deta[q][0] = -1.0 - db_deta * m_1_3; + dN_deta[q][1] = 0.0 - db_deta * m_1_3; + dN_deta[q][2] = 1.0 - db_deta * m_1_3; + + // Out-of-plane derivatives are zero + for (CfemInt i = 0; i < 4; ++i) { + dN_dzeta[q][i] = 0.0; + } + } + } + + void evaluateShapesSecondDerivativesBatch(const DynamicVector &xiPoints, + DynamicMatrix &d2N_dxi2, + DynamicMatrix &d2N_deta2, + DynamicMatrix &d2N_dzeta2, + DynamicMatrix &d2N_dxideta, + DynamicMatrix &d2N_dxidzeta, + DynamicMatrix &d2N_detadzeta) const override + { + // Flat null second derivatives + const CfemInt nQp = static_cast(xiPoints.size()); + for (CfemInt q = 0; q < nQp; ++q) { + const Vector3D& xi = xiPoints[q]; + const CfemReal L1 = xi.x; + const CfemReal L2 = xi.y; + + // Second derivatives of the bubble function + const CfemReal d2b_dxi2 = -54.0 * L2; + const CfemReal d2b_deta2 = -54.0 * L1; + const CfemReal d2b_dxideta = 27.0 * (1.0 - 2.0 * L1 - 2.0 * L2); + + // Node 3 (Bubble) + d2N_dxi2[q][3] = d2b_dxi2; + d2N_deta2[q][3] = d2b_deta2; + d2N_dxideta[q][3] = d2b_dxideta; + + // Nodes 0, 1, 2 inherit the bubble's linear correction term for (CfemInt i = 0; i < 3; ++i) { - out.hessians[i].xx() = -d2b_dxx / 3.0; - out.hessians[i].yy() = -d2b_dyy / 3.0; - out.hessians[i].xy() = -d2b_dxy / 3.0; + d2N_dxi2[q][i] = -d2b_dxi2 * m_1_3; + d2N_deta2[q][i] = -d2b_deta2 * m_1_3; + d2N_dxideta[q][i] = -d2b_dxideta * m_1_3; + } + + // Initialize all remaining out-of-plane and zeta cross-derivatives to zero + for (CfemInt i = 0; i < 4; ++i) { + d2N_dzeta2[q][i] = 0.0; + d2N_dxidzeta[q][i] = 0.0; + d2N_detadzeta[q][i] = 0.0; } } - out.validFlags = requested; } Vector3D projectFacetRefCoordToCellRefCoord(CfemInt localFacetId, const Vector3D& xiFacet) const override { @@ -266,33 +554,85 @@ namespace cfem { P0ReferenceTriangle() { m_numNodes = 1; m_dimension = 2; - m_nodes.resize(m_numNodes); - m_nodes = { {1.0/3.0, 1.0/3.0, 0.0} }; + m_nodes = { Vector3D{1.0/3.0, 1.0/3.0, 0.0} }; } - void evaluate(const Vector3D& xi, ShapeEvaluationFlags requested, ShapeInfo& out) const override { - const CfemInt n = 1; - if (out.values.size() != n) { - out.setup(n); - } + void evaluateShapesValues(const Vector3D &, CfemReal* values) const override + { + // Constant piecewise field value + values[0] = 1.0; + } - // Valeur constante : N0 = 1 - if (hasFlag(requested, ShapeEvaluationFlags::Value)) { - out.values[0] = 1.0; - } + void evaluateShapesDerivatives(const Vector3D &, + CfemReal* dN_dxi, + CfemReal* dN_deta, + CfemReal* dN_dzeta) const override + { + // Constant function results in flat zero gradients everywhere + dN_dxi[0] = 0.0; + dN_deta[0] = 0.0; + dN_dzeta[0] = 0.0; + } + + void evaluateShapesSecondDerivatives(const Vector3D &, + CfemReal* d2N_dxi2, + CfemReal* d2N_deta2, + CfemReal* d2N_dzeta2, + CfemReal* d2N_dxideta, + CfemReal* d2N_dxidzeta, + CfemReal* d2N_detadzeta) const override + { + // Flat null second derivatives + d2N_dxi2[0] = 0.0; + d2N_deta2[0] = 0.0; + d2N_dzeta2[0] = 0.0; + d2N_dxideta[0] = 0.0; + d2N_dxidzeta[0] = 0.0; + d2N_detadzeta[0] = 0.0; + } - // Gradient nul : dN0/dxi = 0, dN0/deta = 0 - if (hasFlag(requested, ShapeEvaluationFlags::Gradient)) { - out.gradients[0] = Vector3D{0.0, 0.0, 0.0}; + void evaluateShapesValuesBatch(const DynamicVector &xiPoints, DynamicMatrix& values) const override + { + // Constant piecewise field value + const CfemInt nQp = static_cast(xiPoints.size()); + for (CfemInt q = 0; q < nQp; ++q) { + values[q][0] = 1.0; } + } - if (hasFlag(requested, ShapeEvaluationFlags::Hessian)) { - if (out.hessians.size() != n) out.hessians.assign(n, SymTensor3D()); - out.hessians[0].setZero(); + void evaluateShapesDerivativesBatch(const DynamicVector & xiPoints, + DynamicMatrix &dN_dxi, + DynamicMatrix &dN_deta, + DynamicMatrix &dN_dzeta) const override + { + // Constant function results in flat zero gradients everywhere + const CfemInt nQp = static_cast(xiPoints.size()); + for (CfemInt q = 0; q < nQp; ++q) { + dN_dxi[q][0] = 0.0; + dN_deta[q][0] = 0.0; + dN_dzeta[q][0] = 0.0; } + } - out.validFlags = requested; + void evaluateShapesSecondDerivativesBatch(const DynamicVector &xiPoints, + DynamicMatrix &d2N_dxi2, + DynamicMatrix &d2N_deta2, + DynamicMatrix &d2N_dzeta2, + DynamicMatrix &d2N_dxideta, + DynamicMatrix &d2N_dxidzeta, + DynamicMatrix &d2N_detadzeta) const override + { + // Flat null second derivatives + const CfemInt nQp = static_cast(xiPoints.size()); + for (CfemInt q = 0; q < nQp; ++q) { + d2N_dxi2[q][0] = 0.0; + d2N_deta2[q][0] = 0.0; + d2N_dzeta2[q][0] = 0.0; + d2N_dxideta[q][0] = 0.0; + d2N_dxidzeta[q][0] = 0.0; + d2N_detadzeta[q][0] = 0.0; + } } Vector3D projectFacetRefCoordToCellRefCoord(CfemInt localFacetId, const Vector3D& xiFacet) const override { diff --git a/libs/fem/reference_element/include/ShapeEvaluationFlags.h b/libs/fem/reference_element/include/ShapeEvaluationFlags.h deleted file mode 100644 index f848086..0000000 --- a/libs/fem/reference_element/include/ShapeEvaluationFlags.h +++ /dev/null @@ -1,88 +0,0 @@ -//************************************************************************ -// --- CFEM++ Library -// --- -// --- Copyright 2024-2026 Ismaël Tchinda Ngueyong et al. -// --- ALL RIGHTS RESERVED -// --- -// --- This software is protected by international copyright laws. -// --- Use, distribution, or modification of this software in any form, -// --- source or binary, for personal, academic, or non-commercial -// --- purposes is permitted free of charge, provided that this -// --- copyright notice and this permission notice appear in all -// --- copies and supporting documentation. -// --- -// --- The authors and contributors provide this code "as is" without -// --- any express or implied warranty. In no event shall they be -// --- held liable for any damages arising from the use of this software. -//************************************************************************ - -#ifndef CFEM_SHAPE_EVALUATION_FLAGS_H -#define CFEM_SHAPE_EVALUATION_FLAGS_H - -#include "cfemutils.h" - -namespace cfem -{ - /** - * @brief Bitmask flags to control which shape function quantities are computed. - * These flags are used in @ref computeMapping to optimize performance by avoiding - * unnecessary calculations. For example, a simple mass matrix assembly may only - * require @ref Value, while a stiffness matrix requires @ref Gradient. - */ - enum class ShapeEvaluationFlags : CfemIntFlag - { - /// No fields requested. Useful for clearing state. */ - None = 0, - - /// Compute shape function values \f$ N_i \f$. Required for physical position and value interpolation. - Value = 1 << 0, - - // Compute the physical position - PhysicalPosition = (1 << 1) | Value, - - /// Compute the Jacobian of the mapping from the reference element to the physical element. - Jacobian = 1 << 2, - - /// Compute shape function gradients \f$ \nabla N_i \f$. Required for Jacobian. - Gradient = (1 << 3) | Jacobian, - - /// Compute shape function Hessians \f$ \nabla^2 N_i \f$. Required Jacobian and Shape Values - Hessian = (1 << 4) | Gradient, - - Normal = (1 << 5) | Jacobian, - - /// Compute all available fields (Values, Gradients, and Hessians). - All = Value | PhysicalPosition | Jacobian | Gradient | Hessian | Normal - }; - - /// Enable bitwise OR for ShapeEvaluationFlags - inline ShapeEvaluationFlags operator|( ShapeEvaluationFlags a, ShapeEvaluationFlags b) - { - return static_cast( static_cast(a) | static_cast(b)); - } - - /// Enable bitwise AND for ShapeEvaluationFlags - inline ShapeEvaluationFlags operator&( ShapeEvaluationFlags a, ShapeEvaluationFlags b) - { - return static_cast( static_cast(a) & static_cast(b) ); - } - - inline ShapeEvaluationFlags& operator|=(ShapeEvaluationFlags& a, ShapeEvaluationFlags b) - { - a = a | b; - return a; - } - - inline ShapeEvaluationFlags& operator&=(ShapeEvaluationFlags& a, ShapeEvaluationFlags b) - { - a = a & b; - return a; - } - - inline bool hasFlag(ShapeEvaluationFlags flags, ShapeEvaluationFlags test) - { - return static_cast(flags & test) != 0; - } - -} //namespace -#endif \ No newline at end of file diff --git a/libs/fem/reference_element/include/ShapeInfo.h b/libs/fem/reference_element/include/ShapeInfo.h new file mode 100644 index 0000000..b228c78 --- /dev/null +++ b/libs/fem/reference_element/include/ShapeInfo.h @@ -0,0 +1,194 @@ +#pragma once + +#include "cfemutils.h" + +#include "EvaluationFlags.h" +#include "Tensor3D.h" +#include "DynamicVector.h" +#include "DynamicMatrix.h" + +namespace cfem +{ + + /** + * @struct ShapeInfo + * @brief Storage buffer for shape function evaluations at a specific quadrature point. + * @note Re-use this object across multiple integration points to avoid frequent heap allocations. + */ + struct ShapeInfo + { + CfemInt numNodes = 0; + bool shapeMustBeUpdated = true; + ShapeEvaluationFlags validFlags = ShapeEvaluationFlags::None; + + DynamicVector values; ///< Shape values (N_i) + + DynamicVector dN_dxi; ///< First order shape derivatives: \$ \frac{dN_i}{d\xi} \$ + DynamicVector dN_deta; ///< First order shape derivatives: \$ \frac{dN_i}{d\eta} \$ + DynamicVector dN_dzeta; ///< First order shape derivatives: \$ \frac{dN_i}{d\zeta} \$ + + DynamicVector d2N_dxi2; ///< Second order local shape derivatives: \$ \frac{\partial^2N_i}{\partial\xi^2} \$ + DynamicVector d2N_deta2; ///< Second order local shape derivatives: \$ \frac{\partial^2N_i}{\partial\eta^2} \$ + DynamicVector d2N_dzeta2; ///< Second order local shape derivatives: \$ \frac{\partial^2N_i}{\partial\zeta^2} \$ + + DynamicVector d2N_dxideta; ///< Second order local shape derivatives: \$ \frac{\partial^2N_i}{\partial\xi \partial\eta} \$ + DynamicVector d2N_dxidzeta; ///< Second order local shape derivatives: \$ \frac{\partial^2N_i}{\partial\xi \partial\zeta} \$ + DynamicVector d2N_detadzeta; ///< Second order local shape derivatives: \$ \frac{\partial^2N_i}{\partial\eta \partial\zeta} \$ + + /** + * @brief Prepares buffer sizes to prevent runtime reallocations. + * @param nNodes Number of nodes in the element. + * @param includeSecondDerivatives If true, allocates memory for the second order derivatives + */ + void setup(CfemInt nNodes, bool includeSecondDerivatives = false) + { + numNodes = nNodes; + + if (values.size() != static_cast(numNodes)) { + values.resize(numNodes); + dN_dxi.resize(numNodes); + dN_deta.resize(numNodes); + dN_dzeta.resize(numNodes); + } + + if (includeSecondDerivatives && d2N_dxi2.size() != static_cast(numNodes)){ + d2N_dxi2.resize(numNodes); + d2N_deta2.resize(numNodes); + d2N_dzeta2.resize(numNodes); + + d2N_dxideta.resize(numNodes); + d2N_dxidzeta.resize(numNodes); + d2N_detadzeta.resize(numNodes); + } + + validFlags = ShapeEvaluationFlags::None; + } + + void resizeFirstDerivativeBuffers(CfemInt nNodes) + { + numNodes = nNodes; + dN_dxi.resize(numNodes); + dN_deta.resize(numNodes); + dN_dzeta.resize(numNodes); + + } + + void resizeSecondDerivativeBuffers(CfemInt nNodes) + { + numNodes = nNodes; + d2N_dxi2.resize(numNodes); + d2N_deta2.resize(numNodes); + d2N_dzeta2.resize(numNodes); + + d2N_dxideta.resize(numNodes); + d2N_dxidzeta.resize(numNodes); + d2N_detadzeta.resize(numNodes); + + } + + inline Vector3D gradient(CfemInt node) const + { + CFEM_ASSERT(node >= 0 && node < numNodes); + return Vector3D(dN_dxi[node], dN_deta[node], dN_dzeta[node]); + } + + inline SymTensor3D hessian(CfemInt node) const + { + CFEM_ASSERT(node >= 0 && node < numNodes); + return SymTensor3D(d2N_dxi2[node], d2N_deta2[node], d2N_dzeta2[node], // The diagonal entries + d2N_detadzeta[node], d2N_dxidzeta[node], d2N_dxideta[node]); + } + + /** + * @brief Checks if a specific field (Value/Gradient/Hessian) is marked as valid. + */ + bool has(ShapeEvaluationFlags field) const + { + return hasFlag(validFlags, field); + } + }; + + /** + * @struct ShapeInfoBatch + * @brief Storage buffer for shape function evaluations at a collection of quadrature points. + * @note Optimized for HPC using a Structure of Arrays (SoA) layout via DynamicMatrix. + */ + struct ShapeInfoBatch + { + CfemInt numQuadraturePoints = 0; + CfemInt numNodes = 0; + bool shapeMustBeUpdated = true; + ShapeEvaluationFlags validFlags = ShapeEvaluationFlags::None; + + DynamicMatrix values; // Shape values: values[qp][i] + + DynamicMatrix dN_dxi; // First order local shapes' derivatives at a collection of quadrature points: \$ \frac{\partial N_i}{\partial\xi}[q] \$ + DynamicMatrix dN_deta; // First order local shapes' derivatives at a collection of quadrature points: \$ \frac{\partial N_i}{\partial\eta}[q] \$ + DynamicMatrix dN_dzeta; // First order local shapes' derivatives at a collection of quadrature points: \$ \frac{\partial N_i}{\partial\xi}[q] \$ + + DynamicMatrix d2N_dxi2; // Second order local shapes' derivatives at a collection of quadrature points \$ \frac{\partial^2N_i}{\partial\xi^2}[q] \$ + DynamicMatrix d2N_deta2; // Second order local shapes' derivatives at a collection of quadrature points \$ \frac{\partial^2N_i}{\partial\eta^2}[q] \$ + DynamicMatrix d2N_dzeta2; // Second order local shapes' derivatives at a collection of quadrature points \$ \frac{\partial^2N_i}{\partial\zeta}[q] \$ + + DynamicMatrix d2N_dxideta; // Second order local shapes' derivatives at a collection of quadrature points \$ \frac{\partial^2N_i}{\partial\xi\partial\eta}[q] \$ + DynamicMatrix d2N_dxidzeta; // Second order local shapes' derivatives at a collection of quadrature points \$ \frac{\partial^2N_i}{\partial\xi\partial\zeta}[q] \$ + DynamicMatrix d2N_detadzeta; // Second order local shapes' derivatives at a collection of quadrature points \$ \frac{\partial^2N_i}{\partial\eta\partial\zeta}[q] \$ + + /** + * @brief Prepares buffer sizes to prevent runtime reallocations. + */ + void setup(CfemInt nQp, CfemInt nNodes, bool includeSecondDerivatives = false) + { + numQuadraturePoints = nQp; + numNodes = nNodes; + + if (values.size() != nQp * nNodes) + { + values.resize(nQp, nNodes); + dN_dxi.resize(nQp, nNodes); + dN_deta.resize(nQp, nNodes); + dN_dzeta.resize(nQp, nNodes); + } + + if (includeSecondDerivatives && d2N_dxi2.size() != nQp * nNodes) + { + d2N_dxi2.resize(nQp, nNodes); + d2N_deta2.resize(nQp, nNodes); + d2N_dzeta2.resize(nQp, nNodes); + + d2N_dxideta.resize(nQp, nNodes); + d2N_dxidzeta.resize(nQp, nNodes); + d2N_detadzeta.resize(nQp, nNodes); + } + + validFlags = ShapeEvaluationFlags::None; + } + + /** + * @brief Reconstructs the reference gradient vector for a specific qp and node. + */ + inline Vector3D gradient(CfemInt q, CfemInt i) const + { + CFEM_ASSERT(q >= 0 && q < numQuadraturePoints); + CFEM_ASSERT(i >= 0 && i < numNodes); + return Vector3D(dN_dxi(q, i), dN_deta(q, i), dN_dzeta(q, i)); + } + + /** + * @brief Reconstructs the reference Hessian tensor for a specific qp and node. + */ + inline SymTensor3D hessian(CfemInt q, CfemInt i) const + { + CFEM_ASSERT(q >= 0 && q < numQuadraturePoints); + CFEM_ASSERT(i >= 0 && i < numNodes); + return SymTensor3D(d2N_dxi2(q, i), d2N_deta2(q, i), d2N_dzeta2(q, i), + d2N_detadzeta(q, i), d2N_dxidzeta(q, i), d2N_dxideta(q, i)); + } + + bool has(ShapeEvaluationFlags field) const + { + return hasFlag(validFlags, field); + } + }; + +} \ No newline at end of file diff --git a/libs/prepostprocessing/numerical_integration/include/FunctionIntegrator.h b/libs/prepostprocessing/numerical_integration/include/FunctionIntegrator.h index 29f9c66..2f8e4eb 100644 --- a/libs/prepostprocessing/numerical_integration/include/FunctionIntegrator.h +++ b/libs/prepostprocessing/numerical_integration/include/FunctionIntegrator.h @@ -77,11 +77,11 @@ namespace cfem const MeshEntity &entity, const MeshEntity &); - inline bool needsMapping(ShapeEvaluationFlags flags) - { - return hasFlag(flags, ShapeEvaluationFlags::Gradient) || - hasFlag(flags, ShapeEvaluationFlags::Hessian); - } + // inline bool needsMapping(ShapeEvaluationFlags flags) + // { + // return hasFlag(flags, ShapeEvaluationFlags::Gradient) || + // hasFlag(flags, ShapeEvaluationFlags::Hessian); + // } public: /** diff --git a/libs/prepostprocessing/numerical_integration/src/FunctionIntegrator.cpp b/libs/prepostprocessing/numerical_integration/src/FunctionIntegrator.cpp index b3b617a..cc0aa27 100644 --- a/libs/prepostprocessing/numerical_integration/src/FunctionIntegrator.cpp +++ b/libs/prepostprocessing/numerical_integration/src/FunctionIntegrator.cpp @@ -203,7 +203,8 @@ namespace cfem // ANALYSE ET BINDING O(1) // ============================================================ std::vector activeIdx; - ShapeEvaluationFlags fieldFlags = ShapeEvaluationFlags::None; + ShapeEvaluationFlags fieldShapesFlags = ShapeEvaluationFlags::None; + bool fieldsDependsOnGeoNormal = false; std::set> requiredSpaces; CfemInt geoOrder = m_mesh->getOrderInt(); @@ -219,7 +220,9 @@ namespace cfem if (targets[i].second) *(targets[i].second) = 0.0; activeIdx.push_back(i); - fieldFlags |= targets[i].first->requiredShapeFlags(); + fieldShapesFlags |= targets[i].first->requiredShapeFlags(); + fieldsDependsOnGeoNormal = fieldsDependsOnGeoNormal || targets[i].first->dependsOnNormalGeometricNormal(); + targets[i].first->collectRequiredSpaces(requiredSpaces); } @@ -238,14 +241,23 @@ namespace cfem targets[idx].first->bind(spaceToId); } - const bool needsFieldGradients = hasFlag(fieldFlags, ShapeEvaluationFlags::Gradient); - const ShapeEvaluationFlags geoFlags = ShapeEvaluationFlags::Value | ShapeEvaluationFlags::Jacobian; - const ShapeEvaluationFlags normalEvalFlag = hasFlag(fieldFlags, ShapeEvaluationFlags::Normal)? ShapeEvaluationFlags::Normal : ShapeEvaluationFlags::None; + const bool needsFieldFirstDerivatives = hasFlag(fieldShapesFlags, ShapeEvaluationFlags::FirstDerivatives); + const bool needsFieldSecondDerivatives = hasFlag(fieldShapesFlags, ShapeEvaluationFlags::SecondDerivatives); + + MappingEvaluationFlags geoMappingFlags = MappingEvaluationFlags::PhysicalPosition| + MappingEvaluationFlags::Determinant | + (fieldsDependsOnGeoNormal? MappingEvaluationFlags::Normal : MappingEvaluationFlags::None); + ShapeEvaluationFlags geoShapeFlags = transformMappingToShapeFlags(geoMappingFlags) ; + + if (needsFieldFirstDerivatives) { + geoShapeFlags |= ShapeEvaluationFlags::FirstDerivatives; + geoMappingFlags |= MappingEvaluationFlags::InverseJacobian; + } IntegrationScheme scheme; // ============================================================ - // 2. QUADRATURE ET CACHING DE REFERENCE (Read-Only) + // QUADRATURE ET CACHING DE REFERENCE (Read-Only) // ============================================================ struct SpaceCache { CfemInt contextIndex; @@ -276,13 +288,14 @@ namespace cfem } for (size_t q = 0; q < lQuadPts.size(); ++q) { - refElGeo.evaluate(lQuadPts[q].xi, geoFlags, fastCache[idx].geoShapes[q]); + refElGeo.evaluateShapes(lQuadPts[q].xi, geoShapeFlags, fastCache[idx].geoShapes[q]); fastCache[idx].geoShapes[q].shapeMustBeUpdated = false; for (size_t sIdx = 0; sIdx < idToSpace.size(); ++sIdx) { CfemInt spaceOrder = idToSpace[sIdx]->getOrderInt(); const auto& refElSpace = ReferenceElementFactory::getReferenceElement(cell_type, spaceOrder); - refElSpace.evaluate(lQuadPts[q].xi, geoFlags | fieldFlags, fastCache[idx].spaceCaches[sIdx].shapes[q]); + + refElSpace.evaluateShapes(lQuadPts[q].xi, fieldShapesFlags, fastCache[idx].spaceCaches[sIdx].shapes[q]); fastCache[idx].spaceCaches[sIdx].shapes[q].shapeMustBeUpdated = false; } } @@ -334,7 +347,14 @@ namespace cfem const bool isAffine = (geoOrder == 1) || m_mesh->isCellAffine(cellid); if (isAffine) { - refElGeo->computeMapping(lQuadPts[0].xi, physCoords, ShapeEvaluationFlags::Jacobian | normalEvalFlag, cache->geoShapes[0], mapGeo); + // CFEM_INFO << cache->geoShapes[0].gradient(2); + refElGeo->computeMapping( + lQuadPts[0].xi, + physCoords, + geoMappingFlags, + cache->geoShapes[0], + mapGeo + ); } for (size_t q = 0; q < lQuadPts.size(); ++q) @@ -342,11 +362,21 @@ namespace cfem const auto &qp = lQuadPts[q]; if (isAffine) { - refElGeo->computeMapping(qp.xi, physCoords, ShapeEvaluationFlags::PhysicalPosition, cache->geoShapes[q], mapGeo); + refElGeo->computeMapping( + qp.xi, + physCoords, + MappingEvaluationFlags::PhysicalPosition, + cache->geoShapes[q], + mapGeo + ); } else { - refElGeo->computeMapping(qp.xi, physCoords, - ShapeEvaluationFlags::PhysicalPosition | ShapeEvaluationFlags::Jacobian | normalEvalFlag, - cache->geoShapes[q], mapGeo); + refElGeo->computeMapping( + qp.xi, + physCoords, + geoMappingFlags, + cache->geoShapes[q], + mapGeo + ); } const CfemReal wDetJ = mapGeo.detJ * qp.weight; @@ -355,8 +385,8 @@ namespace cfem auto bookmark = ctx.getBookmark(); // Sauvegarde l'état exact de l'Arena mémoire ctx.currentCellId = cellid; - ctx.currentXi = qp.xi; - ctx.physPos = mapGeo.physicalPosition; + ctx.singleWS.xi = qp.xi; + ctx.singleWS.physPos = mapGeo.physicalPosition; ctx.geometricMapping = &mapGeo; for (size_t sIdx = 0; sIdx < cache->spaceCaches.size(); ++sIdx) { @@ -367,9 +397,25 @@ namespace cfem mapSpace.invJacobian = mapGeo.invJacobian; mapSpace.physicalPosition = mapGeo.physicalPosition; - if (needsFieldGradients) { - mapSpace.computePhysicalGradientsOnly(spCache.shapes[q]); + // CFEM_INFO << "gradient de reference: "; + // for (CfemInt l = 0; l < 3; ++l) + // { + // CFEM_INFO + // << spCache.shapes[q].gradient(l); + // } + + if (needsFieldFirstDerivatives) { + mapSpace.computePhysicalFirstDerivativesOnly(spCache.shapes[q]); } + + // CFEM_INFO << "gradient de Physiques: "; + + // for (CfemInt l = 0; l < 3; ++l) + // { + // CFEM_INFO + // << mapSpace.gradient(l); + // } + ctx.setSpaceData(spCache.contextIndex, &spCache.shapes[q], &mapSpace); } @@ -401,8 +447,9 @@ namespace cfem if (targets.empty() || !boundary.containsFacets() || !volume.containsCells()) return; std::vector activeIdx; - ShapeEvaluationFlags fieldFlags = ShapeEvaluationFlags::None; + ShapeEvaluationFlags fieldShapesFlags = ShapeEvaluationFlags::None; std::set> requiredSpaces; + bool fieldsDependsOnGeoNormal = false; for (size_t i = 0; i < targets.size(); ++i) { if (!targets[i].first) continue; @@ -416,8 +463,10 @@ namespace cfem if (targets[i].second) *(targets[i].second) = 0.0; activeIdx.push_back(i); - fieldFlags |= targets[i].first->requiredShapeFlags(); + fieldShapesFlags |= targets[i].first->requiredShapeFlags(); targets[i].first->collectRequiredSpaces(requiredSpaces); + + fieldsDependsOnGeoNormal = fieldsDependsOnGeoNormal || targets[i].first->dependsOnNormalGeometricNormal(); } if (activeIdx.empty()) return; @@ -433,9 +482,21 @@ namespace cfem targets[idx].first->bind(spaceToId); } - const bool needsFieldGradients = hasFlag(fieldFlags, ShapeEvaluationFlags::Gradient); - const ShapeEvaluationFlags geoFlags = ShapeEvaluationFlags::Value | ShapeEvaluationFlags::Jacobian; - const ShapeEvaluationFlags normalEvalFlag = hasFlag(fieldFlags, ShapeEvaluationFlags::Normal)? ShapeEvaluationFlags::Normal : ShapeEvaluationFlags::None; + // const bool needsFieldFirstDerivatives = hasFlag(fieldShapesFlags, ShapeEvaluationFlags::Gradient); + // const ShapeEvaluationFlags geoFlags = ShapeEvaluationFlags::Value | ShapeEvaluationFlags::Jacobian; + // const ShapeEvaluationFlags normalEvalFlag = hasFlag(fieldShapesFlags, ShapeEvaluationFlags::Normal)? ShapeEvaluationFlags::Normal : ShapeEvaluationFlags::None; + + const bool needsFieldFirstDerivatives = hasFlag(fieldShapesFlags, ShapeEvaluationFlags::FirstDerivatives); + const bool needsFieldSecondDerivatives = hasFlag(fieldShapesFlags, ShapeEvaluationFlags::SecondDerivatives); + + const MappingEvaluationFlags facetGeoMappingFlags = MappingEvaluationFlags::PhysicalPosition | + MappingEvaluationFlags::Determinant | + (fieldsDependsOnGeoNormal? MappingEvaluationFlags::Normal : MappingEvaluationFlags::None); + const ShapeEvaluationFlags facetGeoShapeFlags = fieldShapesFlags | transformMappingToShapeFlags(facetGeoMappingFlags); + const ShapeEvaluationFlags parentGeoShapeFlags = fieldShapesFlags; + const MappingEvaluationFlags parentGeoMappingFlags = transformShapeToMappingFlags(fieldShapesFlags); + + IntegrationScheme scheme; struct BoundarySpaceCache { @@ -482,17 +543,18 @@ namespace cfem { Vector3D xi_parent = refElParentGeo.projectFacetRefCoordToCellRefCoord(f_local, lQuadPts[q].xi); - refElFacetGeo.evaluate(lQuadPts[q].xi, geoFlags, entry.geoShapesFacet[q]); + //refElFacetGeo.evaluate(lQuadPts[q].xi, geoFlags, entry.geoShapesFacet[q]); + refElFacetGeo.evaluateShapes(lQuadPts[q].xi, facetGeoShapeFlags, entry.geoShapesFacet[q]); entry.geoShapesFacet[q].shapeMustBeUpdated = false; - refElParentGeo.evaluate(xi_parent, geoFlags, entry.geoShapesParent[q]); + refElParentGeo.evaluateShapes(xi_parent, parentGeoShapeFlags, entry.geoShapesParent[q]); entry.geoShapesParent[q].shapeMustBeUpdated = false; for (size_t sIdx = 0; sIdx < idToSpace.size(); ++sIdx) { CfemInt spaceOrder = idToSpace[sIdx]->getOrderInt(); const auto& refElSpace = ReferenceElementFactory::getReferenceElement(cType, spaceOrder); - refElSpace.evaluate(xi_parent, geoFlags | fieldFlags, entry.spaceCaches[sIdx].shapes[q]); + refElSpace.evaluateShapes(xi_parent, fieldShapesFlags, entry.spaceCaches[sIdx].shapes[q]); entry.spaceCaches[sIdx].shapes[q].shapeMustBeUpdated = false; } } @@ -555,8 +617,20 @@ namespace cfem const bool isAffine = (geoOrder == 1) || m_mesh->isCellAffine(parentId); if (isAffine) { - refElParentGeo.computeMapping( lQuadPts[0].xi, parentNodes, ShapeEvaluationFlags::Jacobian, cache.geoShapesParent[0], mapParentGeo); - refElFacet.computeMapping(lQuadPts[0].xi, facetNodes, ShapeEvaluationFlags::Jacobian | normalEvalFlag, cache.geoShapesFacet[0], mapSurf); + refElParentGeo.computeMapping( + lQuadPts[0].xi, + parentNodes, + parentGeoMappingFlags, + cache.geoShapesParent[0], + mapParentGeo + ); + refElFacet.computeMapping( + lQuadPts[0].xi, + facetNodes, + facetGeoMappingFlags, + cache.geoShapesFacet[0], + mapSurf + ); } for (size_t q = 0; q < lQuadPts.size(); ++q) @@ -567,14 +641,28 @@ namespace cfem if (isAffine) { // On ne calcule QUE la position physique, detJ et invJ sont déjà dans mapSurf - refElFacet.computeMapping(qp.xi, facetNodes, ShapeEvaluationFlags::PhysicalPosition, cache.geoShapesFacet[q], mapSurf); + refElFacet.computeMapping( + qp.xi, + facetNodes, + MappingEvaluationFlags::PhysicalPosition, + cache.geoShapesFacet[q], + mapSurf + ); } else { - if (needsFieldGradients) { - refElParentGeo.computeMapping( xi_parent, parentNodes, ShapeEvaluationFlags::Jacobian, cache.geoShapesParent[q], mapParentGeo); + if (needsFieldFirstDerivatives || needsFieldSecondDerivatives) { + refElParentGeo.computeMapping( + xi_parent, + parentNodes, + parentGeoMappingFlags, + cache.geoShapesParent[q], + mapParentGeo + ); } // pour une face courbee, on doit recalculer le jacobien de la transformation - refElFacet.computeMapping(qp.xi, facetNodes, - ShapeEvaluationFlags::PhysicalPosition | ShapeEvaluationFlags::Jacobian | normalEvalFlag, + refElFacet.computeMapping( + qp.xi, + facetNodes, + facetGeoMappingFlags, cache.geoShapesFacet[q], mapSurf ); @@ -584,19 +672,19 @@ namespace cfem ctx.resetBuffers(); ctx.currentCellId = parentId; - ctx.currentXi = xi_parent; - ctx.physPos = mapSurf.physicalPosition; + ctx.singleWS.xi = xi_parent; + ctx.singleWS.physPos = mapSurf.physicalPosition; ctx.geometricMapping = &mapSurf; for (size_t sIdx = 0; sIdx < cache.spaceCaches.size(); ++sIdx) { const auto& spCache = cache.spaceCaches[sIdx]; MappingInfo& mapSpace = spaceMappings[sIdx]; - if (needsFieldGradients) { + if (needsFieldFirstDerivatives) { mapSpace.detJ = mapParentGeo.detJ; mapSpace.invJacobian = mapParentGeo.invJacobian; mapSpace.physicalPosition = mapSurf.physicalPosition; - mapSpace.computePhysicalGradientsOnly(spCache.shapes[q]); + mapSpace.computePhysicalFirstDerivativesOnly(spCache.shapes[q]); } ctx.setSpaceData(spCache.contextIndex, &spCache.shapes[q], &mapSpace); diff --git a/libs/utils/include/Vector3D.h b/libs/utils/include/Vector3D.h index a770622..17607ac 100644 --- a/libs/utils/include/Vector3D.h +++ b/libs/utils/include/Vector3D.h @@ -68,6 +68,12 @@ namespace cfem { } + constexpr void setZero() noexcept{ + x = 0.0; + y = 0.0; + z = 0.0; + } + // Basic Arithmetic Operators /** @brief Vector addition. */ diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index df7dea2..c179c10 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -4,7 +4,7 @@ add_executable(unit_tests solvers/test_solvers.cpp expressions/test_symbolic_simplification.cpp fem/test_fem.cpp - fem/test_domain_integrator.cpp + #fem/test_domain_integrator.cpp fem/test_reference_element.cpp fem/test_quadrature.cpp fem/test_fields.cpp diff --git a/tests/fem/test_fields.cpp b/tests/fem/test_fields.cpp index e964b93..1fb826c 100644 --- a/tests/fem/test_fields.cpp +++ b/tests/fem/test_fields.cpp @@ -33,7 +33,7 @@ class AdvancedExpressionTest : public ::testing::Test { CfemInt testCellId = 42; void SetUp() override { - ctx.physPos = testPoint; + ctx.singleWS.physPos = testPoint; ctx.currentCellId = testCellId; } diff --git a/tests/fem/test_quadrature.cpp b/tests/fem/test_quadrature.cpp index adacbe7..f066ed7 100644 --- a/tests/fem/test_quadrature.cpp +++ b/tests/fem/test_quadrature.cpp @@ -135,7 +135,14 @@ class MappingAccuracyTest : public ::testing::Test { // L'appel à computeMapping déclenche toute ta chaîne : // evaluate() -> computeJacobian() -> detJ - ref.computeMapping(pt.xi, nodes, ShapeEvaluationFlags::Jacobian, shape, map); + ref.computeMapping( + pt.xi, + nodes, + ShapeEvaluationFlags::FirstDerivatives, + MappingEvaluationFlags::Determinant, + shape, + map + ); // Intégrale = Somme( |detJ| * w ) volume += std::abs(map.detJ) * pt.weight; @@ -184,7 +191,9 @@ TEST_F(MappingAccuracyTest, TransformedTriangle3) { TEST_F(MappingAccuracyTest, InterpolationConsistency) { Q1ReferenceQuad ref; ShapeInfo shape; - shape.setup(ref.getNumNodes()); + shape.values.resize(ref.getNumNodes()); + //shape.setup(ref.getNumNodes()); + // On définit un carré de côté 2, centré sur (5,5) DynamicVector nodes(4); @@ -195,7 +204,7 @@ TEST_F(MappingAccuracyTest, InterpolationConsistency) { // On teste au centre de l'élément de référence (0,0) Vector3D xi_center{0.0, 0.0, 0.0}; - ref.evaluate(xi_center, ShapeEvaluationFlags::Value, shape); + ref.evaluateShapes(xi_center, ShapeEvaluationFlags::Value, shape); // L'interpolation des coordonnées des noeuds doit donner le centre physique (5,5) Vector3D x_phys = ref.interpolate(shape, nodes); @@ -221,7 +230,14 @@ TEST_F(MappingAccuracyTest, PhysicalGradientLinearField) { // Gradient physique attendu : [2, 3, 0] DynamicVector nodalValues = {0.0, 2.0, 3.0}; - ref.computeMapping(q.getPoints()[0].xi, nodes, ShapeEvaluationFlags::Gradient, shape, map); + ref.computeMapping( + q.getPoints()[0].xi, + nodes, + ShapeEvaluationFlags::FirstDerivatives, + MappingEvaluationFlags::PhysicalFirstDerivatives, + shape, + map + ); Vector3D gradU = ref.interpolateGradient(map, nodalValues); EXPECT_NEAR(gradU.x, 2.0, 1e-13); @@ -265,7 +281,14 @@ TEST_F(MappingAccuracyTest, SlantedGradient3D) { // Sur le noeud 1 (s,0,s), u = s + s = sqrt(2) DynamicVector nodalValues = {0.0, std::sqrt(2.0), 0.0}; - ref.computeMapping(ref.referenceNodes()[1], nodes, ShapeEvaluationFlags::Gradient, shape, map); + ref.computeMapping( + ref.referenceNodes()[1], + nodes, + ShapeEvaluationFlags::FirstDerivatives, + MappingEvaluationFlags::PhysicalFirstDerivatives, + shape, + map + ); Vector3D gradU = ref.interpolateGradient(map, nodalValues); // Le gradient de x+z est [1, 0, 1] @@ -307,7 +330,14 @@ TEST_F(MappingAccuracyTest, PolynomialIntegrationOnRealElement) { CfemReal numeric = 0.0; for (const auto& pt : q.getPoints()) { // Mapping complet au point de quadrature - ref.computeMapping(pt.xi, nodes, ShapeEvaluationFlags::PhysicalPosition | ShapeEvaluationFlags::Jacobian, shape, map); + ref.computeMapping( + pt.xi, + nodes, + ShapeEvaluationFlags::Value | ShapeEvaluationFlags::FirstDerivatives, + MappingEvaluationFlags::PhysicalPosition | MappingEvaluationFlags::Determinant, + shape, + map + ); // On récupère la position physique x(xi) et le poids detJ * w numeric += f(map.physicalPosition) * std::abs(map.detJ) * pt.weight; diff --git a/tests/fem/test_reference_element.cpp b/tests/fem/test_reference_element.cpp index 0b5eb7e..defab83 100644 --- a/tests/fem/test_reference_element.cpp +++ b/tests/fem/test_reference_element.cpp @@ -98,7 +98,7 @@ TEST_P(ReferenceElementTest, KroneckerDeltaProperty) { CfemInt n = el->getNumNodes(); for (CfemInt j = 0; j < n; ++j) { - el->evaluate(nodes[j], ShapeEvaluationFlags::Value, info); + el->evaluateShapes(nodes[j], ShapeEvaluationFlags::Value, info); for (CfemInt i = 0; i < n; ++i) { CfemReal expected = (i == j) ? 1.0 : 0.0; EXPECT_NEAR(info.values[i], expected, tol) @@ -114,7 +114,7 @@ TEST_P(ReferenceElementTest, PartitionOfUnity) { DynamicVector test_points = getTestPointsForDim(dim); for (const auto& xi : test_points) { - el->evaluate(xi, ShapeEvaluationFlags::Value, info); + el->evaluateShapes(xi, ShapeEvaluationFlags::Value, info); CfemReal sum = 0.0; for (CfemInt i = 0; i < el->getNumNodes(); ++i) { @@ -136,13 +136,11 @@ TEST_P(ReferenceElementTest, GradientSumIsZero) { for (const auto& xi : test_points){ - el->evaluate(xi, ShapeEvaluationFlags::Gradient, info); + el->evaluateShapes(xi, ShapeEvaluationFlags::FirstDerivatives, info); Vector3D gradSum{0.0, 0.0, 0.0}; for (CfemInt i = 0; i < el->getNumNodes(); ++i) { - gradSum.x += info.gradients[i].x; - gradSum.y += info.gradients[i].y; - gradSum.z += info.gradients[i].z; + gradSum += info.gradient(i); } EXPECT_NEAR(gradSum.x, 0.0, tol); @@ -165,7 +163,7 @@ TEST_P(ReferenceElementTest, HessianFullConsistency) { ShapeInfo info, infoPlus, infoMinus; for (const auto& xi : test_points) { - el->evaluate(xi, ShapeEvaluationFlags::Hessian | ShapeEvaluationFlags::Gradient, info); + el->evaluateShapes(xi, ShapeEvaluationFlags::FirstDerivatives | ShapeEvaluationFlags::SecondDerivatives, info); for (CfemInt i = 0; i < n; ++i) { for (CfemInt r = 0; r < dim; ++r) { @@ -178,16 +176,16 @@ TEST_P(ReferenceElementTest, HessianFullConsistency) { else if (c == 1) { xiPlus.y += eps; xiMinus.y -= eps; } else if (c == 2) { xiPlus.z += eps; xiMinus.z -= eps; } - el->evaluate(xiPlus, ShapeEvaluationFlags::Gradient, infoPlus); - el->evaluate(xiMinus, ShapeEvaluationFlags::Gradient, infoMinus); + el->evaluateShapes(xiPlus, ShapeEvaluationFlags::FirstDerivatives, infoPlus); + el->evaluateShapes(xiMinus, ShapeEvaluationFlags::FirstDerivatives, infoMinus); // Valeur du gradient au nœud i pour la composante r - CfemReal grad_r_plus = getComp(infoPlus.gradients[i], r); - CfemReal grad_r_minus = getComp(infoMinus.gradients[i], r); + CfemReal grad_r_plus = getComp(infoPlus.gradient(i), r); + CfemReal grad_r_minus = getComp(infoMinus.gradient(i), r); CfemReal d2N_drc_fd = (grad_r_plus - grad_r_minus) / (2.0 * eps); - EXPECT_NEAR(info.hessians[i](r, c), d2N_drc_fd, h_tol) + EXPECT_NEAR(info.hessian(i)(r, c), d2N_drc_fd, h_tol) << "Hessian mismatch at node " << i << " component (" << r << "," << c << ")" << " for element " << typeid(*el).name(); @@ -207,20 +205,26 @@ TEST_F(MappingTest, QuadPlanarMapping) { Vector3D xi{0.5, 0.5, 0}; // Un point dans l'élément // On vérifie que le mapping ne crash pas malgré le déterminant 3D nul (Z=0) - // NOTE: Si ton inverse() est purement 3x3, il faudra s'assurer que J(2,2) = 1 - // ou traiter le cas 2D séparément. // Si on simule une épaisseur ou un élément 3D "aplati" : for(CfemInt i=0; i<4; ++i) coords[i].z = 0.0; // Ici on teste surtout la cohérence des gradients dN/dx et dN/dy - ShapeEvaluationFlags lRequestedFields = ShapeEvaluationFlags::Gradient; - quad.computeMapping(xi, coords, lRequestedFields, shape, map); + ShapeEvaluationFlags shapeFlags = ShapeEvaluationFlags::FirstDerivatives; + MappingEvaluationFlags mappingFlags = MappingEvaluationFlags::PhysicalFirstDerivatives; + quad.computeMapping( + xi, + coords, + shapeFlags, + mappingFlags, + shape, + map + ); // dN/dz doit être nul car l'élément est dans le plan XY for(CfemInt i=0; i<4; ++i) { - EXPECT_NEAR(map.dNdz(i), 0.0, tol); + EXPECT_NEAR(map.dN_dz[i], 0.0, tol); } } @@ -236,9 +240,17 @@ TEST_F(MappingTest, Hex8ShearMapping) { MappingInfo map; ShapeInfo shape; Vector3D xi{0, 0, 0}; - ShapeEvaluationFlags lRequestedFields = ShapeEvaluationFlags::Jacobian; - - hex.computeMapping(xi, coords, lRequestedFields, shape, map); + ShapeEvaluationFlags shapeFlags = ShapeEvaluationFlags::FirstDerivatives; + MappingEvaluationFlags mappingFlags = MappingEvaluationFlags::Jacobian | MappingEvaluationFlags::Determinant; + + hex.computeMapping( + xi, + coords, + shapeFlags, + mappingFlags, + shape, + map + ); // Le déterminant doit rester 1.0 (le cisaillement préserve le volume) EXPECT_NEAR(map.detJ, 1.0, tol); @@ -272,14 +284,23 @@ TEST_F(MappingTest, P2TriangleCurvedHessians) { Vector3D xi{1.0/3.0, 1.0/3.0, 0}; // On demande explicitement les Hessiennes et gradients - ShapeEvaluationFlags lRequestedFields = ShapeEvaluationFlags::Hessian; + ShapeEvaluationFlags shapeFlags = ShapeEvaluationFlags::FirstDerivatives | ShapeEvaluationFlags::SecondDerivatives; + MappingEvaluationFlags mappingFlags = MappingEvaluationFlags::PhysicalFirstDerivatives | MappingEvaluationFlags::PhysicalSecondDerivatives; + + tri.computeMapping( + xi, + coords, + shapeFlags, + mappingFlags, + shape, + map + ); - tri.computeMapping(xi, coords, lRequestedFields, shape, map); EXPECT_GT(map.detJ, 0.0) << "Jacobian determinant should be positive"; bool hasNonZeroHessian = false; for (CfemInt i=0; i<6; ++i) { - const auto& H = map.physicalShapeHessians[i]; + const auto& H = map.hessian(i); if (std::abs(H.xx()) > 1e-5 || std::abs(H.yy()) > 1e-5 || std::abs(H.zz()) > 1e-5 || std::abs(H.xy()) > 1e-5 || std::abs(H.xz()) > 1e-5 || std::abs(H.yz()) > 1e-5) { @@ -291,9 +312,9 @@ TEST_F(MappingTest, P2TriangleCurvedHessians) { // Check that the gradient components are reasonable (not NaN) for (CfemInt i=0; i<6; ++i) { - EXPECT_FALSE(std::isnan(map.physicalShapeGradients[i].x)); - EXPECT_FALSE(std::isnan(map.physicalShapeGradients[i].y)); - EXPECT_FALSE(std::isnan(map.physicalShapeGradients[i].z)); + EXPECT_FALSE(std::isnan(map.dN_dx[i])); + EXPECT_FALSE(std::isnan(map.dN_dy[i])); + EXPECT_FALSE(std::isnan(map.dN_dz[i])); } } @@ -339,11 +360,20 @@ TEST_F(MappingTest, PhysicalGradientSumIsZero) { ShapeInfo shape; Vector3D xi{0.25, 0.25, 0.0}; - ShapeEvaluationFlags req = ShapeEvaluationFlags::Gradient; - quad.computeMapping(xi, coords, req, shape, map); + ShapeEvaluationFlags shapeFlags = ShapeEvaluationFlags::FirstDerivatives; + MappingEvaluationFlags mappingFlags = MappingEvaluationFlags::PhysicalFirstDerivatives; + + quad.computeMapping( + xi, + coords, + shapeFlags, + mappingFlags, + shape, + map + ); Vector3D sumGrad{0.0, 0.0, 0.0}; - for (CfemInt i=0; i<4; ++i) sumGrad += map.physicalShapeGradients[i]; + for (CfemInt i=0; i<4; ++i) sumGrad += map.gradient(i); EXPECT_NEAR(sumGrad.x, 0.0, tol); EXPECT_NEAR(sumGrad.y, 0.0, tol); @@ -358,8 +388,17 @@ TEST_F(MappingTest, JacobianInverseCheck) { ShapeInfo shape; Vector3D xi{0.0, 0.0, 0.0}; - ShapeEvaluationFlags req = ShapeEvaluationFlags::Jacobian; - hex.computeMapping(xi, coords, req, shape, map); + ShapeEvaluationFlags shapeFlags = ShapeEvaluationFlags::FirstDerivatives; + MappingEvaluationFlags mappingFlags = MappingEvaluationFlags::InverseJacobian; + + hex.computeMapping( + xi, + coords, + shapeFlags, + mappingFlags, + shape, + map + ); Tensor3D identity; identity.setZero(); @@ -431,16 +470,16 @@ TEST_F(MappingTest, P2TetraCurvedHessians) { ShapeInfo shape; Vector3D xi{0.25, 0.25, 0.25}; // Evaluation point (inside the tet) - ShapeEvaluationFlags req = ShapeEvaluationFlags::Gradient | - ShapeEvaluationFlags::Hessian; + ShapeEvaluationFlags shapeFlags = ShapeEvaluationFlags::FirstDerivatives | ShapeEvaluationFlags::SecondDerivatives; + MappingEvaluationFlags mappingFlags = MappingEvaluationFlags::PhysicalFirstDerivatives | MappingEvaluationFlags::PhysicalSecondDerivatives; - ASSERT_NO_THROW(tet.computeMapping(xi, coords, req, shape, map)); + ASSERT_NO_THROW(tet.computeMapping(xi, coords, shapeFlags, mappingFlags, shape, map)); // VALIDATION: The Partition of Unity for Hessians: The sum of physical Hessians must be exactly zero. SymTensor3D hessianSum; hessianSum.setZero(); for (CfemInt i = 0; i < n; ++i) { - hessianSum += map.physicalShapeHessians[i]; + hessianSum += map.hessian(i); } for (CfemInt i = 0; i < 6; ++i) { @@ -452,7 +491,7 @@ TEST_F(MappingTest, P2TetraCurvedHessians) { SymTensor3D fieldHessian; fieldHessian.setZero(); for (CfemInt i = 0; i < n; ++i) { - fieldHessian += map.physicalShapeHessians[i] * (coords[i].x + coords[i].y + coords[i].z); + fieldHessian += map.hessian(i) * (coords[i].x + coords[i].y + coords[i].z); } for (CfemInt i = 0; i < 6; ++i) { @@ -481,7 +520,12 @@ TEST_F(MappingTest, P2TetraQuadraticFieldReproduction) { MappingInfo map; ShapeInfo shape; Vector3D xi{0.2, 0.3, 0.1}; - tet.computeMapping(xi, coords, ShapeEvaluationFlags::Hessian, shape, map); + + + ShapeEvaluationFlags shapeFlags = ShapeEvaluationFlags::FirstDerivatives | ShapeEvaluationFlags::SecondDerivatives; + MappingEvaluationFlags mappingFlags = MappingEvaluationFlags::PhysicalFirstDerivatives | MappingEvaluationFlags::PhysicalSecondDerivatives; + + tet.computeMapping(xi, coords, shapeFlags, mappingFlags, shape, map); // Quadratic Field: u = x^2 + y^2 + z^2 + xy + xz + yz auto fieldFunc = [](const Vector3D& p) { @@ -492,7 +536,7 @@ TEST_F(MappingTest, P2TetraQuadraticFieldReproduction) { SymTensor3D interpInHessian; interpInHessian.setZero(); for (CfemInt i = 0; i < n; ++i) { - interpInHessian += map.physicalShapeHessians[i] * fieldFunc(coords[i]); + interpInHessian += map.hessian(i) * fieldFunc(coords[i]); } // 4. VALIDATION: Compare against the analytical Hessian @@ -516,12 +560,14 @@ TEST_F(MappingTest, DegenerateTetrahedron) { MappingInfo map; ShapeInfo shape; Vector3D xi{0.25,0.25,0.25}; - ShapeEvaluationFlags req = ShapeEvaluationFlags::Gradient; + + ShapeEvaluationFlags shapeFlags = ShapeEvaluationFlags::FirstDerivatives; + MappingEvaluationFlags mappingFlags = MappingEvaluationFlags::InverseJacobian; // Collapse two nodes -> degenerate coords[3] = coords[2]; EXPECT_THROW({ - tet.computeMapping(xi, coords, req, shape, map); + tet.computeMapping(xi, coords, shapeFlags, mappingFlags, shape, map); }, AssertionException); #endif } @@ -552,7 +598,11 @@ TEST_F(MappingTest, P2TetraInterpolationSuite) { ShapeInfo shape; MappingInfo map; // We need Gradients for the gradient tests later - tet.computeMapping(xi, coords, ShapeEvaluationFlags::PhysicalPosition | ShapeEvaluationFlags::Gradient, shape, map); + + ShapeEvaluationFlags shapeFlags = ShapeEvaluationFlags::Value | ShapeEvaluationFlags::FirstDerivatives; + MappingEvaluationFlags mappingFlags = MappingEvaluationFlags::PhysicalPosition | MappingEvaluationFlags::PhysicalFirstDerivatives; + + tet.computeMapping(xi, coords, shapeFlags, mappingFlags, shape, map); // --- TEST A: Scalar Value Interpolation --- CfemReal expectedScalar = map.physicalPosition.x + map.physicalPosition.y; @@ -620,4 +670,8 @@ INSTANTIATE_TEST_SUITE_P( &p0prism, &p1prism, &p2prism, &miniprism, &q0hex, &q1hex, &q2hex, &minihex ) + // ::testing::Values( + // &p0seg, &p1seg, &p2seg, + // &p0tri, &p1tri, &minitri + // ) ); \ No newline at end of file diff --git a/tests/test_main.cpp b/tests/test_main.cpp index 0685bb4..180ced1 100644 --- a/tests/test_main.cpp +++ b/tests/test_main.cpp @@ -22,7 +22,7 @@ #include int main(int argc, char **argv) { - // omp_set_num_threads(1); + omp_set_num_threads(1); ::testing::InitGoogleTest(&argc, argv); cfem::Environment::initialize(argc, argv); From 7ef4001575928bc00c5a8335ed20ee5b35cffa7c Mon Sep 17 00:00:00 2001 From: itchinda Date: Sat, 23 May 2026 10:12:17 -0400 Subject: [PATCH 3/9] Etat temporaire --- apps/main.cpp | 92 ++--- libs/expressions/CMakeLists.txt | 2 +- libs/expressions/include/BinaryExpression.h | 6 +- libs/expressions/include/CachedExpression.h | 4 +- libs/expressions/include/CfemExpression.h | 2 +- .../include/DivergenceExpression.h | 4 +- .../include/GeometricNormalExpression.h | 2 +- libs/expressions/include/GradExpression.h | 4 +- .../include/LambdaExpressionBase.h | 6 +- .../include/MagnitudeSqExpression.h | 4 +- libs/expressions/include/MathOperators.h | 3 +- libs/expressions/include/ScaledExpression.h | 2 +- libs/expressions/include/TensorExpression.h | 20 +- libs/expressions/include/UnaryExpression.h | 2 +- libs/expressions/include/VectorExpression.h | 8 +- libs/expressions/src/GradExpression.cpp | 2 - .../fem/exportation/src/VTKFamilyExporter.cpp | 319 +++++++++++++++++- libs/fem/fefield/include/FEField.h | 8 - libs/fem/fefield/src/FEField.cpp | 5 +- libs/fem/fespace/CMakeLists.txt | 2 - libs/fem/fespace/include/FESpace.h | 1 - .../src/FunctionIntegrator.cpp | 6 +- tests/CMakeLists.txt | 5 +- 23 files changed, 405 insertions(+), 104 deletions(-) diff --git a/apps/main.cpp b/apps/main.cpp index baa4f42..dead808 100644 --- a/apps/main.cpp +++ b/apps/main.cpp @@ -101,7 +101,7 @@ static char help[] = "CFEM Example: Field interpolation on a quadratic mesh.\n\n int main(int argc, char **argv) { - omp_set_num_threads(1); + // omp_set_num_threads(1); Environment::initialize(argc, argv, help); { #define CFEM_USE_DYNAMIC_SPACE_CACHE @@ -112,7 +112,7 @@ int main(int argc, char **argv) CfemReal y0 = 0; CfemReal z0 = 0; CfemReal r0 = 0.5; - CfemReal h = 0.25; + CfemReal h = 0.0125; { PROFILE_SCOPE("MeshBuilding"); @@ -136,15 +136,15 @@ int main(int argc, char **argv) // mesh = Mesh::disk(MeshOrder::Quadratic, 0.1, Vector3D{0,0,0}, 0.5); - mesh = Mesh::block( - MeshOrder::Linear, - Mesh::BlockSubdivisions{150, 150, 150}, // Mesh::BlockSubdivisions{5*(2<dependsOnShape() || m_rhs->dependsOnShape(); } - bool dependsOnNormalGeometricNormal() const override { - return m_lhs->dependsOnNormalGeometricNormal() || - m_rhs->dependsOnNormalGeometricNormal(); + bool dependsOnGeometricNormal() const override { + return m_lhs->dependsOnGeometricNormal() || + m_rhs->dependsOnGeometricNormal(); } ShapeEvaluationFlags requiredShapeFlags() const override { diff --git a/libs/expressions/include/CachedExpression.h b/libs/expressions/include/CachedExpression.h index 3a28e2e..da377a8 100644 --- a/libs/expressions/include/CachedExpression.h +++ b/libs/expressions/include/CachedExpression.h @@ -65,8 +65,8 @@ namespace cfem::sym { } bool dependsOnFEField() const override { return m_inner->dependsOnFEField(); } bool dependsOnShape() const override { return m_inner->dependsOnShape(); } - bool dependsOnNormalGeometricNormal() const override { - return m_inner->dependsOnNormalGeometricNormal(); + bool dependsOnGeometricNormal() const override { + return m_inner->dependsOnGeometricNormal(); } ShapeEvaluationFlags requiredShapeFlags() const override { return m_inner->requiredShapeFlags(); diff --git a/libs/expressions/include/CfemExpression.h b/libs/expressions/include/CfemExpression.h index a19e59d..83556ab 100644 --- a/libs/expressions/include/CfemExpression.h +++ b/libs/expressions/include/CfemExpression.h @@ -110,7 +110,7 @@ namespace cfem::sym virtual bool isConstant() const { return false; } virtual bool dependsOnFEField() const { return false; } virtual bool dependsOnShape() const { return true; } - virtual bool dependsOnNormalGeometricNormal() const { return false; } + virtual bool dependsOnGeometricNormal() const { return false; } virtual void bind(const std::map& spaceToId) const {} diff --git a/libs/expressions/include/DivergenceExpression.h b/libs/expressions/include/DivergenceExpression.h index d65ec07..072e701 100644 --- a/libs/expressions/include/DivergenceExpression.h +++ b/libs/expressions/include/DivergenceExpression.h @@ -80,8 +80,8 @@ namespace cfem::sym bool dependsOnFEField() const override { return m_expr->dependsOnFEField(); } bool dependsOnShape() const override { return m_expr->dependsOnShape(); } - bool dependsOnNormalGeometricNormal() const override { - return m_expr->dependsOnNormalGeometricNormal(); + bool dependsOnGeometricNormal() const override { + return m_expr->dependsOnGeometricNormal(); } ShapeEvaluationFlags requiredShapeFlags() const override { diff --git a/libs/expressions/include/GeometricNormalExpression.h b/libs/expressions/include/GeometricNormalExpression.h index 287170f..8d71304 100644 --- a/libs/expressions/include/GeometricNormalExpression.h +++ b/libs/expressions/include/GeometricNormalExpression.h @@ -61,7 +61,7 @@ namespace cfem::sym return ExpressionType::Variable; } - virtual bool dependsOnNormalGeometricNormal() const override { return true; } + virtual bool dependsOnGeometricNormal() const override { return true; } /** * @brief Evaluates the normal vector at a specific quadrature point. diff --git a/libs/expressions/include/GradExpression.h b/libs/expressions/include/GradExpression.h index fcdb4bf..7314f7b 100644 --- a/libs/expressions/include/GradExpression.h +++ b/libs/expressions/include/GradExpression.h @@ -63,8 +63,8 @@ namespace cfem::sym ExpressionType getType() const override { return ExpressionType::Gradient; } bool dependsOnFEField() const override { return m_expr->dependsOnFEField(); } bool dependsOnShape() const override { return m_expr->dependsOnShape(); } - bool dependsOnNormalGeometricNormal() const override { - return m_expr->dependsOnNormalGeometricNormal(); + bool dependsOnGeometricNormal() const override { + return m_expr->dependsOnGeometricNormal(); } ShapeEvaluationFlags requiredShapeFlags() const override { diff --git a/libs/expressions/include/LambdaExpressionBase.h b/libs/expressions/include/LambdaExpressionBase.h index f8a7393..0b4757b 100644 --- a/libs/expressions/include/LambdaExpressionBase.h +++ b/libs/expressions/include/LambdaExpressionBase.h @@ -88,10 +88,10 @@ namespace cfem::sym return dep; } - bool dependsOnNormalGeometricNormal() const override { + bool dependsOnGeometricNormal() const override { bool dep = false; - if(m_gradient) dep = dep || m_gradient->dependsOnNormalGeometricNormal(); - if(m_timeDeriv) dep = dep || m_timeDeriv->dependsOnNormalGeometricNormal(); + if(m_gradient) dep = dep || m_gradient->dependsOnGeometricNormal(); + if(m_timeDeriv) dep = dep || m_timeDeriv->dependsOnGeometricNormal(); return dep; } diff --git a/libs/expressions/include/MagnitudeSqExpression.h b/libs/expressions/include/MagnitudeSqExpression.h index d599c43..c2c1db4 100644 --- a/libs/expressions/include/MagnitudeSqExpression.h +++ b/libs/expressions/include/MagnitudeSqExpression.h @@ -58,8 +58,8 @@ namespace cfem::sym bool dependsOnFEField() const override { return m_expr->dependsOnFEField(); } bool dependsOnShape() const override { return m_expr->dependsOnShape(); } - bool dependsOnNormalGeometricNormal() const override { - return m_expr->dependsOnNormalGeometricNormal(); + bool dependsOnGeometricNormal() const override { + return m_expr->dependsOnGeometricNormal(); } ShapeEvaluationFlags requiredShapeFlags() const override { return m_expr->requiredShapeFlags(); diff --git a/libs/expressions/include/MathOperators.h b/libs/expressions/include/MathOperators.h index 8af3a56..dee56c3 100644 --- a/libs/expressions/include/MathOperators.h +++ b/libs/expressions/include/MathOperators.h @@ -21,7 +21,7 @@ #include #include "CfemExpression.h" #include "ConstantExpression.h" -#include "Argument.h" +// #include "Argument.h" // #include "FEField.h" // #include "FESpace.h" @@ -31,6 +31,7 @@ namespace cfem { class FEField; class FESpace; + class TestFunction; } namespace cfem::sym { diff --git a/libs/expressions/include/ScaledExpression.h b/libs/expressions/include/ScaledExpression.h index 57d02eb..d77c0f4 100644 --- a/libs/expressions/include/ScaledExpression.h +++ b/libs/expressions/include/ScaledExpression.h @@ -50,7 +50,7 @@ namespace cfem::sym { } bool dependsOnFEField() const override { return m_base->dependsOnFEField(); } bool dependsOnShape() const override { return m_base->dependsOnShape(); } - bool dependsOnNormalGeometricNormal() const override { return m_base->dependsOnNormalGeometricNormal(); } + bool dependsOnGeometricNormal() const override { return m_base->dependsOnGeometricNormal(); } ShapeEvaluationFlags requiredShapeFlags() const override { return m_base->requiredShapeFlags(); diff --git a/libs/expressions/include/TensorExpression.h b/libs/expressions/include/TensorExpression.h index 9e376f6..b86276c 100644 --- a/libs/expressions/include/TensorExpression.h +++ b/libs/expressions/include/TensorExpression.h @@ -126,16 +126,16 @@ namespace cfem::sym } - bool dependsOnNormalGeometricNormal() const override { - return m_components[0]->dependsOnNormalGeometricNormal() || - m_components[1]->dependsOnNormalGeometricNormal() || - m_components[2]->dependsOnNormalGeometricNormal() || - m_components[3]->dependsOnNormalGeometricNormal() || - m_components[4]->dependsOnNormalGeometricNormal() || - m_components[5]->dependsOnNormalGeometricNormal() || - m_components[6]->dependsOnNormalGeometricNormal() || - m_components[7]->dependsOnNormalGeometricNormal() || - m_components[8]->dependsOnNormalGeometricNormal(); + bool dependsOnGeometricNormal() const override { + return m_components[0]->dependsOnGeometricNormal() || + m_components[1]->dependsOnGeometricNormal() || + m_components[2]->dependsOnGeometricNormal() || + m_components[3]->dependsOnGeometricNormal() || + m_components[4]->dependsOnGeometricNormal() || + m_components[5]->dependsOnGeometricNormal() || + m_components[6]->dependsOnGeometricNormal() || + m_components[7]->dependsOnGeometricNormal() || + m_components[8]->dependsOnGeometricNormal(); } ShapeEvaluationFlags requiredShapeFlags() const override { diff --git a/libs/expressions/include/UnaryExpression.h b/libs/expressions/include/UnaryExpression.h index 297af52..a88873b 100644 --- a/libs/expressions/include/UnaryExpression.h +++ b/libs/expressions/include/UnaryExpression.h @@ -47,7 +47,7 @@ namespace cfem::sym bool dependsOnFEField() const override { return m_expr->dependsOnFEField(); } bool dependsOnShape() const override { return m_expr->dependsOnShape(); } - bool dependsOnNormalGeometricNormal() const override { return m_expr->dependsOnNormalGeometricNormal();} + bool dependsOnGeometricNormal() const override { return m_expr->dependsOnGeometricNormal();} ShapeEvaluationFlags requiredShapeFlags() const override { return m_expr->requiredShapeFlags(); diff --git a/libs/expressions/include/VectorExpression.h b/libs/expressions/include/VectorExpression.h index 39bcbf3..6829430 100644 --- a/libs/expressions/include/VectorExpression.h +++ b/libs/expressions/include/VectorExpression.h @@ -98,10 +98,10 @@ namespace cfem::sym m_components[2]->dependsOnShape(); } - bool dependsOnNormalGeometricNormal() const override { - return m_components[0]->dependsOnNormalGeometricNormal() || - m_components[1]->dependsOnNormalGeometricNormal() || - m_components[2]->dependsOnNormalGeometricNormal(); + bool dependsOnGeometricNormal() const override { + return m_components[0]->dependsOnGeometricNormal() || + m_components[1]->dependsOnGeometricNormal() || + m_components[2]->dependsOnGeometricNormal(); } ShapeEvaluationFlags requiredShapeFlags() const override { diff --git a/libs/expressions/src/GradExpression.cpp b/libs/expressions/src/GradExpression.cpp index cf81af7..f50ce6a 100644 --- a/libs/expressions/src/GradExpression.cpp +++ b/libs/expressions/src/GradExpression.cpp @@ -20,8 +20,6 @@ #include "BinaryExpression.h" #include "BinaryExpression.h" #include "MagnitudeSqExpression.h" -#include "FEField.h" - namespace cfem::sym { diff --git a/libs/fem/exportation/src/VTKFamilyExporter.cpp b/libs/fem/exportation/src/VTKFamilyExporter.cpp index 626ac01..55ebfbb 100644 --- a/libs/fem/exportation/src/VTKFamilyExporter.cpp +++ b/libs/fem/exportation/src/VTKFamilyExporter.cpp @@ -138,6 +138,314 @@ namespace cfem return geo; } +// VTKFamilyExporter::EvaluatedData VTKFamilyExporter::evaluateFields(const ExtractedGeometry& geo, + +// const std::vector& entries, + +// CfemInt entityId, + +// bool isDiscontinuous) const + +// { + +// EvaluatedData data; + +// const MeshEntity& entity = m_mesh.getEntityConst(entityId); + +// bool isVolume = (entity.getTopologicalDimension() == m_mesh.getTopologicalDimension()); + +// bool isFacet = (entity.getTopologicalDimension() == m_mesh.getTopologicalDimension() - 1); + + + +// size_t numEntries = entries.size(); + +// ShapeEvaluationFlags shapeEvalFlags = ShapeEvaluationFlags::None; + +// MappingEvaluationFlags mappingEvalFlags = MappingEvaluationFlags::PhysicalPosition; + +// bool needsNormal = false; + +// // if (isDiscontinuous && isFacet) fieldFlags |= ShapeEvaluationFlags::Normal; + + + +// data.fields.resize(numEntries); + +// for (size_t i = 0; i < numEntries; ++i) { + +// entries[i].expr->prepare(); + +// shapeEvalFlags |= entries[i].expr->requiredShapeFlags(); + +// needsNormal = needsNormal || entries[i].expr->dependsOnGeometricNormal(); + +// data.fields[i].resize(geo.numActiveVerts * entries[i].expr->getNumComponents(), 0.0); + +// } + + + +// mappingEvalFlags |= transformShapeToMappingFlags(shapeEvalFlags) | MappingEvaluationFlags::PhysicalPosition | +// (needsNormal? MappingEvaluationFlags::Jacobian : MappingEvaluationFlags::None) ; + + + +// struct TypeCache { std::vector nodeShapes; const ReferenceElement *refEl; }; + +// std::unordered_map geoCache; + +// CfemInt geoOrder = m_mesh.getOrderInt(); + + + +// auto getOrCacheRefEl = [&](CellType type) -> TypeCache& { + +// auto it = geoCache.find(type); + +// if (it == geoCache.end()) { + +// auto &cache = geoCache[type]; + +// cache.refEl = &ReferenceElementFactory::getReferenceElement(type, geoOrder); + +// for (const auto &xi : cache.refEl->referenceNodes()) { + +// ShapeInfo info; + +// cache.refEl->evaluateShapes(xi, shapeEvalFlags, info); + + + +// info.shapeMustBeUpdated = false; + +// cache.nodeShapes.push_back(info); + +// } + +// return cache; + +// } + +// return it->second; + +// }; + + + +// EvalContext ctx; + +// MappingInfo scratchFacetMapping; + +// DynamicVector physCoordsElem, physCoordsParent; + +// std::vector computed(geo.numActiveVerts, 0); + + + +// CfemInt ptIdx = 0; + +// for (CfemInt elemId : *geo.pActiveElements) { + +// CfemInt parentCellId = elemId; + +// const std::vector* localFaceConnectivity = nullptr; + + + +// if (isFacet) { + +// const auto& face = m_mesh.getFacet(elemId); + +// parentCellId = face.left_cell; + +// localFaceConnectivity = &Cell::getLocalFacesIndices(m_mesh.getCell(parentCellId).type)[face.left_cell_facet_id]; + +// } + + + +// auto elemType = isVolume ? m_mesh.getCell(elemId).type : m_mesh.getFacet(elemId).type; + +// auto parentCellType = m_mesh.getCell(parentCellId).type; + + + +// auto &elemCache = getOrCacheRefEl(elemType); + +// auto &parentCache = getOrCacheRefEl(parentCellType); + + + +// if (isFacet) m_mesh.getFacetVerticesCoords(elemId, physCoordsElem); + +// m_mesh.getCellVerticesCoords(parentCellId, physCoordsParent); + + + +// const auto &conn = isVolume ? m_mesh.getCellConnectivity(elemId) : m_mesh.getFacetConnectivity(elemId); + + + +// for (size_t n = 0; n < conn.size(); ++n) { + +// // FIX: Unified index resolution. O(1) for both Continuous and DG + +// CfemInt evalIdx = isDiscontinuous ? ptIdx : geo.globalToLocal[conn[n]]; + + + +// if (!isDiscontinuous && computed[evalIdx]) continue; + + + +// CfemInt parentNodeIdx = isVolume ? n : (*localFaceConnectivity)[n]; + +// ctx.resetBuffers(); + +// MappingInfo& mapParent = ctx.scratchMapping; + +// parentCache.refEl->computeMapping( +// parentCache.refEl->referenceNodes()[parentNodeIdx], +// physCoordsParent, +// mappingEvalFlags, +// parentCache.nodeShapes[parentNodeIdx], +// mapParent + +// ); + +// if (needsNormal) { +// CFEM_INFO << "Hiii"; +// elemCache.refEl->computeMapping( +// elemCache.refEl->referenceNodes()[n], +// physCoordsElem, +// MappingEvaluationFlags::Normal, +// elemCache.nodeShapes[n], +// scratchFacetMapping + +// ); +// mapParent.normal = scratchFacetMapping.normal; +// } + +// ctx.singleWS.physPos = mapParent.physicalPosition; +// ctx.geometricMapping = &mapParent; + +// for (size_t i = 0; i < numEntries; ++i) { + +// CfemInt dim = entries[i].expr->getNumComponents(); + +// std::span outSpan(&data.fields[i][evalIdx * dim], dim); + +// entries[i].expr->evaluate(parentCellId, parentCache.refEl->referenceNodes()[parentNodeIdx], outSpan, ctx); + +// } + + + +// if (!isDiscontinuous) computed[evalIdx] = 1; + +// if (isDiscontinuous) ptIdx++; + +// } + +// } + + + +// for (const auto &name : m_exportedEntityNames) { + + + +// if ( !m_mesh.isPartOfTheMeshEntities(name) ) continue; + + + +// const MeshEntity& ent = m_mesh.getEntityConst(name); + + + +// std::vector isInEntity(m_mesh.getNumVertices(), 0); + +// for (CfemInt vId : ent.getVertexIds()) isInEntity[vId] = 1; + + + +// std::vector mask(geo.numActiveVerts, 0.0f); + +// for (size_t i = 0; i < geo.numActiveVerts; ++i) { + +// if (isInEntity[geo.localToGlobal[i]]) mask[i] = 1.0f; + +// } + +// data.nodeMasks.push_back(std::move(mask)); + +// } + + + +// if (m_exportQuality) { + +// data.cellQuality.reserve(geo.numActiveCells); + +// for (CfemInt elemId : *geo.pActiveElements) { + +// data.cellQuality.push_back(isVolume ? m_mesh.computeCellQuality(elemId) : m_mesh.computeFacetQuality(elemId)); + +// } + +// } + + + +// for (const auto &name : m_exportedEntityNames) { + +// if ( !m_mesh.isPartOfTheMeshEntities(name) ) continue; + + + +// const MeshEntity& ent = m_mesh.getEntityConst(name); + + + +// std::vector isEntityCell(m_mesh.getNumCells(), 0); + +// std::vector isEntityFacet(m_mesh.getNumFacets(), 0); + + + +// if (isVolume) for (CfemInt cId : ent.getCellIds()) isEntityCell[cId] = 1; + +// else for (CfemInt fId : ent.getFacetIds()) isEntityFacet[fId] = 1; + + + +// std::vector mask(geo.numActiveCells, 0.0f); + +// CfemInt idx = 0; + +// for (CfemInt elemId : *geo.pActiveElements) { + +// if ((isVolume && isEntityCell[elemId]) || (!isVolume && isEntityFacet[elemId])) mask[idx] = 1.0f; + +// idx++; + +// } + +// data.cellMasks.push_back(std::move(mask)); + +// } + + + +// return data; + +// } + +// } + + + VTKFamilyExporter::EvaluatedData VTKFamilyExporter::evaluateFields(const ExtractedGeometry& geo, const std::vector& entries, CfemInt entityId, @@ -145,8 +453,9 @@ namespace cfem { EvaluatedData data; const MeshEntity& entity = m_mesh.getEntityConst(entityId); - bool isVolume = (entity.getTopologicalDimension() == m_mesh.getTopologicalDimension()); - bool isFacet = (entity.getTopologicalDimension() == m_mesh.getTopologicalDimension() - 1); + const CfemInt mesh_top_dim = m_mesh.getTopologicalDimension(); + bool isVolume = (entity.getTopologicalDimension() == mesh_top_dim); + bool isFacet = (entity.getTopologicalDimension() == mesh_top_dim - 1); size_t numEntries = entries.size(); ShapeEvaluationFlags shapeEvalFlags = ShapeEvaluationFlags::None; @@ -158,7 +467,7 @@ namespace cfem for (size_t i = 0; i < numEntries; ++i) { entries[i].expr->prepare(); shapeEvalFlags |= entries[i].expr->requiredShapeFlags(); - needsNormal = needsNormal || entries[i].expr->dependsOnNormalGeometricNormal(); + needsNormal = needsNormal || entries[i].expr->dependsOnGeometricNormal(); data.fields[i].resize(geo.numActiveVerts * entries[i].expr->getNumComponents(), 0.0); } @@ -230,10 +539,10 @@ namespace cfem mapParent ); - if (isFacet && needsNormal) { + if (needsNormal && (isFacet || mesh_top_dim == 2 ) ) { elemCache.refEl->computeMapping( elemCache.refEl->referenceNodes()[n], - physCoordsElem, + (isFacet? physCoordsElem : physCoordsParent), MappingEvaluationFlags::Normal, elemCache.nodeShapes[n], scratchFacetMapping diff --git a/libs/fem/fefield/include/FEField.h b/libs/fem/fefield/include/FEField.h index a964935..6fa03fc 100644 --- a/libs/fem/fefield/include/FEField.h +++ b/libs/fem/fefield/include/FEField.h @@ -72,14 +72,6 @@ namespace cfem bool dependsOnFEField() const override { return true; } bool dependsOnShape() const override { return true; } - ShapeEvaluationFlags requiredShapeFlags() const override { - return ShapeEvaluationFlags::Value; - } - - void collectRequiredSpaces(std::set>& spaces) const override { - spaces.insert(m_space); - } - /** * @brief Lie le champ à son index de cache O(1). */ diff --git a/libs/fem/fefield/src/FEField.cpp b/libs/fem/fefield/src/FEField.cpp index b75bd79..1073a7f 100644 --- a/libs/fem/fefield/src/FEField.cpp +++ b/libs/fem/fefield/src/FEField.cpp @@ -374,7 +374,6 @@ namespace cfem // Extract contiguous cell vertex coordinates into context scratchpad m_space->getMesh()->getCellVerticesCoords(cellId, ctx.scratchCoords); - CFEM_INFO << "HIIIII"; const CfemInt expectedNodes = refEl.getNumNodes(); if (ctx.scratchCoords.size() > static_cast(expectedNodes)) { ctx.scratchCoords.resize(expectedNodes); @@ -419,7 +418,7 @@ namespace cfem for (CfemInt i = 0; i < nNodes; ++i) { const CfemReal gx = gradsX[i]; - const CfemReal gy = (spatialDimension == 2) ? gradsY[i] : 0.0; + const CfemReal gy = (spatialDimension >= 2) ? gradsY[i] : 0.0; const CfemReal gz = (spatialDimension == 3) ? gradsZ[i] : 0.0; // Loop over physical field components @@ -536,7 +535,7 @@ namespace cfem // Query mathematical dependencies to determine which reference derivatives are needed const auto recipeFlags = recipe.requiredShapeFlags(); - const bool needsGeoNormals = recipe.dependsOnNormalGeometricNormal(); + const bool needsGeoNormals = recipe.dependsOnGeometricNormal(); // Align evaluation flags to match the exact mathematical types required by the system const ShapeEvaluationFlags shapeFlags = ShapeEvaluationFlags::Value | recipeFlags | (needsGeoNormals? ShapeEvaluationFlags::FirstDerivatives : ShapeEvaluationFlags::None); diff --git a/libs/fem/fespace/CMakeLists.txt b/libs/fem/fespace/CMakeLists.txt index 0c6728a..c64bc45 100644 --- a/libs/fem/fespace/CMakeLists.txt +++ b/libs/fem/fespace/CMakeLists.txt @@ -16,6 +16,4 @@ target_link_libraries(cfem_fespace cfem_utils cfem_reference_element cfem_mesh - cfem_expressions - cfem_fefield ) diff --git a/libs/fem/fespace/include/FESpace.h b/libs/fem/fespace/include/FESpace.h index d4af2e6..7a49348 100644 --- a/libs/fem/fespace/include/FESpace.h +++ b/libs/fem/fespace/include/FESpace.h @@ -169,7 +169,6 @@ class FESpace : public MeshObserver, public std::enable_shared_from_this field); - // void registerField(std::shared_ptr field); protected: FESpace(std::shared_ptr mesh, Order order, Continuity cont, FieldType fieldType) diff --git a/libs/prepostprocessing/numerical_integration/src/FunctionIntegrator.cpp b/libs/prepostprocessing/numerical_integration/src/FunctionIntegrator.cpp index cc0aa27..e9c0b33 100644 --- a/libs/prepostprocessing/numerical_integration/src/FunctionIntegrator.cpp +++ b/libs/prepostprocessing/numerical_integration/src/FunctionIntegrator.cpp @@ -221,7 +221,7 @@ namespace cfem if (targets[i].second) *(targets[i].second) = 0.0; activeIdx.push_back(i); fieldShapesFlags |= targets[i].first->requiredShapeFlags(); - fieldsDependsOnGeoNormal = fieldsDependsOnGeoNormal || targets[i].first->dependsOnNormalGeometricNormal(); + fieldsDependsOnGeoNormal = fieldsDependsOnGeoNormal || targets[i].first->dependsOnGeometricNormal(); targets[i].first->collectRequiredSpaces(requiredSpaces); } @@ -466,7 +466,7 @@ namespace cfem fieldShapesFlags |= targets[i].first->requiredShapeFlags(); targets[i].first->collectRequiredSpaces(requiredSpaces); - fieldsDependsOnGeoNormal = fieldsDependsOnGeoNormal || targets[i].first->dependsOnNormalGeometricNormal(); + fieldsDependsOnGeoNormal = fieldsDependsOnGeoNormal || targets[i].first->dependsOnGeometricNormal(); } if (activeIdx.empty()) return; @@ -706,4 +706,4 @@ namespace cfem } } -} // namespace cfem \ No newline at end of file +} // namespace cfem diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index c179c10..4d3c953 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -4,7 +4,7 @@ add_executable(unit_tests solvers/test_solvers.cpp expressions/test_symbolic_simplification.cpp fem/test_fem.cpp - #fem/test_domain_integrator.cpp + fem/test_domain_integrator.cpp fem/test_reference_element.cpp fem/test_quadrature.cpp fem/test_fields.cpp @@ -25,13 +25,14 @@ add_executable(unit_tests target_link_libraries(unit_tests PRIVATE GTest::gtest - cfem_openmp_interface + cfem_openmp_interface cfem_solvers cfem_utils cfem_quadrature cfem_reference_element cfem_mesh cfem_fespace + cfem_fefield cfem_expressions cfem_numerical_integration ) From 13deeef8ac463dfaf3cd72483c078dbe713f0809 Mon Sep 17 00:00:00 2001 From: itchinda Date: Sun, 24 May 2026 13:14:57 -0400 Subject: [PATCH 4/9] Must ressent stable version --- CMakeLists.txt | 2 +- apps/main.cpp | 50 +- libs/expressions/include/BinaryExpression.h | 9 +- libs/expressions/include/CachedExpression.h | 8 +- libs/expressions/include/CfemExpression.h | 21 + .../include/DivergenceExpression.h | 26 +- libs/expressions/include/GradExpression.h | 26 +- .../include/LambdaExpressionBase.h | 22 + .../include/MagnitudeSqExpression.h | 7 +- libs/expressions/include/ScaledExpression.h | 8 +- libs/expressions/include/TensorExpression.h | 16 +- libs/expressions/include/UnaryExpression.h | 8 +- libs/expressions/include/VariableExpression.h | 24 +- libs/expressions/include/VectorExpression.h | 10 +- libs/fem/fefield/include/Argument.h | 5 + .../include/ReferenceElement.tpp | 444 +++++++++----- .../include/ReferenceTetrahedron.h | 554 ++++++++---------- 17 files changed, 725 insertions(+), 515 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index de20da1..9cd07c4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -48,7 +48,7 @@ if(PETSC_CXX_RAW) set(CMAKE_CXX_COMPILER ${PETSC_CXX_ABS}) message(STATUS "SUCCESS: Forcing CXX Compiler from PETSc: ${PETSC_CXX_ABS}") else() - message(FATAL_ERROR "Impossible de trouver le chemin absolu pour ${PETSC_CXX_RAW}.") + message(FATAL_ERROR "Absolute path not found for ${PETSC_CXX_RAW}.") endif() endif() diff --git a/apps/main.cpp b/apps/main.cpp index dead808..9c58b3d 100644 --- a/apps/main.cpp +++ b/apps/main.cpp @@ -136,15 +136,15 @@ int main(int argc, char **argv) // mesh = Mesh::disk(MeshOrder::Quadratic, 0.1, Vector3D{0,0,0}, 0.5); - // mesh = Mesh::block( - // MeshOrder::Linear, - // Mesh::BlockSubdivisions{150, 150, 150}, // Mesh::BlockSubdivisions{5*(2<collectSpaceRequirements(reqs, childFlags); + } + } + public: DivergenceExpression(std::shared_ptr expr) : m_expr(std::move(expr)) diff --git a/libs/expressions/include/GradExpression.h b/libs/expressions/include/GradExpression.h index 7314f7b..a395a97 100644 --- a/libs/expressions/include/GradExpression.h +++ b/libs/expressions/include/GradExpression.h @@ -35,7 +35,7 @@ namespace cfem::sym **************************************************************************** */ class GradExpression : public CfemExpression { - private: + protected: std::shared_ptr m_expr; std::shared_ptr createGradient() const override{ @@ -46,6 +46,30 @@ namespace cfem::sym return std::make_shared(m_expr->timeDerivative()); } + void doCollectSpaceRequirements(SpaceRequirements& reqs, + ShapeEvaluationFlags inheritedFlags) const override + { + ShapeEvaluationFlags childFlags = ShapeEvaluationFlags::None; + + if (hasFlag(inheritedFlags, ShapeEvaluationFlags::Value)) { + childFlags |= ShapeEvaluationFlags::FirstDerivatives; + } + + if (hasFlag(inheritedFlags, ShapeEvaluationFlags::FirstDerivatives)) { + childFlags |= ShapeEvaluationFlags::SecondDerivatives; + } + + if (hasFlag(inheritedFlags, ShapeEvaluationFlags::SecondDerivatives)) { + CFEM_THROW("Error in AST: A differential operator was asked for its Second Derivatives. " + "This requires Third Derivatives from the underlying field, which are not " + "supported by the current CFEM++ implementation."); + } + + if (m_expr) { + m_expr->collectSpaceRequirements(reqs, childFlags); + } + } + public: explicit GradExpression(std::shared_ptr expr) : m_expr(std::move(expr)) { if (auto lambda = std::dynamic_pointer_cast(expr) ){ diff --git a/libs/expressions/include/LambdaExpressionBase.h b/libs/expressions/include/LambdaExpressionBase.h index 0b4757b..b1a4d63 100644 --- a/libs/expressions/include/LambdaExpressionBase.h +++ b/libs/expressions/include/LambdaExpressionBase.h @@ -60,6 +60,28 @@ namespace cfem::sym return std::make_shared(m_dimension); } + void doCollectSpaceRequirements(SpaceRequirements& reqs, + ShapeEvaluationFlags inheritedFlags) const override + { + ShapeEvaluationFlags gradFlags = ShapeEvaluationFlags::None; + + if (hasFlag(inheritedFlags, ShapeEvaluationFlags::FirstDerivatives)) { + gradFlags |= ShapeEvaluationFlags::Value; + } + + if (hasFlag(inheritedFlags, ShapeEvaluationFlags::SecondDerivatives)) { + gradFlags |= ShapeEvaluationFlags::FirstDerivatives; + } + + if (m_gradient && gradFlags != ShapeEvaluationFlags::None) { + m_gradient->collectSpaceRequirements(reqs, gradFlags); + } + else if (!m_gradient && gradFlags != ShapeEvaluationFlags::None) { + CFEM_THROW("Error in AST: Derivatives requested from a LambdaExpression, " + "but no analytical gradient expression was provided during its construction."); + } + } + /** * @brief Constructeur de la base. diff --git a/libs/expressions/include/MagnitudeSqExpression.h b/libs/expressions/include/MagnitudeSqExpression.h index c2c1db4..5d7329b 100644 --- a/libs/expressions/include/MagnitudeSqExpression.h +++ b/libs/expressions/include/MagnitudeSqExpression.h @@ -36,12 +36,17 @@ namespace cfem::sym **************************************************************************** */ class MagnitudeSqExpression : public CfemExpression { - private: + protected: CfemInt m_innerDim = 0; std::shared_ptr m_expr; std::shared_ptr createGradient() const override; std::shared_ptr createTimeDerivative() const override; + void doCollectSpaceRequirements(SpaceRequirements& reqs, + ShapeEvaluationFlags inheritedFlags) const override + { + if (m_expr) m_expr->collectSpaceRequirements(reqs, inheritedFlags); + } public: explicit MagnitudeSqExpression(std::shared_ptr expr) diff --git a/libs/expressions/include/ScaledExpression.h b/libs/expressions/include/ScaledExpression.h index d77c0f4..5df4f05 100644 --- a/libs/expressions/include/ScaledExpression.h +++ b/libs/expressions/include/ScaledExpression.h @@ -102,7 +102,7 @@ namespace cfem::sym { return coeffStr + " * " + baseStr; } - private: + protected: CfemReal m_coeff; std::shared_ptr m_base; @@ -119,5 +119,11 @@ namespace cfem::sym { CFEM_ASSERT(m_base, "ScaledExpression::createTimeFDerivative: base expression must be valid"); return std::make_shared(m_coeff, m_base->timeDerivative()); } + + void doCollectSpaceRequirements(SpaceRequirements& reqs, + ShapeEvaluationFlags inheritedFlags) const override + { + if (m_base) m_base->collectSpaceRequirements(reqs, inheritedFlags); + } }; } \ No newline at end of file diff --git a/libs/expressions/include/TensorExpression.h b/libs/expressions/include/TensorExpression.h index b86276c..78dd52f 100644 --- a/libs/expressions/include/TensorExpression.h +++ b/libs/expressions/include/TensorExpression.h @@ -35,7 +35,7 @@ namespace cfem::sym **************************************************************************** */ class TensorExpression : public CfemExpression { - private: + protected: std::shared_ptr m_components[9]; std::shared_ptr createGradient() const override { CFEM_THROW("Gradient is not yet supported for tensor expressions. It would required a 3rd order tensor which is not implemented."); @@ -43,6 +43,20 @@ namespace cfem::sym } std::shared_ptr createTimeDerivative() const override; + void doCollectSpaceRequirements(SpaceRequirements& reqs, + ShapeEvaluationFlags inheritedFlags) const override + { + m_components[0]->collectSpaceRequirements(reqs, inheritedFlags); + m_components[1]->collectSpaceRequirements(reqs, inheritedFlags); + m_components[2]->collectSpaceRequirements(reqs, inheritedFlags); + m_components[3]->collectSpaceRequirements(reqs, inheritedFlags); + m_components[4]->collectSpaceRequirements(reqs, inheritedFlags); + m_components[5]->collectSpaceRequirements(reqs, inheritedFlags); + m_components[6]->collectSpaceRequirements(reqs, inheritedFlags); + m_components[7]->collectSpaceRequirements(reqs, inheritedFlags); + m_components[8]->collectSpaceRequirements(reqs, inheritedFlags); + } + public: TensorExpression (std::shared_ptr expr_xx, std::shared_ptr expr_xy, std::shared_ptr expr_xz, std::shared_ptr expr_yx, std::shared_ptr expr_yy, std::shared_ptr expr_yz, diff --git a/libs/expressions/include/UnaryExpression.h b/libs/expressions/include/UnaryExpression.h index a88873b..19c83ab 100644 --- a/libs/expressions/include/UnaryExpression.h +++ b/libs/expressions/include/UnaryExpression.h @@ -71,7 +71,7 @@ namespace cfem::sym CfemInt precedence() const override; ExpressionType getType() const override { return ExpressionType::Unary; } - private: + protected: std::shared_ptr m_expr; MathOp m_op; CfemInt m_dimIn; @@ -81,6 +81,12 @@ namespace cfem::sym std::shared_ptr createTimeDerivative() const override; std::shared_ptr getDerivativeWrtInner() const; + + void doCollectSpaceRequirements(SpaceRequirements& reqs, + ShapeEvaluationFlags inheritedFlags) const override + { + if(m_expr) m_expr->collectSpaceRequirements(reqs, inheritedFlags); + } }; } diff --git a/libs/expressions/include/VariableExpression.h b/libs/expressions/include/VariableExpression.h index 7b25e8b..4339c23 100644 --- a/libs/expressions/include/VariableExpression.h +++ b/libs/expressions/include/VariableExpression.h @@ -31,7 +31,7 @@ namespace cfem::sym { class VariableExpression : public CfemExpression { - private: + protected: std::string m_name; const CfemReal* m_dataPtr; CfemInt m_dim; @@ -57,6 +57,28 @@ namespace cfem::sym return m_timeDerivExpr; } + void doCollectSpaceRequirements(SpaceRequirements& reqs, + ShapeEvaluationFlags inheritedFlags) const override + { + ShapeEvaluationFlags gradFlags = ShapeEvaluationFlags::None; + + if (hasFlag(inheritedFlags, ShapeEvaluationFlags::FirstDerivatives)) { + gradFlags |= ShapeEvaluationFlags::Value; + } + + if (hasFlag(inheritedFlags, ShapeEvaluationFlags::SecondDerivatives)) { + gradFlags |= ShapeEvaluationFlags::FirstDerivatives; + } + + if (m_gradExpr && gradFlags != ShapeEvaluationFlags::None) { + m_gradExpr->collectSpaceRequirements(reqs, gradFlags); + } + else if (!m_gradExpr && gradFlags != ShapeEvaluationFlags::None) { + CFEM_THROW("Error in AST: Derivatives requested from a LambdaExpression, " + "but no analytical gradient expression was provided during its construction."); + } + } + public: VariableExpression(const std::string& name, const CfemReal* ptr, CfemInt dim, diff --git a/libs/expressions/include/VectorExpression.h b/libs/expressions/include/VectorExpression.h index 6829430..b4d685e 100644 --- a/libs/expressions/include/VectorExpression.h +++ b/libs/expressions/include/VectorExpression.h @@ -35,11 +35,19 @@ namespace cfem::sym **************************************************************************** */ class VectorExpression : public CfemExpression { - private: + protected: std::shared_ptr m_components[3]; std::shared_ptr createGradient() const override; std::shared_ptr createTimeDerivative() const override; + void doCollectSpaceRequirements(SpaceRequirements& reqs, + ShapeEvaluationFlags inheritedFlags) const override + { + m_components[0]->collectSpaceRequirements(reqs, inheritedFlags); + m_components[1]->collectSpaceRequirements(reqs, inheritedFlags); + m_components[2]->collectSpaceRequirements(reqs, inheritedFlags); + } + public: VectorExpression (std::shared_ptr expr_x, std::shared_ptr expr_y, diff --git a/libs/fem/fefield/include/Argument.h b/libs/fem/fefield/include/Argument.h index a1886a2..61f364d 100644 --- a/libs/fem/fefield/include/Argument.h +++ b/libs/fem/fefield/include/Argument.h @@ -73,6 +73,11 @@ class Argument : public CfemExpression { return nullptr; } + void doCollectSpaceRequirements( SpaceRequirements& reqs, ShapeEvaluationFlags inheritedFlags) const override + { + reqs.addRequirement(m_space.get(), inheritedFlags); + } + public: /** diff --git a/libs/fem/reference_element/include/ReferenceElement.tpp b/libs/fem/reference_element/include/ReferenceElement.tpp index 1a88266..bc4d1bf 100644 --- a/libs/fem/reference_element/include/ReferenceElement.tpp +++ b/libs/fem/reference_element/include/ReferenceElement.tpp @@ -41,7 +41,10 @@ namespace cfem inline void ReferenceElement::evaluateShapesBatch(const DynamicVector &xiPoints, ShapeEvaluationFlags requestedShape, ShapeInfoBatch& shapeBatch) const { + const CfemInt nQp = static_cast(xiPoints.size()); const bool includeSecondDerivatives = hasFlag(requestedShape, ShapeEvaluationFlags::SecondDerivatives); + shapeBatch.setup(nQp, m_numNodes, includeSecondDerivatives); + if (hasFlag(requestedShape, ShapeEvaluationFlags::Value)) { evaluateShapesValuesBatch(xiPoints, shapeBatch.values); } @@ -86,39 +89,37 @@ namespace cfem const ShapeInfo &shape, MappingInfo &out) const { - // Préparation des buffers physiques (gère l'allocation ou la réutilisation) out.setup(m_numNodes, hasFlag(requestedMapping, MappingEvaluationFlags::PhysicalSecondDerivatives)); - // Position physique interpolée if (hasFlag(requestedMapping, MappingEvaluationFlags::PhysicalPosition)) { - out.physicalPosition.setZero(); + CfemReal px = 0.0, py = 0.0, pz = 0.0; const CfemReal* N = shape.values.data(); for (CfemInt i = 0; i < m_numNodes; ++i) { - out.physicalPosition += physicalNodesCoords[i] * N[i]; + const Vector3D& pos = physicalNodesCoords[i]; + const CfemReal ni = N[i]; + px += pos.x * ni; + py += pos.y * ni; + pz += pos.z * ni; } + out.physicalPosition = Vector3D(px, py, pz); } - // Base locale / Jacobien if (hasFlag(requestedMapping, MappingEvaluationFlags::Jacobian)) { computeJacobian(shape, physicalNodesCoords, out); } - // Déterminant (Calcule et stocke le tenseur métrique SymTensor2D/3D pour éviter le double calcul) if (hasFlag(requestedMapping, MappingEvaluationFlags::Determinant)) { computeDeterminant(shape, physicalNodesCoords, out); } - // Inverse / Pseudo-inverse du Jacobien (Réutilise le tenseur métrique mis en cache) if (hasFlag(requestedMapping, MappingEvaluationFlags::InverseJacobian)) { computeInverseJacobian(shape, physicalNodesCoords, out); } - // Gradients physiques SoA (dN/dx, dN/dy, dN/dz) if (hasFlag(requestedMapping, MappingEvaluationFlags::PhysicalFirstDerivatives)) { computePhysicalFirstDerivatives(shape, out); } - // Hessiens physiques translatés (avec prise en compte de la courbure géométrique) if (hasFlag(requestedMapping, MappingEvaluationFlags::PhysicalSecondDerivatives)) { computePhysicalSecondDerivatives(physicalNodesCoords, shape, out); } @@ -161,14 +162,20 @@ namespace cfem // Initialisation du lot physique outBatch.setup(nQp, m_numNodes, m_dimension, hasFlag(requestedMapping, MappingEvaluationFlags::PhysicalSecondDerivatives)); - // Interpolation des positions physiques par point de quadrature if (hasFlag(requestedMapping, MappingEvaluationFlags::PhysicalPosition)) { for (CfemInt q = 0; q < nQp; ++q) { - outBatch.physicalPositions[q].setZero(); - const auto rowValues = shapeBatch.values[q]; + CfemReal px = 0.0, py = 0.0, pz = 0.0; + + const CfemReal* N = shapeBatch.values[q].data(); + for (CfemInt i = 0; i < m_numNodes; ++i) { - outBatch.physicalPositions[q] += physicalNodesCoords[i] * rowValues[i]; + const Vector3D& pos = physicalNodesCoords[i]; + const CfemReal ni = N[i]; + px += pos.x * ni; + py += pos.y * ni; + pz += pos.z * ni; } + outBatch.physicalPositions[q] = Vector3D(px, py, pz); // Écriture unique } } @@ -279,7 +286,7 @@ namespace cfem CfemReal g12 = j00*j01 + j10*j11 + j20*j21; out.metric2D = SymTensor2D(g11, g22, g12); - out.detJ = std::sqrt(std::max(0.0, g11 * g22 - g12 * g12)); + out.detJ = std::sqrt(std::max(0.0, g11 * g22 - g12 * g12)); } else if (m_dimension == 1) { const CfemReal j00 = out.jacobian.data[Tensor3D::idx_xx]; @@ -313,7 +320,7 @@ namespace cfem CfemReal g12 = j00*j01 + j10*j11 + j20*j21; out.metric2Ds[q] = SymTensor2D{g11, g22, g12}; - out.detJs[q] = std::sqrt(std::max(0.0, g11 * g22 - g12 * g12)); + out.detJs[q] = std::sqrt(std::max(0.0, g11 * g22 - g12 * g12)); } } else if (m_dimension == 1) { @@ -330,36 +337,110 @@ namespace cfem } + /** + * @brief Computes the contravariant geometric transformation operator G. + * + * Maps reference-space derivatives to physical-space derivatives: + * \$ \nabla_x(u) = G^T \nabla_{\xi}(u) \$ + * + * G is defined as the Moore-Penrose pseudo-inverse of the Jacobian J, + * depending on the manifold dimension: + * - 3D (Volume) : \$ G = J^{-1} \$ + * - 2D (Surface) : \$ G = (J^T J)^{-1} J^T \$ + * - 1D (Curve) : \$ G = J^T / ||J||^2 \$ + * + * @pre `out.jacobian`, `out.detJ`, and the appropriate metric tensor + * (`metric2D` or `metric1D`) must be precomputed. + * @note Stored in `out.invJacobian` for historical compatibility. + * + * @param[in] shape Element shape information (unused). + * @param[in] physicalNodesCoords Physical node coordinates (unused). + * @param[out] out Mapping structure receiving the operator. + */ inline void ReferenceElement::computeInverseJacobian(const ShapeInfo &shape, const DynamicVector &physicalNodesCoords, MappingInfo &out) const { - CFEM_ASSERT(std::abs(out.detJ) > cfem::ZERO_TOLERANCE, "Degenerate Jacobian detected"); + CFEM_ASSERT( std::abs(out.detJ) > cfem::ZERO_TOLERANCE, "Degenerate Jacobian detected" ); - if (m_dimension == 3) { - out.invJacobian = inverse(out.jacobian, out.detJ); - } - else if (m_dimension == 2) { + Tensor3D& G = out.invJacobian; + + // ================================================================ + // 3D volumetric element G = J^{-1} + // ================================================================ + if (m_dimension == 3) + { + G = inverse(out.jacobian, out.detJ); + return; + } + + // ================================================================ + // 2D embedded manifold G = (J^T J)^{-1} J^T + // ================================================================ + if (m_dimension == 2) + { const CfemReal g11 = out.metric2D.xx(); const CfemReal g22 = out.metric2D.yy(); const CfemReal g12 = out.metric2D.xy(); - CfemReal invDetG = 1.0 / (g11 * g22 - g12 * g12); + // det(G_metric) = det(J)^2 + const CfemReal invDetMetric = 1.0 / (out.detJ * out.detJ); - for (CfemInt i = 0; i < 3; ++i) { - out.invJacobian(0, i) = (g22 * out.jacobian(i, 0) - g12 * out.jacobian(i, 1)) * invDetG; - out.invJacobian(1, i) = (g11 * out.jacobian(i, 1) - g12 * out.jacobian(i, 0)) * invDetG; - out.invJacobian(2, i) = 0.0; - } + // Inverse metric tensor coefficients + const CfemReal m11 = g22 * invDetMetric; + const CfemReal m22 = g11 * invDetMetric; + const CfemReal m12 = -g12 * invDetMetric; + + // Jacobian columns (surface tangents) + const CfemReal j00 = out.jacobian.xx(); + const CfemReal j10 = out.jacobian.yx(); + const CfemReal j20 = out.jacobian.zx(); + + const CfemReal j01 = out.jacobian.xy(); + const CfemReal j11 = out.jacobian.yy(); + const CfemReal j21 = out.jacobian.zy(); + + // First contravariant basis vector + G.xx() = m11*j00 + m12*j01; + G.xy() = m11*j10 + m12*j11; + G.xz() = m11*j20 + m12*j21; + + // Second contravariant basis vector + G.yx() = m12*j00 + m22*j01; + G.yy() = m12*j10 + m22*j11; + G.yz() = m12*j20 + m22*j21; + + // Third row unused for 2D manifolds + G.zx() = 0.0; + G.zy() = 0.0; + G.zz() = 0.0; + + return; } - else if (m_dimension == 1) { - CfemReal invG11 = 1.0 / out.metric1D; - for (CfemInt i = 0; i < 3; ++i) { - out.invJacobian(i, 0) = out.jacobian(i, 0) * invG11; - out.invJacobian(i, 1) = 0.0; - out.invJacobian(i, 2) = 0.0; - } + + // ================================================================ + // 1D embedded manifold G = J^T / ||J||^2 + // ================================================================ + if (m_dimension == 1) + { + const CfemReal invMetric = 1.0 / out.metric1D; + + G(0,0) = out.jacobian(0,0) * invMetric; + G(0,1) = out.jacobian(1,0) * invMetric; + G(0,2) = out.jacobian(2,0) * invMetric; + + G(1,0) = 0.0; + G(1,1) = 0.0; + G(1,2) = 0.0; + + G(2,0) = 0.0; + G(2,1) = 0.0; + G(2,2) = 0.0; + + return; } + + CFEM_ASSERT(false, "Unsupported manifold dimension"); } inline void ReferenceElement::computeInverseJacobianBatch(const ShapeInfoBatch &shape, @@ -381,23 +462,33 @@ namespace cfem const CfemReal g22 = out.metric2Ds[q].yy(); const CfemReal g12 = out.metric2Ds[q].xy(); - CfemReal invDetG = 1.0 / (g11 * g22 - g12 * g12); + // OPTIMISATION : Réutilisation du déterminant (sauve 2 multiplications, 1 soustraction) + const CfemReal invDetMetric = 1.0 / (out.detJs[q] * out.detJs[q]); - for (CfemInt i = 0; i < 3; ++i) { - out.invJacobians[q](0, i) = (g22 * out.jacobians[q](i, 0) - g12 * out.jacobians[q](i, 1)) * invDetG; - out.invJacobians[q](1, i) = (g11 * out.jacobians[q](i, 1) - g12 * out.jacobians[q](i, 0)) * invDetG; - out.invJacobians[q](2, i) = 0.0; - } + const CfemReal m11 = g22 * invDetMetric; + const CfemReal m22 = g11 * invDetMetric; + const CfemReal m12 = -g12 * invDetMetric; + + const auto& jac_q = out.jacobians[q]; + const CfemReal j00 = jac_q.xx(), j10 = jac_q.yx(), j20 = jac_q.zx(); + const CfemReal j01 = jac_q.xy(), j11 = jac_q.yy(), j21 = jac_q.zy(); + + // OPTIMISATION : Déroulage manuel total (zéro boucle) + auto& G = out.invJacobians[q]; + G.xx() = m11*j00 + m12*j01; G.xy() = m11*j10 + m12*j11; G.xz() = m11*j20 + m12*j21; + G.yx() = m12*j00 + m22*j01; G.yy() = m12*j10 + m22*j11; G.yz() = m12*j20 + m22*j21; + G.zx() = 0.0; G.zy() = 0.0; G.zz() = 0.0; } } else if (m_dimension == 1) { for (CfemInt q = 0; q < nQp; ++q){ - CfemReal invG11 = 1.0 / out.metric1Ds[q]; - for (CfemInt i = 0; i < 3; ++i) { - out.invJacobians[q](i, 0) = out.jacobians[q](i, 0) * invG11; - out.invJacobians[q](i, 1) = 0.0; - out.invJacobians[q](i, 2) = 0.0; - } + const CfemReal invMetric = 1.0 / out.metric1Ds[q]; + const auto& jac_q = out.jacobians[q]; + auto& G = out.invJacobians[q]; + + G.xx() = jac_q.xx() * invMetric; G.xy() = jac_q.yx() * invMetric; G.xz() = jac_q.zx() * invMetric; + G.yx() = 0.0; G.yy() = 0.0; G.yz() = 0.0; + G.zx() = 0.0; G.zy() = 0.0; G.zz() = 0.0; } } @@ -414,21 +505,25 @@ namespace cfem const CfemReal i20 = out.invJacobian.zx(), i21 = out.invJacobian.zy(), i22 = out.invJacobian.zz(); // Pointeurs SoA (lecture) - const CfemReal* dxi = shape.dN_dxi.data(); - const CfemReal* deta = shape.dN_deta.data(); - const CfemReal* dzeta = shape.dN_dzeta.data(); + const CfemReal* __restrict dxi = shape.dN_dxi.data(); + const CfemReal* __restrict deta = shape.dN_deta.data(); + const CfemReal* __restrict dzeta = shape.dN_dzeta.data(); // Pointeurs SoA (écriture) - CfemReal* dx = out.dN_dx.data(); - CfemReal* dy = out.dN_dy.data(); - CfemReal* dz = out.dN_dz.data(); + CfemReal* __restrict dx = out.dN_dx.data(); + CfemReal* __restrict dy = out.dN_dy.data(); + CfemReal* __restrict dz = out.dN_dz.data(); // Boucle SIMD ultra-rapide for (CfemInt a = 0; a < n; ++a) { - dx[a] = i00 * dxi[a] + i10 * deta[a] + i20 * dzeta[a]; - dy[a] = i01 * dxi[a] + i11 * deta[a] + i21 * dzeta[a]; - dz[a] = i02 * dxi[a] + i12 * deta[a] + i22 * dzeta[a]; + const CfemReal gx = dxi[a]; + const CfemReal gy = deta[a]; + const CfemReal gz = dzeta[a]; + + dx[a] = i00 * gx + i10 * gy + i20 * gz; + dy[a] = i01 * gx + i11 * gy + i21 * gz; + dz[a] = i02 * gx + i12 * gy + i22 * gz; } } @@ -441,26 +536,30 @@ namespace cfem for (CfemInt q = 0; q < nQp; ++q) { // Cache des composantes de l'inverse du Jacobien dans les registres pour le point q - const CfemReal i00 = out.invJacobians[q](0,0), i01 = out.invJacobians[q](0,1), i02 = out.invJacobians[q](0,2); - const CfemReal i10 = out.invJacobians[q](1,0), i11 = out.invJacobians[q](1,1), i12 = out.invJacobians[q](1,2); - const CfemReal i20 = out.invJacobians[q](2,0), i21 = out.invJacobians[q](2,1), i22 = out.invJacobians[q](2,2); + const CfemReal i00 = out.invJacobians[q].xx(), i01 = out.invJacobians[q].xy(), i02 = out.invJacobians[q].xz(); + const CfemReal i10 = out.invJacobians[q].yx(), i11 = out.invJacobians[q].yy(), i12 = out.invJacobians[q].yz(); + const CfemReal i20 = out.invJacobians[q].zx(), i21 = out.invJacobians[q].zy(), i22 = out.invJacobians[q].zz(); // Pointeurs SoA vers les lignes de la matrice de référence (Lecture) - const CfemReal* dxi = shape.dN_dxi[q].data(); - const CfemReal* deta = shape.dN_deta[q].data(); - const CfemReal* dzeta = shape.dN_dzeta[q].data(); + const CfemReal* __restrict dxi = shape.dN_dxi[q].data(); + const CfemReal* __restrict deta = shape.dN_deta[q].data(); + const CfemReal* __restrict dzeta = shape.dN_dzeta[q].data(); // Pointeurs SoA vers les lignes de la matrice physique (Écriture) - CfemReal* dx = out.dN_dx[q].data(); - CfemReal* dy = out.dN_dy[q].data(); - CfemReal* dz = out.dN_dz[q].data(); + CfemReal* __restrict dx = out.dN_dx[q].data(); + CfemReal* __restrict dy = out.dN_dy[q].data(); + CfemReal* __restrict dz = out.dN_dz[q].data(); // Boucle sur les nœuds interne : vectorisation SIMD maximale for (CfemInt a = 0; a < n; ++a) { - dx[a] = i00 * dxi[a] + i10 * deta[a] + i20 * dzeta[a]; - dy[a] = i01 * dxi[a] + i11 * deta[a] + i21 * dzeta[a]; - dz[a] = i02 * dxi[a] + i12 * deta[a] + i22 * dzeta[a]; + const CfemReal gx = dxi[a]; + const CfemReal gy = deta[a]; + const CfemReal gz = dzeta[a]; + + dx[a] = i00 * gx + i10 * gy + i20 * gz; + dy[a] = i01 * gx + i11 * gy + i21 * gz; + dz[a] = i02 * gx + i12 * gy + i22 * gz; } } } @@ -477,12 +576,12 @@ namespace cfem CfemReal hZ_xx = 0, hZ_yy = 0, hZ_zz = 0, hZ_xy = 0, hZ_xz = 0, hZ_yz = 0; // Pointeurs SoA pour les Hessiens de référence - const CfemReal* d2xi2 = shape.d2N_dxi2.data(); - const CfemReal* d2eta2 = shape.d2N_deta2.data(); - const CfemReal* d2zeta2 = shape.d2N_dzeta2.data(); - const CfemReal* d2xideta = shape.d2N_dxideta.data(); - const CfemReal* d2xidzeta = shape.d2N_dxidzeta.data(); - const CfemReal* d2etadzeta = shape.d2N_detadzeta.data(); + const CfemReal* __restrict d2xi2 = shape.d2N_dxi2.data(); + const CfemReal* __restrict d2eta2 = shape.d2N_deta2.data(); + const CfemReal* __restrict d2zeta2 = shape.d2N_dzeta2.data(); + const CfemReal* __restrict d2xideta = shape.d2N_dxideta.data(); + const CfemReal* __restrict d2xidzeta = shape.d2N_dxidzeta.data(); + const CfemReal* __restrict d2etadzeta = shape.d2N_detadzeta.data(); for (CfemInt a = 0; a < n; ++a) { @@ -506,53 +605,68 @@ namespace cfem hZ_xy += hxy * pos.z; hZ_xz += hxz * pos.z; hZ_yz += hyz * pos.z; } - const CfemReal i00 = out.invJacobian(0,0), i01 = out.invJacobian(0,1), i02 = out.invJacobian(0,2); - const CfemReal i10 = out.invJacobian(1,0), i11 = out.invJacobian(1,1), i12 = out.invJacobian(1,2); - const CfemReal i20 = out.invJacobian(2,0), i21 = out.invJacobian(2,1), i22 = out.invJacobian(2,2); - - // Helper lambda pour la transformation de congruence - auto computeComp = [&](CfemInt p, CfemInt q, CfemReal r_xx, CfemReal r_yy, CfemReal r_zz, - CfemReal r_xy, CfemReal r_xz, CfemReal r_yz) { - CfemReal res = out.invJacobian(0,p)*r_xx*out.invJacobian(0,q) + - out.invJacobian(1,p)*r_yy*out.invJacobian(1,q) + - out.invJacobian(2,p)*r_zz*out.invJacobian(2,q); - res += (out.invJacobian(0,p)*r_xy*out.invJacobian(1,q) + out.invJacobian(1,p)*r_xy*out.invJacobian(0,q)) + - (out.invJacobian(0,p)*r_xz*out.invJacobian(2,q) + out.invJacobian(2,p)*r_xz*out.invJacobian(0,q)) + - (out.invJacobian(1,p)*r_yz*out.invJacobian(2,q) + out.invJacobian(2,p)*r_yz*out.invJacobian(1,q)); - return res; - }; + const CfemReal i00 = out.invJacobian.xx(), i01 = out.invJacobian.xy(), i02 = out.invJacobian.xz(); + const CfemReal i10 = out.invJacobian.yx(), i11 = out.invJacobian.yy(), i12 = out.invJacobian.yz(); + const CfemReal i20 = out.invJacobian.zx(), i21 = out.invJacobian.zy(), i22 = out.invJacobian.zz(); // Pointeurs SoA pour la lecture des gradients physiques - const CfemReal* dNdx = out.dN_dx.data(); - const CfemReal* dNdy = out.dN_dy.data(); - const CfemReal* dNdz = out.dN_dz.data(); + const CfemReal* __restrict dNdx = out.dN_dx.data(); + const CfemReal* __restrict dNdy = out.dN_dy.data(); + const CfemReal* __restrict dNdz = out.dN_dz.data(); // Pointeurs SoA pour l'écriture des Hessiens physiques - CfemReal* d2x2 = out.d2N_dx2.data(); - CfemReal* d2y2 = out.d2N_dy2.data(); - CfemReal* d2z2 = out.d2N_dz2.data(); - CfemReal* dxdy = out.d2N_dxdy.data(); - CfemReal* dxdz = out.d2N_dxdz.data(); - CfemReal* dydz = out.d2N_dydz.data(); + CfemReal* __restrict d2x2 = out.d2N_dx2.data(); + CfemReal* __restrict d2y2 = out.d2N_dy2.data(); + CfemReal* __restrict d2z2 = out.d2N_dz2.data(); + CfemReal* __restrict dxdy = out.d2N_dxdy.data(); + CfemReal* __restrict dxdz = out.d2N_dxdz.data(); + CfemReal* __restrict dydz = out.d2N_dydz.data(); // --- Étape 2 : Matrice R et Transformation vers l'espace physique --- for (CfemInt a = 0; a < n; ++a) { - // R = H_ref - sum( dN/dx_k * H_geom_k ) - CfemReal r_xx = d2xi2[a] - (dNdx[a] * hX_xx + dNdy[a] * hY_xx + dNdz[a] * hZ_xx); - CfemReal r_yy = d2eta2[a] - (dNdx[a] * hX_yy + dNdy[a] * hY_yy + dNdz[a] * hZ_yy); - CfemReal r_zz = d2zeta2[a] - (dNdx[a] * hX_zz + dNdy[a] * hY_zz + dNdz[a] * hZ_zz); - CfemReal r_xy = d2xideta[a] - (dNdx[a] * hX_xy + dNdy[a] * hY_xy + dNdz[a] * hZ_xy); - CfemReal r_xz = d2xidzeta[a] - (dNdx[a] * hX_xz + dNdy[a] * hY_xz + dNdz[a] * hZ_xz); - CfemReal r_yz = d2etadzeta[a] - (dNdx[a] * hX_yz + dNdy[a] * hY_yz + dNdz[a] * hZ_yz); - - // Écriture directe et contiguë des 6 composantes (Congruence) - d2x2[a] = computeComp(0, 0, r_xx, r_yy, r_zz, r_xy, r_xz, r_yz); - d2y2[a] = computeComp(1, 1, r_xx, r_yy, r_zz, r_xy, r_xz, r_yz); - d2z2[a] = computeComp(2, 2, r_xx, r_yy, r_zz, r_xy, r_xz, r_yz); - dxdy[a] = computeComp(0, 1, r_xx, r_yy, r_zz, r_xy, r_xz, r_yz); - dxdz[a] = computeComp(0, 2, r_xx, r_yy, r_zz, r_xy, r_xz, r_yz); - dydz[a] = computeComp(1, 2, r_xx, r_yy, r_zz, r_xy, r_xz, r_yz); + // ========================================================= + // Construction de R + // ========================================================= + + const CfemReal gx = dNdx[a]; + const CfemReal gy = dNdy[a]; + const CfemReal gz = dNdz[a]; + + const CfemReal rxx = d2xi2[a] - (gx*hX_xx + gy*hY_xx + gz*hZ_xx); + const CfemReal ryy = d2eta2[a] - (gx*hX_yy + gy*hY_yy + gz*hZ_yy); + const CfemReal rzz = d2zeta2[a] - (gx*hX_zz + gy*hY_zz + gz*hZ_zz); + const CfemReal rxy = d2xideta[a] - (gx*hX_xy + gy*hY_xy + gz*hZ_xy); + const CfemReal rxz = d2xidzeta[a] - (gx*hX_xz + gy*hY_xz + gz*hZ_xz); + const CfemReal ryz = d2etadzeta[a] - (gx*hX_yz + gy*hY_yz + gz*hZ_yz); + + // ========================================================= + // T = R * invJ + // ========================================================= + + const CfemReal t00 = rxx*i00 + rxy*i10 + rxz*i20; + const CfemReal t01 = rxx*i01 + rxy*i11 + rxz*i21; + const CfemReal t02 = rxx*i02 + rxy*i12 + rxz*i22; + + const CfemReal t10 = rxy*i00 + ryy*i10 + ryz*i20; + const CfemReal t11 = rxy*i01 + ryy*i11 + ryz*i21; + const CfemReal t12 = rxy*i02 + ryy*i12 + ryz*i22; + + const CfemReal t20 = rxz*i00 + ryz*i10 + rzz*i20; + const CfemReal t21 = rxz*i01 + ryz*i11 + rzz*i21; + const CfemReal t22 = rxz*i02 + ryz*i12 + rzz*i22; + + // ========================================================= + // H = invJ^T * T + // ========================================================= + + d2x2[a] = i00*t00 + i10*t10 + i20*t20; + d2y2[a] = i01*t01 + i11*t11 + i21*t21; + d2z2[a] = i02*t02 + i12*t12 + i22*t22; + + dxdy[a] = i00*t01 + i10*t11 + i20*t21; + dxdz[a] = i00*t02 + i10*t12 + i20*t22; + dydz[a] = i01*t02 + i11*t12 + i21*t22; } } @@ -570,12 +684,12 @@ namespace cfem CfemReal hY_xx = 0, hY_yy = 0, hY_zz = 0, hY_xy = 0, hY_xz = 0, hY_yz = 0; CfemReal hZ_xx = 0, hZ_yy = 0, hZ_zz = 0, hZ_xy = 0, hZ_xz = 0, hZ_yz = 0; - const CfemReal* d2xi2 = shape.d2N_dxi2[q].data(); - const CfemReal* d2eta2 = shape.d2N_deta2[q].data(); - const CfemReal* d2zeta2 = shape.d2N_dzeta2[q].data(); - const CfemReal* d2xideta = shape.d2N_dxideta[q].data(); - const CfemReal* d2xidzeta = shape.d2N_dxidzeta[q].data(); - const CfemReal* d2etadzeta = shape.d2N_detadzeta[q].data(); + const CfemReal* __restrict d2xi2 = shape.d2N_dxi2[q].data(); + const CfemReal* __restrict d2eta2 = shape.d2N_deta2[q].data(); + const CfemReal* __restrict d2zeta2 = shape.d2N_dzeta2[q].data(); + const CfemReal* __restrict d2xideta = shape.d2N_dxideta[q].data(); + const CfemReal* __restrict d2xidzeta = shape.d2N_dxidzeta[q].data(); + const CfemReal* __restrict d2etadzeta = shape.d2N_detadzeta[q].data(); for (CfemInt a = 0; a < n; ++a) { @@ -600,47 +714,67 @@ namespace cfem // Récupération de l'inverse du Jacobien pour le point q const auto& invJ = out.invJacobians[q]; - // Lambda helper local pour la transformation de congruence au point q - auto computeComp = [&](CfemInt p, CfemInt qIndex, CfemReal r_xx, CfemReal r_yy, CfemReal r_zz, - CfemReal r_xy, CfemReal r_xz, CfemReal r_yz) { - CfemReal res = invJ(0,p)*r_xx*invJ(0,qIndex) + - invJ(1,p)*r_yy*invJ(1,qIndex) + - invJ(2,p)*r_zz*invJ(2,qIndex); - res += (invJ(0,p)*r_xy*invJ(1,qIndex) + invJ(1,p)*r_xy*invJ(0,qIndex)) + - (invJ(0,p)*r_xz*invJ(2,qIndex) + invJ(2,p)*r_xz*invJ(0,qIndex)) + - (invJ(1,p)*r_yz*invJ(2,qIndex) + invJ(2,p)*r_yz*invJ(0,qIndex)); - return res; - }; + // ========================================================= + // PRÉ-CHARGEMENT : Extraction dans les registres locaux + // ========================================================= + const CfemReal i00 = invJ(0,0), i01 = invJ(0,1), i02 = invJ(0,2); + const CfemReal i10 = invJ(1,0), i11 = invJ(1,1), i12 = invJ(1,2); + const CfemReal i20 = invJ(2,0), i21 = invJ(2,1), i22 = invJ(2,2); // Recouvrement des pointeurs de lignes physiques (Lecture des gradients, Écriture des Hessiens) - const CfemReal* dNdx = out.dN_dx[q].data(); - const CfemReal* dNdy = out.dN_dy[q].data(); - const CfemReal* dNdz = out.dN_dz[q].data(); + const CfemReal* __restrict dNdx = out.dN_dx[q].data(); + const CfemReal* __restrict dNdy = out.dN_dy[q].data(); + const CfemReal* __restrict dNdz = out.dN_dz[q].data(); - CfemReal* d2x2 = out.d2N_dx2[q].data(); - CfemReal* d2y2 = out.d2N_dy2[q].data(); - CfemReal* d2z2 = out.d2N_dz2[q].data(); - CfemReal* dxdy = out.d2N_dxdy[q].data(); - CfemReal* dxdz = out.d2N_dxdz[q].data(); - CfemReal* dydz = out.d2N_dydz[q].data(); + CfemReal* __restrict d2x2 = out.d2N_dx2[q].data(); + CfemReal* __restrict d2y2 = out.d2N_dy2[q].data(); + CfemReal* __restrict d2z2 = out.d2N_dz2[q].data(); + CfemReal* __restrict dxdy = out.d2N_dxdy[q].data(); + CfemReal* __restrict dxdz = out.d2N_dxdz[q].data(); + CfemReal* __restrict dydz = out.d2N_dydz[q].data(); // Étape 2 : Calcul de R et application de la congruence pour chaque nœud for (CfemInt a = 0; a < n; ++a) { - CfemReal r_xx = d2xi2[a] - (dNdx[a] * hX_xx + dNdy[a] * hY_xx + dNdz[a] * hZ_xx); - CfemReal r_yy = d2eta2[a] - (dNdx[a] * hX_yy + dNdy[a] * hY_yy + dNdz[a] * hZ_yy); - CfemReal r_zz = d2zeta2[a] - (dNdx[a] * hX_zz + dNdy[a] * hY_zz + dNdz[a] * hZ_zz); - CfemReal r_xy = d2xideta[a] - (dNdx[a] * hX_xy + dNdy[a] * hY_xy + dNdz[a] * hZ_xy); - CfemReal r_xz = d2xidzeta[a] - (dNdx[a] * hX_xz + dNdy[a] * hY_xz + dNdz[a] * hZ_xz); - CfemReal r_yz = d2etadzeta[a] - (dNdx[a] * hX_yz + dNdy[a] * hY_yz + dNdz[a] * hZ_yz); - - // Enregistrement direct dans les structures matricielles contigues du Batch - d2x2[a] = computeComp(0, 0, r_xx, r_yy, r_zz, r_xy, r_xz, r_yz); - d2y2[a] = computeComp(1, 1, r_xx, r_yy, r_zz, r_xy, r_xz, r_yz); - d2z2[a] = computeComp(2, 2, r_xx, r_yy, r_zz, r_xy, r_xz, r_yz); - dxdy[a] = computeComp(0, 1, r_xx, r_yy, r_zz, r_xy, r_xz, r_yz); - dxdz[a] = computeComp(0, 2, r_xx, r_yy, r_zz, r_xy, r_xz, r_yz); - dydz[a] = computeComp(1, 2, r_xx, r_yy, r_zz, r_xy, r_xz, r_yz); + // ========================================================= + // Construction de R + // ========================================================= + const CfemReal gx = dNdx[a]; + const CfemReal gy = dNdy[a]; + const CfemReal gz = dNdz[a]; + + const CfemReal rxx = d2xi2[a] - (gx*hX_xx + gy*hY_xx + gz*hZ_xx); + const CfemReal ryy = d2eta2[a] - (gx*hX_yy + gy*hY_yy + gz*hZ_yy); + const CfemReal rzz = d2zeta2[a] - (gx*hX_zz + gy*hY_zz + gz*hZ_zz); + const CfemReal rxy = d2xideta[a] - (gx*hX_xy + gy*hY_xy + gz*hZ_xy); + const CfemReal rxz = d2xidzeta[a] - (gx*hX_xz + gy*hY_xz + gz*hZ_xz); + const CfemReal ryz = d2etadzeta[a] - (gx*hX_yz + gy*hY_yz + gz*hZ_yz); + + // ========================================================= + // T = R * invJ + // ========================================================= + const CfemReal t00 = rxx*i00 + rxy*i10 + rxz*i20; + const CfemReal t01 = rxx*i01 + rxy*i11 + rxz*i21; + const CfemReal t02 = rxx*i02 + rxy*i12 + rxz*i22; + + const CfemReal t10 = rxy*i00 + ryy*i10 + ryz*i20; + const CfemReal t11 = rxy*i01 + ryy*i11 + ryz*i21; + const CfemReal t12 = rxy*i02 + ryy*i12 + ryz*i22; + + const CfemReal t20 = rxz*i00 + ryz*i10 + rzz*i20; + const CfemReal t21 = rxz*i01 + ryz*i11 + rzz*i21; + const CfemReal t22 = rxz*i02 + ryz*i12 + rzz*i22; + + // ========================================================= + // H = invJ^T * T + // ========================================================= + d2x2[a] = i00*t00 + i10*t10 + i20*t20; + d2y2[a] = i01*t01 + i11*t11 + i21*t21; + d2z2[a] = i02*t02 + i12*t12 + i22*t22; + + dxdy[a] = i00*t01 + i10*t11 + i20*t21; + dxdz[a] = i00*t02 + i10*t12 + i20*t22; + dydz[a] = i01*t02 + i11*t12 + i21*t22; } } } @@ -652,8 +786,8 @@ namespace cfem { // Variété 2D (Surface, Triangle/Quad) dans l'espace 3D // Les deux vecteurs tangents sont les deux premières colonnes du Jacobien - Vector3D t1(out.jacobian(0, 0), out.jacobian(1, 0), out.jacobian(2, 0)); - Vector3D t2(out.jacobian(0, 1), out.jacobian(1, 1), out.jacobian(2, 1)); + Vector3D t1(out.jacobian.xx(), out.jacobian.yx(), out.jacobian.zx()); + Vector3D t2(out.jacobian.xy(), out.jacobian.yy(), out.jacobian.zy()); out.normal = (t1.cross(t2)).normalized(); @@ -661,8 +795,8 @@ namespace cfem else if (m_dimension == 1) { // Variété 1D (Ligne) dans un espace 2D (ou 3D z=0) - CfemReal dx = out.jacobian(0, 0); - CfemReal dy = out.jacobian(1, 0); + CfemReal dx = out.jacobian.xx(); + CfemReal dy = out.jacobian.yx(); CfemReal norm = std::sqrt(dx * dx + dy * dy); if (norm > cfem::ZERO_TOLERANCE) { diff --git a/libs/fem/reference_element/include/ReferenceTetrahedron.h b/libs/fem/reference_element/include/ReferenceTetrahedron.h index cb31267..58f4209 100644 --- a/libs/fem/reference_element/include/ReferenceTetrahedron.h +++ b/libs/fem/reference_element/include/ReferenceTetrahedron.h @@ -20,11 +20,12 @@ #define CFEM_REFERENCE_TETRAHEDRON #include "ReferenceElement.h" +#include namespace cfem::reference_elem::utils{ inline Vector3D projectFacetRefCoordToTetraRefCoord(CfemInt localFacetId, - const Vector3D& xiFacet) + const Vector3D& xiFacet) { const CfemReal u = xiFacet.x; const CfemReal v = xiFacet.y; @@ -35,27 +36,115 @@ namespace cfem::reference_elem::utils{ case 1: return Vector3D(u, 0.0, v); // Face {0,1,3} : y=0 case 2: return Vector3D(n0, u, v); // Face {1,2,3} : x+y+z=1 case 3: return Vector3D(0.0, n0, v); // Face {2,0,3} : x=0 - default: CFEM_ERROR << "Invalid facet ID for Tetra"; + default: CFEM_THROW("Invalid facet ID for Tetra"); } return Vector3D{}; // Make the compiler happy } - static const std::vector> tet4_face_nodes = { + inline constexpr std::array,4> tet4_face_nodes = {{ {0, 2, 1}, {0, 1, 3}, {1, 2, 3}, {2, 0, 3} - }; + }}; - static const std::vector> tet10_face_nodes = { + inline constexpr std::array,4> tet10_face_nodes = {{ {0, 2, 1, 6, 5, 4}, {0, 1, 3, 4, 8, 7}, {1, 2, 3, 5, 9, 8}, {2, 0, 3, 6, 7, 9} - }; + }}; - static const std::vector> tet1_face_nodes = { + inline constexpr std::array,4> tet1_face_nodes = {{ {0}, {0}, {0}, {0} + }}; + + inline void computeTetraBarycentric(const Vector3D& xi, + CfemReal& L0, + CfemReal& L1, + CfemReal& L2, + CfemReal& L3) noexcept + { + L1 = xi.x; + L2 = xi.y; + L3 = xi.z; + L0 = 1.0 - L1 - L2 - L3; + } + +} + +namespace cfem::reference_elem::kernels { + + // Note : L'utilisation de __restrict (ou __restrict__ sous GCC/Clang) + // garantit au compilateur qu'il n'y a pas d'aliasing entre les pointeurs. + + struct P1TetraKernel { + [[gnu::always_inline]] static inline void values(CfemReal L0, CfemReal L1, CfemReal L2, CfemReal L3, + CfemReal* __restrict N) noexcept + { + N[0] = L0; N[1] = L1; N[2] = L2; N[3] = L3; + } + + [[gnu::always_inline]] static inline void grads(CfemReal* __restrict dx, + CfemReal* __restrict dy, + CfemReal* __restrict dz) noexcept + { + dx[0] = -1.0; dx[1] = 1.0; dx[2] = 0.0; dx[3] = 0.0; + dy[0] = -1.0; dy[1] = 0.0; dy[2] = 1.0; dy[3] = 0.0; + dz[0] = -1.0; dz[1] = 0.0; dz[2] = 0.0; dz[3] = 1.0; + } + + [[gnu::always_inline]] static inline void hess(CfemReal* __restrict d2x, CfemReal* __restrict d2y, CfemReal* __restrict d2z, + CfemReal* __restrict dxy, CfemReal* __restrict dxz, CfemReal* __restrict dyz) noexcept + { + for(CfemInt i = 0; i < 4; ++i) { + d2x[i] = d2y[i] = d2z[i] = dxy[i] = dxz[i] = dyz[i] = 0.0; + } + } + }; + + struct P2TetraKernel { + [[gnu::always_inline]] static inline void values(CfemReal L0, CfemReal L1, CfemReal L2, CfemReal L3, + CfemReal* __restrict N) noexcept + { + N[0]=L0*(2*L0-1); N[1]=L1*(2*L1-1); N[2]=L2*(2*L2-1); N[3]=L3*(2*L3-1); + N[4]=4*L0*L1; N[5]=4*L1*L2; N[6]=4*L2*L0; + N[7]=4*L0*L3; N[8]=4*L1*L3; N[9]=4*L2*L3; + } + + [[gnu::always_inline]] static inline void grads(CfemReal L0, CfemReal L1, CfemReal L2, CfemReal L3, + CfemReal* __restrict dx, + CfemReal* __restrict dy, + CfemReal* __restrict dz) noexcept + { + dx[0]=1-4*L0; dx[1]=4*L1-1; dx[2]=0; dx[3]=0; dx[4]=4*(L0-L1); dx[5]=4*L2; dx[6]=-4*L2; dx[7]=-4*L3; dx[8]=4*L3; dx[9]=0; + dy[0]=1-4*L0; dy[1]=0; dy[2]=4*L2-1; dy[3]=0; dy[4]=-4*L1; dy[5]=4*L1; dy[6]=4*(L0-L2); dy[7]=-4*L3; dy[8]=0; dy[9]=4*L3; + dz[0]=1-4*L0; dz[1]=0; dz[2]=0; dz[3]=4*L3-1; dz[4]=-4*L1; dz[5]=0; dz[6]=-4*L2; dz[7]=4*(L0-L3); dz[8]=4*L1; dz[9]=4*L2; + } + + [[gnu::always_inline]] static inline void hess(CfemReal* __restrict d2x, CfemReal* __restrict d2y, CfemReal* __restrict d2z, + CfemReal* __restrict dxy, CfemReal* __restrict dxz, CfemReal* __restrict dyz) noexcept + { + // Lignes = composantes (xx, yy, zz, xy, xz, yz) + // Colonnes = nœuds (0 à 9) + static constexpr CfemReal H[6][10] = { + {4.0, 4.0, 0.0, 0.0,-8.0, 0.0, 0.0, 0.0, 0.0, 0.0}, // d2x2 (CORRIGÉ: Noeuds 1, 6, 7) + {4.0, 0.0, 4.0, 0.0, 0.0, 0.0,-8.0, 0.0, 0.0, 0.0}, // d2y2 + {4.0, 0.0, 0.0, 4.0, 0.0, 0.0, 0.0,-8.0, 0.0, 0.0}, // d2z2 + {4.0, 0.0, 0.0, 0.0,-4.0, 4.0,-4.0, 0.0, 0.0, 0.0}, // dxy + {4.0, 0.0, 0.0, 0.0,-4.0, 0.0, 0.0,-4.0, 4.0, 0.0}, // dxz + {4.0, 0.0, 0.0, 0.0, 0.0, 0.0,-4.0,-4.0, 0.0, 4.0} // dyz + }; + + for(CfemInt i = 0; i < 10; ++i) { + d2x[i] = H[0][i]; + d2y[i] = H[1][i]; + d2z[i] = H[2][i]; + dxy[i] = H[3][i]; + dxz[i] = H[4][i]; + dyz[i] = H[5][i]; + } + } }; } @@ -78,92 +167,76 @@ namespace cfem { }; } - void evaluateShapesValues(const Vector3D& xi, CfemReal* values) const override - { - values[0] = 1.0 - xi.x - xi.y - xi.z; - values[1] = xi.x; - values[2] = xi.y; - values[3] = xi.z; + void evaluateShapesValues(const Vector3D& xi, CfemReal* values) const override { + CfemReal L0, L1, L2, L3; + reference_elem::utils::computeTetraBarycentric(xi, L0, L1, L2, L3); + reference_elem::kernels::P1TetraKernel::values(L0, L1, L2, L3, values); } - void evaluateShapesDerivatives(const Vector3D&, - CfemReal* dN_dxi, - CfemReal* dN_deta, - CfemReal* dN_dzeta) const override - { - // d/dxi - dN_dxi[0] = -1.0; dN_dxi[1] = 1.0; dN_dxi[2] = 0.0; dN_dxi[3] = 0.0; - // d/deta - dN_deta[0] = -1.0; dN_deta[1] = 0.0; dN_deta[2] = 1.0; dN_deta[3] = 0.0; - // d/dzeta - dN_dzeta[0] = -1.0; dN_dzeta[1] = 0.0; dN_dzeta[2] = 0.0; dN_dzeta[3] = 1.0; + void evaluateShapesDerivatives(const Vector3D&, CfemReal* dN_dxi, CfemReal* dN_deta, CfemReal* dN_dzeta) const override { + reference_elem::kernels::P1TetraKernel::grads(dN_dxi, dN_deta, dN_dzeta); } void evaluateShapesSecondDerivatives(const Vector3D&, - CfemReal* d2N_dxi2, - CfemReal* d2N_deta2, - CfemReal* d2N_dzeta2, - CfemReal* d2N_dxideta, - CfemReal* d2N_dxidzeta, - CfemReal* d2N_detadzeta) const override + CfemReal* d2N_dxi2, + CfemReal* d2N_deta2, + CfemReal* d2N_dzeta2, + CfemReal* d2N_dxideta, + CfemReal* d2N_dxidzeta, + CfemReal* d2N_detadzeta) const override { - for (CfemInt i = 0; i < 4; ++i) { - d2N_dxi2[i] = 0.0; - d2N_deta2[i] = 0.0; - d2N_dzeta2[i] = 0.0; - d2N_dxideta[i] = 0.0; - d2N_dxidzeta[i] = 0.0; - d2N_detadzeta[i] = 0.0; - } + reference_elem::kernels::P1TetraKernel::hess(d2N_dxi2, d2N_deta2, d2N_dzeta2, + d2N_dxideta, d2N_dxidzeta, d2N_detadzeta); } - void evaluateShapesValuesBatch(const DynamicVector &xiPoints, DynamicMatrix& values) const override - { - // Constant piecewise field value + // ===================================================================== + // INTERFACES BATCH + // ===================================================================== + void evaluateShapesValuesBatch(const DynamicVector& xiPoints, DynamicMatrix& values) const override { const CfemInt nQp = static_cast(xiPoints.size()); for (CfemInt q = 0; q < nQp; ++q) { - const auto& xi = xiPoints[q]; - values[q][0] = 1.0 - xi.x - xi.y - xi.z; - values[q][1] = xi.x; - values[q][2] = xi.y; - values[q][3] = xi.z; + const Vector3D& xi = xiPoints[q]; + CfemReal L0, L1, L2, L3; + reference_elem::utils::computeTetraBarycentric(xi, L0, L1, L2, L3); + reference_elem::kernels::P1TetraKernel::values(L0, L1, L2, L3, values[q].data()); } } - void evaluateShapesDerivativesBatch(const DynamicVector & xiPoints, - DynamicMatrix &dN_dxi, - DynamicMatrix &dN_deta, - DynamicMatrix &dN_dzeta) const override - { - // Constant function results in flat zero gradients everywhere + void evaluateShapesDerivativesBatch(const DynamicVector& xiPoints, + DynamicMatrix& dN_dxi, + DynamicMatrix& dN_deta, + DynamicMatrix& dN_dzeta) const override { const CfemInt nQp = static_cast(xiPoints.size()); + for (CfemInt q = 0; q < nQp; ++q) { - // d/dxi - dN_dxi[q][0] = -1.0; dN_dxi[q][1] = 1.0; dN_dxi[q][2] = 0.0; dN_dxi[q][3] = 0.0; - // d/deta - dN_deta[q][0] = -1.0; dN_deta[q][1] = 0.0; dN_deta[q][2] = 1.0; dN_deta[q][3] = 0.0; - // d/dzeta - dN_dzeta[q][0] = -1.0; dN_dzeta[q][1] = 0.0; dN_dzeta[q][2] = 0.0; dN_dzeta[q][3] = 1.0; + reference_elem::kernels::P1TetraKernel::grads(dN_dxi[q].data(), dN_deta[q].data(), dN_dzeta[q].data()); } } - void evaluateShapesSecondDerivativesBatch(const DynamicVector &xiPoints, - DynamicMatrix &d2N_dxi2, - DynamicMatrix &d2N_deta2, - DynamicMatrix &d2N_dzeta2, - DynamicMatrix &d2N_dxideta, - DynamicMatrix &d2N_dxidzeta, - DynamicMatrix &d2N_detadzeta) const override + void evaluateShapesSecondDerivativesBatch(const DynamicVector& xiPoints, + DynamicMatrix& d2N_dxi2, + DynamicMatrix& d2N_deta2, + DynamicMatrix& d2N_dzeta2, + DynamicMatrix& d2N_dxideta, + DynamicMatrix& d2N_dxidzeta, + DynamicMatrix& d2N_detadzeta) const override { - // Flat null second derivatives const CfemInt nQp = static_cast(xiPoints.size()); - for (CfemInt q = 0; q < nQp; ++q) { - d2N_dxi2[q][0] = 0.0; - d2N_deta2[q][0] = 0.0; - d2N_dzeta2[q][0] = 0.0; - d2N_dxideta[q][0] = 0.0; - d2N_dxidzeta[q][0] = 0.0; - d2N_detadzeta[q][0] = 0.0; + if (nQp == 0) return; + + reference_elem::kernels::P1TetraKernel::hess( + d2N_dxi2[0].data(), d2N_deta2[0].data(), d2N_dzeta2[0].data(), + d2N_dxideta[0].data(), d2N_dxidzeta[0].data(), d2N_detadzeta[0].data() + ); + + const size_t bytesToCopy = m_numNodes * sizeof(CfemReal); + for (CfemInt q = 1; q < nQp; ++q) { + std::memcpy(d2N_dxi2[q].data(), d2N_dxi2[0].data(), bytesToCopy); + std::memcpy(d2N_deta2[q].data(), d2N_deta2[0].data(), bytesToCopy); + std::memcpy(d2N_dzeta2[q].data(), d2N_dzeta2[0].data(), bytesToCopy); + std::memcpy(d2N_dxideta[q].data(), d2N_dxideta[0].data(), bytesToCopy); + std::memcpy(d2N_dxidzeta[q].data(), d2N_dxidzeta[0].data(), bytesToCopy); + std::memcpy(d2N_detadzeta[q].data(), d2N_detadzeta[0].data(), bytesToCopy); } } @@ -200,255 +273,99 @@ namespace cfem { }; } - void evaluateShapesValues(const Vector3D& xi, CfemReal* values) const override + // ========================================================================= + // FIRST DERIVATIVES + // ========================================================================= + void evaluateShapesValues(const Vector3D& xi, CfemReal* values) const override { - const CfemReal L1 = xi.x; - const CfemReal L2 = xi.y; - const CfemReal L3 = xi.z; - const CfemReal L0 = 1.0 - L1 - L2 - L3; - - // Sommets - values[0] = L0 * (2.0 * L0 - 1.0); - values[1] = L1 * (2.0 * L1 - 1.0); - values[2] = L2 * (2.0 * L2 - 1.0); - values[3] = L3 * (2.0 * L3 - 1.0); - - // Arêtes horizontales (base) - values[4] = 4.0 * L0 * L1; - values[5] = 4.0 * L1 * L2; - values[6] = 4.0 * L2 * L0; - - // Arêtes verticales (vers L3) - values[7] = 4.0 * L0 * L3; - values[8] = 4.0 * L1 * L3; - values[9] = 4.0 * L2 * L3; + CfemReal L0, L1, L2, L3; + reference_elem::utils::computeTetraBarycentric(xi, L0, L1, L2, L3); + reference_elem::kernels::P2TetraKernel::values(L0, L1, L2, L3, values); } - void evaluateShapesDerivatives(const Vector3D& xi, - CfemReal* dN_dxi, - CfemReal* dN_deta, - CfemReal* dN_dzeta) const override + void evaluateShapesValuesBatch(const DynamicVector& xiPoints, + DynamicMatrix& values) const override { - const CfemReal L1 = xi.x; - const CfemReal L2 = xi.y; - const CfemReal L3 = xi.z; - - // Dérivées par rapport à xi (x) - dN_dxi[0] = -3.0 + 4.0*L1 + 4.0*L2 + 4.0*L3; - dN_dxi[1] = 4.0*L1 - 1.0; - dN_dxi[2] = 0.0; - dN_dxi[3] = 0.0; - dN_dxi[4] = 4.0 - 8.0*L1 - 4.0*L2 - 4.0*L3; - dN_dxi[5] = 4.0*L2; - dN_dxi[6] = -4.0*L2; - dN_dxi[7] = -4.0*L3; - dN_dxi[8] = 4.0*L3; - dN_dxi[9] = 0.0; - - // Dérivées par rapport à eta (y) - dN_deta[0] = -3.0 + 4.0*L1 + 4.0*L2 + 4.0*L3; - dN_deta[1] = 0.0; - dN_deta[2] = 4.0*L2 - 1.0; - dN_deta[3] = 0.0; - dN_deta[4] = -4.0*L1; - dN_deta[5] = 4.0*L1; - dN_deta[6] = 4.0 - 4.0*L1 - 8.0*L2 - 4.0*L3; - dN_deta[7] = -4.0*L3; - dN_deta[8] = 0.0; - dN_deta[9] = 4.0*L3; - - // Dérivées par rapport à zeta (z) - dN_dzeta[0] = -3.0 + 4.0*L1 + 4.0*L2 + 4.0*L3; - dN_dzeta[1] = 0.0; - dN_dzeta[2] = 0.0; - dN_dzeta[3] = 4.0*L3 - 1.0; - dN_dzeta[4] = -4.0*L1; - dN_dzeta[5] = 0.0; - dN_dzeta[6] = -4.0*L2; - dN_dzeta[7] = 4.0 - 4.0*L1 - 4.0*L2 - 8.0*L3; - dN_dzeta[8] = 4.0*L1; - dN_dzeta[9] = 4.0*L2; + const CfemInt nQp = static_cast(xiPoints.size()); + for (CfemInt q = 0; q < nQp; ++q) { + const Vector3D& xi = xiPoints[q]; + CfemReal L0, L1, L2, L3; + reference_elem::utils::computeTetraBarycentric(xi, L0, L1, L2, L3); + + reference_elem::kernels::P2TetraKernel::values(L0, L1, L2, L3, values[q].data()); + } } - - void evaluateShapesSecondDerivatives(const Vector3D&, - CfemReal* d2N_dxi2, - CfemReal* d2N_deta2, - CfemReal* d2N_dzeta2, - CfemReal* d2N_dxideta, - CfemReal* d2N_dxidzeta, - CfemReal* d2N_detadzeta) const override + + // ========================================================================= + // FIRST DERIVATIVES + // ========================================================================= + void evaluateShapesDerivatives(const Vector3D& xi, + CfemReal* dN_dxi, + CfemReal* dN_deta, + CfemReal* dN_dzeta) const override { - // Nœud 0 - d2N_dxi2[0] = 4.0; d2N_deta2[0] = 4.0; d2N_dzeta2[0] = 4.0; - d2N_dxideta[0] = 4.0; d2N_dxidzeta[0] = 4.0; d2N_detadzeta[0] = 4.0; - - // Nœud 1 - d2N_dxi2[1] = 4.0; d2N_deta2[1] = 0.0; d2N_dzeta2[1] = 0.0; - d2N_dxideta[1] = 0.0; d2N_dxidzeta[1] = 0.0; d2N_detadzeta[1] = 0.0; - - // Nœud 2 - d2N_dxi2[2] = 0.0; d2N_deta2[2] = 4.0; d2N_dzeta2[2] = 0.0; - d2N_dxideta[2] = 0.0; d2N_dxidzeta[2] = 0.0; d2N_detadzeta[2] = 0.0; - - // Nœud 3 - d2N_dxi2[3] = 0.0; d2N_deta2[3] = 0.0; d2N_dzeta2[3] = 4.0; - d2N_dxideta[3] = 0.0; d2N_dxidzeta[3] = 0.0; d2N_detadzeta[3] = 0.0; - - // Nœud 4 - d2N_dxi2[4] =-8.0; d2N_deta2[4] = 0.0; d2N_dzeta2[4] = 0.0; - d2N_dxideta[4] =-4.0; d2N_dxidzeta[4] =-4.0; d2N_detadzeta[4] = 0.0; - - // Nœud 5 - d2N_dxi2[5] = 0.0; d2N_deta2[5] = 0.0; d2N_dzeta2[5] = 0.0; - d2N_dxideta[5] = 4.0; d2N_dxidzeta[5] = 0.0; d2N_detadzeta[5] = 0.0; - - // Nœud 6 - d2N_dxi2[6] = 0.0; d2N_deta2[6] =-8.0; d2N_dzeta2[6] = 0.0; - d2N_dxideta[6] =-4.0; d2N_dxidzeta[6] = 0.0; d2N_detadzeta[6] =-4.0; - - // Nœud 7 - d2N_dxi2[7] = 0.0; d2N_deta2[7] = 0.0; d2N_dzeta2[7] =-8.0; - d2N_dxideta[7] = 0.0; d2N_dxidzeta[7] =-4.0; d2N_detadzeta[7] =-4.0; - - // Nœud 8 - d2N_dxi2[8] = 0.0; d2N_deta2[8] = 0.0; d2N_dzeta2[8] = 0.0; - d2N_dxideta[8] = 0.0; d2N_dxidzeta[8] = 4.0; d2N_detadzeta[8] = 0.0; - - // Nœud 9 - d2N_dxi2[9] = 0.0; d2N_deta2[9] = 0.0; d2N_dzeta2[9] = 0.0; - d2N_dxideta[9] = 0.0; d2N_dxidzeta[9] = 0.0; d2N_detadzeta[9] = 4.0; + CfemReal L0, L1, L2, L3; + reference_elem::utils::computeTetraBarycentric(xi, L0, L1, L2, L3); + reference_elem::kernels::P2TetraKernel::grads(L0, L1, L2, L3, dN_dxi, dN_deta, dN_dzeta); } - void evaluateShapesValuesBatch(const DynamicVector &xiPoints, DynamicMatrix& values) const override + void evaluateShapesDerivativesBatch(const DynamicVector& xiPoints, + DynamicMatrix& dN_dxi, + DynamicMatrix& dN_deta, + DynamicMatrix& dN_dzeta) const override { - // Constant piecewise field value const CfemInt nQp = static_cast(xiPoints.size()); for (CfemInt q = 0; q < nQp; ++q) { - const Vector3D& xi = xiPoints[q]; - const CfemReal L1 = xi.x; - const CfemReal L2 = xi.y; - const CfemReal L3 = xi.z; - const CfemReal L0 = 1.0 - L1 - L2 - L3; - - // Sommets - values[q][0] = L0 * (2.0 * L0 - 1.0); - values[q][1] = L1 * (2.0 * L1 - 1.0); - values[q][2] = L2 * (2.0 * L2 - 1.0); - values[q][3] = L3 * (2.0 * L3 - 1.0); - - // Arêtes horizontales (base) - values[q][4] = 4.0 * L0 * L1; - values[q][5] = 4.0 * L1 * L2; - values[q][6] = 4.0 * L2 * L0; - - // Arêtes verticales (vers L3) - values[q][7] = 4.0 * L0 * L3; - values[q][8] = 4.0 * L1 * L3; - values[q][9] = 4.0 * L2 * L3; + const Vector3D& xi = xiPoints[q]; + CfemReal L0, L1, L2, L3; + reference_elem::utils::computeTetraBarycentric(xi, L0, L1, L2, L3); + + reference_elem::kernels::P2TetraKernel::grads(L0, L1, L2, L3, + dN_dxi[q].data(), + dN_deta[q].data(), + dN_dzeta[q].data()); } } - void evaluateShapesDerivativesBatch(const DynamicVector & xiPoints, - DynamicMatrix &dN_dxi, - DynamicMatrix &dN_deta, - DynamicMatrix &dN_dzeta) const override + // ========================================================================= + // SECOND DERIVATIVES + // ========================================================================= + void evaluateShapesSecondDerivatives(const Vector3D& /*xi*/, + CfemReal* d2N_dxi2, + CfemReal* d2N_deta2, + CfemReal* d2N_dzeta2, + CfemReal* d2N_dxideta, + CfemReal* d2N_dxidzeta, + CfemReal* d2N_detadzeta) const override { - // Constant function results in flat zero gradients everywhere - const CfemInt nQp = static_cast(xiPoints.size()); - for (CfemInt q = 0; q < nQp; ++q) { - const Vector3D& xi = xiPoints[q]; - const CfemReal L1 = xi.x; - const CfemReal L2 = xi.y; - const CfemReal L3 = xi.z; - - // Dérivées par rapport à xi (x) - dN_dxi[q][0] = -3.0 + 4.0*L1 + 4.0*L2 + 4.0*L3; - dN_dxi[q][1] = 4.0*L1 - 1.0; - dN_dxi[q][2] = 0.0; - dN_dxi[q][3] = 0.0; - dN_dxi[q][4] = 4.0 - 8.0*L1 - 4.0*L2 - 4.0*L3; - dN_dxi[q][5] = 4.0*L2; - dN_dxi[q][6] = -4.0*L2; - dN_dxi[q][7] = -4.0*L3; - dN_dxi[q][8] = 4.0*L3; - dN_dxi[q][9] = 0.0; - - // Dérivées par rapport à eta (y) - dN_deta[q][0] = -3.0 + 4.0*L1 + 4.0*L2 + 4.0*L3; - dN_deta[q][1] = 0.0; - dN_deta[q][2] = 4.0*L2 - 1.0; - dN_deta[q][3] = 0.0; - dN_deta[q][4] = -4.0*L1; - dN_deta[q][5] = 4.0*L1; - dN_deta[q][6] = 4.0 - 4.0*L1 - 8.0*L2 - 4.0*L3; - dN_deta[q][7] = -4.0*L3; - dN_deta[q][8] = 0.0; - dN_deta[q][9] = 4.0*L3; - - // Dérivées par rapport à zeta (z) - dN_dzeta[q][0] = -3.0 + 4.0*L1 + 4.0*L2 + 4.0*L3; - dN_dzeta[q][1] = 0.0; - dN_dzeta[q][2] = 0.0; - dN_dzeta[q][3] = 4.0*L3 - 1.0; - dN_dzeta[q][4] = -4.0*L1; - dN_dzeta[q][5] = 0.0; - dN_dzeta[q][6] = -4.0*L2; - dN_dzeta[q][7] = 4.0 - 4.0*L1 - 4.0*L2 - 8.0*L3; - dN_dzeta[q][8] = 4.0*L1; - dN_dzeta[q][9] = 4.0*L2; - } + reference_elem::kernels::P2TetraKernel::hess(d2N_dxi2, d2N_deta2, d2N_dzeta2, + d2N_dxideta, d2N_dxidzeta, d2N_detadzeta); } - void evaluateShapesSecondDerivativesBatch(const DynamicVector &xiPoints, - DynamicMatrix &d2N_dxi2, - DynamicMatrix &d2N_deta2, - DynamicMatrix &d2N_dzeta2, - DynamicMatrix &d2N_dxideta, - DynamicMatrix &d2N_dxidzeta, - DynamicMatrix &d2N_detadzeta) const override + void evaluateShapesSecondDerivativesBatch(const DynamicVector& xiPoints, + DynamicMatrix& d2N_dxi2, + DynamicMatrix& d2N_deta2, + DynamicMatrix& d2N_dzeta2, + DynamicMatrix& d2N_dxideta, + DynamicMatrix& d2N_dxidzeta, + DynamicMatrix& d2N_detadzeta) const override { - // Flat null second derivatives const CfemInt nQp = static_cast(xiPoints.size()); - for (CfemInt q = 0; q < nQp; ++q) { - // Nœud 0 - d2N_dxi2[q][0] = 4.0; d2N_deta2[q][0] = 4.0; d2N_dzeta2[q][0] = 4.0; - d2N_dxideta[q][0] = 4.0; d2N_dxidzeta[q][0] = 4.0; d2N_detadzeta[q][0] = 4.0; - - // Nœud 1 - d2N_dxi2[q][1] = 4.0; d2N_deta2[q][1] = 0.0; d2N_dzeta2[q][1] = 0.0; - d2N_dxideta[q][1] = 0.0; d2N_dxidzeta[q][1] = 0.0; d2N_detadzeta[q][1] = 0.0; - - // Nœud 2 - d2N_dxi2[q][2] = 0.0; d2N_deta2[q][2] = 4.0; d2N_dzeta2[q][2] = 0.0; - d2N_dxideta[q][2] = 0.0; d2N_dxidzeta[q][2] = 0.0; d2N_detadzeta[q][2] = 0.0; - - // Nœud 3 - d2N_dxi2[q][3] = 0.0; d2N_deta2[q][3] = 0.0; d2N_dzeta2[q][3] = 4.0; - d2N_dxideta[q][3] = 0.0; d2N_dxidzeta[q][3] = 0.0; d2N_detadzeta[q][3] = 0.0; - - // Nœud 4 - d2N_dxi2[q][4] =-8.0; d2N_deta2[q][4] = 0.0; d2N_dzeta2[q][4] = 0.0; - d2N_dxideta[q][4] =-4.0; d2N_dxidzeta[q][4] =-4.0; d2N_detadzeta[q][4] = 0.0; - - // Nœud 5 - d2N_dxi2[q][5] = 0.0; d2N_deta2[q][5] = 0.0; d2N_dzeta2[q][5] = 0.0; - d2N_dxideta[q][5] = 4.0; d2N_dxidzeta[q][5] = 0.0; d2N_detadzeta[q][5] = 0.0; - - // Nœud 6 - d2N_dxi2[q][6] = 0.0; d2N_deta2[q][6] =-8.0; d2N_dzeta2[q][6] = 0.0; - d2N_dxideta[q][6] =-4.0; d2N_dxidzeta[q][6] = 0.0; d2N_detadzeta[q][6] =-4.0; - - // Nœud 7 - d2N_dxi2[q][7] = 0.0; d2N_deta2[q][7] = 0.0; d2N_dzeta2[q][7] =-8.0; - d2N_dxideta[q][7] = 0.0; d2N_dxidzeta[q][7] =-4.0; d2N_detadzeta[q][7] =-4.0; - - // Nœud 8 - d2N_dxi2[q][8] = 0.0; d2N_deta2[q][8] = 0.0; d2N_dzeta2[q][8] = 0.0; - d2N_dxideta[q][8] = 0.0; d2N_dxidzeta[q][8] = 4.0; d2N_detadzeta[q][8] = 0.0; - - // Nœud 9 - d2N_dxi2[q][9] = 0.0; d2N_deta2[q][9] = 0.0; d2N_dzeta2[q][9] = 0.0; - d2N_dxideta[q][9] = 0.0; d2N_dxidzeta[q][9] = 0.0; d2N_detadzeta[q][9] = 4.0; + if (nQp == 0) return; + + reference_elem::kernels::P2TetraKernel::hess( + d2N_dxi2[0].data(), d2N_deta2[0].data(), d2N_dzeta2[0].data(), + d2N_dxideta[0].data(), d2N_dxidzeta[0].data(), d2N_detadzeta[0].data() + ); + + const size_t bytesToCopy = m_numNodes * sizeof(CfemReal); + for (CfemInt q = 1; q < nQp; ++q) { + std::memcpy(d2N_dxi2[q].data(), d2N_dxi2[0].data(), bytesToCopy); + std::memcpy(d2N_deta2[q].data(), d2N_deta2[0].data(), bytesToCopy); + std::memcpy(d2N_dzeta2[q].data(), d2N_dzeta2[0].data(), bytesToCopy); + std::memcpy(d2N_dxideta[q].data(), d2N_dxideta[0].data(), bytesToCopy); + std::memcpy(d2N_dxidzeta[q].data(), d2N_dxidzeta[0].data(), bytesToCopy); + std::memcpy(d2N_detadzeta[q].data(), d2N_detadzeta[0].data(), bytesToCopy); } } @@ -478,10 +395,8 @@ namespace cfem { void evaluateShapesValues(const Vector3D& xi, CfemReal* values) const override { - const CfemReal L1 = xi.x; - const CfemReal L2 = xi.y; - const CfemReal L3 = xi.z; - const CfemReal L0 = 1.0 - L1 - L2 - L3; + CfemReal L0, L1, L2, L3; + reference_elem::utils::computeTetraBarycentric(xi, L0, L1, L2, L3); // Bubble function: 256 * L0 * L1 * L2 * L3 const CfemReal b = 256.0 * L0 * L1 * L2 * L3; @@ -498,10 +413,8 @@ namespace cfem { CfemReal* dN_deta, CfemReal* dN_dzeta) const override { - const CfemReal L1 = xi.x; - const CfemReal L2 = xi.y; - const CfemReal L3 = xi.z; - const CfemReal L0 = 1.0 - L1 - L2 - L3; + CfemReal L0, L1, L2, L3; + reference_elem::utils::computeTetraBarycentric(xi, L0, L1, L2, L3); // Bubble gradients const CfemReal db_dxi = 256.0 * L2 * L3 * (L0 - L1); @@ -542,10 +455,8 @@ namespace cfem { CfemReal* d2N_dxidzeta, CfemReal* d2N_detadzeta) const override { - const CfemReal L1 = xi.x; - const CfemReal L2 = xi.y; - const CfemReal L3 = xi.z; - const CfemReal L0 = 1.0 - L1 - L2 - L3; + CfemReal L0, L1, L2, L3; + reference_elem::utils::computeTetraBarycentric(xi, L0, L1, L2, L3); // Second derivatives of the bubble function const CfemReal d2b_dxi2 = -512.0 * L2 * L3; @@ -579,10 +490,8 @@ namespace cfem { // Constant piecewise field value const CfemInt nQp = static_cast(xiPoints.size()); for (CfemInt q = 0; q < nQp; ++q) { - const CfemReal L1 = xiPoints[q].x; - const CfemReal L2 = xiPoints[q].y; - const CfemReal L3 = xiPoints[q].z; - const CfemReal L0 = 1.0 - L1 - L2 - L3; + CfemReal L0, L1, L2, L3; + reference_elem::utils::computeTetraBarycentric(xiPoints[q], L0, L1, L2, L3); // Bubble function: 256 * L0 * L1 * L2 * L3 const CfemReal b = 256.0 * L0 * L1 * L2 * L3; @@ -603,11 +512,8 @@ namespace cfem { // Constant function results in flat zero gradients everywhere const CfemInt nQp = static_cast(xiPoints.size()); for (CfemInt q = 0; q < nQp; ++q) { - const Vector3D& xi = xiPoints[q]; - const CfemReal L1 = xi.x; - const CfemReal L2 = xi.y; - const CfemReal L3 = xi.z; - const CfemReal L0 = 1.0 - L1 - L2 - L3; + CfemReal L0, L1, L2, L3; + reference_elem::utils::computeTetraBarycentric(xiPoints[q], L0, L1, L2, L3); // Bubble gradients const CfemReal db_dxi = 256.0 * L2 * L3 * (L0 - L1); From 9b9033a1eb1af99382cb7cafda7b53971a4af9d0 Mon Sep 17 00:00:00 2001 From: itchinda Date: Mon, 25 May 2026 16:58:44 -0400 Subject: [PATCH 5/9] Code Refactoring --- libs/fem/fefield/include/Argument.h | 8 +- libs/fem/fefield/src/FEField.cpp | 37 +- libs/fem/fespace/include/DGSpace.h | 4 +- libs/fem/fespace/include/FESpace.h | 4 +- libs/fem/fespace/include/LagrangeSpace.h | 4 +- libs/fem/fespace/src/DGSpace.cpp | 43 +- libs/fem/fespace/src/LagrangeSpace.cpp | 39 +- .../reference_element/include/MappingInfo.h | 455 ++++-- .../include/ReferenceElement.h | 40 +- .../include/ReferenceElement.tpp | 159 +-- .../include/ReferenceHexahedron.h | 1218 +++++++---------- .../include/ReferencePrism.h | 984 ++++++------- .../reference_element/include/ReferenceQuad.h | 1011 ++++++-------- .../include/ReferenceSegment.h | 507 +++---- .../include/ReferenceTetrahedron.h | 655 ++++----- .../include/ReferenceTriangle.h | 838 +++++------- .../fem/reference_element/include/ShapeInfo.h | 495 +++++-- .../src/FunctionIntegrator.cpp | 4 +- tests/fem/test_quadrature.cpp | 6 +- tests/fem/test_reference_element.cpp | 16 +- 20 files changed, 3074 insertions(+), 3453 deletions(-) diff --git a/libs/fem/fefield/include/Argument.h b/libs/fem/fefield/include/Argument.h index 61f364d..20c0bf3 100644 --- a/libs/fem/fefield/include/Argument.h +++ b/libs/fem/fefield/include/Argument.h @@ -195,7 +195,7 @@ class Argument : public CfemExpression { const CfemInt comp = static_cast(localDofIdx % dim); std::fill(out.begin(), out.end(), 0.0); - out[comp] = shape->values[node]; + out[comp] = shape->values()[node]; } /** @@ -212,9 +212,9 @@ class Argument : public CfemExpression { const CfemInt node = static_cast(localDofIdx / dim); const CfemInt comp = static_cast(localDofIdx % dim); - const auto& gx = mapping->dN_dx[node]; - const auto& gy = mapping->dN_dy[node]; - const auto& gz = mapping->dN_dz[node]; + const auto& gx = mapping->dN_dx()[node]; + const auto& gy = mapping->dN_dy()[node]; + const auto& gz = mapping->dN_dz()[node]; std::fill(out.begin(), out.end(), 0.0); // Output span contains full gradient: [dxUx, dyUx, dzUx, dxUy, dyUy, dzUy, dxUz, dyUz, dzUz] diff --git a/libs/fem/fefield/src/FEField.cpp b/libs/fem/fefield/src/FEField.cpp index 1073a7f..6750abd 100644 --- a/libs/fem/fefield/src/FEField.cpp +++ b/libs/fem/fefield/src/FEField.cpp @@ -268,8 +268,8 @@ namespace cfem // Gather all local cell coefficients into cache-friendly dense layout this->extractCellDofs(cellId, ctx.scratchCellDofs, ctx); - const CfemInt nQp = shapeBatch.values.n_rows(); - const CfemInt nNodes = shapeBatch.values.n_cols(); + const CfemInt nQp = shapeBatch.numQuadraturePoints; + const CfemInt nNodes = shapeBatch.numNodes; const CfemInt fDim = this->getNumComponents(); outValuesBatch.resize(nQp, fDim); @@ -277,8 +277,9 @@ namespace cfem // Vectorized dense matrix contraction: Out[q][d] = Sum_i ( Shape[q][i] * CellDofs[i][d] ) for (CfemInt q = 0; q < nQp; ++q) { + const auto* values = shapeBatch.values(q); for (CfemInt i = 0; i < nNodes; ++i) { - const CfemReal Ni = shapeBatch.values[q][i]; + const CfemReal Ni = values[i]; for (CfemInt d = 0; d < fDim; ++d) { outValuesBatch[q][d] += Ni * ctx.scratchCellDofs[i][d]; } @@ -298,8 +299,9 @@ namespace cfem const Cell &cell = m_space->getMesh()->getCell(cellId); const auto &refEl = ReferenceElementFactory::getReferenceElement(cell.type, m_space->getOrderInt()); - ctx.singleWS.shape.values.resize(refEl.getNumNodes()); - refEl.evaluateShapesValues(xi, ctx.singleWS.shape.values.data()); + ctx.singleWS.shape.setup(refEl.getNumNodes(), false); + ctx.singleWS.shape.validFlags |= ShapeEvaluationFlags::Value; + refEl.evaluateShapesValues(xi, ctx.singleWS.shape); this->evaluateWithShape(cellId, ctx.singleWS.shape, out); } @@ -315,8 +317,8 @@ namespace cfem const CfemInt nComp = this->getNumComponents(); std::fill_n(out.data(), nComp, 0.0); - const auto &shapeValues = shape.values; - const CfemInt nNodes = static_cast(shapeValues.size()); + const auto &shapeValues = shape.values(); + const CfemInt nNodes = shape.numNodes; // Contract nodal degrees of freedom linearly against element values for (CfemInt i = 0; i < nNodes; ++i) @@ -409,10 +411,10 @@ namespace cfem // Reset output buffer tracking the exact required tensor memory footprint std::fill_n(out.data(), nComp * spatialDimension, 0.0); - const auto &gradsX = mapping.dN_dx; - const auto &gradsY = mapping.dN_dy; - const auto &gradsZ = mapping.dN_dz; - const CfemInt nNodes = static_cast(gradsX.size()); + const auto &gradsX = mapping.dN_dx(); + const auto &gradsY = mapping.dN_dy(); + const auto &gradsZ = mapping.dN_dz(); + const CfemInt nNodes = mapping.numNodes; // static_cast(gradsX.size()); // Loop over element basis functions (nodes) for (CfemInt i = 0; i < nNodes; ++i) @@ -459,8 +461,8 @@ namespace cfem // Gather all local cell coefficients into the memory arena scratchpad matrix this->extractCellDofs(cellId, ctx.scratchCellDofs, ctx); - const CfemInt nQp = mappingBatch.dN_dx.n_rows(); - const CfemInt nNodes = mappingBatch.dN_dx.n_cols(); + const CfemInt nQp = mappingBatch.numQuadraturePoints; + const CfemInt nNodes = mappingBatch.numNodes; const CfemInt fDim = this->getNumComponents(); const CfemInt spatialDimension = m_space->getMesh()->getSpatialDimension(); @@ -472,6 +474,9 @@ namespace cfem for (CfemInt q = 0; q < nQp; ++q) { // Loop over field dimensions (components) first to establish continuous row offsets + const auto* dN_dx = mappingBatch.dN_dx(q); + const auto* dN_dy = mappingBatch.dN_dy(q); + const auto* dN_dz = mappingBatch.dN_dz(q); for (CfemInt d = 0; d < fDim; ++d) { const CfemInt rowOffset = d * spatialDimension; @@ -484,10 +489,10 @@ namespace cfem { const CfemReal u_nodal = ctx.scratchCellDofs[i][d]; - grad_x += mappingBatch.dN_dx[q][i] * u_nodal; - grad_y += mappingBatch.dN_dy[q][i] * u_nodal; + grad_x += dN_dx[i] * u_nodal; + grad_y += dN_dy[i] * u_nodal; if (spatialDimension == 3) { - grad_z += mappingBatch.dN_dz[q][i] * u_nodal; + grad_z += dN_dz[i] * u_nodal; } } diff --git a/libs/fem/fespace/include/DGSpace.h b/libs/fem/fespace/include/DGSpace.h index 0e44ba2..9cf6dfd 100644 --- a/libs/fem/fespace/include/DGSpace.h +++ b/libs/fem/fespace/include/DGSpace.h @@ -71,8 +71,8 @@ class DGSpace : public FESpace { void computeSparsityPattern(std::vector& nnz) const override; void getEntityDofs(CfemInt internalEntityId, std::vector& boundaryDofs) const override; - void evaluateBasis(CfemInt cellId, const Vector3D& xi, std::vector& values) const override; - void evaluateBasisGradients(CfemInt cellId, const Vector3D& xi, std::vector& grads) const override; + void evaluateBasis(CfemInt cellId, const Vector3D& xi, ShapeInfo& shape, std::vector& values) const override; + void evaluateBasisFirstDerivatives(CfemInt cellId, const Vector3D& xi, ShapeInfo& shape, MappingInfo& map) const override; }; } // namespace cfem diff --git a/libs/fem/fespace/include/FESpace.h b/libs/fem/fespace/include/FESpace.h index 7a49348..f20f88f 100644 --- a/libs/fem/fespace/include/FESpace.h +++ b/libs/fem/fespace/include/FESpace.h @@ -146,12 +146,12 @@ class FESpace : public MeshObserver, public std::enable_shared_from_this& values) const = 0; + virtual void evaluateBasis(CfemInt cellId, const Vector3D& xi, ShapeInfo& shape, std::vector& values) const = 0; /** * @brief Evaluates gradients of basis functions (physical or local as needed). */ - virtual void evaluateBasisGradients(CfemInt cellId, const Vector3D& xi, std::vector& grads) const = 0; + virtual void evaluateBasisFirstDerivatives(CfemInt cellId, const Vector3D& xi, ShapeInfo& shape, MappingInfo& map) const = 0; // Support for Assembler and PETSc diff --git a/libs/fem/fespace/include/LagrangeSpace.h b/libs/fem/fespace/include/LagrangeSpace.h index 17b206a..f1e6bad 100644 --- a/libs/fem/fespace/include/LagrangeSpace.h +++ b/libs/fem/fespace/include/LagrangeSpace.h @@ -86,8 +86,8 @@ class LagrangeSpace : public FESpace { CfemInt getGlobalDofFromMeshNode(CfemInt meshNodeId, CfemInt component) const override; // Basis Function Evaluation - void evaluateBasis(CfemInt cellId, const Vector3D& xi, std::vector& values) const override; - void evaluateBasisGradients(CfemInt cellId, const Vector3D& xi, std::vector& grads) const override; + void evaluateBasis(CfemInt cellId, const Vector3D& xi, ShapeInfo& shape, std::vector& values) const override; + void evaluateBasisFirstDerivatives(CfemInt cellId, const Vector3D& xi, ShapeInfo& shape, MappingInfo& map) const override; // Legacy Accessors (Optional, strictly internal to Lagrange) CfemInt getLocalNodeIdx(CfemInt meshNodeId) const { return m_nodeToLocalIdx[meshNodeId]; } diff --git a/libs/fem/fespace/src/DGSpace.cpp b/libs/fem/fespace/src/DGSpace.cpp index a09c41d..3b63fb9 100644 --- a/libs/fem/fespace/src/DGSpace.cpp +++ b/libs/fem/fespace/src/DGSpace.cpp @@ -163,18 +163,18 @@ void DGSpace::computeSparsityPattern(std::vector& nnz) const { * @param xi Reference/Local coordinates inside the cell's geometric space. * @param values Destination vector container to hold the evaluated scalar basis functions. */ - void DGSpace::evaluateBasis(CfemInt cellId, const Vector3D& xi, std::vector& values) const + void DGSpace::evaluateBasis(CfemInt cellId, const Vector3D& xi, ShapeInfo& shape, std::vector& values) const { - ShapeInfo shape; const Cell& cell = m_mesh->getCell(cellId); const ReferenceElement& refEl = ReferenceElementFactory::getReferenceElement(cell.type, getOrderInt()); - // Evaluate reference basis values directly into the ShapeInfo container - shape.values.resize(refEl.getNumNodes()); - refEl.evaluateShapesValues(xi, shape.values.data()); + shape.setup(refEl.getNumNodes(), false); + shape.validFlags = ShapeEvaluationFlags::Value; - // Direct contiguous copy remains valid for scalar values - values.assign(shape.values.begin(), shape.values.end()); + refEl.evaluateShapesValues(xi, shape); + + const CfemReal* ptr = shape.values(); + values.assign(ptr, ptr + shape.numNodes); } /** @@ -183,7 +183,7 @@ void DGSpace::computeSparsityPattern(std::vector& nnz) const { * @param xi Reference/Local coordinates inside the cell's geometric space. * @param grads Destination vector container to pack the calculated physical Vector3D gradients. */ - void DGSpace::evaluateBasisGradients(CfemInt cellId, const Vector3D& xi, std::vector& grads) const + void DGSpace::evaluateBasisFirstDerivatives(CfemInt cellId, const Vector3D& xi, ShapeInfo& shape, MappingInfo& map) const { // Thread-local scratchpad to guarantee OpenMP safety and bypass dynamic allocations static thread_local DynamicVector t_physCoords; @@ -192,9 +192,6 @@ void DGSpace::computeSparsityPattern(std::vector& nnz) const { const Cell& cell = m_mesh->getCell(cellId); const ReferenceElement& refEl = ReferenceElementFactory::getReferenceElement(cell.type, getOrderInt()); - ShapeInfo shape; - MappingInfo map; - refEl.computeMapping( xi, t_physCoords, @@ -204,18 +201,22 @@ void DGSpace::computeSparsityPattern(std::vector& nnz) const { map ); - const CfemInt nNodes = static_cast(map.dN_dx.size()); - const CfemInt spatialDimension = m_mesh->getSpatialDimension(); + // const CfemInt nNodes = map.numNodes; + // const CfemInt spatialDimension = m_mesh->getSpatialDimension(); - // Resize the destination vector to accommodate the layout without memory spikes - grads.resize(nNodes); + // // Resize the destination vector to accommodate the layout without memory spikes + // grads.resize(nNodes); - for (CfemInt i = 0; i < nNodes; ++i) - { - grads[i].x = map.dN_dx[i]; - grads[i].y = (spatialDimension >= 2) ? map.dN_dy[i] : 0.0; - grads[i].z = (spatialDimension == 3) ? map.dN_dz[i] : 0.0; - } + // const auto& dNdx = map.dN_dx(); + // const auto& dNdy = map.dN_dy(); + // const auto& dNdz = map.dN_dz(); + + // for (CfemInt i = 0; i < nNodes; ++i) + // { + // grads[i].x = dNdx[i]; + // grads[i].y = (spatialDimension >= 2) ? dNdy[i] : 0.0; + // grads[i].z = (spatialDimension == 3) ? dNdz[i] : 0.0; + // } } /** diff --git a/libs/fem/fespace/src/LagrangeSpace.cpp b/libs/fem/fespace/src/LagrangeSpace.cpp index 99b6a1b..4c6d560 100644 --- a/libs/fem/fespace/src/LagrangeSpace.cpp +++ b/libs/fem/fespace/src/LagrangeSpace.cpp @@ -249,18 +249,20 @@ void LagrangeSpace::getElementDofs(CfemInt cellId, std::vector& globalI // MATH & EVALUATION -void LagrangeSpace::evaluateBasis(CfemInt cellId, const Vector3D& xi, std::vector& values) const { +void LagrangeSpace::evaluateBasis(CfemInt cellId, const Vector3D& xi, ShapeInfo& shape, std::vector& values) const { const Cell& cell = m_mesh->getCell(cellId); const ReferenceElement& refEl = ReferenceElementFactory::getReferenceElement( cell.type, getOrderInt() ); - ShapeInfo shape; - shape.values.resize(refEl.getNumNodes()); - refEl.evaluateShapesValues(xi, shape.values.data()); + shape.setup(refEl.getNumNodes(), false); + shape.validFlags = ShapeEvaluationFlags::Value; - values.assign(shape.values.begin(), shape.values.end()); + refEl.evaluateShapesValues(xi, shape); + + const CfemReal* ptr = shape.values(); + values.assign(ptr, ptr + shape.numNodes); } -void LagrangeSpace::evaluateBasisGradients(CfemInt cellId, const Vector3D& xi, std::vector& grads) const +void LagrangeSpace::evaluateBasisFirstDerivatives(CfemInt cellId, const Vector3D& xi, ShapeInfo& shape, MappingInfo& map) const { const Cell& cell = m_mesh->getCell(cellId); const ReferenceElement& refEl = ReferenceElementFactory::getReferenceElement(cell.type, getOrderInt()); @@ -269,9 +271,6 @@ void LagrangeSpace::evaluateBasisGradients(CfemInt cellId, const Vector3D& xi, s static thread_local DynamicVector t_physicalCoords; m_mesh->getCellVerticesCoords(cellId, t_physicalCoords); - ShapeInfo shape; - MappingInfo map; - refEl.computeMapping( xi, t_physicalCoords, @@ -281,17 +280,21 @@ void LagrangeSpace::evaluateBasisGradients(CfemInt cellId, const Vector3D& xi, s map ); - const CfemInt nNodes = static_cast(map.dN_dx.size()); - const CfemInt spatialDimension = m_mesh->getSpatialDimension(); + // const CfemInt nNodes = map.numNodes; + // const CfemInt spatialDimension = m_mesh->getSpatialDimension(); - grads.resize(nNodes); + // grads.resize(nNodes); - for (CfemInt i = 0; i < nNodes; ++i) - { - grads[i].x = map.dN_dx[i]; - grads[i].y = (spatialDimension >= 2) ? map.dN_dy[i] : 0.0; - grads[i].z = (spatialDimension == 3) ? map.dN_dz[i] : 0.0; - } + // const auto& dNdx = map.dN_dx(); + // const auto& dNdy = map.dN_dy(); + // const auto& dNdz = map.dN_dz(); + + // for (CfemInt i = 0; i < nNodes; ++i) + // { + // grads[i].x = dNdx[i]; + // grads[i].y = (spatialDimension >= 2) ? dNdy[i] : 0.0; + // grads[i].z = (spatialDimension == 3) ? dNdz[i] : 0.0; + // } } //PETSc & SYSTEM SUPPORT diff --git a/libs/fem/reference_element/include/MappingInfo.h b/libs/fem/reference_element/include/MappingInfo.h index 16450e3..343c801 100644 --- a/libs/fem/reference_element/include/MappingInfo.h +++ b/libs/fem/reference_element/include/MappingInfo.h @@ -12,13 +12,29 @@ namespace cfem { - /** +/** * @struct MappingInfo - * @brief Stores metric data resulting from the geometric transformation (Reference -> Physical). + * @brief Contiguous, cache-aligned storage buffer for physical shape function derivatives and mapping data. + * + * This structure implements a Structure-of-Arrays (SoA) layout to store physical gradients + * and Hessians for a set of quadrature points. + * * ### Performance Design + * - **Contiguous Memory:** Derivatives are stored in a single `DynamicVector` allocation. + * - **Cache Locality:** Optimized for L1/L2 cache prefetching by using padded strides. + * - **Zero Overhead:** Accessors provide lightweight views (raw pointers) to data without + * requiring heavy abstractions in hot loops. */ struct MappingInfo { - + // ==================================================================== + // Paramètres de taille pour le buffer SoA + // ==================================================================== + static constexpr CfemInt NUM_FIRST_ORDER_FIELDS = 3; ///< \f$ \left\{\frac{\partial N}{\partial x}, \frac{\partial N}{\partial y}, \frac{\partial N}{\partial z}\right\} \f$ + static constexpr CfemInt NUM_SECOND_ORDER_FIELDS = 6; ///< \f$ \left\{\frac{\partial^2 N}{\partial x^2}, \frac{\partial^2 N}{\partial y^2}, \frac{\partial^2 N}{\partial z^2}, \frac{\partial^2 N}{\partial x \partial y}, d2N/dxdz, \frac{\partial^2 N}{\partial y \partial z} \right\} \f$ + + // ==================================================================== + // Données d'état et Géométrie Locale (Small Tensors / Scalars) + // ==================================================================== CfemInt numNodes = 0; MappingEvaluationFlags validFlags = MappingEvaluationFlags::None; @@ -28,118 +44,226 @@ namespace cfem CfemReal detJ = 0.0; Vector3D normal; - DynamicVector dN_dx; ///< First order derivatives with respect to \$ x \$ - DynamicVector dN_dy; ///< First order derivatives with respect to \$ y \$ - DynamicVector dN_dz; ///< First order derivatives with respect to \$ z \$ - - DynamicVector d2N_dx2; ///< Second order derivatives with respect to \$ x \$ - DynamicVector d2N_dy2; ///< Second order derivatives with respect to \$ y \$ - DynamicVector d2N_dz2; ///< Second order derivatives with respect to \$ z \$ - - DynamicVector d2N_dxdy; ///< Second order derivatives with respect to \$ x and y \$ - DynamicVector d2N_dxdz; ///< Second order derivatives with respect to \$ x and z \$ - DynamicVector d2N_dydz; ///< Second order derivatives with respect to \$ y and z \$ - CfemReal metric1D = 0.0; SymTensor2D metric2D; - void setup(CfemInt nNodes, bool includeSecondDerivatives = false) + // ==================================================================== + // Stockage Contigu HPC pour les Dérivées Physiques Nodales + // ==================================================================== + DynamicVector storage; ///< Centralized contiguous buffer + CfemInt stride = 0; ///< Padded stride to ensure SIMD alignment + bool hasSecondDerivatives = false; + + /** + * @brief Allocates and configures the internal storage layout. + */ + inline void setup(CfemInt nNodes, bool includeSecondDerivatives = false) { numNodes = nNodes; + hasSecondDerivatives = includeSecondDerivatives; + stride = computePaddedStride(numNodes); - if (dN_dx.size() != static_cast(numNodes)) - { - dN_dx.resize(numNodes); - dN_dy.resize(numNodes); - dN_dz.resize(numNodes); - } + const CfemInt nFields = NUM_FIRST_ORDER_FIELDS + + (includeSecondDerivatives ? NUM_SECOND_ORDER_FIELDS : 0); - if (includeSecondDerivatives && d2N_dx2.size() != static_cast(numNodes)) - { - d2N_dx2.resize(numNodes); - d2N_dy2.resize(numNodes); - d2N_dz2.resize(numNodes); + const size_t totalSize = static_cast(nFields) * static_cast(stride); - d2N_dxdy.resize(numNodes); - d2N_dxdz.resize(numNodes); - d2N_dydz.resize(numNodes); + if (storage.size() != totalSize) + { + storage.resize(totalSize); } validFlags = MappingEvaluationFlags::None; } + inline void invalidate() + { + validFlags = MappingEvaluationFlags::None; + } + + // ================================================================================================ + // SHAPES FIRST DERIVATIVES (Physical Space) + // ================================================================================================ + + [[nodiscard]] inline CfemReal* dN_dx() { + CFEM_ASSERT(hasFlag(validFlags, MappingEvaluationFlags::PhysicalFirstDerivatives), "Physical first derivatives not computed?"); + return storage.data() + 0 * stride; + } + + [[nodiscard]] inline CfemReal* dN_dy() { + CFEM_ASSERT(hasFlag(validFlags, MappingEvaluationFlags::PhysicalFirstDerivatives), "Physical first derivatives not computed?"); + return storage.data() + 1 * stride; + } + + [[nodiscard]] inline CfemReal* dN_dz() { + CFEM_ASSERT(hasFlag(validFlags, MappingEvaluationFlags::PhysicalFirstDerivatives), "Physical first derivatives not computed?"); + return storage.data() + 2 * stride; + } + + // Versions const + [[nodiscard]] inline const CfemReal* dN_dx() const { + CFEM_ASSERT(hasFlag(validFlags, MappingEvaluationFlags::PhysicalFirstDerivatives), "Physical first derivatives not computed?"); + return storage.data() + 0 * stride; + } + + [[nodiscard]] inline const CfemReal* dN_dy() const { + CFEM_ASSERT(hasFlag(validFlags, MappingEvaluationFlags::PhysicalFirstDerivatives), "Physical first derivatives not computed?"); + return storage.data() + 1 * stride; + } + + [[nodiscard]] inline const CfemReal* dN_dz() const { + CFEM_ASSERT(hasFlag(validFlags, MappingEvaluationFlags::PhysicalFirstDerivatives), "Physical first derivatives not computed?"); + return storage.data() + 2 * stride; + } + + // ================================================================================================ + // SHAPES SECOND DERIVATIVES (Physical Space) + // ================================================================================================ + + [[nodiscard]] inline CfemReal* d2N_dx2() { + CFEM_ASSERT(hasSecondDerivatives, "Second derivatives not allocated?"); + return storage.data() + 3 * stride; + } + + [[nodiscard]] inline CfemReal* d2N_dy2() { + CFEM_ASSERT(hasSecondDerivatives, "Second derivatives not allocated?"); + return storage.data() + 4 * stride; + } + + [[nodiscard]] inline CfemReal* d2N_dz2() { + CFEM_ASSERT(hasSecondDerivatives, "Second derivatives not allocated?"); + return storage.data() + 5 * stride; + } + + [[nodiscard]] inline CfemReal* d2N_dxdy() { + CFEM_ASSERT(hasSecondDerivatives, "Second derivatives not allocated?"); + return storage.data() + 6 * stride; + } + + [[nodiscard]] inline CfemReal* d2N_dxdz() { + CFEM_ASSERT(hasSecondDerivatives, "Second derivatives not allocated?"); + return storage.data() + 7 * stride; + } + + [[nodiscard]] inline CfemReal* d2N_dydz() { + CFEM_ASSERT(hasSecondDerivatives, "Second derivatives not allocated?"); + return storage.data() + 8 * stride; + } + + // Versions const + [[nodiscard]] inline const CfemReal* d2N_dx2() const { + CFEM_ASSERT(hasSecondDerivatives, "Second derivatives not allocated?"); + return storage.data() + 3 * stride; + } + + [[nodiscard]] inline const CfemReal* d2N_dy2() const { + CFEM_ASSERT(hasSecondDerivatives, "Second derivatives not allocated?"); + return storage.data() + 4 * stride; + } + + [[nodiscard]] inline const CfemReal* d2N_dz2() const { + CFEM_ASSERT(hasSecondDerivatives, "Second derivatives not allocated?"); + return storage.data() + 5 * stride; + } + + [[nodiscard]] inline const CfemReal* d2N_dxdy() const { + CFEM_ASSERT(hasSecondDerivatives, "Second derivatives not allocated?"); + return storage.data() + 6 * stride; + } + + [[nodiscard]] inline const CfemReal* d2N_dxdz() const { + CFEM_ASSERT(hasSecondDerivatives, "Second derivatives not allocated?"); + return storage.data() + 7 * stride; + } + + [[nodiscard]] inline const CfemReal* d2N_dydz() const { + CFEM_ASSERT(hasSecondDerivatives, "Second derivatives not allocated?"); + return storage.data() + 8 * stride; + } + + // ============================================================ + // Getters + // ============================================================ + inline Vector3D gradient(CfemInt node) const { CFEM_ASSERT(node >= 0 && node < numNodes); - return Vector3D(dN_dx[node], dN_dy[node], dN_dz[node]); + return Vector3D(dN_dx()[node], dN_dy()[node], dN_dz()[node]); } inline SymTensor3D hessian(CfemInt node) const { - // CFEM_INFO << "Hiiiiiiiiiiiiiiiiiiiiiii"; - // CFEM_INFO << "Size = " << d2N_dx2.size(); CFEM_ASSERT(node >= 0 && node < numNodes); - - return SymTensor3D(d2N_dx2[node], d2N_dy2[node], d2N_dz2[node], - d2N_dydz[node], d2N_dxdz[node], d2N_dxdy[node]); + CFEM_ASSERT(hasSecondDerivatives); + return SymTensor3D(d2N_dx2()[node], d2N_dy2()[node], d2N_dz2()[node], + d2N_dydz()[node], d2N_dxdz()[node], d2N_dxdy()[node]); } - inline void invalidate() - { - validFlags = MappingEvaluationFlags::None; - } + // ============================================================ + // Calcul (Optimisé HPC) + // ============================================================ void computePhysicalFirstDerivativesOnly(const ShapeInfo &fieldShape) { const CfemInt n = fieldShape.numNodes; CFEM_ASSERT(n == numNodes); - const CfemReal i00 = invJacobian(0, 0); - const CfemReal i01 = invJacobian(0, 1); - const CfemReal i02 = invJacobian(0, 2); + // Hoisting (mise en cache des variables invariantes de la boucle) + const CfemReal i00 = invJacobian.xx(); + const CfemReal i01 = invJacobian.xy(); + const CfemReal i02 = invJacobian.xz(); - const CfemReal i10 = invJacobian(1, 0); - const CfemReal i11 = invJacobian(1, 1); - const CfemReal i12 = invJacobian(1, 2); + const CfemReal i10 = invJacobian.yx(); + const CfemReal i11 = invJacobian.yy(); + const CfemReal i12 = invJacobian.yz(); - const CfemReal i20 = invJacobian(2, 0); - const CfemReal i21 = invJacobian(2, 1); - const CfemReal i22 = invJacobian(2, 2); + const CfemReal i20 = invJacobian.zx(); + const CfemReal i21 = invJacobian.zy(); + const CfemReal i22 = invJacobian.zz(); - const CfemReal *dxi = fieldShape.dN_dxi.data(); - const CfemReal *deta = fieldShape.dN_deta.data(); - const CfemReal *dzeta = fieldShape.dN_dzeta.data(); - // CFEM_INFO << "inv jacobian"; - // CFEM_INFO << invJacobian; + // Pointeurs en lecture (restrict = promesse de non-aliasing au compilateur) + const CfemReal* __restrict dxi = fieldShape.dN_dxi(); + const CfemReal* __restrict deta = fieldShape.dN_deta(); + const CfemReal* __restrict dzeta = fieldShape.dN_dzeta(); - // CFEM_INFO << "Printing de dN_dzeta"; - // for (CfemInt l = 0; l < fieldShape.dN_dzeta.size(); ++l) - // CFEM_INFO << fieldShape.dN_dzeta[l] << " "; - - CfemReal *dx = dN_dx.data(); - CfemReal *dy = dN_dy.data(); - CfemReal *dz = dN_dz.data(); + // L'astuce : On contourne temporairement l'ASSERT de validFlags pour l'écriture + // (car ils ne seront valides qu'à la fin de cette fonction) + CfemReal* __restrict dx = storage.data() + 0 * stride; + CfemReal* __restrict dy = storage.data() + 1 * stride; + CfemReal* __restrict dz = storage.data() + 2 * stride; + // Boucle principale vectorisable par le compilateur for (CfemInt i = 0; i < n; ++i) { dx[i] = i00 * dxi[i] + i10 * deta[i] + i20 * dzeta[i]; dy[i] = i01 * dxi[i] + i11 * deta[i] + i21 * dzeta[i]; dz[i] = i02 * dxi[i] + i12 * deta[i] + i22 * dzeta[i]; } + + validFlags |= MappingEvaluationFlags::PhysicalFirstDerivatives; } }; /** * @struct MappingInfoBatch * @brief Stores metric data resulting from the geometric transformation for a batch of points. + * + * HPC Design: Nodal derivatives for ALL quadrature points and ALL fields are flattened + * into a single `storage` buffer. This guarantees perfect SIMD vectorization and eliminates + * multiple allocations and `DynamicMatrix` overhead. */ struct MappingInfoBatch { + static constexpr CfemInt NUM_FIRST_ORDER_FIELDS = 3; ///< {dN_dx, dN_dy, dN_dz} + static constexpr CfemInt NUM_SECOND_ORDER_FIELDS = 6; ///< {d2N_dx2, d2N_dy2, d2N_dz2, d2N_dxdy, d2N_dxdz, d2N_dydz} + CfemInt numQuadraturePoints = 0; CfemInt numNodes = 0; MappingEvaluationFlags validFlags = MappingEvaluationFlags::None; // --- Global quantities per quadrature point [qp] --- + // Ces éléments restent dans leurs propres DynamicVector car ils sont + // par point de quadrature (1 scalaire/tenseur par QP), ils sont donc déjà contigus. DynamicVector physicalPositions; DynamicVector jacobians; DynamicVector invJacobians; @@ -149,24 +273,19 @@ namespace cfem DynamicVector metric1Ds; DynamicVector metric2Ds; - // --- Nodal quantities (SoA) [qp][node] --- - DynamicMatrix dN_dx; - DynamicMatrix dN_dy; - DynamicMatrix dN_dz; - - DynamicMatrix d2N_dx2; - DynamicMatrix d2N_dy2; - DynamicMatrix d2N_dz2; - DynamicMatrix d2N_dxdy; - DynamicMatrix d2N_dxdz; - DynamicMatrix d2N_dydz; - + // --- Stockage Contigu HPC pour les Dérivées Physiques Nodales --- + DynamicVector storage; ///< Centralized contiguous buffer for all QPs and all fields + CfemInt paddedNodeStride = 0; ///< Stride per field (padded to SIMD boundary) + CfemInt qpStride = 0; ///< Stride per quadrature point (all fields combined) + bool hasSecondDerivatives = false; void setup(CfemInt nQp, CfemInt nNodes, CfemInt dim, bool includeSecondDerivatives = false) { numQuadraturePoints = nQp; numNodes = nNodes; + hasSecondDerivatives = includeSecondDerivatives; + // Resize des quantités globales par QP if (physicalPositions.size() != static_cast(nQp)) { physicalPositions.resize(nQp); jacobians.resize(nQp); @@ -182,49 +301,168 @@ namespace cfem } } - if (dN_dx.size() != nQp * nNodes) { - dN_dx.resize(nQp, nNodes); - dN_dy.resize(nQp, nNodes); - dN_dz.resize(nQp, nNodes); - } + // Calcul des strides et de la taille totale du buffer + paddedNodeStride = computePaddedStride(numNodes); + const CfemInt nFields = NUM_FIRST_ORDER_FIELDS + + (includeSecondDerivatives ? NUM_SECOND_ORDER_FIELDS : 0); + + qpStride = nFields * paddedNodeStride; + const size_t totalStorageSize = static_cast(nQp) * static_cast(qpStride); - if (includeSecondDerivatives && d2N_dx2.size() != nQp * nNodes) { - d2N_dx2.resize(nQp, nNodes); - d2N_dy2.resize(nQp, nNodes); - d2N_dz2.resize(nQp, nNodes); - - d2N_dxdy.resize(nQp, nNodes); - d2N_dxdz.resize(nQp, nNodes); - d2N_dydz.resize(nQp, nNodes); + if (storage.size() != totalStorageSize) { + storage.resize(totalStorageSize); } validFlags = MappingEvaluationFlags::None; } + inline void invalidate() { + validFlags = MappingEvaluationFlags::None; + } + + // ================================================================================================ + // VUES LÉGÈRES (Lightweight Views) : Renvoient un pointeur vers le début du champ pour le QP 'q' + // ================================================================================================ + + [[nodiscard]] inline CfemReal* dN_dx(CfemInt q) { + CFEM_ASSERT(q >= 0 && q < numQuadraturePoints, "Invalid QP index"); + CFEM_ASSERT(hasFlag(validFlags, MappingEvaluationFlags::PhysicalFirstDerivatives), "Derivatives not computed"); + return storage.data() + q * qpStride + 0 * paddedNodeStride; + } + + [[nodiscard]] inline CfemReal* dN_dy(CfemInt q) { + CFEM_ASSERT(q >= 0 && q < numQuadraturePoints, "Invalid QP index"); + CFEM_ASSERT(hasFlag(validFlags, MappingEvaluationFlags::PhysicalFirstDerivatives), "Derivatives not computed"); + return storage.data() + q * qpStride + 1 * paddedNodeStride; + } + + [[nodiscard]] inline CfemReal* dN_dz(CfemInt q) { + CFEM_ASSERT(q >= 0 && q < numQuadraturePoints, "Invalid QP index"); + CFEM_ASSERT(hasFlag(validFlags, MappingEvaluationFlags::PhysicalFirstDerivatives), "Derivatives not computed"); + return storage.data() + q * qpStride + 2 * paddedNodeStride; + } + + [[nodiscard]] inline CfemReal* d2N_dx2(CfemInt q) { + CFEM_ASSERT(q >= 0 && q < numQuadraturePoints, "Invalid QP index"); + CFEM_ASSERT(hasFlag(validFlags, MappingEvaluationFlags::PhysicalSecondDerivatives), "Second derivatives not allocated"); + return storage.data() + q * qpStride + 3 * paddedNodeStride; + } + + [[nodiscard]] inline CfemReal* d2N_dy2(CfemInt q) { + CFEM_ASSERT(q >= 0 && q < numQuadraturePoints, "Invalid QP index"); + CFEM_ASSERT(hasFlag(validFlags, MappingEvaluationFlags::PhysicalSecondDerivatives), "Second derivatives not allocated"); + return storage.data() + q * qpStride + 4 * paddedNodeStride; + } + + [[nodiscard]] inline CfemReal* d2N_dz2(CfemInt q) { + CFEM_ASSERT(q >= 0 && q < numQuadraturePoints, "Invalid QP index"); + CFEM_ASSERT(hasFlag(validFlags, MappingEvaluationFlags::PhysicalSecondDerivatives), "Second derivatives not allocated"); + return storage.data() + q * qpStride + 5 * paddedNodeStride; + } + + [[nodiscard]] inline CfemReal* d2N_dxdy(CfemInt q) { + CFEM_ASSERT(q >= 0 && q < numQuadraturePoints, "Invalid QP index"); + CFEM_ASSERT(hasFlag(validFlags, MappingEvaluationFlags::PhysicalSecondDerivatives), "Second derivatives not allocated"); + return storage.data() + q * qpStride + 6 * paddedNodeStride; + } + + [[nodiscard]] inline CfemReal* d2N_dxdz(CfemInt q) { + CFEM_ASSERT(q >= 0 && q < numQuadraturePoints, "Invalid QP index"); + CFEM_ASSERT(hasFlag(validFlags, MappingEvaluationFlags::PhysicalSecondDerivatives), "Second derivatives not allocated"); + return storage.data() + q * qpStride + 7 * paddedNodeStride; + } + + [[nodiscard]] inline CfemReal* d2N_dydz(CfemInt q) { + CFEM_ASSERT(q >= 0 && q < numQuadraturePoints, "Invalid QP index"); + CFEM_ASSERT(hasFlag(validFlags, MappingEvaluationFlags::PhysicalSecondDerivatives), "Second derivatives not allocated"); + return storage.data() + q * qpStride + 8 * paddedNodeStride; + } + + // const versions + + [[nodiscard]] inline const CfemReal* dN_dx(CfemInt q) const{ + CFEM_ASSERT(q >= 0 && q < numQuadraturePoints, "Invalid QP index"); + CFEM_ASSERT(hasFlag(validFlags, MappingEvaluationFlags::PhysicalFirstDerivatives), "First derivatives not computed"); + return storage.data() + q * qpStride + 0 * paddedNodeStride; + } + + [[nodiscard]] inline const CfemReal* dN_dy(CfemInt q) const{ + CFEM_ASSERT(q >= 0 && q < numQuadraturePoints, "Invalid QP index"); + CFEM_ASSERT(hasFlag(validFlags, MappingEvaluationFlags::PhysicalFirstDerivatives), "First derivatives not computed"); + return storage.data() + q * qpStride + 1 * paddedNodeStride; + } + + [[nodiscard]] inline const CfemReal* dN_dz(CfemInt q) const{ + CFEM_ASSERT(q >= 0 && q < numQuadraturePoints, "Invalid QP index"); + CFEM_ASSERT(hasFlag(validFlags, MappingEvaluationFlags::PhysicalFirstDerivatives), "First derivatives not computed"); + return storage.data() + q * qpStride + 2 * paddedNodeStride; + } + + [[nodiscard]] inline const CfemReal* d2N_dx2(CfemInt q) const{ + CFEM_ASSERT(q >= 0 && q < numQuadraturePoints, "Invalid QP index"); + CFEM_ASSERT(hasFlag(validFlags, MappingEvaluationFlags::PhysicalSecondDerivatives), "Second derivatives not allocated"); + return storage.data() + q * qpStride + 3 * paddedNodeStride; + } + + [[nodiscard]] inline const CfemReal* d2N_dy2(CfemInt q) const{ + CFEM_ASSERT(q >= 0 && q < numQuadraturePoints, "Invalid QP index"); + CFEM_ASSERT(hasFlag(validFlags, MappingEvaluationFlags::PhysicalSecondDerivatives), "Second derivatives not allocated"); + return storage.data() + q * qpStride + 4 * paddedNodeStride; + } + + [[nodiscard]] inline const CfemReal* d2N_dz2(CfemInt q) const{ + CFEM_ASSERT(q >= 0 && q < numQuadraturePoints, "Invalid QP index"); + CFEM_ASSERT(hasFlag(validFlags, MappingEvaluationFlags::PhysicalSecondDerivatives), "Second derivatives not allocated"); + return storage.data() + q * qpStride + 5 * paddedNodeStride; + } + + [[nodiscard]] inline const CfemReal* d2N_dxdy(CfemInt q) const{ + CFEM_ASSERT(q >= 0 && q < numQuadraturePoints, "Invalid QP index"); + CFEM_ASSERT(hasFlag(validFlags, MappingEvaluationFlags::PhysicalSecondDerivatives), "Second derivatives not allocated"); + return storage.data() + q * qpStride + 6 * paddedNodeStride; + } + + [[nodiscard]] inline const CfemReal* d2N_dxdz(CfemInt q) const{ + CFEM_ASSERT(q >= 0 && q < numQuadraturePoints, "Invalid QP index"); + CFEM_ASSERT(hasFlag(validFlags, MappingEvaluationFlags::PhysicalSecondDerivatives), "Second derivatives not allocated"); + return storage.data() + q * qpStride + 7 * paddedNodeStride; + } + + [[nodiscard]] inline const CfemReal* d2N_dydz(CfemInt q) const{ + CFEM_ASSERT(q >= 0 && q < numQuadraturePoints, "Invalid QP index"); + CFEM_ASSERT(hasFlag(validFlags, MappingEvaluationFlags::PhysicalSecondDerivatives), "Second derivatives not allocated"); + return storage.data() + q * qpStride + 8 * paddedNodeStride; + } + + // ============================================================ + // Getters + // ============================================================ + inline Vector3D gradient(CfemInt q, CfemInt node) const { - CFEM_ASSERT(q >= 0 && q < numQuadraturePoints); - CFEM_ASSERT(node >= 0 && node < numNodes); - return Vector3D(dN_dx(q, node), dN_dy(q, node), dN_dz(q, node)); + CFEM_ASSERT(node >= 0 && node < numNodes, "Invalid node index"); + CFEM_ASSERT(hasFlag(validFlags, MappingEvaluationFlags::PhysicalFirstDerivatives), "Physical first derivative not computed?"); + + return Vector3D( dN_dx(q)[node], dN_dy(q)[node], dN_dz(q)[node] ); } inline SymTensor3D hessian(CfemInt q, CfemInt node) const { - CFEM_ASSERT(q >= 0 && q < numQuadraturePoints); - CFEM_ASSERT(node >= 0 && node < numNodes); - return SymTensor3D(d2N_dx2(q, node), d2N_dy2(q, node), d2N_dz2(q, node), - d2N_dydz(q, node), d2N_dxdz(q, node), d2N_dxdy(q, node)); + CFEM_ASSERT(node >= 0 && node < numNodes, "Invalid node index"); + CFEM_ASSERT(hasFlag(validFlags, MappingEvaluationFlags::PhysicalSecondDerivatives), "Physical second derivative not computed?"); + return SymTensor3D(d2N_dx2(q)[node], d2N_dy2(q)[node], d2N_dz2(q)[node], + d2N_dydz(q)[node], d2N_dxdz(q)[node], d2N_dxdy(q)[node]); } - inline void invalidate() { - validFlags = MappingEvaluationFlags::None; - } + // ============================================================ + // Calcul (Optimisé HPC) + // ============================================================ /** * @brief Computes physical gradients for the entire batch. * @note Hyper-optimized utilizing SIMD friendly contiguous memory rows. */ void computePhysicalFirstDerivativesOnly(const ShapeInfoBatch& fieldShape) { - CFEM_ASSERT(fieldShape.numQuadraturePoints == numQuadraturePoints); - CFEM_ASSERT(fieldShape.numNodes == numNodes); + CFEM_ASSERT(fieldShape.numQuadraturePoints == numQuadraturePoints, "Mismatch QP"); + CFEM_ASSERT(fieldShape.numNodes == numNodes, "Mismatch Nodes"); for (CfemInt q = 0; q < numQuadraturePoints; ++q) { @@ -233,15 +471,17 @@ namespace cfem const CfemReal i10 = invJacobians[q].data[Tensor3D::idx_yx], i11 = invJacobians[q].data[Tensor3D::idx_yy], i12 = invJacobians[q].data[Tensor3D::idx_yz]; const CfemReal i20 = invJacobians[q].data[Tensor3D::idx_zx], i21 = invJacobians[q].data[Tensor3D::idx_zy], i22 = invJacobians[q].data[Tensor3D::idx_zz]; - // Extract contiguous raw pointers for the current row (quadrature point 'q') - // This utilizes the std::span returned by DynamicMatrix::operator[] - const CfemReal* dxi = fieldShape.dN_dxi[q].data(); - const CfemReal* deta = fieldShape.dN_deta[q].data(); - const CfemReal* dzeta = fieldShape.dN_dzeta[q].data(); + // Remarque : Si vous avez appliqué la même logique à ShapeInfoBatch, + // ceci devrait être `fieldShape.dN_dxi(q);`. + // Si ShapeInfoBatch utilise encore l'ancienne méthode (DynamicMatrix), laissez `.data()`. + const CfemReal* __restrict dxi = fieldShape.dN_dxi(q); + const CfemReal* __restrict deta = fieldShape.dN_deta(q); + const CfemReal* __restrict dzeta = fieldShape.dN_dzeta(q); - CfemReal* dx = dN_dx[q].data(); - CfemReal* dy = dN_dy[q].data(); - CfemReal* dz = dN_dz[q].data(); + // Extraction des pointeurs en écriture (on contourne les ASSERT temporairement) + CfemReal* __restrict dx = storage.data() + q * qpStride + 0 * paddedNodeStride; + CfemReal* __restrict dy = storage.data() + q * qpStride + 1 * paddedNodeStride; + CfemReal* __restrict dz = storage.data() + q * qpStride + 2 * paddedNodeStride; // Inner loop over nodes: perfectly contiguous, ripe for auto-vectorization for (CfemInt i = 0; i < numNodes; ++i) { @@ -250,6 +490,9 @@ namespace cfem dz[i] = i02 * dxi[i] + i12 * deta[i] + i22 * dzeta[i]; } } + + // Marquer comme valide une fois la boucle de calcul terminée + validFlags |= MappingEvaluationFlags::PhysicalFirstDerivatives; } }; diff --git a/libs/fem/reference_element/include/ReferenceElement.h b/libs/fem/reference_element/include/ReferenceElement.h index 708f904..954c225 100644 --- a/libs/fem/reference_element/include/ReferenceElement.h +++ b/libs/fem/reference_element/include/ReferenceElement.h @@ -68,11 +68,9 @@ class ReferenceElement { * @param xi The coordinates of the evaluation point in the reference element ($\xi, \eta, \zeta$). * @param values Raw pointer to a pre-allocated array of size [numNodes] to be populated. */ - virtual void evaluateShapesValues(const Vector3D &xi, - CfemReal* values) const = 0; - + virtual void evaluateShapesValues(const Vector3D& xi, ShapeInfo& info) const = 0; virtual void evaluateShapesValuesBatch(const DynamicVector &xi, - DynamicMatrix &values) const = 0; + ShapeInfoBatch& shapeBatch) const = 0; /** * @brief Evaluates the first-order derivatives of the shape functions (local gradients) @@ -82,43 +80,19 @@ class ReferenceElement { * @param dN_deta Raw pointer to the reference coordinate $\eta$ derivative array (size [numNodes]). * @param dN_dzeta Raw pointer to the reference coordinate $\zeta$ derivative array (size [numNodes]). */ - virtual void evaluateShapesDerivatives(const Vector3D &xi, - CfemReal* dN_dxi, - CfemReal* dN_deta, - CfemReal* dN_dzeta) const = 0; - + virtual void evaluateShapesDerivatives(const Vector3D& xi, ShapeInfo& info) const = 0; virtual void evaluateShapesDerivativesBatch(const DynamicVector &xi, - DynamicMatrix &dN_dxi, - DynamicMatrix &dN_deta, - DynamicMatrix &dN_dzeta) const = 0; + ShapeInfoBatch& shapeBatch) const = 0; /** * @brief Evaluates the second-order derivatives of the shape functions (local Hessians) * with respect to the reference coordinate system. * - * @param xi The coordinates of the evaluation point in the reference element ($\xi, \eta, \zeta$). - * @param d2N_dxi2 Raw pointer to the $\frac{\partial^2 N}{\partial \xi^2}$ array (size [numNodes]). - * @param d2N_deta2 Raw pointer to the $\frac{\partial^2 N}{\partial \eta^2}$ array (size [numNodes]). - * @param d2N_dzeta2 Raw pointer to the $\frac{\partial^2 N}{\partial \zeta^2}$ array (size [numNodes]). - * @param d2N_dxideta Raw pointer to the $\frac{\partial^2 N}{\partial \xi \partial \eta}$ array (size [numNodes]). - * @param d2N_dxidzeta Raw pointer to the $\frac{\partial^2 N}{\partial \xi \partial \zeta}$ array (size [numNodes]). - * @param d2N_detadzeta Raw pointer to the $\frac{\partial^2 N}{\partial \eta \partial \zeta}$ array (size [numNodes]). + * @param info The ShapeInfo object containing buffers to be filled. */ - virtual void evaluateShapesSecondDerivatives(const Vector3D &xi, - CfemReal* d2N_dxi2, - CfemReal* d2N_deta2, - CfemReal* d2N_dzeta2, - CfemReal* d2N_dxideta, - CfemReal* d2N_dxidzeta, - CfemReal* d2N_detadzeta) const = 0; - + virtual void evaluateShapesSecondDerivatives(const Vector3D& xi, ShapeInfo& info) const = 0; virtual void evaluateShapesSecondDerivativesBatch(const DynamicVector &xi, - DynamicMatrix &d2N_dxi2, - DynamicMatrix &d2N_deta2, - DynamicMatrix &d2N_dzeta2, - DynamicMatrix &d2N_dxideta, - DynamicMatrix &d2N_dxidzeta, - DynamicMatrix &d2N_detadzeta) const = 0; + ShapeInfoBatch& shapeBatch) const = 0; virtual Vector3D projectFacetRefCoordToCellRefCoord(CfemInt localFacetId, const Vector3D& xiFacet) const = 0; /** diff --git a/libs/fem/reference_element/include/ReferenceElement.tpp b/libs/fem/reference_element/include/ReferenceElement.tpp index bc4d1bf..2367272 100644 --- a/libs/fem/reference_element/include/ReferenceElement.tpp +++ b/libs/fem/reference_element/include/ReferenceElement.tpp @@ -25,17 +25,17 @@ namespace cfem shape.setup(m_numNodes, includeSecondDerivatives); if (hasFlag(requestedShape, ShapeEvaluationFlags::Value)) { - evaluateShapesValues(xi, shape.values.data()); + shape.validFlags |= ShapeEvaluationFlags::Value; + evaluateShapesValues(xi, shape); } if (hasFlag(requestedShape, ShapeEvaluationFlags::FirstDerivatives)) { - evaluateShapesDerivatives(xi, shape.dN_dxi.data(), shape.dN_deta.data(), shape.dN_dzeta.data()); + shape.validFlags |= ShapeEvaluationFlags::FirstDerivatives; + evaluateShapesDerivatives(xi, shape); } if (includeSecondDerivatives) { - evaluateShapesSecondDerivatives(xi, - shape.d2N_dxi2.data(), shape.d2N_deta2.data(), shape.d2N_dzeta2.data(), - shape.d2N_dxideta.data(), shape.d2N_dxidzeta.data(), shape.d2N_detadzeta.data()); + shape.validFlags |= ShapeEvaluationFlags::SecondDerivatives; + evaluateShapesSecondDerivatives(xi, shape); } - shape.validFlags = requestedShape; } @@ -46,18 +46,13 @@ namespace cfem shapeBatch.setup(nQp, m_numNodes, includeSecondDerivatives); if (hasFlag(requestedShape, ShapeEvaluationFlags::Value)) { - evaluateShapesValuesBatch(xiPoints, shapeBatch.values); + evaluateShapesValuesBatch(xiPoints, shapeBatch); } if (hasFlag(requestedShape, ShapeEvaluationFlags::FirstDerivatives)) { - evaluateShapesDerivativesBatch(xiPoints, - shapeBatch.dN_dxi, - shapeBatch.dN_deta, - shapeBatch.dN_dzeta); + evaluateShapesDerivativesBatch(xiPoints, shapeBatch); } if (includeSecondDerivatives) { - evaluateShapesSecondDerivativesBatch(xiPoints, - shapeBatch.d2N_dxi2, shapeBatch.d2N_deta2, shapeBatch.d2N_dzeta2, - shapeBatch.d2N_dxideta, shapeBatch.d2N_dxidzeta, shapeBatch.d2N_detadzeta); + evaluateShapesSecondDerivativesBatch(xiPoints, shapeBatch); } shapeBatch.validFlags = requestedShape; @@ -93,7 +88,7 @@ namespace cfem if (hasFlag(requestedMapping, MappingEvaluationFlags::PhysicalPosition)) { CfemReal px = 0.0, py = 0.0, pz = 0.0; - const CfemReal* N = shape.values.data(); + const CfemReal* N = shape.values(); for (CfemInt i = 0; i < m_numNodes; ++i) { const Vector3D& pos = physicalNodesCoords[i]; const CfemReal ni = N[i]; @@ -105,26 +100,32 @@ namespace cfem } if (hasFlag(requestedMapping, MappingEvaluationFlags::Jacobian)) { + out.validFlags |= MappingEvaluationFlags::Jacobian; computeJacobian(shape, physicalNodesCoords, out); } if (hasFlag(requestedMapping, MappingEvaluationFlags::Determinant)) { + out.validFlags |= MappingEvaluationFlags::Determinant; computeDeterminant(shape, physicalNodesCoords, out); } if (hasFlag(requestedMapping, MappingEvaluationFlags::InverseJacobian)) { + out.validFlags |= MappingEvaluationFlags::InverseJacobian; computeInverseJacobian(shape, physicalNodesCoords, out); } if (hasFlag(requestedMapping, MappingEvaluationFlags::PhysicalFirstDerivatives)) { + out.validFlags |= MappingEvaluationFlags::PhysicalFirstDerivatives; computePhysicalFirstDerivatives(shape, out); } if (hasFlag(requestedMapping, MappingEvaluationFlags::PhysicalSecondDerivatives)) { + out.validFlags |= MappingEvaluationFlags::PhysicalSecondDerivatives; computePhysicalSecondDerivatives(physicalNodesCoords, shape, out); } if (hasFlag(requestedMapping, MappingEvaluationFlags::Normal)){ + out.validFlags |= MappingEvaluationFlags::Normal; computeGeometricNormal(shape, out); } @@ -166,7 +167,7 @@ namespace cfem for (CfemInt q = 0; q < nQp; ++q) { CfemReal px = 0.0, py = 0.0, pz = 0.0; - const CfemReal* N = shapeBatch.values[q].data(); + const CfemReal* N = shapeBatch.values(q); for (CfemInt i = 0; i < m_numNodes; ++i) { const Vector3D& pos = physicalNodesCoords[i]; @@ -218,9 +219,9 @@ namespace cfem CfemReal j20 = 0.0, j21 = 0.0, j22 = 0.0; // Pointeurs SoA pour ce point de quadrature précis - const CfemReal* dxi = shape.dN_dxi.data(); - const CfemReal* deta = shape.dN_deta.data(); - const CfemReal* dzeta = shape.dN_dzeta.data(); + const CfemReal* dxi = shape.dN_dxi(); + const CfemReal* deta = shape.dN_deta(); + const CfemReal* dzeta = shape.dN_dzeta(); for (CfemInt i = 0; i < n; ++i) { @@ -250,9 +251,9 @@ namespace cfem CfemReal j20 = 0.0, j21 = 0.0, j22 = 0.0; // Pointeurs SoA pour ce point de quadrature précis - const CfemReal* dxi = shape.dN_dxi[q].data(); - const CfemReal* deta = shape.dN_deta[q].data(); - const CfemReal* dzeta = shape.dN_dzeta[q].data(); + const CfemReal* dxi = shape.dN_dxi(q); + const CfemReal* deta = shape.dN_deta(q); + const CfemReal* dzeta = shape.dN_dzeta(q); for (CfemInt i = 0; i < n; ++i) { @@ -505,14 +506,14 @@ namespace cfem const CfemReal i20 = out.invJacobian.zx(), i21 = out.invJacobian.zy(), i22 = out.invJacobian.zz(); // Pointeurs SoA (lecture) - const CfemReal* __restrict dxi = shape.dN_dxi.data(); - const CfemReal* __restrict deta = shape.dN_deta.data(); - const CfemReal* __restrict dzeta = shape.dN_dzeta.data(); + const CfemReal* __restrict dxi = shape.dN_dxi(); + const CfemReal* __restrict deta = shape.dN_deta(); + const CfemReal* __restrict dzeta = shape.dN_dzeta(); // Pointeurs SoA (écriture) - CfemReal* __restrict dx = out.dN_dx.data(); - CfemReal* __restrict dy = out.dN_dy.data(); - CfemReal* __restrict dz = out.dN_dz.data(); + CfemReal* __restrict dx = out.dN_dx(); + CfemReal* __restrict dy = out.dN_dy(); + CfemReal* __restrict dz = out.dN_dz(); // Boucle SIMD ultra-rapide for (CfemInt a = 0; a < n; ++a) @@ -541,14 +542,14 @@ namespace cfem const CfemReal i20 = out.invJacobians[q].zx(), i21 = out.invJacobians[q].zy(), i22 = out.invJacobians[q].zz(); // Pointeurs SoA vers les lignes de la matrice de référence (Lecture) - const CfemReal* __restrict dxi = shape.dN_dxi[q].data(); - const CfemReal* __restrict deta = shape.dN_deta[q].data(); - const CfemReal* __restrict dzeta = shape.dN_dzeta[q].data(); + const CfemReal* __restrict dxi = shape.dN_dxi(q); + const CfemReal* __restrict deta = shape.dN_deta(q); + const CfemReal* __restrict dzeta = shape.dN_dzeta(q); // Pointeurs SoA vers les lignes de la matrice physique (Écriture) - CfemReal* __restrict dx = out.dN_dx[q].data(); - CfemReal* __restrict dy = out.dN_dy[q].data(); - CfemReal* __restrict dz = out.dN_dz[q].data(); + CfemReal* __restrict dx = out.dN_dx(q); + CfemReal* __restrict dy = out.dN_dy(q); + CfemReal* __restrict dz = out.dN_dz(q); // Boucle sur les nœuds interne : vectorisation SIMD maximale for (CfemInt a = 0; a < n; ++a) @@ -576,12 +577,12 @@ namespace cfem CfemReal hZ_xx = 0, hZ_yy = 0, hZ_zz = 0, hZ_xy = 0, hZ_xz = 0, hZ_yz = 0; // Pointeurs SoA pour les Hessiens de référence - const CfemReal* __restrict d2xi2 = shape.d2N_dxi2.data(); - const CfemReal* __restrict d2eta2 = shape.d2N_deta2.data(); - const CfemReal* __restrict d2zeta2 = shape.d2N_dzeta2.data(); - const CfemReal* __restrict d2xideta = shape.d2N_dxideta.data(); - const CfemReal* __restrict d2xidzeta = shape.d2N_dxidzeta.data(); - const CfemReal* __restrict d2etadzeta = shape.d2N_detadzeta.data(); + const CfemReal* __restrict d2xi2 = shape.d2N_dxi2(); + const CfemReal* __restrict d2eta2 = shape.d2N_deta2(); + const CfemReal* __restrict d2zeta2 = shape.d2N_dzeta2(); + const CfemReal* __restrict d2xideta = shape.d2N_dxideta(); + const CfemReal* __restrict d2xidzeta = shape.d2N_dxidzeta(); + const CfemReal* __restrict d2etadzeta = shape.d2N_detadzeta(); for (CfemInt a = 0; a < n; ++a) { @@ -610,17 +611,17 @@ namespace cfem const CfemReal i20 = out.invJacobian.zx(), i21 = out.invJacobian.zy(), i22 = out.invJacobian.zz(); // Pointeurs SoA pour la lecture des gradients physiques - const CfemReal* __restrict dNdx = out.dN_dx.data(); - const CfemReal* __restrict dNdy = out.dN_dy.data(); - const CfemReal* __restrict dNdz = out.dN_dz.data(); + const CfemReal* __restrict dNdx = out.dN_dx(); + const CfemReal* __restrict dNdy = out.dN_dy(); + const CfemReal* __restrict dNdz = out.dN_dz(); // Pointeurs SoA pour l'écriture des Hessiens physiques - CfemReal* __restrict d2x2 = out.d2N_dx2.data(); - CfemReal* __restrict d2y2 = out.d2N_dy2.data(); - CfemReal* __restrict d2z2 = out.d2N_dz2.data(); - CfemReal* __restrict dxdy = out.d2N_dxdy.data(); - CfemReal* __restrict dxdz = out.d2N_dxdz.data(); - CfemReal* __restrict dydz = out.d2N_dydz.data(); + CfemReal* __restrict d2x2 = out.d2N_dx2(); + CfemReal* __restrict d2y2 = out.d2N_dy2(); + CfemReal* __restrict d2z2 = out.d2N_dz2(); + CfemReal* __restrict dxdy = out.d2N_dxdy(); + CfemReal* __restrict dxdz = out.d2N_dxdz(); + CfemReal* __restrict dydz = out.d2N_dydz(); // --- Étape 2 : Matrice R et Transformation vers l'espace physique --- for (CfemInt a = 0; a < n; ++a) @@ -674,6 +675,10 @@ namespace cfem const ShapeInfoBatch &shape, MappingInfoBatch &out) const { + CFEM_ASSERT(hasFlag(shape.validFlags, ShapeEvaluationFlags::SecondDerivatives), "Second derivative not computed."); + CFEM_ASSERT(hasFlag(out.validFlags, MappingEvaluationFlags::InverseJacobian), "Inverse Jacobian not computed."); + CFEM_ASSERT(hasFlag(out.validFlags, MappingEvaluationFlags::PhysicalFirstDerivatives), "Physical First Derivatives not computed."); + const CfemInt nQp = shape.numQuadraturePoints; const CfemInt n = shape.numNodes; @@ -684,12 +689,12 @@ namespace cfem CfemReal hY_xx = 0, hY_yy = 0, hY_zz = 0, hY_xy = 0, hY_xz = 0, hY_yz = 0; CfemReal hZ_xx = 0, hZ_yy = 0, hZ_zz = 0, hZ_xy = 0, hZ_xz = 0, hZ_yz = 0; - const CfemReal* __restrict d2xi2 = shape.d2N_dxi2[q].data(); - const CfemReal* __restrict d2eta2 = shape.d2N_deta2[q].data(); - const CfemReal* __restrict d2zeta2 = shape.d2N_dzeta2[q].data(); - const CfemReal* __restrict d2xideta = shape.d2N_dxideta[q].data(); - const CfemReal* __restrict d2xidzeta = shape.d2N_dxidzeta[q].data(); - const CfemReal* __restrict d2etadzeta = shape.d2N_detadzeta[q].data(); + const CfemReal* __restrict d2xi2 = shape.d2N_dxi2(q); + const CfemReal* __restrict d2eta2 = shape.d2N_deta2(q); + const CfemReal* __restrict d2zeta2 = shape.d2N_dzeta2(q); + const CfemReal* __restrict d2xideta = shape.d2N_dxideta(q); + const CfemReal* __restrict d2xidzeta = shape.d2N_dxidzeta(q); + const CfemReal* __restrict d2etadzeta = shape.d2N_detadzeta(q); for (CfemInt a = 0; a < n; ++a) { @@ -717,21 +722,21 @@ namespace cfem // ========================================================= // PRÉ-CHARGEMENT : Extraction dans les registres locaux // ========================================================= - const CfemReal i00 = invJ(0,0), i01 = invJ(0,1), i02 = invJ(0,2); - const CfemReal i10 = invJ(1,0), i11 = invJ(1,1), i12 = invJ(1,2); - const CfemReal i20 = invJ(2,0), i21 = invJ(2,1), i22 = invJ(2,2); + const CfemReal i00 = invJ.xx(), i01 = invJ.xy(), i02 = invJ.xz(); + const CfemReal i10 = invJ.yx(), i11 = invJ.yy(), i12 = invJ.yz(); + const CfemReal i20 = invJ.zx(), i21 = invJ.zy(), i22 = invJ.zz(); // Recouvrement des pointeurs de lignes physiques (Lecture des gradients, Écriture des Hessiens) - const CfemReal* __restrict dNdx = out.dN_dx[q].data(); - const CfemReal* __restrict dNdy = out.dN_dy[q].data(); - const CfemReal* __restrict dNdz = out.dN_dz[q].data(); + const CfemReal* __restrict dNdx = out.dN_dx(q); + const CfemReal* __restrict dNdy = out.dN_dy(q); + const CfemReal* __restrict dNdz = out.dN_dz(q); - CfemReal* __restrict d2x2 = out.d2N_dx2[q].data(); - CfemReal* __restrict d2y2 = out.d2N_dy2[q].data(); - CfemReal* __restrict d2z2 = out.d2N_dz2[q].data(); - CfemReal* __restrict dxdy = out.d2N_dxdy[q].data(); - CfemReal* __restrict dxdz = out.d2N_dxdz[q].data(); - CfemReal* __restrict dydz = out.d2N_dydz[q].data(); + CfemReal* __restrict d2x2 = out.d2N_dx2(q); + CfemReal* __restrict d2y2 = out.d2N_dy2(q); + CfemReal* __restrict d2z2 = out.d2N_dz2(q); + CfemReal* __restrict dxdy = out.d2N_dxdy(q); + CfemReal* __restrict dxdz = out.d2N_dxdz(q); + CfemReal* __restrict dydz = out.d2N_dydz(q); // Étape 2 : Calcul de R et application de la congruence pour chaque nœud for (CfemInt a = 0; a < n; ++a) @@ -867,7 +872,7 @@ namespace cfem CFEM_ASSERT(nodalValues.size() == n, "Nodal values size mismatch"); T result{}; - const CfemReal* N = shape.values.data(); + const CfemReal* N = shape.values(); for (CfemInt i = 0; i < n; ++i){ result += nodalValues[i] * N[i]; @@ -889,9 +894,9 @@ namespace cfem CfemReal gradZ = 0.0; // Pointeurs vers la mémoire SoA - const CfemReal* dx = map.dN_dx.data(); - const CfemReal* dy = map.dN_dy.data(); - const CfemReal* dz = map.dN_dz.data(); + const CfemReal* dx = map.dN_dx(); + const CfemReal* dy = map.dN_dy(); + const CfemReal* dz = map.dN_dz(); for (CfemInt i = 0; i < n; ++i) { const CfemReal u = nodalValues[i]; @@ -914,12 +919,12 @@ namespace cfem CfemReal h11 = 0.0, h22 = 0.0, h33 = 0.0; CfemReal h12 = 0.0, h13 = 0.0, h23 = 0.0; - const CfemReal* d2x = map.d2N_dx2.data(); - const CfemReal* d2y = map.d2N_dy2.data(); - const CfemReal* d2z = map.d2N_dz2.data(); - const CfemReal* dxdy = map.d2N_dxdy.data(); - const CfemReal* dxdz = map.d2N_dxdz.data(); - const CfemReal* dydz = map.d2N_dydz.data(); + const CfemReal* d2x = map.d2N_dx2(); + const CfemReal* d2y = map.d2N_dy2(); + const CfemReal* d2z = map.d2N_dz2(); + const CfemReal* dxdy = map.d2N_dxdy(); + const CfemReal* dxdz = map.d2N_dxdz(); + const CfemReal* dydz = map.d2N_dydz(); for (CfemInt i = 0; i < n; ++i) { diff --git a/libs/fem/reference_element/include/ReferenceHexahedron.h b/libs/fem/reference_element/include/ReferenceHexahedron.h index 9bf4855..d2b12a3 100644 --- a/libs/fem/reference_element/include/ReferenceHexahedron.h +++ b/libs/fem/reference_element/include/ReferenceHexahedron.h @@ -20,6 +20,8 @@ #define CFEM_REFERENCE_HEXAHEDRON #include "ReferenceElement.h" +#include +#include namespace cfem::reference_elem::utils { @@ -50,812 +52,599 @@ namespace cfem::reference_elem::utils return Vector3D{}; // Make the compiler happy } - static const std::vector> hex8_face_nodes = { + static constexpr std::array, 6> hex8_face_nodes = {{ {0, 3, 2, 1}, // Face 0 (Bottom) {4, 5, 6, 7}, // Face 1 (Top) {0, 1, 5, 4}, // Face 2 (Front) {1, 2, 6, 5}, // Face 3 (Right) {2, 3, 7, 6}, // Face 4 (Back) {3, 0, 4, 7} // Face 5 (Left) - }; + }}; - static const std::vector> hex27_face_nodes = { + static constexpr std::array, 6> hex27_face_nodes = {{ {0, 3, 2, 1, 11, 10, 9, 8, 24}, // Face 0 {4, 5, 6, 7, 12, 13, 14, 15, 25}, // Face 1 {0, 1, 5, 4, 8, 17, 12, 16, 22}, // Face 2 {1, 2, 6, 5, 9, 18, 13, 17, 21}, // Face 3 {2, 3, 7, 6, 10, 19, 14, 18, 23}, // Face 4 {3, 0, 4, 7, 11, 16, 15, 19, 20} // Face 5 - }; + }}; - static const std::vector> hex1_face_nodes = { - {0}, {0}, {0}, {0}, {0}, {0}}; + static constexpr std::array, 6> hex1_face_nodes = {{ + {0}, {0}, {0}, {0}, {0}, {0} + }}; } -namespace cfem -{ +namespace cfem::reference_elem::kernels { + // ========================================================================= + // Q0 HEX KERNEL (1 node, constant) + // ========================================================================= + struct Q0HexaKernel { + [[gnu::always_inline]] static inline void values(CfemReal* __restrict N) noexcept { + N[0] = 1.0; + } + + [[gnu::always_inline]] static inline void grads(CfemReal* __restrict dx, + CfemReal* __restrict dy, + CfemReal* __restrict dz) noexcept { + dx[0] = dy[0] = dz[0] = 0.0; + } + + [[gnu::always_inline]] static inline void hess(CfemReal* __restrict d2x, CfemReal* __restrict d2y, CfemReal* __restrict d2z, + CfemReal* __restrict dxy, CfemReal* __restrict dxz, CfemReal* __restrict dyz) noexcept { + d2x[0] = d2y[0] = d2z[0] = dxy[0] = dxz[0] = dyz[0] = 0.0; + } + }; + + // ========================================================================= + // MINI HEX KERNEL (9 nodes : 8 vertices Q1 + 1 bubble) + // ========================================================================= + struct MiniHexaKernel { + private: + static constexpr CfemReal xr[8] = {-1.0, 1.0, 1.0, -1.0, -1.0, 1.0, 1.0, -1.0}; + static constexpr CfemReal ys[8] = {-1.0, -1.0, 1.0, 1.0, -1.0, -1.0, 1.0, 1.0}; + static constexpr CfemReal zt[8] = {-1.0, -1.0,-1.0, -1.0, 1.0, 1.0, 1.0, 1.0}; + static constexpr CfemReal o8 = 0.125; - class Q1ReferenceHexahedron : public ReferenceElement - { public: - Q1ReferenceHexahedron() + [[gnu::always_inline]] static inline void values(CfemReal x, CfemReal y, CfemReal z, + CfemReal* __restrict N) noexcept { - m_numNodes = 8; - m_dimension = 3; - m_nodes.resize(m_numNodes); - // Ordre VTK : Face Z=-1 (0,1,2,3), puis Face Z=1 (4,5,6,7) - m_nodes[0] = {-1, -1, -1}; - m_nodes[1] = {1, -1, -1}; - m_nodes[2] = {1, 1, -1}; - m_nodes[3] = {-1, 1, -1}; - m_nodes[4] = {-1, -1, 1}; - m_nodes[5] = {1, -1, 1}; - m_nodes[6] = {1, 1, 1}; - m_nodes[7] = {-1, 1, 1}; - } - - void evaluateShapesValues(const Vector3D &xi, CfemReal *values) const override - { - const CfemReal xm = 1.0 - xi.x; - const CfemReal xp = 1.0 + xi.x; - const CfemReal ym = 1.0 - xi.y; - const CfemReal yp = 1.0 + xi.y; - const CfemReal zm = 1.0 - xi.z; - const CfemReal zp = 1.0 + xi.z; - - constexpr CfemReal o8 = 0.125; // 1.0 / 8.0 + const CfemReal b = (1.0 - x * x) * (1.0 - y * y) * (1.0 - z * z); + N[8] = b; - values[0] = o8 * xm * ym * zm; - values[1] = o8 * xp * ym * zm; - values[2] = o8 * xp * yp * zm; - values[3] = o8 * xm * yp * zm; - values[4] = o8 * xm * ym * zp; - values[5] = o8 * xp * ym * zp; - values[6] = o8 * xp * yp * zp; - values[7] = o8 * xm * yp * zp; + const CfemReal correction = b * o8; + for (CfemInt i = 0; i < 8; ++i) { + const CfemReal px = 1.0 + xr[i] * x; + const CfemReal py = 1.0 + ys[i] * y; + const CfemReal pz = 1.0 + zt[i] * z; + N[i] = (o8 * px * py * pz) - correction; + } } - void evaluateShapesDerivatives(const Vector3D &xi, - CfemReal *dN_dxi, - CfemReal *dN_deta, - CfemReal *dN_dzeta) const override + [[gnu::always_inline]] static inline void grads(CfemReal x, CfemReal y, CfemReal z, + CfemReal* __restrict dx, + CfemReal* __restrict dy, + CfemReal* __restrict dz) noexcept { - const CfemReal xm = 1.0 - xi.x; - const CfemReal xp = 1.0 + xi.x; - const CfemReal ym = 1.0 - xi.y; - const CfemReal yp = 1.0 + xi.y; - const CfemReal zm = 1.0 - xi.z; - const CfemReal zp = 1.0 + xi.z; + const CfemReal mx2 = 1.0 - x * x; + const CfemReal my2 = 1.0 - y * y; + const CfemReal mz2 = 1.0 - z * z; - constexpr CfemReal o8 = 0.125; + const CfemReal db_dx = -2.0 * x * my2 * mz2; + const CfemReal db_dy = -2.0 * y * mx2 * mz2; + const CfemReal db_dz = -2.0 * z * mx2 * my2; - // First derivatives with respect to xi (x) - dN_dxi[0] = -o8 * ym * zm; - dN_dxi[1] = o8 * ym * zm; - dN_dxi[2] = o8 * yp * zm; - dN_dxi[3] = -o8 * yp * zm; - dN_dxi[4] = -o8 * ym * zp; - dN_dxi[5] = o8 * ym * zp; - dN_dxi[6] = o8 * yp * zp; - dN_dxi[7] = -o8 * yp * zp; - - // First derivatives with respect to eta (y) - dN_deta[0] = -o8 * xm * zm; - dN_deta[1] = -o8 * xp * zm; - dN_deta[2] = o8 * xp * zm; - dN_deta[3] = o8 * xm * zm; - dN_deta[4] = -o8 * xm * zp; - dN_deta[5] = -o8 * xp * zp; - dN_deta[6] = o8 * xp * zp; - dN_deta[7] = o8 * xm * zp; - - // First derivatives with respect to zeta (z) - dN_dzeta[0] = -o8 * xm * ym; - dN_dzeta[1] = -o8 * xp * ym; - dN_dzeta[2] = -o8 * xp * yp; - dN_dzeta[3] = -o8 * xm * yp; - dN_dzeta[4] = o8 * xm * ym; - dN_dzeta[5] = o8 * xp * ym; - dN_dzeta[6] = o8 * xp * yp; - dN_dzeta[7] = o8 * xm * yp; - } - - void evaluateShapesSecondDerivatives(const Vector3D &xi, - CfemReal *d2N_dxi2, - CfemReal *d2N_deta2, - CfemReal *d2N_dzeta2, - CfemReal *d2N_dxideta, - CfemReal *d2N_dxidzeta, - CfemReal *d2N_detadzeta) const override - { - const CfemReal xm = 1.0 - xi.x; - const CfemReal xp = 1.0 + xi.x; - const CfemReal ym = 1.0 - xi.y; - const CfemReal yp = 1.0 + xi.y; - const CfemReal zm = 1.0 - xi.z; - const CfemReal zp = 1.0 + xi.z; + dx[8] = db_dx; dy[8] = db_dy; dz[8] = db_dz; - constexpr CfemReal o8 = 0.125; + const CfemReal corr_dx = db_dx * o8; + const CfemReal corr_dy = db_dy * o8; + const CfemReal corr_dz = db_dz * o8; - // Pure second derivatives are strictly zero for trilinear fields - for (CfemInt i = 0; i < 8; ++i) - { - d2N_dxi2[i] = 0.0; - d2N_deta2[i] = 0.0; - d2N_dzeta2[i] = 0.0; + for (CfemInt i = 0; i < 8; ++i) { + const CfemReal px = 1.0 + xr[i] * x; + const CfemReal py = 1.0 + ys[i] * y; + const CfemReal pz = 1.0 + zt[i] * z; + + dx[i] = (o8 * xr[i] * py * pz) - corr_dx; + dy[i] = (o8 * ys[i] * px * pz) - corr_dy; + dz[i] = (o8 * zt[i] * px * py) - corr_dz; } + } - // Cross derivatives: d2N / (dxi * deta) - d2N_dxideta[0] = o8 * zm; - d2N_dxideta[1] = -o8 * zm; - d2N_dxideta[2] = o8 * zm; - d2N_dxideta[3] = -o8 * zm; - d2N_dxideta[4] = o8 * zp; - d2N_dxideta[5] = -o8 * zp; - d2N_dxideta[6] = o8 * zp; - d2N_dxideta[7] = -o8 * zp; - - // Cross derivatives: d2N / (dxi * dzeta) - d2N_dxidzeta[0] = o8 * ym; - d2N_dxidzeta[1] = -o8 * ym; - d2N_dxidzeta[2] = -o8 * yp; - d2N_dxidzeta[3] = o8 * yp; - d2N_dxidzeta[4] = -o8 * ym; - d2N_dxidzeta[5] = o8 * ym; - d2N_dxidzeta[6] = o8 * yp; - d2N_dxidzeta[7] = -o8 * yp; - - // Cross derivatives: d2N / (deta * dzeta) - d2N_detadzeta[0] = o8 * xm; - d2N_detadzeta[1] = o8 * xp; - d2N_detadzeta[2] = -o8 * xp; - d2N_detadzeta[3] = -o8 * xm; - d2N_detadzeta[4] = -o8 * xm; - d2N_detadzeta[5] = -o8 * xp; - d2N_detadzeta[6] = o8 * xp; - d2N_detadzeta[7] = o8 * xm; - } - - void evaluateShapesValuesBatch(const DynamicVector &xiPoints, - DynamicMatrix &valuesBatch) const override + [[gnu::always_inline]] static inline void hess(CfemReal x, CfemReal y, CfemReal z, + CfemReal* __restrict d2x, CfemReal* __restrict d2y, CfemReal* __restrict d2z, + CfemReal* __restrict dxy, CfemReal* __restrict dxz, CfemReal* __restrict dyz) noexcept { - constexpr CfemReal o8 = 0.125; + const CfemReal mx2 = 1.0 - x * x; + const CfemReal my2 = 1.0 - y * y; + const CfemReal mz2 = 1.0 - z * z; - const CfemInt nQp = static_cast(xiPoints.size()); - for (CfemInt q = 0; q < nQp; ++q) - { - const CfemReal xm = 1.0 - xiPoints[q].x; - const CfemReal xp = 1.0 + xiPoints[q].x; - const CfemReal ym = 1.0 - xiPoints[q].y; - const CfemReal yp = 1.0 + xiPoints[q].y; - const CfemReal zm = 1.0 - xiPoints[q].z; - const CfemReal zp = 1.0 + xiPoints[q].z; - - valuesBatch[q][0] = o8 * xm * ym * zm; - valuesBatch[q][1] = o8 * xp * ym * zm; - valuesBatch[q][2] = o8 * xp * yp * zm; - valuesBatch[q][3] = o8 * xm * yp * zm; - valuesBatch[q][4] = o8 * xm * ym * zp; - valuesBatch[q][5] = o8 * xp * ym * zp; - valuesBatch[q][6] = o8 * xp * yp * zp; - valuesBatch[q][7] = o8 * xm * yp * zp; + const CfemReal d2b_dx2 = -2.0 * my2 * mz2; + const CfemReal d2b_dy2 = -2.0 * mx2 * mz2; + const CfemReal d2b_dz2 = -2.0 * mx2 * my2; + const CfemReal d2b_dxy = 4.0 * x * y * mz2; + const CfemReal d2b_dxz = 4.0 * x * z * my2; + const CfemReal d2b_dyz = 4.0 * y * z * mx2; + + d2x[8] = d2b_dx2; d2y[8] = d2b_dy2; d2z[8] = d2b_dz2; + dxy[8] = d2b_dxy; dxz[8] = d2b_dxz; dyz[8] = d2b_dyz; + + const CfemReal corr_dx2 = d2b_dx2 * o8; + const CfemReal corr_dy2 = d2b_dy2 * o8; + const CfemReal corr_dz2 = d2b_dz2 * o8; + const CfemReal corr_dxy = d2b_dxy * o8; + const CfemReal corr_dxz = d2b_dxz * o8; + const CfemReal corr_dyz = d2b_dyz * o8; + + for (CfemInt i = 0; i < 8; ++i) { + const CfemReal px = 1.0 + xr[i] * x; + const CfemReal py = 1.0 + ys[i] * y; + const CfemReal pz = 1.0 + zt[i] * z; + + d2x[i] = -corr_dx2; // Pure Q1 second derivatives are 0 + d2y[i] = -corr_dy2; + d2z[i] = -corr_dz2; + dxy[i] = (o8 * xr[i] * ys[i] * pz) - corr_dxy; + dxz[i] = (o8 * xr[i] * py * zt[i]) - corr_dxz; + dyz[i] = (o8 * px * ys[i] * zt[i]) - corr_dyz; } } + }; - void evaluateShapesDerivativesBatch(const DynamicVector &xiPoints, - DynamicMatrix &dN_dxi, - DynamicMatrix &dN_deta, - DynamicMatrix &dN_dzeta) const override + // ========================================================================= + // Q1 HEX KERNEL (8 nodes, trilinear) + // ========================================================================= + struct Q1HexaKernel { + [[gnu::always_inline]] static inline void values(CfemReal x, CfemReal y, CfemReal z, + CfemReal* __restrict N) noexcept { - constexpr CfemReal o8 = 0.125; + const CfemReal xm = 1.0 - x; const CfemReal xp = 1.0 + x; + const CfemReal ym = 1.0 - y; const CfemReal yp = 1.0 + y; + const CfemReal zm = 1.0 - z; const CfemReal zp = 1.0 + z; - const CfemInt nQp = static_cast(xiPoints.size()); - for (CfemInt q = 0; q < nQp; ++q) - { - const Vector3D &xi = xiPoints[q]; - const CfemReal xm = 1.0 - xi.x; - const CfemReal xp = 1.0 + xi.x; - const CfemReal ym = 1.0 - xi.y; - const CfemReal yp = 1.0 + xi.y; - const CfemReal zm = 1.0 - xi.z; - const CfemReal zp = 1.0 + xi.z; - - // d/dxi - dN_dxi[q][0] = -o8 * ym * zm; - dN_dxi[q][1] = o8 * ym * zm; - dN_dxi[q][2] = o8 * yp * zm; - dN_dxi[q][3] = -o8 * yp * zm; - dN_dxi[q][4] = -o8 * ym * zp; - dN_dxi[q][5] = o8 * ym * zp; - dN_dxi[q][6] = o8 * yp * zp; - dN_dxi[q][7] = -o8 * yp * zp; - - // d/deta - dN_deta[q][0] = -o8 * xm * zm; - dN_deta[q][1] = -o8 * xp * zm; - dN_deta[q][2] = o8 * xp * zm; - dN_deta[q][3] = o8 * xm * zm; - dN_deta[q][4] = -o8 * xm * zp; - dN_deta[q][5] = -o8 * xp * zp; - dN_deta[q][6] = o8 * xp * zp; - dN_deta[q][7] = o8 * xm * zp; - - // d/dzeta - dN_dzeta[q][0] = -o8 * xm * ym; - dN_dzeta[q][1] = -o8 * xp * ym; - dN_dzeta[q][2] = -o8 * xp * yp; - dN_dzeta[q][3] = -o8 * xm * yp; - dN_dzeta[q][4] = o8 * xm * ym; - dN_dzeta[q][5] = o8 * xp * ym; - dN_dzeta[q][6] = o8 * xp * yp; - dN_dzeta[q][7] = o8 * xm * yp; - } + constexpr CfemReal o8 = 0.125; // 1.0 / 8.0 + + N[0] = o8 * xm * ym * zm; + N[1] = o8 * xp * ym * zm; + N[2] = o8 * xp * yp * zm; + N[3] = o8 * xm * yp * zm; + N[4] = o8 * xm * ym * zp; + N[5] = o8 * xp * ym * zp; + N[6] = o8 * xp * yp * zp; + N[7] = o8 * xm * yp * zp; } - void evaluateShapesSecondDerivativesBatch(const DynamicVector &xiPoints, - DynamicMatrix &d2N_dxi2, - DynamicMatrix &d2N_deta2, - DynamicMatrix &d2N_dzeta2, - DynamicMatrix &d2N_dxideta, - DynamicMatrix &d2N_dxidzeta, - DynamicMatrix &d2N_detadzeta) const override + [[gnu::always_inline]] static inline void grads(CfemReal x, CfemReal y, CfemReal z, + CfemReal* __restrict dx, + CfemReal* __restrict dy, + CfemReal* __restrict dz) noexcept { + const CfemReal xm = 1.0 - x; const CfemReal xp = 1.0 + x; + const CfemReal ym = 1.0 - y; const CfemReal yp = 1.0 + y; + const CfemReal zm = 1.0 - z; const CfemReal zp = 1.0 + z; + constexpr CfemReal o8 = 0.125; - const CfemInt nQp = static_cast(xiPoints.size()); - for (CfemInt q = 0; q < nQp; ++q) - { - const Vector3D &xi = xiPoints[q]; - const CfemReal xm = 1.0 - xi.x; - const CfemReal xp = 1.0 + xi.x; - const CfemReal ym = 1.0 - xi.y; - const CfemReal yp = 1.0 + xi.y; - const CfemReal zm = 1.0 - xi.z; - const CfemReal zp = 1.0 + xi.z; - - for (CfemInt i = 0; i < 8; ++i) - { - d2N_dxi2[q][i] = 0.0; - d2N_deta2[q][i] = 0.0; - d2N_dzeta2[q][i] = 0.0; - } + // d / dx + dx[0] = -o8 * ym * zm; dx[1] = o8 * ym * zm; + dx[2] = o8 * yp * zm; dx[3] = -o8 * yp * zm; + dx[4] = -o8 * ym * zp; dx[5] = o8 * ym * zp; + dx[6] = o8 * yp * zp; dx[7] = -o8 * yp * zp; - // d2N / (dxi * deta) - d2N_dxideta[q][0] = o8 * zm; - d2N_dxideta[q][1] = -o8 * zm; - d2N_dxideta[q][2] = o8 * zm; - d2N_dxideta[q][3] = -o8 * zm; - d2N_dxideta[q][4] = o8 * zp; - d2N_dxideta[q][5] = -o8 * zp; - d2N_dxideta[q][6] = o8 * zp; - d2N_dxideta[q][7] = -o8 * zp; - - // d2N / (dxi * dzeta) - d2N_dxidzeta[q][0] = o8 * ym; - d2N_dxidzeta[q][1] = -o8 * ym; - d2N_dxidzeta[q][2] = -o8 * yp; - d2N_dxidzeta[q][3] = o8 * yp; - d2N_dxidzeta[q][4] = -o8 * ym; - d2N_dxidzeta[q][5] = o8 * ym; - d2N_dxidzeta[q][6] = o8 * yp; - d2N_dxidzeta[q][7] = -o8 * yp; - - // d2N / (deta * dzeta) - d2N_detadzeta[q][0] = o8 * xm; - d2N_detadzeta[q][1] = o8 * xp; - d2N_detadzeta[q][2] = -o8 * xp; - d2N_detadzeta[q][3] = -o8 * xm; - d2N_detadzeta[q][4] = -o8 * xm; - d2N_detadzeta[q][5] = -o8 * xp; - d2N_detadzeta[q][6] = o8 * xp; - d2N_detadzeta[q][7] = o8 * xm; - } - } + // d / dy + dy[0] = -o8 * xm * zm; dy[1] = -o8 * xp * zm; + dy[2] = o8 * xp * zm; dy[3] = o8 * xm * zm; + dy[4] = -o8 * xm * zp; dy[5] = -o8 * xp * zp; + dy[6] = o8 * xp * zp; dy[7] = o8 * xm * zp; - Vector3D projectFacetRefCoordToCellRefCoord(CfemInt localFacetId, const Vector3D &xiFacet) const override - { - return cfem::reference_elem::utils::projectFacetRefCoordToHexaRefCoord(localFacetId, xiFacet); // Make the compiler happy + // d / dz + dz[0] = -o8 * xm * ym; dz[1] = -o8 * xp * ym; + dz[2] = -o8 * xp * yp; dz[3] = -o8 * xm * yp; + dz[4] = o8 * xm * ym; dz[5] = o8 * xp * ym; + dz[6] = o8 * xp * yp; dz[7] = o8 * xm * yp; } - std::span getFaceNodes(CfemInt iFace) const override + [[gnu::always_inline]] static inline void hess(CfemReal x, CfemReal y, CfemReal z, + CfemReal* __restrict d2x, CfemReal* __restrict d2y, CfemReal* __restrict d2z, + CfemReal* __restrict dxy, CfemReal* __restrict dxz, CfemReal* __restrict dyz) noexcept { - return cfem::reference_elem::utils::hex8_face_nodes[iFace]; + const CfemReal xm = 1.0 - x; const CfemReal xp = 1.0 + x; + const CfemReal ym = 1.0 - y; const CfemReal yp = 1.0 + y; + const CfemReal zm = 1.0 - z; const CfemReal zp = 1.0 + z; + + constexpr CfemReal o8 = 0.125; + + // Pure second derivatives are strictly zero for trilinear fields + for (CfemInt i = 0; i < 8; ++i) { + d2x[i] = 0.0; + d2y[i] = 0.0; + d2z[i] = 0.0; + } + + // Cross derivatives: d2N / (dx * dy) + dxy[0] = o8 * zm; dxy[1] = -o8 * zm; dxy[2] = o8 * zm; dxy[3] = -o8 * zm; + dxy[4] = o8 * zp; dxy[5] = -o8 * zp; dxy[6] = o8 * zp; dxy[7] = -o8 * zp; + + // Cross derivatives: d2N / (dx * dz) + dxz[0] = o8 * ym; dxz[1] = -o8 * ym; dxz[2] = -o8 * yp; dxz[3] = o8 * yp; + dxz[4] = -o8 * ym; dxz[5] = o8 * ym; dxz[6] = o8 * yp; dxz[7] = -o8 * yp; + + // Cross derivatives: d2N / (dy * dz) + dyz[0] = o8 * xm; dyz[1] = o8 * xp; dyz[2] = -o8 * xp; dyz[3] = -o8 * xm; + dyz[4] = -o8 * xm; dyz[5] = -o8 * xp; dyz[6] = o8 * xp; dyz[7] = o8 * xm; } }; - class Q2ReferenceHexahedron : public ReferenceElement - { + // ========================================================================= + // Q2 HEX KERNEL (27 nodes, tri-quadratic) + // ========================================================================= + struct Q2HexaKernel { + public: + // i (X) parcourt : 0 (-1), 1 (0), 2 (+1) + // j (Y) parcourt : 0 (-1), 1 (0), 2 (+1) + // k (Z) parcourt : 0 (-1), 1 (0), 2 (+1) + static constexpr CfemInt node_map[3][3][3] = { + { {0, 16, 4}, {11, 20, 15}, {3, 19, 7} }, + { {8, 22, 12}, {24, 26, 25}, {10, 23, 14} }, + { {1, 17, 5}, {9, 21, 13}, {2, 18, 6} } + }; + private: - // N[0] correspond à -1.0 | N[1] correspond à 0.0 | N[2] correspond à +1.0 - static inline void eval1D_shape(CfemReal u, CfemReal *N) - { + static inline void eval1D_shape(CfemReal u, CfemReal *N) noexcept { const CfemReal u2 = u * u; N[0] = 0.5 * (u2 - u); N[1] = 1.0 - u2; N[2] = 0.5 * (u2 + u); } - static inline void eval1D_deriv(CfemReal u, CfemReal *N, CfemReal *dN) - { + static inline void eval1D_deriv(CfemReal u, CfemReal *N, CfemReal *dN) noexcept { const CfemReal u2 = u * u; - N[0] = 0.5 * (u2 - u); - dN[0] = u - 0.5; - N[1] = 1.0 - u2; - dN[1] = -2.0 * u; - N[2] = 0.5 * (u2 + u); - dN[2] = u + 0.5; + N[0] = 0.5 * (u2 - u); dN[0] = u - 0.5; + N[1] = 1.0 - u2; dN[1] = -2.0 * u; + N[2] = 0.5 * (u2 + u); dN[2] = u + 0.5; } - static inline void eval1D_hessian(CfemReal u, CfemReal *N, CfemReal *dN, CfemReal *d2N) - { + static inline void eval1D_hessian(CfemReal u, CfemReal *N, CfemReal *dN, CfemReal *d2N) noexcept { const CfemReal u2 = u * u; - N[0] = 0.5 * (u2 - u); - dN[0] = u - 0.5; - d2N[0] = 1.0; - N[1] = 1.0 - u2; - dN[1] = -2.0 * u; - d2N[1] = -2.0; - N[2] = 0.5 * (u2 + u); - dN[2] = u + 0.5; - d2N[2] = 1.0; + N[0] = 0.5 * (u2 - u); dN[0] = u - 0.5; d2N[0] = 1.0; + N[1] = 1.0 - u2; dN[1] = -2.0 * u; d2N[1] = -2.0; + N[2] = 0.5 * (u2 + u); dN[2] = u + 0.5; d2N[2] = 1.0; } - // i (X) parcourt : 0 (-1), 1 (0), 2 (+1) - // j (Y) parcourt : 0 (-1), 1 (0), 2 (+1) - // k (Z) parcourt : 0 (-1), 1 (0), 2 (+1) - static constexpr CfemInt node_map[3][3][3] = { - // i = 0 (X = -1.0) - { - {0, 16, 4}, // j=0 (Y=-1) | k=0,1,2 (Z=-1,0,1) - {11, 20, 15}, // j=1 (Y= 0) | k=0,1,2 (Z=-1,0,1) - {3, 19, 7} // j=2 (Y=+1) | k=0,1,2 (Z=-1,0,1) - }, - // i = 1 (X = 0.0) - { - {8, 22, 12}, // j=0 (Y=-1) | k=0,1,2 (Z=-1,0,1) - {24, 26, 25}, // j=1 (Y= 0) | k=0,1,2 (Z=-1,0,1) - {10, 23, 14} // j=2 (Y=+1) | k=0,1,2 (Z=-1,0,1) - }, - // i = 2 (X = +1.0) - { - {1, 17, 5}, // j=0 (Y=-1) | k=0,1,2 (Z=-1,0,1) - {9, 21, 13}, // j=1 (Y= 0) | k=0,1,2 (Z=-1,0,1) - {2, 18, 6} // j=2 (Y=+1) | k=0,1,2 (Z=-1,0,1) - }}; - public: - Q2ReferenceHexahedron() - { - m_numNodes = 27; - m_dimension = 3; - m_nodes.resize(m_numNodes); - // On définit les positions 1D pour r, s, t : -1.0, 0.0, 1.0 - CfemReal pos[3] = {-1.0, 0.0, 1.0}; - - for (CfemInt i = 0; i < 3; ++i) - for (CfemInt j = 0; j < 3; ++j) - for (CfemInt k = 0; k < 3; ++k) - m_nodes[node_map[i][j][k]] = {pos[i], pos[j], pos[k]}; - } - - void evaluateShapesValues(const Vector3D &xi, - CfemReal *values) const override + [[gnu::always_inline]] static inline void values(CfemReal x, CfemReal y, CfemReal z, + CfemReal* __restrict N) noexcept { CfemReal Nx[3], Ny[3], Nz[3]; - eval1D_shape(xi.x, Nx); - eval1D_shape(xi.y, Ny); - eval1D_shape(xi.z, Nz); - - for (CfemInt i = 0; i < 3; ++i) - { - for (CfemInt j = 0; j < 3; ++j) - { - for (CfemInt k = 0; k < 3; ++k) - { - values[node_map[i][j][k]] = Nx[i] * Ny[j] * Nz[k]; + eval1D_shape(x, Nx); + eval1D_shape(y, Ny); + eval1D_shape(z, Nz); + + for (CfemInt i = 0; i < 3; ++i) { + for (CfemInt j = 0; j < 3; ++j) { + for (CfemInt k = 0; k < 3; ++k) { + N[node_map[i][j][k]] = Nx[i] * Ny[j] * Nz[k]; } } } } - void evaluateShapesDerivatives(const Vector3D &xi, - CfemReal *dN_dxi, - CfemReal *dN_deta, - CfemReal *dN_dzeta) const override + [[gnu::always_inline]] static inline void grads(CfemReal x, CfemReal y, CfemReal z, + CfemReal* __restrict dx, + CfemReal* __restrict dy, + CfemReal* __restrict dz) noexcept { - CfemReal Nx[3], Ny[3], Nz[3], dNx[3], dNy[3], dNz[3]; - eval1D_deriv(xi.x, Nx, dNx); - eval1D_deriv(xi.y, Ny, dNy); - eval1D_deriv(xi.z, Nz, dNz); + eval1D_deriv(x, Nx, dNx); + eval1D_deriv(y, Ny, dNy); + eval1D_deriv(z, Nz, dNz); - for (CfemInt i = 0; i < 3; ++i) - { - for (CfemInt j = 0; j < 3; ++j) - { - for (CfemInt k = 0; k < 3; ++k) - { + for (CfemInt i = 0; i < 3; ++i) { + for (CfemInt j = 0; j < 3; ++j) { + for (CfemInt k = 0; k < 3; ++k) { const CfemInt node = node_map[i][j][k]; - dN_dxi[node] = dNx[i] * Ny[j] * Nz[k]; - dN_deta[node] = Nx[i] * dNy[j] * Nz[k]; - dN_dzeta[node] = Nx[i] * Ny[j] * dNz[k]; + dx[node] = dNx[i] * Ny[j] * Nz[k]; + dy[node] = Nx[i] * dNy[j] * Nz[k]; + dz[node] = Nx[i] * Ny[j] * dNz[k]; } } } } - void evaluateShapesSecondDerivatives(const Vector3D &xi, - CfemReal *d2N_dxi2, - CfemReal *d2N_deta2, - CfemReal *d2N_dzeta2, - CfemReal *d2N_dxideta, - CfemReal *d2N_dxidzeta, - CfemReal *d2N_detadzeta) const override + [[gnu::always_inline]] static inline void hess(CfemReal x, CfemReal y, CfemReal z, + CfemReal* __restrict d2x, CfemReal* __restrict d2y, CfemReal* __restrict d2z, + CfemReal* __restrict dxy, CfemReal* __restrict dxz, CfemReal* __restrict dyz) noexcept { CfemReal Nx[3], Ny[3], Nz[3], dNx[3], dNy[3], dNz[3], d2Nx[3], d2Ny[3], d2Nz[3]; - eval1D_hessian(xi.x, Nx, dNx, d2Nx); - eval1D_hessian(xi.y, Ny, dNy, d2Ny); - eval1D_hessian(xi.z, Nz, dNz, d2Nz); + eval1D_hessian(x, Nx, dNx, d2Nx); + eval1D_hessian(y, Ny, dNy, d2Ny); + eval1D_hessian(z, Nz, dNz, d2Nz); - for (CfemInt i = 0; i < 3; ++i) - { - for (CfemInt j = 0; j < 3; ++j) - { - for (CfemInt k = 0; k < 3; ++k) - { + for (CfemInt i = 0; i < 3; ++i) { + for (CfemInt j = 0; j < 3; ++j) { + for (CfemInt k = 0; k < 3; ++k) { const CfemInt node = node_map[i][j][k]; - d2N_dxi2[node] = d2Nx[i] * Ny[j] * Nz[k]; - d2N_deta2[node] = Nx[i] * d2Ny[j] * Nz[k]; - d2N_dzeta2[node] = Nx[i] * Ny[j] * d2Nz[k]; - - d2N_dxideta[node] = dNx[i] * dNy[j] * Nz[k]; - d2N_dxidzeta[node] = dNx[i] * Ny[j] * dNz[k]; - d2N_detadzeta[node] = Nx[i] * dNy[j] * dNz[k]; + + d2x[node] = d2Nx[i] * Ny[j] * Nz[k]; + d2y[node] = Nx[i] * d2Ny[j] * Nz[k]; + d2z[node] = Nx[i] * Ny[j] * d2Nz[k]; + + dxy[node] = dNx[i] * dNy[j] * Nz[k]; + dxz[node] = dNx[i] * Ny[j] * dNz[k]; + dyz[node] = Nx[i] * dNy[j] * dNz[k]; } } } } + }; +} - void evaluateShapesValuesBatch(const DynamicVector &xiPoints, - DynamicMatrix &valuesBatch) const override +namespace cfem +{ + /** + * @brief Constant Reference Hexahedron (1 node, Q0) + */ + class Q0ReferenceHexahedron : public ReferenceElement + { + public: + Q0ReferenceHexahedron() { - const CfemInt nQp = static_cast(xiPoints.size()); - for (CfemInt q = 0; q < nQp; ++q) - { - CfemReal Nx[3], Ny[3], Nz[3]; - eval1D_shape(xiPoints[q].x, Nx); - eval1D_shape(xiPoints[q].y, Ny); - eval1D_shape(xiPoints[q].z, Nz); - - for (CfemInt i = 0; i < 3; ++i) - { - for (CfemInt j = 0; j < 3; ++j) - { - for (CfemInt k = 0; k < 3; ++k) - { - valuesBatch[q][node_map[i][j][k]] = Nx[i] * Ny[j] * Nz[k]; - } - } - } + m_numNodes = 1; + m_dimension = 3; + // Single node at the reference center [-1, 1]^3 + m_nodes = {Vector3D{0.0, 0.0, 0.0}}; + } + + // ========================================================================= + // SCALAR INTERFACES + // ========================================================================= + + void evaluateShapesValues(const Vector3D &, ShapeInfo& info) const override + { + reference_elem::kernels::Q0HexaKernel::values(info.values()); + } + + void evaluateShapesDerivatives(const Vector3D &, ShapeInfo& info) const override + { + reference_elem::kernels::Q0HexaKernel::grads(info.dN_dxi(), info.dN_deta(), info.dN_dzeta()); + } + + void evaluateShapesSecondDerivatives(const Vector3D &, ShapeInfo& info) const override + { + reference_elem::kernels::Q0HexaKernel::hess(info.d2N_dxi2(), info.d2N_deta2(), info.d2N_dzeta2(), + info.d2N_dxideta(), info.d2N_dxidzeta(), info.d2N_detadzeta()); + } + + // ===================================================================== + // HPC BATCH INTERFACES (Full Memcpy Broadcasting) + // ===================================================================== + + void evaluateShapesValuesBatch(const DynamicVector &xiPoints, ShapeInfoBatch& shapeBatch) const override + { + const CfemInt nQp = shapeBatch.numQuadraturePoints; + if (nQp == 0) return; + + reference_elem::kernels::Q0HexaKernel::values(shapeBatch.values(0)); + + const size_t bytesToCopy = m_numNodes * sizeof(CfemReal); + for (CfemInt q = 1; q < nQp; ++q) { + std::memcpy(shapeBatch.values(q), shapeBatch.values(0), bytesToCopy); } } - void evaluateShapesDerivativesBatch(const DynamicVector &xiPoints, - DynamicMatrix &dN_dxi, - DynamicMatrix &dN_deta, - DynamicMatrix &dN_dzeta) const override + void evaluateShapesDerivativesBatch(const DynamicVector &xiPoints, ShapeInfoBatch& shapeBatch) const override { - const CfemInt nQp = static_cast(xiPoints.size()); - for (CfemInt q = 0; q < nQp; ++q) - { - CfemReal Nx[3], Ny[3], Nz[3], dNx[3], dNy[3], dNz[3]; - eval1D_deriv(xiPoints[q].x, Nx, dNx); - eval1D_deriv(xiPoints[q].y, Ny, dNy); - eval1D_deriv(xiPoints[q].z, Nz, dNz); - - for (CfemInt i = 0; i < 3; ++i) - { - for (CfemInt j = 0; j < 3; ++j) - { - for (CfemInt k = 0; k < 3; ++k) - { - const CfemInt node = node_map[i][j][k]; - dN_dxi[q][node] = dNx[i] * Ny[j] * Nz[k]; - dN_deta[q][node] = Nx[i] * dNy[j] * Nz[k]; - dN_dzeta[q][node] = Nx[i] * Ny[j] * dNz[k]; - } - } - } + const CfemInt nQp = shapeBatch.numQuadraturePoints; + if (nQp == 0) return; + + reference_elem::kernels::Q0HexaKernel::grads(shapeBatch.dN_dxi(0), shapeBatch.dN_deta(0), shapeBatch.dN_dzeta(0)); + + const size_t bytesToCopy = m_numNodes * sizeof(CfemReal); + for (CfemInt q = 1; q < nQp; ++q) { + std::memcpy(shapeBatch.dN_dxi(q), shapeBatch.dN_dxi(0), bytesToCopy); + std::memcpy(shapeBatch.dN_deta(q), shapeBatch.dN_deta(0), bytesToCopy); + std::memcpy(shapeBatch.dN_dzeta(q), shapeBatch.dN_dzeta(0), bytesToCopy); } } - void evaluateShapesSecondDerivativesBatch(const DynamicVector &xiPoints, - DynamicMatrix &d2N_dxi2, - DynamicMatrix &d2N_deta2, - DynamicMatrix &d2N_dzeta2, - DynamicMatrix &d2N_dxideta, - DynamicMatrix &d2N_dxidzeta, - DynamicMatrix &d2N_detadzeta) const override + void evaluateShapesSecondDerivativesBatch(const DynamicVector &xiPoints, ShapeInfoBatch& shapeBatch) const override { - const CfemInt nQp = static_cast(xiPoints.size()); - for (CfemInt q = 0; q < nQp; ++q) - { - CfemReal Nx[3], Ny[3], Nz[3], dNx[3], dNy[3], dNz[3], d2Nx[3], d2Ny[3], d2Nz[3]; - eval1D_hessian(xiPoints[q].x, Nx, dNx, d2Nx); - eval1D_hessian(xiPoints[q].y, Ny, dNy, d2Ny); - eval1D_hessian(xiPoints[q].z, Nz, dNz, d2Nz); - - for (CfemInt i = 0; i < 3; ++i) - { - for (CfemInt j = 0; j < 3; ++j) - { - for (CfemInt k = 0; k < 3; ++k) - { - const CfemInt node = node_map[i][j][k]; - d2N_dxi2[q][node] = d2Nx[i] * Ny[j] * Nz[k]; - d2N_deta2[q][node] = Nx[i] * d2Ny[j] * Nz[k]; - d2N_dzeta2[q][node] = Nx[i] * Ny[j] * d2Nz[k]; - - d2N_dxideta[q][node] = dNx[i] * dNy[j] * Nz[k]; - d2N_dxidzeta[q][node] = dNx[i] * Ny[j] * dNz[k]; - d2N_detadzeta[q][node] = Nx[i] * dNy[j] * dNz[k]; - } - } - } + const CfemInt nQp = shapeBatch.numQuadraturePoints; + if (nQp == 0) return; + + reference_elem::kernels::Q0HexaKernel::hess(shapeBatch.d2N_dxi2(0), shapeBatch.d2N_deta2(0), shapeBatch.d2N_dzeta2(0), + shapeBatch.d2N_dxideta(0), shapeBatch.d2N_dxidzeta(0), shapeBatch.d2N_detadzeta(0)); + + const size_t bytesToCopy = m_numNodes * sizeof(CfemReal); + for (CfemInt q = 1; q < nQp; ++q) { + std::memcpy(shapeBatch.d2N_dxi2(q), shapeBatch.d2N_dxi2(0), bytesToCopy); + std::memcpy(shapeBatch.d2N_deta2(q), shapeBatch.d2N_deta2(0), bytesToCopy); + std::memcpy(shapeBatch.d2N_dzeta2(q), shapeBatch.d2N_dzeta2(0), bytesToCopy); + std::memcpy(shapeBatch.d2N_dxideta(q), shapeBatch.d2N_dxideta(0), bytesToCopy); + std::memcpy(shapeBatch.d2N_dxidzeta(q), shapeBatch.d2N_dxidzeta(0), bytesToCopy); + std::memcpy(shapeBatch.d2N_detadzeta(q), shapeBatch.d2N_detadzeta(0), bytesToCopy); } } Vector3D projectFacetRefCoordToCellRefCoord(CfemInt localFacetId, const Vector3D &xiFacet) const override { - return cfem::reference_elem::utils::projectFacetRefCoordToHexaRefCoord(localFacetId, xiFacet); // Make the compiler happy + return cfem::reference_elem::utils::projectFacetRefCoordToHexaRefCoord(localFacetId, xiFacet); } std::span getFaceNodes(CfemInt iFace) const override { - return cfem::reference_elem::utils::hex27_face_nodes[iFace]; + return cfem::reference_elem::utils::hex1_face_nodes[iFace]; } }; - class MiniReferenceHexahedron : public ReferenceElement + /** + * @brief Trilinear Reference Hexahedron (8 nodes, Q1) + */ + class Q1ReferenceHexahedron : public ReferenceElement { - protected: - static constexpr CfemReal xr[8] = {-1.0, 1.0, 1.0, -1.0, -1.0, 1.0, 1.0, -1.0}; - static constexpr CfemReal ys[8] = {-1.0, -1.0, 1.0, 1.0, -1.0, -1.0, 1.0, 1.0}; - static constexpr CfemReal zt[8] = {-1.0, -1.0, -1.0, -1.0, 1.0, 1.0, 1.0, 1.0}; - static constexpr CfemReal o8 = 0.125; - public: - MiniReferenceHexahedron() + Q1ReferenceHexahedron() { - m_numNodes = 9; // 8 sommets + 1 bulle + m_numNodes = 8; m_dimension = 3; m_nodes.resize(m_numNodes); + // VTK order : Face Z=-1 (0,1,2,3), puis Face Z=1 (4,5,6,7) + m_nodes[0] = {-1.0, -1.0, -1.0}; + m_nodes[1] = { 1.0, -1.0, -1.0}; + m_nodes[2] = { 1.0, 1.0, -1.0}; + m_nodes[3] = {-1.0, 1.0, -1.0}; + m_nodes[4] = {-1.0, -1.0, 1.0}; + m_nodes[5] = { 1.0, -1.0, 1.0}; + m_nodes[6] = { 1.0, 1.0, 1.0}; + m_nodes[7] = {-1.0, 1.0, 1.0}; + } - // Ordre VTK Q1 - m_nodes[0] = {-1, -1, -1}; - m_nodes[1] = {1, -1, -1}; - m_nodes[2] = {1, 1, -1}; - m_nodes[3] = {-1, 1, -1}; - m_nodes[4] = {-1, -1, 1}; - m_nodes[5] = {1, -1, 1}; - m_nodes[6] = {1, 1, 1}; - m_nodes[7] = {-1, 1, 1}; - // Nœud Bulle au centre - m_nodes[8] = {0, 0, 0}; + // ========================================================================= + // SCALAR INTERFACES + // ========================================================================= + + void evaluateShapesValues(const Vector3D &xi, ShapeInfo& info) const override + { + reference_elem::kernels::Q1HexaKernel::values(xi.x, xi.y, xi.z, info.values()); } - void evaluateShapesValues(const Vector3D &xi, CfemReal *values) const override + void evaluateShapesDerivatives(const Vector3D &xi, ShapeInfo& info) const override { - const CfemReal x = xi.x, y = xi.y, z = xi.z; + reference_elem::kernels::Q1HexaKernel::grads(xi.x, xi.y, xi.z, + info.dN_dxi(), info.dN_deta(), info.dN_dzeta()); + } - // Calcul de la bulle - const CfemReal b = (1.0 - x * x) * (1.0 - y * y) * (1.0 - z * z); - values[8] = b; + void evaluateShapesSecondDerivatives(const Vector3D &xi, ShapeInfo& info) const override + { + reference_elem::kernels::Q1HexaKernel::hess(xi.x, xi.y, xi.z, + info.d2N_dxi2(), info.d2N_deta2(), info.d2N_dzeta2(), + info.d2N_dxideta(), info.d2N_dxidzeta(), info.d2N_detadzeta()); + } - // Calcul des sommets Q1 avec correction - const CfemReal correction = b * o8; - for (CfemInt i = 0; i < 8; ++i) + // ===================================================================== + // HPC BATCH INTERFACES + // ===================================================================== + + void evaluateShapesValuesBatch(const DynamicVector &xiPoints, ShapeInfoBatch& shapeBatch) const override + { + const CfemInt nQp = shapeBatch.numQuadraturePoints; + for (CfemInt q = 0; q < nQp; ++q) { - const CfemReal px = 1.0 + xr[i] * x; - const CfemReal py = 1.0 + ys[i] * y; - const CfemReal pz = 1.0 + zt[i] * z; - values[i] = (o8 * px * py * pz) - correction; + reference_elem::kernels::Q1HexaKernel::values(xiPoints[q].x, xiPoints[q].y, xiPoints[q].z, + shapeBatch.values(q)); } } - void evaluateShapesDerivatives(const Vector3D &xi, - CfemReal *dN_dxi, - CfemReal *dN_deta, - CfemReal *dN_dzeta) const override + void evaluateShapesDerivativesBatch(const DynamicVector &xiPoints, ShapeInfoBatch& shapeBatch) const override { - const CfemReal x = xi.x, y = xi.y, z = xi.z; - const CfemReal mx2 = 1.0 - x * x; - const CfemReal my2 = 1.0 - y * y; - const CfemReal mz2 = 1.0 - z * z; + const CfemInt nQp = shapeBatch.numQuadraturePoints; + for (CfemInt q = 0; q < nQp; ++q) + { + reference_elem::kernels::Q1HexaKernel::grads(xiPoints[q].x, xiPoints[q].y, xiPoints[q].z, + shapeBatch.dN_dxi(q), shapeBatch.dN_deta(q), shapeBatch.dN_dzeta(q)); + } + } - // Gradients de la bulle - const CfemReal db_dx = -2.0 * x * my2 * mz2; - const CfemReal db_dy = -2.0 * y * mx2 * mz2; - const CfemReal db_dz = -2.0 * z * mx2 * my2; + void evaluateShapesSecondDerivativesBatch(const DynamicVector &xiPoints, ShapeInfoBatch& shapeBatch) const override + { + const CfemInt nQp = shapeBatch.numQuadraturePoints; + for (CfemInt q = 0; q < nQp; ++q) + { + reference_elem::kernels::Q1HexaKernel::hess(xiPoints[q].x, xiPoints[q].y, xiPoints[q].z, + shapeBatch.d2N_dxi2(q), shapeBatch.d2N_deta2(q), shapeBatch.d2N_dzeta2(q), + shapeBatch.d2N_dxideta(q), shapeBatch.d2N_dxidzeta(q), shapeBatch.d2N_detadzeta(q)); + } + } - dN_dxi[8] = db_dx; - dN_deta[8] = db_dy; - dN_dzeta[8] = db_dz; + Vector3D projectFacetRefCoordToCellRefCoord(CfemInt localFacetId, const Vector3D &xiFacet) const override + { + return cfem::reference_elem::utils::projectFacetRefCoordToHexaRefCoord(localFacetId, xiFacet); + } - // Gradients Q1 corrigés - const CfemReal corr_dx = db_dx * o8; - const CfemReal corr_dy = db_dy * o8; - const CfemReal corr_dz = db_dz * o8; + std::span getFaceNodes(CfemInt iFace) const override + { + return cfem::reference_elem::utils::hex8_face_nodes[iFace]; + } + }; - for (CfemInt i = 0; i < 8; ++i) - { - const CfemReal px = 1.0 + xr[i] * x; - const CfemReal py = 1.0 + ys[i] * y; - const CfemReal pz = 1.0 + zt[i] * z; + /** + * @brief Q1-Mini Reference Hexahedron (9 nodes: 8 vertices + 1 bubble) + */ + class MiniReferenceHexahedron : public ReferenceElement + { + public: + MiniReferenceHexahedron() + { + m_numNodes = 9; // 8 sommets + 1 bulle + m_dimension = 3; + m_nodes.resize(m_numNodes); - dN_dxi[i] = (o8 * xr[i] * py * pz) - corr_dx; - dN_deta[i] = (o8 * ys[i] * px * pz) - corr_dy; - dN_dzeta[i] = (o8 * zt[i] * px * py) - corr_dz; - } + // VTK order Q1 + m_nodes[0] = {-1.0, -1.0, -1.0}; + m_nodes[1] = { 1.0, -1.0, -1.0}; + m_nodes[2] = { 1.0, 1.0, -1.0}; + m_nodes[3] = {-1.0, 1.0, -1.0}; + m_nodes[4] = {-1.0, -1.0, 1.0}; + m_nodes[5] = { 1.0, -1.0, 1.0}; + m_nodes[6] = { 1.0, 1.0, 1.0}; + m_nodes[7] = {-1.0, 1.0, 1.0}; + // Bubble node (center) + m_nodes[8] = { 0.0, 0.0, 0.0}; } - void evaluateShapesSecondDerivatives(const Vector3D &xi, - CfemReal *d2N_dxi2, CfemReal *d2N_deta2, CfemReal *d2N_dzeta2, - CfemReal *d2N_dxideta, CfemReal *d2N_dxidzeta, CfemReal *d2N_detadzeta) const override - { - const CfemReal x = xi.x, y = xi.y, z = xi.z; - const CfemReal mx2 = 1.0 - x * x; - const CfemReal my2 = 1.0 - y * y; - const CfemReal mz2 = 1.0 - z * z; + // ========================================================================= + // SCALAR INTERFACES + // ========================================================================= - // Hessiens de la bulle - const CfemReal d2b_dx2 = -2.0 * my2 * mz2; - const CfemReal d2b_dy2 = -2.0 * mx2 * mz2; - const CfemReal d2b_dz2 = -2.0 * mx2 * my2; - const CfemReal d2b_dxy = 4.0 * x * y * mz2; - const CfemReal d2b_dxz = 4.0 * x * z * my2; - const CfemReal d2b_dyz = 4.0 * y * z * mx2; - - d2N_dxi2[8] = d2b_dx2; - d2N_deta2[8] = d2b_dy2; - d2N_dzeta2[8] = d2b_dz2; - d2N_dxideta[8] = d2b_dxy; - d2N_dxidzeta[8] = d2b_dxz; - d2N_detadzeta[8] = d2b_dyz; - - // Hessiens Q1 corrigés - const CfemReal corr_dx2 = d2b_dx2 * o8; - const CfemReal corr_dy2 = d2b_dy2 * o8; - const CfemReal corr_dz2 = d2b_dz2 * o8; - const CfemReal corr_dxy = d2b_dxy * o8; - const CfemReal corr_dxz = d2b_dxz * o8; - const CfemReal corr_dyz = d2b_dyz * o8; + void evaluateShapesValues(const Vector3D &xi, ShapeInfo& info) const override + { + reference_elem::kernels::MiniHexaKernel::values(xi.x, xi.y, xi.z, info.values()); + } - for (CfemInt i = 0; i < 8; ++i) - { - const CfemReal px = 1.0 + xr[i] * x; - const CfemReal py = 1.0 + ys[i] * y; - const CfemReal pz = 1.0 + zt[i] * z; + void evaluateShapesDerivatives(const Vector3D &xi, ShapeInfo& info) const override + { + reference_elem::kernels::MiniHexaKernel::grads(xi.x, xi.y, xi.z, + info.dN_dxi(), info.dN_deta(), info.dN_dzeta()); + } - d2N_dxi2[i] = -corr_dx2; // La composante pure Q1 est nulle - d2N_deta2[i] = -corr_dy2; - d2N_dzeta2[i] = -corr_dz2; - d2N_dxideta[i] = (o8 * xr[i] * ys[i] * pz) - corr_dxy; - d2N_dxidzeta[i] = (o8 * xr[i] * py * zt[i]) - corr_dxz; - d2N_detadzeta[i] = (o8 * px * ys[i] * zt[i]) - corr_dyz; - } + void evaluateShapesSecondDerivatives(const Vector3D &xi, ShapeInfo& info) const override + { + reference_elem::kernels::MiniHexaKernel::hess(xi.x, xi.y, xi.z, + info.d2N_dxi2(), info.d2N_deta2(), info.d2N_dzeta2(), + info.d2N_dxideta(), info.d2N_dxidzeta(), info.d2N_detadzeta()); } - void evaluateShapesValuesBatch(const DynamicVector &xiPoints, - DynamicMatrix &valuesBatch) const override + // ===================================================================== + // HPC BATCH INTERFACES + // ===================================================================== + + void evaluateShapesValuesBatch(const DynamicVector &xiPoints, ShapeInfoBatch& shapeBatch) const override { - const CfemInt nQp = static_cast(xiPoints.size()); - for (CfemInt q = 0; q < nQp; ++q) - { - const CfemReal x = xiPoints[q].x, y = xiPoints[q].y, z = xiPoints[q].z; - const CfemReal b = (1.0 - x * x) * (1.0 - y * y) * (1.0 - z * z); - - valuesBatch[q][8] = b; - const CfemReal correction = b * o8; - - for (CfemInt i = 0; i < 8; ++i) - { - const CfemReal px = 1.0 + xr[i] * x; - const CfemReal py = 1.0 + ys[i] * y; - const CfemReal pz = 1.0 + zt[i] * z; - valuesBatch[q][i] = (o8 * px * py * pz) - correction; - } + const CfemInt nQp = shapeBatch.numQuadraturePoints; + for (CfemInt q = 0; q < nQp; ++q) { + reference_elem::kernels::MiniHexaKernel::values(xiPoints[q].x, xiPoints[q].y, xiPoints[q].z, + shapeBatch.values(q)); } } - void evaluateShapesDerivativesBatch(const DynamicVector &xiPoints, - DynamicMatrix &dN_dxi, - DynamicMatrix &dN_deta, - DynamicMatrix &dN_dzeta) const override + void evaluateShapesDerivativesBatch(const DynamicVector &xiPoints, ShapeInfoBatch& shapeBatch) const override { - const CfemInt nQp = static_cast(xiPoints.size()); - for (CfemInt q = 0; q < nQp; ++q) - { - const CfemReal x = xiPoints[q].x, y = xiPoints[q].y, z = xiPoints[q].z; - const CfemReal mx2 = 1.0 - x * x; - const CfemReal my2 = 1.0 - y * y; - const CfemReal mz2 = 1.0 - z * z; - - const CfemReal db_dx = -2.0 * x * my2 * mz2; - const CfemReal db_dy = -2.0 * y * mx2 * mz2; - const CfemReal db_dz = -2.0 * z * mx2 * my2; - - dN_dxi[q][8] = db_dx; - dN_deta[q][8] = db_dy; - dN_dzeta[q][8] = db_dz; - - const CfemReal corr_dx = db_dx * o8; - const CfemReal corr_dy = db_dy * o8; - const CfemReal corr_dz = db_dz * o8; - - for (CfemInt i = 0; i < 8; ++i) - { - const CfemReal px = 1.0 + xr[i] * x; - const CfemReal py = 1.0 + ys[i] * y; - const CfemReal pz = 1.0 + zt[i] * z; - - dN_dxi[q][i] = (o8 * xr[i] * py * pz) - corr_dx; - dN_deta[q][i] = (o8 * ys[i] * px * pz) - corr_dy; - dN_dzeta[q][i] = (o8 * zt[i] * px * py) - corr_dz; - } + const CfemInt nQp = shapeBatch.numQuadraturePoints; + for (CfemInt q = 0; q < nQp; ++q) { + reference_elem::kernels::MiniHexaKernel::grads(xiPoints[q].x, xiPoints[q].y, xiPoints[q].z, + shapeBatch.dN_dxi(q), shapeBatch.dN_deta(q), shapeBatch.dN_dzeta(q)); } } - void evaluateShapesSecondDerivativesBatch(const DynamicVector &xiPoints, - DynamicMatrix &d2N_dxi2, - DynamicMatrix &d2N_deta2, - DynamicMatrix &d2N_dzeta2, - DynamicMatrix &d2N_dxideta, - DynamicMatrix &d2N_dxidzeta, - DynamicMatrix &d2N_detadzeta) const override + void evaluateShapesSecondDerivativesBatch(const DynamicVector &xiPoints, ShapeInfoBatch& shapeBatch) const override { - const CfemInt nQp = static_cast(xiPoints.size()); - for (CfemInt q = 0; q < nQp; ++q) - { - const CfemReal x = xiPoints[q].x, y = xiPoints[q].y, z = xiPoints[q].z; - const CfemReal mx2 = 1.0 - x * x; - const CfemReal my2 = 1.0 - y * y; - const CfemReal mz2 = 1.0 - z * z; - - const CfemReal d2b_dx2 = -2.0 * my2 * mz2; - const CfemReal d2b_dy2 = -2.0 * mx2 * mz2; - const CfemReal d2b_dz2 = -2.0 * mx2 * my2; - const CfemReal d2b_dxy = 4.0 * x * y * mz2; - const CfemReal d2b_dxz = 4.0 * x * z * my2; - const CfemReal d2b_dyz = 4.0 * y * z * mx2; - - d2N_dxi2[q][8] = d2b_dx2; - d2N_deta2[q][8] = d2b_dy2; - d2N_dzeta2[q][8] = d2b_dz2; - d2N_dxideta[q][8] = d2b_dxy; - d2N_dxidzeta[q][8] = d2b_dxz; - d2N_detadzeta[q][8] = d2b_dyz; - - const CfemReal corr_dx2 = d2b_dx2 * o8; - const CfemReal corr_dy2 = d2b_dy2 * o8; - const CfemReal corr_dz2 = d2b_dz2 * o8; - const CfemReal corr_dxy = d2b_dxy * o8; - const CfemReal corr_dxz = d2b_dxz * o8; - const CfemReal corr_dyz = d2b_dyz * o8; - - for (CfemInt i = 0; i < 8; ++i) - { - const CfemReal px = 1.0 + xr[i] * x; - const CfemReal py = 1.0 + ys[i] * y; - const CfemReal pz = 1.0 + zt[i] * z; - - d2N_dxi2[q][i] = -corr_dx2; - d2N_deta2[q][i] = -corr_dy2; - d2N_dzeta2[q][i] = -corr_dz2; - d2N_dxideta[q][i] = (o8 * xr[i] * ys[i] * pz) - corr_dxy; - d2N_dxidzeta[q][i] = (o8 * xr[i] * py * zt[i]) - corr_dxz; - d2N_detadzeta[q][i] = (o8 * px * ys[i] * zt[i]) - corr_dyz; - } + const CfemInt nQp = shapeBatch.numQuadraturePoints; + // Les dérivées dépendent de la position, pas de memcpy + for (CfemInt q = 0; q < nQp; ++q) { + reference_elem::kernels::MiniHexaKernel::hess(xiPoints[q].x, xiPoints[q].y, xiPoints[q].z, + shapeBatch.d2N_dxi2(q), shapeBatch.d2N_deta2(q), shapeBatch.d2N_dzeta2(q), + shapeBatch.d2N_dxideta(q), shapeBatch.d2N_dxidzeta(q), shapeBatch.d2N_detadzeta(q)); } } @@ -866,106 +655,99 @@ namespace cfem std::span getFaceNodes(CfemInt iFace) const override { - return cfem::reference_elem::utils::hex8_face_nodes[iFace]; + return cfem::reference_elem::utils::hex8_face_nodes[iFace]; // Les faces n'incluent pas la bulle (noeud 8) } }; - class Q0ReferenceHexahedron : public ReferenceElement + /** + * @brief Tri-Quadratic Reference Hexahedron (27 nodes, Q2) + */ + class Q2ReferenceHexahedron : public ReferenceElement { public: - Q0ReferenceHexahedron() + Q2ReferenceHexahedron() { - m_numNodes = 1; + m_numNodes = 27; m_dimension = 3; - // Un seul nœud au centre du cube de référence [-1, 1]^3 - m_nodes = {Vector3D{0.0, 0.0, 0.0}}; + m_nodes.resize(m_numNodes); + + // Define 1D positions r, s, t : -1.0, 0.0, 1.0 + CfemReal pos[3] = {-1.0, 0.0, 1.0}; + + for (CfemInt i = 0; i < 3; ++i) { + for (CfemInt j = 0; j < 3; ++j) { + for (CfemInt k = 0; k < 3; ++k) { + m_nodes[reference_elem::kernels::Q2HexaKernel::node_map[i][j][k]] = {pos[i], pos[j], pos[k]}; + } + } + } } - void evaluateShapesValues(const Vector3D &, CfemReal *values) const override + // ========================================================================= + // SCALAR INTERFACES + // ========================================================================= + + void evaluateShapesValues(const Vector3D &xi, ShapeInfo& info) const override { - values[0] = 1.0; + reference_elem::kernels::Q2HexaKernel::values(xi.x, xi.y, xi.z, info.values()); } - void evaluateShapesDerivatives(const Vector3D &, - CfemReal *dN_dxi, - CfemReal *dN_deta, - CfemReal *dN_dzeta) const override + void evaluateShapesDerivatives(const Vector3D &xi, ShapeInfo& info) const override { - dN_dxi[0] = 0.0; - dN_deta[0] = 0.0; - dN_dzeta[0] = 0.0; + reference_elem::kernels::Q2HexaKernel::grads(xi.x, xi.y, xi.z, + info.dN_dxi(), info.dN_deta(), info.dN_dzeta()); } - void evaluateShapesSecondDerivatives(const Vector3D &, - CfemReal *d2N_dxi2, - CfemReal *d2N_deta2, - CfemReal *d2N_dzeta2, - CfemReal *d2N_dxideta, - CfemReal *d2N_dxidzeta, - CfemReal *d2N_detadzeta) const override + void evaluateShapesSecondDerivatives(const Vector3D &xi, ShapeInfo& info) const override { - d2N_dxi2[0] = 0.0; - d2N_deta2[0] = 0.0; - d2N_dzeta2[0] = 0.0; - d2N_dxideta[0] = 0.0; - d2N_dxidzeta[0] = 0.0; - d2N_detadzeta[0] = 0.0; + reference_elem::kernels::Q2HexaKernel::hess(xi.x, xi.y, xi.z, + info.d2N_dxi2(), info.d2N_deta2(), info.d2N_dzeta2(), + info.d2N_dxideta(), info.d2N_dxidzeta(), info.d2N_detadzeta()); } - void evaluateShapesValuesBatch(const DynamicVector &xiPoints, DynamicMatrix &values) const override + // ===================================================================== + // HPC BATCH INTERFACES + // ===================================================================== + + void evaluateShapesValuesBatch(const DynamicVector &xiPoints, ShapeInfoBatch& shapeBatch) const override { - // Constant piecewise field value - const CfemInt nQp = static_cast(xiPoints.size()); + const CfemInt nQp = shapeBatch.numQuadraturePoints; for (CfemInt q = 0; q < nQp; ++q) { - values[q][0] = 1.0; + reference_elem::kernels::Q2HexaKernel::values(xiPoints[q].x, xiPoints[q].y, xiPoints[q].z, + shapeBatch.values(q)); } } - void evaluateShapesDerivativesBatch(const DynamicVector &xiPoints, - DynamicMatrix &dN_dxi, - DynamicMatrix &dN_deta, - DynamicMatrix &dN_dzeta) const override + void evaluateShapesDerivativesBatch(const DynamicVector &xiPoints, ShapeInfoBatch& shapeBatch) const override { - // Constant function results in flat zero gradients everywhere - const CfemInt nQp = static_cast(xiPoints.size()); + const CfemInt nQp = shapeBatch.numQuadraturePoints; for (CfemInt q = 0; q < nQp; ++q) { - dN_dxi[q][0] = 0.0; - dN_deta[q][0] = 0.0; - dN_dzeta[q][0] = 0.0; + reference_elem::kernels::Q2HexaKernel::grads(xiPoints[q].x, xiPoints[q].y, xiPoints[q].z, + shapeBatch.dN_dxi(q), shapeBatch.dN_deta(q), shapeBatch.dN_dzeta(q)); } } - void evaluateShapesSecondDerivativesBatch(const DynamicVector &xiPoints, - DynamicMatrix &d2N_dxi2, - DynamicMatrix &d2N_deta2, - DynamicMatrix &d2N_dzeta2, - DynamicMatrix &d2N_dxideta, - DynamicMatrix &d2N_dxidzeta, - DynamicMatrix &d2N_detadzeta) const override + void evaluateShapesSecondDerivativesBatch(const DynamicVector &xiPoints, ShapeInfoBatch& shapeBatch) const override { - // Flat null second derivatives - const CfemInt nQp = static_cast(xiPoints.size()); + const CfemInt nQp = shapeBatch.numQuadraturePoints; for (CfemInt q = 0; q < nQp; ++q) { - d2N_dxi2[q][0] = 0.0; - d2N_deta2[q][0] = 0.0; - d2N_dzeta2[q][0] = 0.0; - d2N_dxideta[q][0] = 0.0; - d2N_dxidzeta[q][0] = 0.0; - d2N_detadzeta[q][0] = 0.0; + reference_elem::kernels::Q2HexaKernel::hess(xiPoints[q].x, xiPoints[q].y, xiPoints[q].z, + shapeBatch.d2N_dxi2(q), shapeBatch.d2N_deta2(q), shapeBatch.d2N_dzeta2(q), + shapeBatch.d2N_dxideta(q), shapeBatch.d2N_dxidzeta(q), shapeBatch.d2N_detadzeta(q)); } } Vector3D projectFacetRefCoordToCellRefCoord(CfemInt localFacetId, const Vector3D &xiFacet) const override { - return cfem::reference_elem::utils::projectFacetRefCoordToHexaRefCoord(localFacetId, xiFacet); + return cfem::reference_elem::utils::projectFacetRefCoordToHexaRefCoord(localFacetId, xiFacet); } std::span getFaceNodes(CfemInt iFace) const override { - return cfem::reference_elem::utils::hex1_face_nodes[iFace]; + return cfem::reference_elem::utils::hex27_face_nodes[iFace]; } }; diff --git a/libs/fem/reference_element/include/ReferencePrism.h b/libs/fem/reference_element/include/ReferencePrism.h index b293da7..5a620b0 100644 --- a/libs/fem/reference_element/include/ReferencePrism.h +++ b/libs/fem/reference_element/include/ReferencePrism.h @@ -20,6 +20,8 @@ #define CFEM_REFERENCE_PRISM #include "ReferenceElement.h" +#include +#include namespace cfem::reference_elem::utils{ @@ -43,7 +45,7 @@ namespace cfem::reference_elem::utils{ return Vector3D{}; // Make the compiler happy } - static const std::vector> prism6_face_nodes = { + static const std::vector> prism6_face_nodes = { {0, 2, 1}, // Face 0 (Tri) {3, 4, 5}, // Face 1 (Tri) {0, 1, 4, 3}, // Face 2 (Quad) @@ -51,7 +53,7 @@ namespace cfem::reference_elem::utils{ {2, 0, 3, 5} // Face 4 (Quad) }; - static const std::vector> prism18_face_nodes = { + static const std::vector> prism18_face_nodes = { {0, 2, 1, 8, 7, 6}, // Face 0 (Tri6) {3, 4, 5, 9, 10, 11}, // Face 1 (Tri6) {0, 1, 4, 3, 6, 13, 9, 12, 15}, // Face 2 (Quad9) @@ -59,228 +61,84 @@ namespace cfem::reference_elem::utils{ {2, 0, 3, 5, 8, 12, 11, 14, 17} // Face 4 (Quad9) }; - static const std::vector> prism1_face_nodes = { + static const std::vector> prism1_face_nodes = { {0}, {0}, {0}, {0}, {0} }; } -namespace cfem{ +namespace cfem::reference_elem::kernels { - class P1ReferencePrism : public ReferenceElement { - private: - static constexpr CfemInt node_map[3][2] = { - {0, 3}, // Nœuds du Triangle 0 - {1, 4}, // Nœuds du Triangle 1 - {2, 5} // Nœuds du Triangle 2 - }; - public: - P1ReferencePrism() { - m_numNodes = 6; - m_dimension = 3; - m_nodes.resize(m_numNodes); - // Ordre VTK : Triangle inférieur Z=-1 (0,1,2), puis supérieur Z=1 (3,4,5) - m_nodes[0] = {0,0,-1}; m_nodes[1] = {1,0,-1}; m_nodes[2] = {0,1,-1}; - m_nodes[3] = {0,0, 1}; m_nodes[4] = {1,0, 1}; m_nodes[5] = {0,1, 1}; - } + struct P1PrismKernel { + static constexpr CfemReal o2 = 0.5; - void evaluateShapesValues(const Vector3D& xi, - CfemReal* values) const override + [[gnu::always_inline]] static inline void values(CfemReal x, CfemReal y, CfemReal z, + CfemReal* __restrict N) noexcept { - constexpr CfemReal o2 = 0.5; - const CfemReal x = xi.x; - const CfemReal y = xi.y; - const CfemReal z = xi.z; - - // Fonctions du Triangle P1 const CfemReal Nt[3] = {1.0 - x - y, x, y}; - // Fonctions de la Ligne P1 const CfemReal Nl[2] = {o2 * (1.0 - z), o2 * (1.0 + z)}; for (CfemInt t = 0; t < 3; ++t) { for (CfemInt l = 0; l < 2; ++l) { - values[node_map[t][l]] = Nt[t] * Nl[l]; + N[t + l * 3] = Nt[t] * Nl[l]; } } } - void evaluateShapesDerivatives(const Vector3D& xi, - CfemReal* dN_dxi, - CfemReal* dN_deta, - CfemReal* dN_dzeta) const override + [[gnu::always_inline]] static inline void grads(CfemReal x, CfemReal y, CfemReal z, + CfemReal* __restrict dx, + CfemReal* __restrict dy, + CfemReal* __restrict dz) noexcept { - - constexpr CfemReal o2 = 0.5; - - const CfemReal x = xi.x; - const CfemReal y = xi.y; - const CfemReal z = xi.z; - - const CfemReal Nt[3] = {1.0 - x - y, x, y}; - const CfemReal dNt_dx[3] = {-1.0, 1.0, 0.0}; - const CfemReal dNt_dy[3] = {-1.0, 0.0, 1.0}; - - const CfemReal Nl[2] = {o2 * (1.0 - z), o2 * (1.0 + z)}; - const CfemReal dNl_dz[2] = {-o2, o2}; + const CfemReal Nt[3] = {1.0 - x - y, x, y}; + const CfemReal dNt_dx[3] = {-1.0, 1.0, 0.0}; + const CfemReal dNt_dy[3] = {-1.0, 0.0, 1.0}; + + const CfemReal Nl[2] = {o2 * (1.0 - z), o2 * (1.0 + z)}; + const CfemReal dNl_dz[2] = {-o2, o2}; for (CfemInt t = 0; t < 3; ++t) { for (CfemInt l = 0; l < 2; ++l) { - const CfemInt node = node_map[t][l]; - dN_dxi[node] = dNt_dx[t] * Nl[l]; - dN_deta[node] = dNt_dy[t] * Nl[l]; - dN_dzeta[node] = Nt[t] * dNl_dz[l]; + const int node = t * 2 + l; + dx[node] = dNt_dx[t] * Nl[l]; + dy[node] = dNt_dy[t] * Nl[l]; + dz[node] = Nt[t] * dNl_dz[l]; } } } - void evaluateShapesSecondDerivatives(const Vector3D& xiPoints, - CfemReal* d2N_dxi2, - CfemReal* d2N_deta2, - CfemReal* d2N_dzeta2, - CfemReal* d2N_dxideta, - CfemReal* d2N_dxidzeta, - CfemReal* d2N_detadzeta) const override + [[gnu::always_inline]] static inline void hess(CfemReal x, CfemReal y, CfemReal z, + CfemReal* __restrict d2x, CfemReal* __restrict d2y, CfemReal* __restrict d2z, + CfemReal* __restrict dxy, CfemReal* __restrict dxz, CfemReal* __restrict dyz) noexcept { - - constexpr CfemReal o2 = 0.5; - - const CfemReal dNt_dx[3] = {-1.0, 1.0, 0.0}; - const CfemReal dNt_dy[3] = {-1.0, 0.0, 1.0}; - const CfemReal dNl_dz[2] = {-o2, o2}; + const CfemReal dNt_dx[3] = {-1.0, 1.0, 0.0}; + const CfemReal dNt_dy[3] = {-1.0, 0.0, 1.0}; + const CfemReal dNl_dz[2] = {-o2, o2}; for (CfemInt t = 0; t < 3; ++t) { for (CfemInt l = 0; l < 2; ++l) { - const CfemInt node = node_map[t][l]; - - // Les dérivées secondes pures sont nulles pour un champ linéaire (P1xP1) - d2N_dxi2[node] = 0.0; - d2N_deta2[node] = 0.0; - d2N_dzeta2[node] = 0.0; - d2N_dxideta[node] = 0.0; - - // Les dérivées croisées impliquant 'z' survivent au produit tensoriel ! - d2N_dxidzeta[node] = dNt_dx[t] * dNl_dz[l]; - d2N_detadzeta[node] = dNt_dy[t] * dNl_dz[l]; - } - } - } - - void evaluateShapesValuesBatch(const DynamicVector& xiPoints, - DynamicMatrix& valuesBatch) const override - { - const CfemInt nQp = static_cast(xiPoints.size()); - constexpr CfemReal o2 = 0.5; - - for (CfemInt q = 0; q < nQp; ++q) { - const CfemReal x = xiPoints[q].x; - const CfemReal y = xiPoints[q].y; - const CfemReal z = xiPoints[q].z; - - // Fonctions du Triangle P1 - const CfemReal Nt[3] = {1.0 - x - y, x, y}; - // Fonctions de la Ligne P1 - const CfemReal Nl[2] = {o2 * (1.0 - z), o2 * (1.0 + z)}; - - for (CfemInt t = 0; t < 3; ++t) { - for (CfemInt l = 0; l < 2; ++l) { - valuesBatch[q][node_map[t][l]] = Nt[t] * Nl[l]; - } + const int node = t * 2 + l; + // Pure second derivatives are zero + d2x[node] = d2y[node] = d2z[node] = dxy[node] = 0.0; + // Mixed derivatives with z + dxz[node] = dNt_dx[t] * dNl_dz[l]; + dyz[node] = dNt_dy[t] * dNl_dz[l]; } } } - - void evaluateShapesDerivativesBatch(const DynamicVector& xiPoints, - DynamicMatrix& dN_dxi, - DynamicMatrix& dN_deta, - DynamicMatrix& dN_dzeta) const override - { - const CfemInt nQp = static_cast(xiPoints.size()); - constexpr CfemReal o2 = 0.5; - - for (CfemInt q = 0; q < nQp; ++q) { - const CfemReal x = xiPoints[q].x; - const CfemReal y = xiPoints[q].y; - const CfemReal z = xiPoints[q].z; - - const CfemReal Nt[3] = {1.0 - x - y, x, y}; - const CfemReal dNt_dx[3] = {-1.0, 1.0, 0.0}; - const CfemReal dNt_dy[3] = {-1.0, 0.0, 1.0}; - - const CfemReal Nl[2] = {o2 * (1.0 - z), o2 * (1.0 + z)}; - const CfemReal dNl_dz[2] = {-o2, o2}; - - for (CfemInt t = 0; t < 3; ++t) { - for (CfemInt l = 0; l < 2; ++l) { - const CfemInt node = node_map[t][l]; - dN_dxi[q][node] = dNt_dx[t] * Nl[l]; - dN_deta[q][node] = dNt_dy[t] * Nl[l]; - dN_dzeta[q][node] = Nt[t] * dNl_dz[l]; - } - } - } - } - - void evaluateShapesSecondDerivativesBatch(const DynamicVector& xiPoints, - DynamicMatrix& d2N_dxi2, - DynamicMatrix& d2N_deta2, - DynamicMatrix& d2N_dzeta2, - DynamicMatrix& d2N_dxideta, - DynamicMatrix& d2N_dxidzeta, - DynamicMatrix& d2N_detadzeta) const override - { - const CfemInt nQp = static_cast(xiPoints.size()); - constexpr CfemReal o2 = 0.5; - - for (CfemInt q = 0; q < nQp; ++q) { - const CfemReal dNt_dx[3] = {-1.0, 1.0, 0.0}; - const CfemReal dNt_dy[3] = {-1.0, 0.0, 1.0}; - const CfemReal dNl_dz[2] = {-o2, o2}; - - for (CfemInt t = 0; t < 3; ++t) { - for (CfemInt l = 0; l < 2; ++l) { - const CfemInt node = node_map[t][l]; - - // Les dérivées secondes pures sont nulles pour un champ linéaire (P1xP1) - d2N_dxi2[q][node] = 0.0; - d2N_deta2[q][node] = 0.0; - d2N_dzeta2[q][node] = 0.0; - d2N_dxideta[q][node] = 0.0; - - // Les dérivées croisées impliquant 'z' survivent au produit tensoriel ! - d2N_dxidzeta[q][node] = dNt_dx[t] * dNl_dz[l]; - d2N_detadzeta[q][node] = dNt_dy[t] * dNl_dz[l]; - } - } - } - } - - Vector3D projectFacetRefCoordToCellRefCoord(CfemInt localFacetId, const Vector3D& xiFacet) const override{ - return cfem::reference_elem::utils::projectFacetRefCoordToPrismRefCoord(localFacetId, xiFacet); // Make the compiler happy - } - - std::span getFaceNodes(CfemInt iFace) const override { - return cfem::reference_elem::utils::prism6_face_nodes[iFace]; - } }; - - class P2ReferencePrism : public ReferenceElement { - protected: + struct P2PrismKernel { // Mapping strict selon la connectivité de P2ReferencePrism // t (Triangle P2) : 0..5 // l (Ligne P2) : 0 (-1), 1 (0), 2 (+1) static constexpr CfemInt node_map[6][3] = { - {0, 12, 3}, // Sommet 0 (0,0) - {1, 13, 4}, // Sommet 1 (1,0) - {2, 14, 5}, // Sommet 2 (0,1) - {6, 15, 9}, // Milieu 3 (0.5,0) - {7, 16, 10},// Milieu 4 (0.5,0.5) - {8, 17, 11} // Milieu 5 (0,0.5) + {0, 12, 3}, {1, 13, 4}, {2, 14, 5}, + {6, 15, 9}, {7, 16, 10}, {8, 17, 11} }; - // Helper: Triangle P2 - static inline void eval2D_tri_all(CfemReal x, CfemReal y, + [[gnu::always_inline]] static inline void eval2D_tri_all(CfemReal x, CfemReal y, CfemReal* N, CfemReal* dNx, CfemReal* dNy, - CfemReal* d2Nx, CfemReal* d2Ny, CfemReal* d2Nxy) + CfemReal* d2Nx, CfemReal* d2Ny, CfemReal* d2Nxy) noexcept { const CfemReal L1 = x, L2 = y, L0 = 1.0 - x - y; @@ -302,8 +160,7 @@ namespace cfem{ d2Nx[5] = 0.0; d2Ny[5] = -8.0; d2Nxy[5] = -4.0; } - // Helper: Ligne P2 (Ordre -1.0, 0.0, 1.0) - static inline void eval1D_line_all(CfemReal z, CfemReal* N, CfemReal* dNz, CfemReal* d2Nz) + [[gnu::always_inline]] static inline void eval1D_line_all(CfemReal z, CfemReal* N, CfemReal* dNz, CfemReal* d2Nz) noexcept { const CfemReal z2 = z * z; N[0] = 0.5 * (z2 - z); dNz[0] = z - 0.5; d2Nz[0] = 1.0; @@ -311,296 +168,160 @@ namespace cfem{ N[2] = 0.5 * (z2 + z); dNz[2] = z + 0.5; d2Nz[2] = 1.0; } - public: - P2ReferencePrism() { - m_numNodes = 18; - m_dimension = 3; - m_nodes.resize(m_numNodes); - - // 1. Sommets (0-5) - m_nodes[0] = {0.0, 0.0, -1.0}; m_nodes[1] = {1.0, 0.0, -1.0}; m_nodes[2] = {0.0, 1.0, -1.0}; - m_nodes[3] = {0.0, 0.0, 1.0}; m_nodes[4] = {1.0, 0.0, 1.0}; m_nodes[5] = {0.0, 1.0, 1.0}; - - // 2. Milieux d'arêtes horizontales (6-11) - m_nodes[6] = {0.5, 0.0, -1.0}; m_nodes[7] = {0.5, 0.5, -1.0}; m_nodes[8] = {0.0, 0.5, -1.0}; - m_nodes[9] = {0.5, 0.0, 1.0}; m_nodes[10]= {0.5, 0.5, 1.0}; m_nodes[11]= {0.0, 0.5, 1.0}; - - // 3. Milieux d'arêtes verticales (12-14) - m_nodes[12]= {0.0, 0.0, 0.0}; m_nodes[13]= {1.0, 0.0, 0.0}; m_nodes[14]= {0.0, 1.0, 0.0}; - - // 4. Centres des faces quadrilatérales (15-17) - m_nodes[15]= {0.5, 0.0, 0.0}; m_nodes[16]= {0.5, 0.5, 0.0}; m_nodes[17]= {0.0, 0.5, 0.0}; - } - - void evaluateShapesValues(const Vector3D& xi, - CfemReal* values) const override + [[gnu::always_inline]] static inline void values(CfemReal x, CfemReal y, CfemReal z, + CfemReal* __restrict N) noexcept { CfemReal Nt[6], dNtx[6], dNty[6], d2Ntx[6], d2Nty[6], d2Ntxy[6]; CfemReal Nl[3], dNlz[3], d2Nlz[3]; - eval2D_tri_all(xi.x, xi.y, Nt, dNtx, dNty, d2Ntx, d2Nty, d2Ntxy); - eval1D_line_all(xi.z, Nl, dNlz, d2Nlz); + eval2D_tri_all(x, y, Nt, dNtx, dNty, d2Ntx, d2Nty, d2Ntxy); + eval1D_line_all(z, Nl, dNlz, d2Nlz); for (CfemInt t = 0; t < 6; ++t) { for (CfemInt l = 0; l < 3; ++l) { - values[node_map[t][l]] = Nt[t] * Nl[l]; + N[node_map[t][l]] = Nt[t] * Nl[l]; } } } - void evaluateShapesDerivatives(const Vector3D& xi, - CfemReal* dN_dxi, - CfemReal* dN_deta, - CfemReal* dN_dzeta) const override + [[gnu::always_inline]] static inline void grads(CfemReal x, CfemReal y, CfemReal z, + CfemReal* __restrict dx, + CfemReal* __restrict dy, + CfemReal* __restrict dz) noexcept { CfemReal Nt[6], dNtx[6], dNty[6], d2Ntx[6], d2Nty[6], d2Ntxy[6]; CfemReal Nl[3], dNlz[3], d2Nlz[3]; - eval2D_tri_all(xi.x, xi.y, Nt, dNtx, dNty, d2Ntx, d2Nty, d2Ntxy); - eval1D_line_all(xi.z, Nl, dNlz, d2Nlz); + eval2D_tri_all(x, y, Nt, dNtx, dNty, d2Ntx, d2Nty, d2Ntxy); + eval1D_line_all(z, Nl, dNlz, d2Nlz); for (CfemInt t = 0; t < 6; ++t) { for (CfemInt l = 0; l < 3; ++l) { - const CfemInt node = node_map[t][l]; - dN_dxi[node] = dNtx[t] * Nl[l]; - dN_deta[node] = dNty[t] * Nl[l]; - dN_dzeta[node] = Nt[t] * dNlz[l]; + const int node = node_map[t][l]; + dx[node] = dNtx[t] * Nl[l]; + dy[node] = dNty[t] * Nl[l]; + dz[node] = Nt[t] * dNlz[l]; } } } - void evaluateShapesSecondDerivatives(const Vector3D& xi, - CfemReal* d2N_dxi2, - CfemReal* d2N_deta2, - CfemReal* d2N_dzeta2, - CfemReal* d2N_dxideta, - CfemReal* d2N_dxidzeta, - CfemReal* d2N_detadzeta) const override + [[gnu::always_inline]] static inline void hess(CfemReal x, CfemReal y, CfemReal z, + CfemReal* __restrict d2x, CfemReal* __restrict d2y, CfemReal* __restrict d2z, + CfemReal* __restrict dxy, CfemReal* __restrict dxz, CfemReal* __restrict dyz) noexcept { CfemReal Nt[6], dNtx[6], dNty[6], d2Ntx[6], d2Nty[6], d2Ntxy[6]; CfemReal Nl[3], dNlz[3], d2Nlz[3]; - eval2D_tri_all(xi.x, xi.y, Nt, dNtx, dNty, d2Ntx, d2Nty, d2Ntxy); - eval1D_line_all(xi.z, Nl, dNlz, d2Nlz); + eval2D_tri_all(x, y, Nt, dNtx, dNty, d2Ntx, d2Nty, d2Ntxy); + eval1D_line_all(z, Nl, dNlz, d2Nlz); for (CfemInt t = 0; t < 6; ++t) { for (CfemInt l = 0; l < 3; ++l) { - const CfemInt node = node_map[t][l]; + const int node = node_map[t][l]; - // Règle du produit tensoriel pour les dérivées secondes - d2N_dxi2[node] = d2Ntx[t] * Nl[l]; - d2N_deta2[node] = d2Nty[t] * Nl[l]; - d2N_dzeta2[node] = Nt[t] * d2Nlz[l]; - - d2N_dxideta[node] = d2Ntxy[t] * Nl[l]; - d2N_dxidzeta[node] = dNtx[t] * dNlz[l]; - d2N_detadzeta[node] = dNty[t] * dNlz[l]; - } - } - } - - void evaluateShapesValuesBatch(const DynamicVector& xiPoints, - DynamicMatrix& valuesBatch) const override - { - const CfemInt nQp = static_cast(xiPoints.size()); - for (CfemInt q = 0; q < nQp; ++q) { - CfemReal Nt[6], dNtx[6], dNty[6], d2Ntx[6], d2Nty[6], d2Ntxy[6]; - CfemReal Nl[3], dNlz[3], d2Nlz[3]; - - eval2D_tri_all(xiPoints[q].x, xiPoints[q].y, Nt, dNtx, dNty, d2Ntx, d2Nty, d2Ntxy); - eval1D_line_all(xiPoints[q].z, Nl, dNlz, d2Nlz); - - for (CfemInt t = 0; t < 6; ++t) { - for (CfemInt l = 0; l < 3; ++l) { - valuesBatch[q][node_map[t][l]] = Nt[t] * Nl[l]; - } - } - } - } - - void evaluateShapesDerivativesBatch(const DynamicVector& xiPoints, - DynamicMatrix& dN_dxi, - DynamicMatrix& dN_deta, - DynamicMatrix& dN_dzeta) const override - { - const CfemInt nQp = static_cast(xiPoints.size()); - for (CfemInt q = 0; q < nQp; ++q) { - CfemReal Nt[6], dNtx[6], dNty[6], d2Ntx[6], d2Nty[6], d2Ntxy[6]; - CfemReal Nl[3], dNlz[3], d2Nlz[3]; - - eval2D_tri_all(xiPoints[q].x, xiPoints[q].y, Nt, dNtx, dNty, d2Ntx, d2Nty, d2Ntxy); - eval1D_line_all(xiPoints[q].z, Nl, dNlz, d2Nlz); - - for (CfemInt t = 0; t < 6; ++t) { - for (CfemInt l = 0; l < 3; ++l) { - const CfemInt node = node_map[t][l]; - dN_dxi[q][node] = dNtx[t] * Nl[l]; - dN_deta[q][node] = dNty[t] * Nl[l]; - dN_dzeta[q][node] = Nt[t] * dNlz[l]; - } - } - } - } - - void evaluateShapesSecondDerivativesBatch(const DynamicVector& xiPoints, - DynamicMatrix& d2N_dxi2, - DynamicMatrix& d2N_deta2, - DynamicMatrix& d2N_dzeta2, - DynamicMatrix& d2N_dxideta, - DynamicMatrix& d2N_dxidzeta, - DynamicMatrix& d2N_detadzeta) const override - { - const CfemInt nQp = static_cast(xiPoints.size()); - for (CfemInt q = 0; q < nQp; ++q) { - CfemReal Nt[6], dNtx[6], dNty[6], d2Ntx[6], d2Nty[6], d2Ntxy[6]; - CfemReal Nl[3], dNlz[3], d2Nlz[3]; - - eval2D_tri_all(xiPoints[q].x, xiPoints[q].y, Nt, dNtx, dNty, d2Ntx, d2Nty, d2Ntxy); - eval1D_line_all(xiPoints[q].z, Nl, dNlz, d2Nlz); - - for (CfemInt t = 0; t < 6; ++t) { - for (CfemInt l = 0; l < 3; ++l) { - const CfemInt node = node_map[t][l]; - - // Règle du produit tensoriel pour les dérivées secondes - d2N_dxi2[q][node] = d2Ntx[t] * Nl[l]; - d2N_deta2[q][node] = d2Nty[t] * Nl[l]; - d2N_dzeta2[q][node] = Nt[t] * d2Nlz[l]; - - d2N_dxideta[q][node] = d2Ntxy[t] * Nl[l]; - d2N_dxidzeta[q][node] = dNtx[t] * dNlz[l]; - d2N_detadzeta[q][node] = dNty[t] * dNlz[l]; - } + d2x[node] = d2Ntx[t] * Nl[l]; + d2y[node] = d2Nty[t] * Nl[l]; + d2z[node] = Nt[t] * d2Nlz[l]; + + dxy[node] = d2Ntxy[t] * Nl[l]; + dxz[node] = dNtx[t] * dNlz[l]; + dyz[node] = dNty[t] * dNlz[l]; } } } - - Vector3D projectFacetRefCoordToCellRefCoord(CfemInt localFacetId, const Vector3D& xiFacet) const override{ - return cfem::reference_elem::utils::projectFacetRefCoordToPrismRefCoord(localFacetId, xiFacet); // Make the compiler happy - } - - std::span getFaceNodes(CfemInt iFace) const override { - return cfem::reference_elem::utils::prism18_face_nodes[iFace]; - } }; - class MiniReferencePrism : public ReferenceElement { - public: - MiniReferencePrism() { - m_numNodes = 7; - m_dimension = 3; - m_nodes.resize(7); - m_nodes[0]={0,0,-1}; m_nodes[1]={1,0,-1}; m_nodes[2]={0,1,-1}; - m_nodes[3]={0,0, 1}; m_nodes[4]={1,0, 1}; m_nodes[5]={0,1, 1}; - m_nodes[6]={1.0/3.0, 1.0/3.0, 0.0}; // Bulle - } - - void evaluateShapesValues(const Vector3D& xi, - CfemReal* values) const override + struct MiniPrismKernel { + [[gnu::always_inline]] static inline void values(CfemReal x, CfemReal y, CfemReal z, + CfemReal* __restrict N) noexcept { constexpr CfemReal o6 = 1.0 / 6.0; - const CfemReal r = xi.x; - const CfemReal s = xi.y; - const CfemReal t = xi.z; - - const CfemReal L[3] = {1.0 - r - s, r, s}; - const CfemReal M[2] = {0.5 * (1.0 - t), 0.5 * (1.0 + t)}; + const CfemReal L[3] = {1.0 - x - y, x, y}; + const CfemReal M[2] = {0.5 * (1.0 - z), 0.5 * (1.0 + z)}; // Calcul de la Bulle (Nœud 6) const CfemReal triB = 27.0 * L[0] * L[1] * L[2]; - const CfemReal zB = 1.0 - t * t; + const CfemReal zB = 1.0 - z * z; const CfemReal B = triB * zB; - values[6] = B; + N[6] = B; - // 2. Fonctions des Sommets (0-5) avec correction + // Fonctions des Sommets (0-5) avec correction const CfemReal correction = B * o6; for (CfemInt i = 0; i < 6; ++i) { - const CfemInt iT = i % 3; - const CfemInt iZ = i / 3; - values[i] = L[iT] * M[iZ] - correction; + const int iT = i % 3; + const int iZ = i / 3; + N[i] = L[iT] * M[iZ] - correction; } } - void evaluateShapesDerivatives(const Vector3D& xi, - CfemReal* dN_dxi, - CfemReal* dN_deta, - CfemReal* dN_dzeta) const override + [[gnu::always_inline]] static inline void grads(CfemReal x, CfemReal y, CfemReal z, + CfemReal* __restrict dx, + CfemReal* __restrict dy, + CfemReal* __restrict dz) noexcept { constexpr CfemReal o6 = 1.0 / 6.0; - const CfemReal r = xi.x; - const CfemReal s = xi.y; - const CfemReal t = xi.z; - - const CfemReal L[3] = {1.0 - r - s, r, s}; - const CfemReal M[2] = {0.5 * (1.0 - t), 0.5 * (1.0 + t)}; + const CfemReal L[3] = {1.0 - x - y, x, y}; + const CfemReal M[2] = {0.5 * (1.0 - z), 0.5 * (1.0 + z)}; const CfemReal dM[2] = {-0.5, 0.5}; // Gradients de la bulle const CfemReal triB = 27.0 * L[0] * L[1] * L[2]; - const CfemReal zB = 1.0 - t * t; + const CfemReal zB = 1.0 - z * z; - const CfemReal gBx = 27.0 * s * (1.0 - 2.0*r - s) * zB; - const CfemReal gBy = 27.0 * r * (1.0 - r - 2.0*s) * zB; - const CfemReal gBz = triB * (-2.0 * t); + const CfemReal gBx = 27.0 * y * (1.0 - 2.0*x - y) * zB; + const CfemReal gBy = 27.0 * x * (1.0 - x - 2.0*y) * zB; + const CfemReal gBz = triB * (-2.0 * z); - dN_dxi[6] = gBx; - dN_deta[6] = gBy; - dN_dzeta[6] = gBz; + dx[6] = gBx; + dy[6] = gBy; + dz[6] = gBz; - // Gradients des sommets avec correction + // Gradients des sommets avec correction const CfemReal c_dx = gBx * o6; const CfemReal c_dy = gBy * o6; const CfemReal c_dz = gBz * o6; for (CfemInt i = 0; i < 6; ++i) { - const CfemInt iT = i % 3; - const CfemInt iZ = i / 3; + const int iT = i % 3; + const int iZ = i / 3; const CfemReal drL = (iT == 0 ? -1.0 : (iT == 1 ? 1.0 : 0.0)); const CfemReal dsL = (iT == 0 ? -1.0 : (iT == 2 ? 1.0 : 0.0)); - dN_dxi[i] = drL * M[iZ] - c_dx; - dN_deta[i] = dsL * M[iZ] - c_dy; - dN_dzeta[i] = L[iT] * dM[iZ] - c_dz; + dx[i] = drL * M[iZ] - c_dx; + dy[i] = dsL * M[iZ] - c_dy; + dz[i] = L[iT] * dM[iZ] - c_dz; } } - void evaluateShapesSecondDerivatives(const Vector3D& xi, - CfemReal* d2N_dxi2, - CfemReal* d2N_deta2, - CfemReal* d2N_dzeta2, - CfemReal* d2N_dxideta, - CfemReal* d2N_dxidzeta, - CfemReal* d2N_detadzeta) const override + [[gnu::always_inline]] static inline void hess(CfemReal x, CfemReal y, CfemReal z, + CfemReal* __restrict d2x, CfemReal* __restrict d2y, CfemReal* __restrict d2z, + CfemReal* __restrict dxy, CfemReal* __restrict dxz, CfemReal* __restrict dyz) noexcept { constexpr CfemReal o6 = 1.0 / 6.0; - const CfemReal r = xi.x; - const CfemReal s = xi.y; - const CfemReal t = xi.z; - - const CfemReal L[3] = {1.0 - r - s, r, s}; + const CfemReal L[3] = {1.0 - x - y, x, y}; const CfemReal dM[2] = {-0.5, 0.5}; const CfemReal triB = 27.0 * L[0] * L[1] * L[2]; - const CfemReal zB = 1.0 - t * t; + const CfemReal zB = 1.0 - z * z; - // Hessiens de la bulle (calculs simplifiés sans divisions par zéro) - const CfemReal hBxx = -54.0 * s * zB; - const CfemReal hByy = -54.0 * r * zB; + // Hessiens de la bulle + const CfemReal hBxx = -54.0 * y * zB; + const CfemReal hByy = -54.0 * x * zB; const CfemReal hBzz = -2.0 * triB; - const CfemReal hBxy = 27.0 * (1.0 - 2.0*r - 2.0*s) * zB; - const CfemReal hBxz = -54.0 * s * t * (1.0 - 2.0*r - s); // CORRECTION: evite (gBx / zB) * (-2t) - const CfemReal hByz = -54.0 * r * t * (1.0 - r - 2.0*s); // CORRECTION: evite (gBy / zB) * (-2t) + const CfemReal hBxy = 27.0 * (1.0 - 2.0*x - 2.0*y) * zB; + const CfemReal hBxz = -54.0 * y * z * (1.0 - 2.0*x - y); + const CfemReal hByz = -54.0 * x * z * (1.0 - x - 2.0*y); - d2N_dxi2[6] = hBxx; - d2N_deta2[6] = hByy; - d2N_dzeta2[6] = hBzz; - d2N_dxideta[6] = hBxy; - d2N_dxidzeta[6] = hBxz; - d2N_detadzeta[6] = hByz; + d2x[6] = hBxx; + d2y[6] = hByy; + d2z[6] = hBzz; + dxy[6] = hBxy; + dxz[6] = hBxz; + dyz[6] = hByz; // Hessiens des sommets avec correction const CfemReal c_xx = hBxx * o6; @@ -611,252 +332,322 @@ namespace cfem{ const CfemReal c_yz = hByz * o6; for (CfemInt i = 0; i < 6; ++i) { - const CfemInt iT = i % 3; - const CfemInt iZ = i / 3; + const int iT = i % 3; + const int iZ = i / 3; const CfemReal drL = (iT == 0 ? -1.0 : (iT == 1 ? 1.0 : 0.0)); const CfemReal dsL = (iT == 0 ? -1.0 : (iT == 2 ? 1.0 : 0.0)); - d2N_dxi2[i] = -c_xx; - d2N_deta2[i] = -c_yy; - d2N_dzeta2[i] = -c_zz; - d2N_dxideta[i] = -c_xy; - // La partie croisée linéaire-linéaire n'est non nulle que pour les couples impliquant z - d2N_dxidzeta[i] = drL * dM[iZ] - c_xz; - d2N_detadzeta[i] = dsL * dM[iZ] - c_yz; + d2x[i] = -c_xx; + d2y[i] = -c_yy; + d2z[i] = -c_zz; + dxy[i] = -c_xy; + dxz[i] = drL * dM[iZ] - c_xz; + dyz[i] = dsL * dM[iZ] - c_yz; } } + }; - void evaluateShapesValuesBatch(const DynamicVector& xiPoints, - DynamicMatrix& valuesBatch) const override - { - const CfemInt nQp = static_cast(xiPoints.size()); - constexpr CfemReal o6 = 1.0 / 6.0; + // ========================================================================= + // KERNEL PRISME P0 (1 nœud, constant) + // ========================================================================= + struct P0PrismKernel { + [[gnu::always_inline]] static inline void values(CfemReal* __restrict N) noexcept { + N[0] = 1.0; + } + + [[gnu::always_inline]] static inline void grads(CfemReal* __restrict dx, + CfemReal* __restrict dy, + CfemReal* __restrict dz) noexcept { + dx[0] = dy[0] = dz[0] = 0.0; + } + + [[gnu::always_inline]] static inline void hess(CfemReal* __restrict d2x, CfemReal* __restrict d2y, CfemReal* __restrict d2z, + CfemReal* __restrict dxy, CfemReal* __restrict dxz, CfemReal* __restrict dyz) noexcept { + d2x[0] = d2y[0] = d2z[0] = dxy[0] = dxz[0] = dyz[0] = 0.0; + } + }; + +} +namespace cfem{ + + /** + * @brief P1 Reference Prism (Wedge) (6 nodes, P1) + */ + class P1ReferencePrism : public ReferenceElement { + public: + P1ReferencePrism() { + m_numNodes = 6; + m_dimension = 3; + m_nodes.resize(m_numNodes); + // VTK order : Tri Z=-1 (0,1,2), puis Z=1 (3,4,5) + m_nodes[0] = {0,0,-1}; m_nodes[1] = {1,0,-1}; m_nodes[2] = {0,1,-1}; + m_nodes[3] = {0,0, 1}; m_nodes[4] = {1,0, 1}; m_nodes[5] = {0,1, 1}; + } + + // ========================================================================= + // SCALAR INTERFACES + // ========================================================================= + + void evaluateShapesValues(const Vector3D& xi, ShapeInfo& info) const override { + reference_elem::kernels::P1PrismKernel::values(xi.x, xi.y, xi.z, info.values()); + } + + void evaluateShapesDerivatives(const Vector3D& xi, ShapeInfo& info) const override { + reference_elem::kernels::P1PrismKernel::grads(xi.x, xi.y, xi.z, + info.dN_dxi(), info.dN_deta(), info.dN_dzeta()); + } + + void evaluateShapesSecondDerivatives(const Vector3D& xi, ShapeInfo& info) const override { + reference_elem::kernels::P1PrismKernel::hess(xi.x, xi.y, xi.z, + info.d2N_dxi2(), info.d2N_deta2(), info.d2N_dzeta2(), + info.d2N_dxideta(), info.d2N_dxidzeta(), info.d2N_detadzeta()); + } + + // ===================================================================== + // HPC BATCH INTERFACES + // ===================================================================== + + void evaluateShapesValuesBatch(const DynamicVector& xiPoints, ShapeInfoBatch& shapeBatch) const override { + const CfemInt nQp = shapeBatch.numQuadraturePoints; for (CfemInt q = 0; q < nQp; ++q) { - const CfemReal r = xiPoints[q].x; - const CfemReal s = xiPoints[q].y; - const CfemReal t = xiPoints[q].z; - - const CfemReal L[3] = {1.0 - r - s, r, s}; - const CfemReal M[2] = {0.5 * (1.0 - t), 0.5 * (1.0 + t)}; - - // 1. Calcul de la Bulle (Nœud 6) - const CfemReal triB = 27.0 * L[0] * L[1] * L[2]; - const CfemReal zB = 1.0 - t * t; - const CfemReal B = triB * zB; - - valuesBatch[q][6] = B; - - // 2. Fonctions des Sommets (0-5) avec correction - const CfemReal correction = B * o6; - for (CfemInt i = 0; i < 6; ++i) { - const CfemInt iT = i % 3; - const CfemInt iZ = i / 3; - valuesBatch[q][i] = L[iT] * M[iZ] - correction; - } + reference_elem::kernels::P1PrismKernel::values(xiPoints[q].x, xiPoints[q].y, xiPoints[q].z, + shapeBatch.values(q)); } } - void evaluateShapesDerivativesBatch(const DynamicVector& xiPoints, - DynamicMatrix& dN_dxi, - DynamicMatrix& dN_deta, - DynamicMatrix& dN_dzeta) const override - { - const CfemInt nQp = static_cast(xiPoints.size()); - constexpr CfemReal o6 = 1.0 / 6.0; + void evaluateShapesDerivativesBatch(const DynamicVector& xiPoints, ShapeInfoBatch& shapeBatch) const override { + const CfemInt nQp = shapeBatch.numQuadraturePoints; + for (CfemInt q = 0; q < nQp; ++q) { + reference_elem::kernels::P1PrismKernel::grads(xiPoints[q].x, xiPoints[q].y, xiPoints[q].z, + shapeBatch.dN_dxi(q), shapeBatch.dN_deta(q), shapeBatch.dN_dzeta(q)); + } + } + void evaluateShapesSecondDerivativesBatch(const DynamicVector& xiPoints, ShapeInfoBatch& shapeBatch) const override { + const CfemInt nQp = shapeBatch.numQuadraturePoints; for (CfemInt q = 0; q < nQp; ++q) { - const CfemReal r = xiPoints[q].x; - const CfemReal s = xiPoints[q].y; - const CfemReal t = xiPoints[q].z; - - const CfemReal L[3] = {1.0 - r - s, r, s}; - const CfemReal M[2] = {0.5 * (1.0 - t), 0.5 * (1.0 + t)}; - const CfemReal dM[2] = {-0.5, 0.5}; - - // Gradients de la bulle - const CfemReal triB = 27.0 * L[0] * L[1] * L[2]; - const CfemReal zB = 1.0 - t * t; - - const CfemReal gBx = 27.0 * s * (1.0 - 2.0*r - s) * zB; - const CfemReal gBy = 27.0 * r * (1.0 - r - 2.0*s) * zB; - const CfemReal gBz = triB * (-2.0 * t); - - dN_dxi[q][6] = gBx; - dN_deta[q][6] = gBy; - dN_dzeta[q][6] = gBz; - - // Gradients des sommets avec correction - const CfemReal c_dx = gBx * o6; - const CfemReal c_dy = gBy * o6; - const CfemReal c_dz = gBz * o6; - - for (CfemInt i = 0; i < 6; ++i) { - const CfemInt iT = i % 3; - const CfemInt iZ = i / 3; - const CfemReal drL = (iT == 0 ? -1.0 : (iT == 1 ? 1.0 : 0.0)); - const CfemReal dsL = (iT == 0 ? -1.0 : (iT == 2 ? 1.0 : 0.0)); - - dN_dxi[q][i] = drL * M[iZ] - c_dx; - dN_deta[q][i] = dsL * M[iZ] - c_dy; - dN_dzeta[q][i] = L[iT] * dM[iZ] - c_dz; - } + reference_elem::kernels::P1PrismKernel::hess(xiPoints[q].x, xiPoints[q].y, xiPoints[q].z, + shapeBatch.d2N_dxi2(q), shapeBatch.d2N_deta2(q), shapeBatch.d2N_dzeta2(q), + shapeBatch.d2N_dxideta(q), shapeBatch.d2N_dxidzeta(q), shapeBatch.d2N_detadzeta(q)); } } - void evaluateShapesSecondDerivativesBatch(const DynamicVector& xiPoints, - DynamicMatrix& d2N_dxi2, - DynamicMatrix& d2N_deta2, - DynamicMatrix& d2N_dzeta2, - DynamicMatrix& d2N_dxideta, - DynamicMatrix& d2N_dxidzeta, - DynamicMatrix& d2N_detadzeta) const override - { - const CfemInt nQp = static_cast(xiPoints.size()); - constexpr CfemReal o6 = 1.0 / 6.0; + Vector3D projectFacetRefCoordToCellRefCoord(CfemInt localFacetId, const Vector3D& xiFacet) const override { + return cfem::reference_elem::utils::projectFacetRefCoordToPrismRefCoord(localFacetId, xiFacet); + } + + std::span getFaceNodes(CfemInt iFace) const override { + return cfem::reference_elem::utils::prism6_face_nodes[iFace]; + } + }; + + /** + * @brief P2 Reference Prism (Wedge) (18 nodes, P2) + */ + class P2ReferencePrism : public ReferenceElement { + public: + P2ReferencePrism() { + m_numNodes = 18; + m_dimension = 3; + m_nodes.resize(m_numNodes); + + // 1. Sommets (0-5) + m_nodes[0] = {0.0, 0.0, -1.0}; m_nodes[1] = {1.0, 0.0, -1.0}; m_nodes[2] = {0.0, 1.0, -1.0}; + m_nodes[3] = {0.0, 0.0, 1.0}; m_nodes[4] = {1.0, 0.0, 1.0}; m_nodes[5] = {0.0, 1.0, 1.0}; + + // 2. Milieux d'arêtes horizontales (6-11) + m_nodes[6] = {0.5, 0.0, -1.0}; m_nodes[7] = {0.5, 0.5, -1.0}; m_nodes[8] = {0.0, 0.5, -1.0}; + m_nodes[9] = {0.5, 0.0, 1.0}; m_nodes[10]= {0.5, 0.5, 1.0}; m_nodes[11]= {0.0, 0.5, 1.0}; + + // 3. Milieux d'arêtes verticales (12-14) + m_nodes[12]= {0.0, 0.0, 0.0}; m_nodes[13]= {1.0, 0.0, 0.0}; m_nodes[14]= {0.0, 1.0, 0.0}; + + // 4. Centres des faces quadrilatérales (15-17) + m_nodes[15]= {0.5, 0.0, 0.0}; m_nodes[16]= {0.5, 0.5, 0.0}; m_nodes[17]= {0.0, 0.5, 0.0}; + } + + // ========================================================================= + // SCALAR INTERFACES + // ========================================================================= + + void evaluateShapesValues(const Vector3D& xi, ShapeInfo& info) const override { + reference_elem::kernels::P2PrismKernel::values(xi.x, xi.y, xi.z, info.values()); + } + + void evaluateShapesDerivatives(const Vector3D& xi, ShapeInfo& info) const override { + reference_elem::kernels::P2PrismKernel::grads(xi.x, xi.y, xi.z, + info.dN_dxi(), info.dN_deta(), info.dN_dzeta()); + } + + void evaluateShapesSecondDerivatives(const Vector3D& xi, ShapeInfo& info) const override { + reference_elem::kernels::P2PrismKernel::hess(xi.x, xi.y, xi.z, + info.d2N_dxi2(), info.d2N_deta2(), info.d2N_dzeta2(), + info.d2N_dxideta(), info.d2N_dxidzeta(), info.d2N_detadzeta()); + } + + // ===================================================================== + // HPC BATCH INTERFACES + // ===================================================================== + + void evaluateShapesValuesBatch(const DynamicVector& xiPoints, ShapeInfoBatch& shapeBatch) const override { + const CfemInt nQp = shapeBatch.numQuadraturePoints; for (CfemInt q = 0; q < nQp; ++q) { - const CfemReal r = xiPoints[q].x; - const CfemReal s = xiPoints[q].y; - const CfemReal t = xiPoints[q].z; - - const CfemReal L[3] = {1.0 - r - s, r, s}; - const CfemReal dM[2] = {-0.5, 0.5}; - - const CfemReal triB = 27.0 * L[0] * L[1] * L[2]; - const CfemReal zB = 1.0 - t * t; - - // Hessiens de la bulle (calculs simplifiés sans divisions par zéro) - const CfemReal hBxx = -54.0 * s * zB; - const CfemReal hByy = -54.0 * r * zB; - const CfemReal hBzz = -2.0 * triB; - const CfemReal hBxy = 27.0 * (1.0 - 2.0*r - 2.0*s) * zB; - const CfemReal hBxz = -54.0 * s * t * (1.0 - 2.0*r - s); // CORRECTION: evite (gBx / zB) * (-2t) - const CfemReal hByz = -54.0 * r * t * (1.0 - r - 2.0*s); // CORRECTION: evite (gBy / zB) * (-2t) - - d2N_dxi2[q][6] = hBxx; - d2N_deta2[q][6] = hByy; - d2N_dzeta2[q][6] = hBzz; - d2N_dxideta[q][6] = hBxy; - d2N_dxidzeta[q][6] = hBxz; - d2N_detadzeta[q][6] = hByz; - - // Hessiens des sommets avec correction - const CfemReal c_xx = hBxx * o6; - const CfemReal c_yy = hByy * o6; - const CfemReal c_zz = hBzz * o6; - const CfemReal c_xy = hBxy * o6; - const CfemReal c_xz = hBxz * o6; - const CfemReal c_yz = hByz * o6; - - for (CfemInt i = 0; i < 6; ++i) { - const CfemInt iT = i % 3; - const CfemInt iZ = i / 3; - const CfemReal drL = (iT == 0 ? -1.0 : (iT == 1 ? 1.0 : 0.0)); - const CfemReal dsL = (iT == 0 ? -1.0 : (iT == 2 ? 1.0 : 0.0)); - - d2N_dxi2[q][i] = -c_xx; - d2N_deta2[q][i] = -c_yy; - d2N_dzeta2[q][i] = -c_zz; - d2N_dxideta[q][i] = -c_xy; - // La partie croisée linéaire-linéaire n'est non nulle que pour les couples impliquant z - d2N_dxidzeta[q][i] = drL * dM[iZ] - c_xz; - d2N_detadzeta[q][i] = dsL * dM[iZ] - c_yz; - } + reference_elem::kernels::P2PrismKernel::values(xiPoints[q].x, xiPoints[q].y, xiPoints[q].z, + shapeBatch.values(q)); + } + } + + void evaluateShapesDerivativesBatch(const DynamicVector& xiPoints, ShapeInfoBatch& shapeBatch) const override { + const CfemInt nQp = shapeBatch.numQuadraturePoints; + for (CfemInt q = 0; q < nQp; ++q) { + reference_elem::kernels::P2PrismKernel::grads(xiPoints[q].x, xiPoints[q].y, xiPoints[q].z, + shapeBatch.dN_dxi(q), shapeBatch.dN_deta(q), shapeBatch.dN_dzeta(q)); + } + } + + void evaluateShapesSecondDerivativesBatch(const DynamicVector& xiPoints, ShapeInfoBatch& shapeBatch) const override { + const CfemInt nQp = shapeBatch.numQuadraturePoints; + for (CfemInt q = 0; q < nQp; ++q) { + reference_elem::kernels::P2PrismKernel::hess(xiPoints[q].x, xiPoints[q].y, xiPoints[q].z, + shapeBatch.d2N_dxi2(q), shapeBatch.d2N_deta2(q), shapeBatch.d2N_dzeta2(q), + shapeBatch.d2N_dxideta(q), shapeBatch.d2N_dxidzeta(q), shapeBatch.d2N_detadzeta(q)); + } + } + + Vector3D projectFacetRefCoordToCellRefCoord(CfemInt localFacetId, const Vector3D& xiFacet) const override { + return cfem::reference_elem::utils::projectFacetRefCoordToPrismRefCoord(localFacetId, xiFacet); + } + + std::span getFaceNodes(CfemInt iFace) const override { + return cfem::reference_elem::utils::prism18_face_nodes[iFace]; + } + }; + + /** + * @brief Mini Reference Prism (7 nodes: 6 vertices + 1 bubble) + */ + class MiniReferencePrism : public ReferenceElement { + public: + MiniReferencePrism() { + m_numNodes = 7; + m_dimension = 3; + m_nodes.resize(m_numNodes); + m_nodes[0] = {0.0, 0.0, -1.0}; m_nodes[1] = {1.0, 0.0, -1.0}; m_nodes[2] = {0.0, 1.0, -1.0}; + m_nodes[3] = {0.0, 0.0, 1.0}; m_nodes[4] = {1.0, 0.0, 1.0}; m_nodes[5] = {0.0, 1.0, 1.0}; + m_nodes[6] = {1.0/3.0, 1.0/3.0, 0.0}; // Bulle au centre de gravité + } + + // ========================================================================= + // SCALAR INTERFACES + // ========================================================================= + + void evaluateShapesValues(const Vector3D& xi, ShapeInfo& info) const override { + reference_elem::kernels::MiniPrismKernel::values(xi.x, xi.y, xi.z, info.values()); + } + + void evaluateShapesDerivatives(const Vector3D& xi, ShapeInfo& info) const override { + reference_elem::kernels::MiniPrismKernel::grads(xi.x, xi.y, xi.z, + info.dN_dxi(), info.dN_deta(), info.dN_dzeta()); + } + + void evaluateShapesSecondDerivatives(const Vector3D& xi, ShapeInfo& info) const override { + reference_elem::kernels::MiniPrismKernel::hess(xi.x, xi.y, xi.z, + info.d2N_dxi2(), info.d2N_deta2(), info.d2N_dzeta2(), + info.d2N_dxideta(), info.d2N_dxidzeta(), info.d2N_detadzeta()); + } + + // ===================================================================== + // HPC BATCH INTERFACES + // ===================================================================== + + void evaluateShapesValuesBatch(const DynamicVector& xiPoints, ShapeInfoBatch& shapeBatch) const override { + const CfemInt nQp = shapeBatch.numQuadraturePoints; + for (CfemInt q = 0; q < nQp; ++q) { + reference_elem::kernels::MiniPrismKernel::values(xiPoints[q].x, xiPoints[q].y, xiPoints[q].z, + shapeBatch.values(q)); + } + } + + void evaluateShapesDerivativesBatch(const DynamicVector& xiPoints, ShapeInfoBatch& shapeBatch) const override { + const CfemInt nQp = shapeBatch.numQuadraturePoints; + for (CfemInt q = 0; q < nQp; ++q) { + reference_elem::kernels::MiniPrismKernel::grads(xiPoints[q].x, xiPoints[q].y, xiPoints[q].z, + shapeBatch.dN_dxi(q), shapeBatch.dN_deta(q), shapeBatch.dN_dzeta(q)); + } + } + + void evaluateShapesSecondDerivativesBatch(const DynamicVector& xiPoints, ShapeInfoBatch& shapeBatch) const override { + const CfemInt nQp = shapeBatch.numQuadraturePoints; + for (CfemInt q = 0; q < nQp; ++q) { + reference_elem::kernels::MiniPrismKernel::hess(xiPoints[q].x, xiPoints[q].y, xiPoints[q].z, + shapeBatch.d2N_dxi2(q), shapeBatch.d2N_deta2(q), shapeBatch.d2N_dzeta2(q), + shapeBatch.d2N_dxideta(q), shapeBatch.d2N_dxidzeta(q), shapeBatch.d2N_detadzeta(q)); } } Vector3D projectFacetRefCoordToCellRefCoord(CfemInt localFacetId, const Vector3D& xiFacet) const override { - return reference_elem::utils::projectFacetRefCoordToPrismRefCoord(localFacetId, xiFacet); + return cfem::reference_elem::utils::projectFacetRefCoordToPrismRefCoord(localFacetId, xiFacet); } + std::span getFaceNodes(CfemInt iFace) const override { - return reference_elem::utils::prism6_face_nodes[iFace]; + return cfem::reference_elem::utils::prism6_face_nodes[iFace]; } }; + /** + * @brief P0 Reference Prism (1 node, piecewise constant) + */ class P0ReferencePrism : public ReferenceElement { public: P0ReferencePrism() { m_numNodes = 1; m_dimension = 3; - - // Un seul nœud au centre de gravité du prisme de référence - // Triangle base (0,0), (1,0), (0,1) -> (1/3, 1/3) - // Segment Z de -1 à 1 -> 0 + // Un seul nœud au centre de gravité m_nodes = { Vector3D{1.0/3.0, 1.0/3.0, 0.0} }; } - void evaluateShapesValues(const Vector3D&, CfemReal* values) const override - { - values[0] = 1.0; + // ========================================================================= + // SCALAR INTERFACES + // ========================================================================= + + void evaluateShapesValues(const Vector3D&, ShapeInfo& info) const override { + reference_elem::kernels::P0PrismKernel::values(info.values()); } - void evaluateShapesDerivatives(const Vector3D&, - CfemReal* dN_dxi, - CfemReal* dN_deta, - CfemReal* dN_dzeta) const override - { - dN_dxi[0] = 0.0; - dN_deta[0] = 0.0; - dN_dzeta[0] = 0.0; - } - - void evaluateShapesSecondDerivatives(const Vector3D&, - CfemReal* d2N_dxi2, - CfemReal* d2N_deta2, - CfemReal* d2N_dzeta2, - CfemReal* d2N_dxideta, - CfemReal* d2N_dxidzeta, - CfemReal* d2N_detadzeta) const override - { - d2N_dxi2[0] = 0.0; - d2N_deta2[0] = 0.0; - d2N_dzeta2[0] = 0.0; - d2N_dxideta[0] = 0.0; - d2N_dxidzeta[0] = 0.0; - d2N_detadzeta[0] = 0.0; + void evaluateShapesDerivatives(const Vector3D&, ShapeInfo& info) const override { + reference_elem::kernels::P0PrismKernel::grads(info.dN_dxi(), info.dN_deta(), info.dN_dzeta()); } - void evaluateShapesValuesBatch(const DynamicVector &xiPoints, DynamicMatrix& values) const override - { - // Constant piecewise field value - const CfemInt nQp = static_cast(xiPoints.size()); + void evaluateShapesSecondDerivatives(const Vector3D&, ShapeInfo& info) const override { + reference_elem::kernels::P0PrismKernel::hess(info.d2N_dxi2(), info.d2N_deta2(), info.d2N_dzeta2(), + info.d2N_dxideta(), info.d2N_dxidzeta(), info.d2N_detadzeta()); + } + + // ===================================================================== + // HPC BATCH INTERFACES + // ===================================================================== + + void evaluateShapesValuesBatch(const DynamicVector& xiPoints, ShapeInfoBatch& shapeBatch) const override { + const CfemInt nQp = shapeBatch.numQuadraturePoints; for (CfemInt q = 0; q < nQp; ++q) { - values[q][0] = 1.0; + reference_elem::kernels::P0PrismKernel::values(shapeBatch.values(q)); } } - void evaluateShapesDerivativesBatch(const DynamicVector & xiPoints, - DynamicMatrix &dN_dxi, - DynamicMatrix &dN_deta, - DynamicMatrix &dN_dzeta) const override - { - // Constant function results in flat zero gradients everywhere - const CfemInt nQp = static_cast(xiPoints.size()); + void evaluateShapesDerivativesBatch(const DynamicVector& xiPoints, ShapeInfoBatch& shapeBatch) const override { + const CfemInt nQp = shapeBatch.numQuadraturePoints; for (CfemInt q = 0; q < nQp; ++q) { - dN_dxi[q][0] = 0.0; - dN_deta[q][0] = 0.0; - dN_dzeta[q][0] = 0.0; + reference_elem::kernels::P0PrismKernel::grads(shapeBatch.dN_dxi(q), shapeBatch.dN_deta(q), shapeBatch.dN_dzeta(q)); } } - void evaluateShapesSecondDerivativesBatch(const DynamicVector &xiPoints, - DynamicMatrix &d2N_dxi2, - DynamicMatrix &d2N_deta2, - DynamicMatrix &d2N_dzeta2, - DynamicMatrix &d2N_dxideta, - DynamicMatrix &d2N_dxidzeta, - DynamicMatrix &d2N_detadzeta) const override - { - // Flat null second derivatives - const CfemInt nQp = static_cast(xiPoints.size()); + void evaluateShapesSecondDerivativesBatch(const DynamicVector& xiPoints, ShapeInfoBatch& shapeBatch) const override { + const CfemInt nQp = shapeBatch.numQuadraturePoints; for (CfemInt q = 0; q < nQp; ++q) { - d2N_dxi2[q][0] = 0.0; - d2N_deta2[q][0] = 0.0; - d2N_dzeta2[q][0] = 0.0; - d2N_dxideta[q][0] = 0.0; - d2N_dxidzeta[q][0] = 0.0; - d2N_detadzeta[q][0] = 0.0; + reference_elem::kernels::P0PrismKernel::hess(shapeBatch.d2N_dxi2(q), shapeBatch.d2N_deta2(q), shapeBatch.d2N_dzeta2(q), + shapeBatch.d2N_dxideta(q), shapeBatch.d2N_dxidzeta(q), shapeBatch.d2N_detadzeta(q)); } } @@ -868,7 +659,6 @@ namespace cfem{ return cfem::reference_elem::utils::prism1_face_nodes[iFace]; } }; - +} #endif -} \ No newline at end of file diff --git a/libs/fem/reference_element/include/ReferenceQuad.h b/libs/fem/reference_element/include/ReferenceQuad.h index e08482d..9d0cb83 100644 --- a/libs/fem/reference_element/include/ReferenceQuad.h +++ b/libs/fem/reference_element/include/ReferenceQuad.h @@ -20,642 +20,521 @@ #define CFEM_REFERENCE_QUAD #include "ReferenceElement.h" +#include +#include -namespace cfem::reference_elem::utils -{ +namespace cfem::reference_elem::utils { - inline Vector3D projectFacetRefCoordToQuadRefCoord(CfemInt localFacetId, - const Vector3D &xiFacet) + inline Vector3D projectFacetRefCoordToQuadRefCoord(CfemInt localFacetId, const Vector3D &xiFacet) { const CfemReal u = xiFacet.x; switch (localFacetId) { - case 0: - return Vector3D(u, -1.0, 0.0); // Face {0,1} : y=-1 - case 1: - return Vector3D(1.0, u, 0.0); // Face {1,2} : x=1 - case 2: - return Vector3D(-u, 1.0, 0.0); // Face {2,3} : y=1 - case 3: - return Vector3D(-1.0, -u, 0.0); // Face {3,0} : x=-1 - default: - CFEM_ERROR << "Invalid facet ID for Quad cell"; + case 0: return Vector3D(u, -1.0, 0.0); // Face {0,1} : y=-1 + case 1: return Vector3D(1.0, u, 0.0); // Face {1,2} : x=1 + case 2: return Vector3D(-u, 1.0, 0.0); // Face {2,3} : y=1 + case 3: return Vector3D(-1.0, -u, 0.0); // Face {3,0} : x=-1 + default: CFEM_ERROR << "Invalid facet ID for Quad cell"; } return Vector3D{}; // Make the compiler happy } - static const std::vector> quad4_face_nodes = { - {0, 1}, {1, 2}, {2, 3}, {3, 0}}; - static const std::vector> quad9_face_nodes = { - {0, 1, 4}, {1, 2, 5}, {2, 3, 6}, {3, 0, 7}}; - static const std::vector> quad1_face_nodes = { - {0}, {0}, {0}, {0}}; -} - -namespace cfem -{ - - class Q1ReferenceQuad : public ReferenceElement - { - public: - Q1ReferenceQuad() - { - m_numNodes = 4; - m_dimension = 2; - m_nodes.resize(m_numNodes); + static const std::array, 4> quad4_face_nodes = {{ + {0, 1}, {1, 2}, {2, 3}, {3, 0} + }}; - m_nodes[0] = Vector3D{-1.0, -1.0, 0.0}; - m_nodes[1] = Vector3D{1.0, -1.0, 0.0}; - m_nodes[2] = Vector3D{1.0, 1.0, 0.0}; - m_nodes[3] = Vector3D{-1.0, 1.0, 0.0}; - } + static const std::array, 4> quad9_face_nodes = {{ + {0, 1, 4}, {1, 2, 5}, {2, 3, 6}, {3, 0, 7} + }}; - void evaluateShapesValues(const Vector3D &xi, - CfemReal *values) const override - { - constexpr CfemReal o4 = 0.25; + static const std::array, 4> quad1_face_nodes = {{ + {0}, {0}, {0}, {0} + }}; +} - const CfemReal xm = 1.0 - xi.x; - const CfemReal xp = 1.0 + xi.x; - const CfemReal ym = 1.0 - xi.y; - const CfemReal yp = 1.0 + xi.y; +namespace cfem::reference_elem::kernels { - values[0] = o4 * xm * ym; - values[1] = o4 * xp * ym; - values[2] = o4 * xp * yp; - values[3] = o4 * xm * yp; - } + // ========================================================================= + // KERNEL QUAD Q1 (4 nœuds, bilinéaire) + // ========================================================================= + struct Q1QuadKernel { + static constexpr CfemReal o4 = 0.25; - void evaluateShapesDerivatives(const Vector3D &xi, - CfemReal *dN_dxi, - CfemReal *dN_deta, - CfemReal *dN_dzeta) const override - { - constexpr CfemReal o4 = 0.25; - - const CfemReal xm = 1.0 - xi.x; - const CfemReal xp = 1.0 + xi.x; - const CfemReal ym = 1.0 - xi.y; - const CfemReal yp = 1.0 + xi.y; - - // Dérivées par rapport à x (xi) - dN_dxi[0] = -o4 * ym; - dN_dxi[1] = o4 * ym; - dN_dxi[2] = o4 * yp; - dN_dxi[3] = -o4 * yp; - - // Dérivées par rapport à y (eta) - dN_deta[0] = -o4 * xm; - dN_deta[1] = -o4 * xp; - dN_deta[2] = o4 * xp; - dN_deta[3] = o4 * xm; - - // 2D : Z est nul - for (CfemInt i = 0; i < 4; ++i) - dN_dzeta[i] = 0.0; - } - - void evaluateShapesSecondDerivatives(const Vector3D &xi, - CfemReal *d2N_dxi2, - CfemReal *d2N_deta2, - CfemReal *d2N_dzeta2, - CfemReal *d2N_dxideta, - CfemReal *d2N_dxidzeta, - CfemReal *d2N_detadzeta) const override + [[gnu::always_inline]] static inline void values(CfemReal x, CfemReal y, CfemReal z, + CfemReal* __restrict N) noexcept { - constexpr CfemReal o4 = 0.25; - - for (CfemInt i = 0; i < 4; ++i) - { - d2N_dxi2[i] = 0.0; - d2N_deta2[i] = 0.0; - d2N_dzeta2[i] = 0.0; - d2N_dxidzeta[i] = 0.0; - d2N_detadzeta[i] = 0.0; - } + const CfemReal xm = 1.0 - x; + const CfemReal xp = 1.0 + x; + const CfemReal ym = 1.0 - y; + const CfemReal yp = 1.0 + y; - // Seule la dérivée croisée xy survit dans un bilinéaire - d2N_dxideta[0] = o4; - d2N_dxideta[1] = -o4; - d2N_dxideta[2] = o4; - d2N_dxideta[3] = -o4; + N[0] = o4 * xm * ym; + N[1] = o4 * xp * ym; + N[2] = o4 * xp * yp; + N[3] = o4 * xm * yp; } - void evaluateShapesValuesBatch(const DynamicVector &xiPoints, - DynamicMatrix &valuesBatch) const override + [[gnu::always_inline]] static inline void grads(CfemReal x, CfemReal y, CfemReal z, + CfemReal* __restrict dx, + CfemReal* __restrict dy, + CfemReal* __restrict dz) noexcept { - const CfemInt nQp = static_cast(xiPoints.size()); - constexpr CfemReal o4 = 0.25; - - for (CfemInt q = 0; q < nQp; ++q) - { - const CfemReal xm = 1.0 - xiPoints[q].x; - const CfemReal xp = 1.0 + xiPoints[q].x; - const CfemReal ym = 1.0 - xiPoints[q].y; - const CfemReal yp = 1.0 + xiPoints[q].y; - - valuesBatch[q][0] = o4 * xm * ym; - valuesBatch[q][1] = o4 * xp * ym; - valuesBatch[q][2] = o4 * xp * yp; - valuesBatch[q][3] = o4 * xm * yp; - } - } + const CfemReal xm = 1.0 - x; + const CfemReal xp = 1.0 + x; + const CfemReal ym = 1.0 - y; + const CfemReal yp = 1.0 + y; - void evaluateShapesDerivativesBatch(const DynamicVector &xiPoints, - DynamicMatrix &dN_dxi, - DynamicMatrix &dN_deta, - DynamicMatrix &dN_dzeta) const override - { - const CfemInt nQp = static_cast(xiPoints.size()); - constexpr CfemReal o4 = 0.25; - - for (CfemInt q = 0; q < nQp; ++q) - { - const CfemReal xm = 1.0 - xiPoints[q].x; - const CfemReal xp = 1.0 + xiPoints[q].x; - const CfemReal ym = 1.0 - xiPoints[q].y; - const CfemReal yp = 1.0 + xiPoints[q].y; - - // Dérivées par rapport à x (xi) - dN_dxi[q][0] = -o4 * ym; - dN_dxi[q][1] = o4 * ym; - dN_dxi[q][2] = o4 * yp; - dN_dxi[q][3] = -o4 * yp; - - // Dérivées par rapport à y (eta) - dN_deta[q][0] = -o4 * xm; - dN_deta[q][1] = -o4 * xp; - dN_deta[q][2] = o4 * xp; - dN_deta[q][3] = o4 * xm; - - // 2D : Z est nul - for (CfemInt i = 0; i < 4; ++i) - dN_dzeta[q][i] = 0.0; - } - } + dx[0] = -o4 * ym; + dx[1] = o4 * ym; + dx[2] = o4 * yp; + dx[3] = -o4 * yp; - void evaluateShapesSecondDerivativesBatch(const DynamicVector &xiPoints, - DynamicMatrix &d2N_dxi2, - DynamicMatrix &d2N_deta2, - DynamicMatrix &d2N_dzeta2, - DynamicMatrix &d2N_dxideta, - DynamicMatrix &d2N_dxidzeta, - DynamicMatrix &d2N_detadzeta) const override - { - const CfemInt nQp = static_cast(xiPoints.size()); - constexpr CfemReal o4 = 0.25; - - for (CfemInt q = 0; q < nQp; ++q) - { - for (CfemInt i = 0; i < 4; ++i) - { - d2N_dxi2[q][i] = 0.0; - d2N_deta2[q][i] = 0.0; - d2N_dzeta2[q][i] = 0.0; - d2N_dxidzeta[q][i] = 0.0; - d2N_detadzeta[q][i] = 0.0; - } + dy[0] = -o4 * xm; + dy[1] = -o4 * xp; + dy[2] = o4 * xp; + dy[3] = o4 * xm; - // Seule la dérivée croisée xy survit dans un bilinéaire - d2N_dxideta[q][0] = o4; - d2N_dxideta[q][1] = -o4; - d2N_dxideta[q][2] = o4; - d2N_dxideta[q][3] = -o4; - } + dz[0] = dz[1] = dz[2] = dz[3] = 0.0; } - Vector3D projectFacetRefCoordToCellRefCoord(CfemInt localFacetId, const Vector3D &xiFacet) const override + [[gnu::always_inline]] static inline void hess(CfemReal x, CfemReal y, CfemReal z, + CfemReal* __restrict d2x, CfemReal* __restrict d2y, CfemReal* __restrict d2z, + CfemReal* __restrict dxy, CfemReal* __restrict dxz, CfemReal* __restrict dyz) noexcept { - return cfem::reference_elem::utils::projectFacetRefCoordToQuadRefCoord(localFacetId, xiFacet); // Make the compiler happy - } + for (CfemInt i = 0; i < 4; ++i) { + d2x[i] = 0.0; + d2y[i] = 0.0; + d2z[i] = 0.0; + dxz[i] = 0.0; + dyz[i] = 0.0; + } - std::span getFaceNodes(CfemInt iFace) const override - { - return cfem::reference_elem::utils::quad4_face_nodes[iFace]; + dxy[0] = o4; + dxy[1] = -o4; + dxy[2] = o4; + dxy[3] = -o4; } }; - class Q2ReferenceQuad : public ReferenceElement - { - protected: - // Helpers 1D P2 (-1.0, 0.0, 1.0) - static inline void eval1D_hessian(CfemReal u, CfemReal *N, CfemReal *dN, CfemReal *d2N) - { - const CfemReal u2 = u * u; - N[0] = 0.5 * (u2 - u); - dN[0] = u - 0.5; - d2N[0] = 1.0; - N[1] = 1.0 - u2; - dN[1] = -2.0 * u; - d2N[1] = -2.0; - N[2] = 0.5 * (u2 + u); - dN[2] = u + 0.5; - d2N[2] = 1.0; - } - - // Connectivité Q2 stricte (X, Y) - // i (X) parcourt: 0 (-1), 1 (0), 2 (1) - // j (Y) parcourt: 0 (-1), 1 (0), 2 (1) + // ========================================================================= + // KERNEL QUAD Q2 (9 nœuds, biquadratique) + // ========================================================================= + struct Q2QuadKernel { static constexpr CfemInt node_map[3][3] = { {0, 7, 3}, // X = -1 | Y = -1, 0, 1 {4, 8, 6}, // X = 0 | Y = -1, 0, 1 {1, 5, 2} // X = +1 | Y = -1, 0, 1 }; - public: - Q2ReferenceQuad() + [[gnu::always_inline]] static inline void eval1D_hessian(CfemReal u, CfemReal* N, CfemReal* dN, CfemReal* d2N) noexcept { - m_numNodes = 9; - m_dimension = 2; - m_nodes.resize(m_numNodes); - // Coordonnées de référence sur [-1, 1] - // Sommets - m_nodes[0] = Vector3D{-1.0, -1.0, 0.0}; // i=0, j=0 - m_nodes[1] = Vector3D{1.0, -1.0, 0.0}; // i=2, j=0 - m_nodes[2] = Vector3D{1.0, 1.0, 0.0}; // i=2, j=2 - m_nodes[3] = Vector3D{-1.0, 1.0, 0.0}; // i=0, j=2 - - // Milieux d'arêtes - m_nodes[4] = Vector3D{0.0, -1.0, 0.0}; // i=1, j=0 - m_nodes[5] = Vector3D{1.0, 0.0, 0.0}; // i=2, j=1 - m_nodes[6] = Vector3D{0.0, 1.0, 0.0}; // i=1, j=2 - m_nodes[7] = Vector3D{-1.0, 0.0, 0.0}; // i=0, j=1 - - // Centre - m_nodes[8] = Vector3D{0.0, 0.0, 0.0}; // i=1, j=1 + const CfemReal u2 = u * u; + N[0] = 0.5 * (u2 - u); dN[0] = u - 0.5; d2N[0] = 1.0; + N[1] = 1.0 - u2; dN[1] = -2.0 * u; d2N[1] = -2.0; + N[2] = 0.5 * (u2 + u); dN[2] = u + 0.5; d2N[2] = 1.0; } - void evaluateShapesValues(const Vector3D &xi, - CfemReal *values) const override + [[gnu::always_inline]] static inline void values(CfemReal x, CfemReal y, CfemReal z, + CfemReal* __restrict N) noexcept { CfemReal Nx[3], Ny[3], dNx[3], dNy[3], d2Nx[3], d2Ny[3]; - eval1D_hessian(xi.x, Nx, dNx, d2Nx); - eval1D_hessian(xi.y, Ny, dNy, d2Ny); - - for (CfemInt i = 0; i < 3; ++i) - { - for (CfemInt j = 0; j < 3; ++j) - { - values[node_map[i][j]] = Nx[i] * Ny[j]; + eval1D_hessian(x, Nx, dNx, d2Nx); + eval1D_hessian(y, Ny, dNy, d2Ny); + + for (CfemInt i = 0; i < 3; ++i) { + for (CfemInt j = 0; j < 3; ++j) { + N[node_map[i][j]] = Nx[i] * Ny[j]; } } } - void evaluateShapesDerivatives(const Vector3D &xi, - CfemReal *dN_dxi, - CfemReal *dN_deta, - CfemReal *dN_dzeta) const override + [[gnu::always_inline]] static inline void grads(CfemReal x, CfemReal y, CfemReal z, + CfemReal* __restrict dx, + CfemReal* __restrict dy, + CfemReal* __restrict dz) noexcept { CfemReal Nx[3], Ny[3], dNx[3], dNy[3], d2Nx[3], d2Ny[3]; - eval1D_hessian(xi.x, Nx, dNx, d2Nx); - eval1D_hessian(xi.y, Ny, dNy, d2Ny); - - for (CfemInt i = 0; i < 3; ++i) - { - for (CfemInt j = 0; j < 3; ++j) - { - const CfemInt node = node_map[i][j]; - dN_dxi[node] = dNx[i] * Ny[j]; - dN_deta[node] = Nx[i] * dNy[j]; - dN_dzeta[node] = 0.0; + eval1D_hessian(x, Nx, dNx, d2Nx); + eval1D_hessian(y, Ny, dNy, d2Ny); + + for (CfemInt i = 0; i < 3; ++i) { + for (CfemInt j = 0; j < 3; ++j) { + const int node = node_map[i][j]; + dx[node] = dNx[i] * Ny[j]; + dy[node] = Nx[i] * dNy[j]; + dz[node] = 0.0; } } } - void evaluateShapesSecondDerivatives(const Vector3D &xi, - CfemReal *d2N_dxi2, - CfemReal *d2N_deta2, - CfemReal *d2N_dzeta2, - CfemReal *d2N_dxideta, - CfemReal *d2N_dxidzeta, - CfemReal *d2N_detadzeta) const override + [[gnu::always_inline]] static inline void hess(CfemReal x, CfemReal y, CfemReal z, + CfemReal* __restrict d2x, CfemReal* __restrict d2y, CfemReal* __restrict d2z, + CfemReal* __restrict dxy, CfemReal* __restrict dxz, CfemReal* __restrict dyz) noexcept { - CfemReal Nx[3], Ny[3], dNx[3], dNy[3], d2Nx[3], d2Ny[3]; - eval1D_hessian(xi.x, Nx, dNx, d2Nx); - eval1D_hessian(xi.y, Ny, dNy, d2Ny); - - for (CfemInt i = 0; i < 3; ++i) - { - for (CfemInt j = 0; j < 3; ++j) - { - const CfemInt node = node_map[i][j]; - d2N_dxi2[node] = d2Nx[i] * Ny[j]; - d2N_deta2[node] = Nx[i] * d2Ny[j]; - d2N_dzeta2[node] = 0.0; - - d2N_dxideta[node] = dNx[i] * dNy[j]; - d2N_dxidzeta[node] = 0.0; - d2N_detadzeta[node] = 0.0; + eval1D_hessian(x, Nx, dNx, d2Nx); + eval1D_hessian(y, Ny, dNy, d2Ny); + + for (CfemInt i = 0; i < 3; ++i) { + for (CfemInt j = 0; j < 3; ++j) { + const int node = node_map[i][j]; + d2x[node] = d2Nx[i] * Ny[j]; + d2y[node] = Nx[i] * d2Ny[j]; + d2z[node] = 0.0; + + dxy[node] = dNx[i] * dNy[j]; + dxz[node] = 0.0; + dyz[node] = 0.0; } } } + }; - void evaluateShapesValuesBatch(const DynamicVector &xiPoints, - DynamicMatrix &valuesBatch) const override - { - const CfemInt nQp = static_cast(xiPoints.size()); - for (CfemInt q = 0; q < nQp; ++q) - { - CfemReal Nx[3], Ny[3], dNx[3], dNy[3], d2Nx[3], d2Ny[3]; - eval1D_hessian(xiPoints[q].x, Nx, dNx, d2Nx); - eval1D_hessian(xiPoints[q].y, Ny, dNy, d2Ny); - - for (CfemInt i = 0; i < 3; ++i) - { - for (CfemInt j = 0; j < 3; ++j) - { - valuesBatch[q][node_map[i][j]] = Nx[i] * Ny[j]; - } - } - } - } + // ========================================================================= + // KERNEL QUAD MINI (5 nœuds : 4 sommets + 1 bulle) + // ========================================================================= + struct MiniQuadKernel { + static constexpr CfemReal o4 = 0.25; - void evaluateShapesDerivativesBatch(const DynamicVector &xiPoints, - DynamicMatrix &dN_dxi, - DynamicMatrix &dN_deta, - DynamicMatrix &dN_dzeta) const override + [[gnu::always_inline]] static inline void values(CfemReal x, CfemReal y, CfemReal z, + CfemReal* __restrict N) noexcept { - const CfemInt nQp = static_cast(xiPoints.size()); - for (CfemInt q = 0; q < nQp; ++q) - { - CfemReal Nx[3], Ny[3], dNx[3], dNy[3], d2Nx[3], d2Ny[3]; - eval1D_hessian(xiPoints[q].x, Nx, dNx, d2Nx); - eval1D_hessian(xiPoints[q].y, Ny, dNy, d2Ny); - - for (CfemInt i = 0; i < 3; ++i) - { - for (CfemInt j = 0; j < 3; ++j) - { - const CfemInt node = node_map[i][j]; - dN_dxi[q][node] = dNx[i] * Ny[j]; - dN_deta[q][node] = Nx[i] * dNy[j]; - dN_dzeta[q][node] = 0.0; - } - } + // Bulle Centrale + const CfemReal b = (1.0 - x*x) * (1.0 - y*y); + N[4] = b; + + // Sommets + const CfemReal correction = b * o4; + N[0] = (o4 * (1.0 - x) * (1.0 - y)) - correction; + N[1] = (o4 * (1.0 + x) * (1.0 - y)) - correction; + N[2] = (o4 * (1.0 + x) * (1.0 + y)) - correction; + N[3] = (o4 * (1.0 - x) * (1.0 + y)) - correction; + } + + [[gnu::always_inline]] static inline void grads(CfemReal x, CfemReal y, CfemReal z, + CfemReal* __restrict dx, + CfemReal* __restrict dy, + CfemReal* __restrict dz) noexcept + { + const CfemReal db_dx = -2.0 * x * (1.0 - y*y); + const CfemReal db_dy = -2.0 * y * (1.0 - x*x); + + dx[4] = db_dx; + dy[4] = db_dy; + dz[4] = 0.0; + + const CfemReal c_dx = db_dx * o4; + const CfemReal c_dy = db_dy * o4; + + dx[0] = (-o4 * (1.0 - y)) - c_dx; dy[0] = (-o4 * (1.0 - x)) - c_dy; dz[0] = 0.0; + dx[1] = ( o4 * (1.0 - y)) - c_dx; dy[1] = (-o4 * (1.0 + x)) - c_dy; dz[1] = 0.0; + dx[2] = ( o4 * (1.0 + y)) - c_dx; dy[2] = ( o4 * (1.0 + x)) - c_dy; dz[2] = 0.0; + dx[3] = (-o4 * (1.0 + y)) - c_dx; dy[3] = ( o4 * (1.0 - x)) - c_dy; dz[3] = 0.0; + } + + [[gnu::always_inline]] static inline void hess(CfemReal x, CfemReal y, CfemReal z, + CfemReal* __restrict d2x, CfemReal* __restrict d2y, CfemReal* __restrict d2z, + CfemReal* __restrict dxy, CfemReal* __restrict dxz, CfemReal* __restrict dyz) noexcept + { + // Hessiens de la bulle + const CfemReal d2b_dx2 = -2.0 * (1.0 - y*y); + const CfemReal d2b_dy2 = -2.0 * (1.0 - x*x); + const CfemReal d2b_dxy = 4.0 * x * y; + + d2x[4] = d2b_dx2; + d2y[4] = d2b_dy2; + dxy[4] = d2b_dxy; + d2z[4] = 0.0; dxz[4] = 0.0; dyz[4] = 0.0; + + // Sommets (Correction de la composante purement nulle pour xx et yy du Q1) + const CfemReal c_dx2 = d2b_dx2 * o4; + const CfemReal c_dy2 = d2b_dy2 * o4; + const CfemReal c_dxy = d2b_dxy * o4; + + for (CfemInt i = 0; i < 4; ++i) { + d2x[i] = -c_dx2; + d2y[i] = -c_dy2; + d2z[i] = 0.0; + dxz[i] = 0.0; + dyz[i] = 0.0; } + + dxy[0] = o4 - c_dxy; + dxy[1] = -o4 - c_dxy; + dxy[2] = o4 - c_dxy; + dxy[3] = -o4 - c_dxy; } + }; - void evaluateShapesSecondDerivativesBatch(const DynamicVector &xiPoints, - DynamicMatrix &d2N_dxi2, - DynamicMatrix &d2N_deta2, - DynamicMatrix &d2N_dzeta2, - DynamicMatrix &d2N_dxideta, - DynamicMatrix &d2N_dxidzeta, - DynamicMatrix &d2N_detadzeta) const override - { - const CfemInt nQp = static_cast(xiPoints.size()); - for (CfemInt q = 0; q < nQp; ++q) - { - CfemReal Nx[3], Ny[3], dNx[3], dNy[3], d2Nx[3], d2Ny[3]; - eval1D_hessian(xiPoints[q].x, Nx, dNx, d2Nx); - eval1D_hessian(xiPoints[q].y, Ny, dNy, d2Ny); - - for (CfemInt i = 0; i < 3; ++i) - { - for (CfemInt j = 0; j < 3; ++j) - { - const CfemInt node = node_map[i][j]; - d2N_dxi2[q][node] = d2Nx[i] * Ny[j]; - d2N_deta2[q][node] = Nx[i] * d2Ny[j]; - d2N_dzeta2[q][node] = 0.0; - - d2N_dxideta[q][node] = dNx[i] * dNy[j]; - d2N_dxidzeta[q][node] = 0.0; - d2N_detadzeta[q][node] = 0.0; - } - } - } + // ========================================================================= + // KERNEL QUAD P0 (1 nœud, constant par morceaux) + // ========================================================================= + struct Q0QuadKernel { + [[gnu::always_inline]] static inline void values(CfemReal* __restrict N) noexcept { + N[0] = 1.0; } - Vector3D projectFacetRefCoordToCellRefCoord(CfemInt localFacetId, const Vector3D &xiFacet) const override - { - return cfem::reference_elem::utils::projectFacetRefCoordToQuadRefCoord(localFacetId, xiFacet); // Make the compiler happy + + [[gnu::always_inline]] static inline void grads(CfemReal* __restrict dx, + CfemReal* __restrict dy, + CfemReal* __restrict dz) noexcept { + dx[0] = 0.0; + dy[0] = 0.0; + dz[0] = 0.0; } - std::span getFaceNodes(CfemInt iFace) const override - { - return cfem::reference_elem::utils::quad9_face_nodes[iFace]; + [[gnu::always_inline]] static inline void hess(CfemReal* __restrict d2x, CfemReal* __restrict d2y, CfemReal* __restrict d2z, + CfemReal* __restrict dxy, CfemReal* __restrict dxz, CfemReal* __restrict dyz) noexcept { + d2x[0] = 0.0; + d2y[0] = 0.0; + d2z[0] = 0.0; + dxy[0] = 0.0; + dxz[0] = 0.0; + dyz[0] = 0.0; } }; +} - class MiniReferenceQuad : public ReferenceElement - { +namespace cfem +{ + + /** + * @brief Q1 Reference Quadrilateral (4 nodes, bilinear) + */ + class Q1ReferenceQuad : public ReferenceElement { public: - MiniReferenceQuad() - { - m_numNodes = 5; + Q1ReferenceQuad() { + m_numNodes = 4; m_dimension = 2; - m_nodes.resize(5); - m_nodes[0] = {-1, -1, 0}; - m_nodes[1] = {1, -1, 0}; - m_nodes[2] = {1, 1, 0}; - m_nodes[3] = {-1, 1, 0}; - m_nodes[4] = {0, 0, 0}; // Bulle + m_nodes.resize(m_numNodes); + + m_nodes[0] = Vector3D{-1.0, -1.0, 0.0}; + m_nodes[1] = Vector3D{ 1.0, -1.0, 0.0}; + m_nodes[2] = Vector3D{ 1.0, 1.0, 0.0}; + m_nodes[3] = Vector3D{-1.0, 1.0, 0.0}; } - void evaluateShapesValues(const Vector3D& xi, - CfemReal* values) const override - { - constexpr CfemReal o4 = 0.25; + // ========================================================================= + // SCALAR INTERFACES + // ========================================================================= - const CfemReal x = xi.x; - const CfemReal y = xi.y; + void evaluateShapesValues(const Vector3D &xi, ShapeInfo& info) const override { + reference_elem::kernels::Q1QuadKernel::values(xi.x, xi.y, xi.z, info.values()); + } - // Bulle Centrale - const CfemReal b = (1.0 - x*x) * (1.0 - y*y); - values[4] = b; + void evaluateShapesDerivatives(const Vector3D &xi, ShapeInfo& info) const override { + reference_elem::kernels::Q1QuadKernel::grads(xi.x, xi.y, xi.z, + info.dN_dxi(), info.dN_deta(), info.dN_dzeta()); + } - // Sommets - const CfemReal correction = b * o4; - values[0] = (o4 * (1.0 - x) * (1.0 - y)) - correction; - values[1] = (o4 * (1.0 + x) * (1.0 - y)) - correction; - values[2] = (o4 * (1.0 + x) * (1.0 + y)) - correction; - values[3] = (o4 * (1.0 - x) * (1.0 + y)) - correction; + void evaluateShapesSecondDerivatives(const Vector3D &xi, ShapeInfo& info) const override { + reference_elem::kernels::Q1QuadKernel::hess(xi.x, xi.y, xi.z, + info.d2N_dxi2(), info.d2N_deta2(), info.d2N_dzeta2(), + info.d2N_dxideta(), info.d2N_dxidzeta(), info.d2N_detadzeta()); } - void evaluateShapesDerivatives(const Vector3D& xi, - CfemReal* dN_dxi, - CfemReal* dN_deta, - CfemReal* dN_dzeta) const override - { - constexpr CfemReal o4 = 0.25; + // ===================================================================== + // HPC BATCH INTERFACES + // ===================================================================== - const CfemReal x = xi.x; - const CfemReal y = xi.y; + void evaluateShapesValuesBatch(const DynamicVector &xiPoints, ShapeInfoBatch& shapeBatch) const override { + const CfemInt nQp = shapeBatch.numQuadraturePoints; + for (CfemInt q = 0; q < nQp; ++q) { + reference_elem::kernels::Q1QuadKernel::values(xiPoints[q].x, xiPoints[q].y, xiPoints[q].z, + shapeBatch.values(q)); + } + } - const CfemReal db_dx = -2.0 * x * (1.0 - y*y); - const CfemReal db_dy = -2.0 * y * (1.0 - x*x); + void evaluateShapesDerivativesBatch(const DynamicVector &xiPoints, ShapeInfoBatch& shapeBatch) const override { + const CfemInt nQp = shapeBatch.numQuadraturePoints; + for (CfemInt q = 0; q < nQp; ++q) { + reference_elem::kernels::Q1QuadKernel::grads(xiPoints[q].x, xiPoints[q].y, xiPoints[q].z, + shapeBatch.dN_dxi(q), shapeBatch.dN_deta(q), shapeBatch.dN_dzeta(q)); + } + } - dN_dxi[4] = db_dx; - dN_deta[4] = db_dy; - dN_dzeta[4] = 0.0; + void evaluateShapesSecondDerivativesBatch(const DynamicVector &xiPoints, ShapeInfoBatch& shapeBatch) const override { + const CfemInt nQp = shapeBatch.numQuadraturePoints; + for (CfemInt q = 0; q < nQp; ++q) { + reference_elem::kernels::Q1QuadKernel::hess(xiPoints[q].x, xiPoints[q].y, xiPoints[q].z, + shapeBatch.d2N_dxi2(q), shapeBatch.d2N_deta2(q), shapeBatch.d2N_dzeta2(q), + shapeBatch.d2N_dxideta(q), shapeBatch.d2N_dxidzeta(q), shapeBatch.d2N_detadzeta(q)); + } + } - const CfemReal c_dx = db_dx * o4; - const CfemReal c_dy = db_dy * o4; + Vector3D projectFacetRefCoordToCellRefCoord(CfemInt localFacetId, const Vector3D &xiFacet) const override { + return cfem::reference_elem::utils::projectFacetRefCoordToQuadRefCoord(localFacetId, xiFacet); + } - dN_dxi[0] = (-o4 * (1.0 - y)) - c_dx; dN_deta[0] = (-o4 * (1.0 - x)) - c_dy; dN_dzeta[0] = 0.0; - dN_dxi[1] = ( o4 * (1.0 - y)) - c_dx; dN_deta[1] = (-o4 * (1.0 + x)) - c_dy; dN_dzeta[1] = 0.0; - dN_dxi[2] = ( o4 * (1.0 + y)) - c_dx; dN_deta[2] = ( o4 * (1.0 + x)) - c_dy; dN_dzeta[2] = 0.0; - dN_dxi[3] = (-o4 * (1.0 + y)) - c_dx; dN_deta[3] = ( o4 * (1.0 - x)) - c_dy; dN_dzeta[3] = 0.0; - + std::span getFaceNodes(CfemInt iFace) const override { + return cfem::reference_elem::utils::quad4_face_nodes[iFace]; } + }; - void evaluateShapesSecondDerivatives(const Vector3D& xi, - CfemReal* d2N_dxi2, - CfemReal* d2N_deta2, - CfemReal* d2N_dzeta2, - CfemReal* d2N_dxideta, - CfemReal* d2N_dxidzeta, - CfemReal* d2N_detadzeta) const override - { - constexpr CfemReal o4 = 0.25; + /** + * @brief Q2 Reference Quadrilateral (9 nodes, biquadratic) + */ + class Q2ReferenceQuad : public ReferenceElement { + public: + Q2ReferenceQuad() { + m_numNodes = 9; + m_dimension = 2; + m_nodes.resize(m_numNodes); + + // Sommets + m_nodes[0] = Vector3D{-1.0, -1.0, 0.0}; + m_nodes[1] = Vector3D{ 1.0, -1.0, 0.0}; + m_nodes[2] = Vector3D{ 1.0, 1.0, 0.0}; + m_nodes[3] = Vector3D{-1.0, 1.0, 0.0}; - const CfemReal x = xi.x; - const CfemReal y = xi.y; + // Milieux d'arêtes + m_nodes[4] = Vector3D{ 0.0, -1.0, 0.0}; + m_nodes[5] = Vector3D{ 1.0, 0.0, 0.0}; + m_nodes[6] = Vector3D{ 0.0, 1.0, 0.0}; + m_nodes[7] = Vector3D{-1.0, 0.0, 0.0}; - // Hessiens de la bulle - const CfemReal d2b_dx2 = -2.0 * (1.0 - y*y); - const CfemReal d2b_dy2 = -2.0 * (1.0 - x*x); - const CfemReal d2b_dxy = 4.0 * x * y; + // Centre + m_nodes[8] = Vector3D{ 0.0, 0.0, 0.0}; + } - d2N_dxi2[4] = d2b_dx2; - d2N_deta2[4] = d2b_dy2; - d2N_dxideta[4] = d2b_dxy; - d2N_dzeta2[4] = 0.0; d2N_dxidzeta[4] = 0.0; d2N_detadzeta[4] = 0.0; + // ========================================================================= + // SCALAR INTERFACES + // ========================================================================= - // Sommets (Correction de la composante purement nulle pour xx et yy du Q1) - const CfemReal c_dx2 = d2b_dx2 * o4; - const CfemReal c_dy2 = d2b_dy2 * o4; - const CfemReal c_dxy = d2b_dxy * o4; + void evaluateShapesValues(const Vector3D &xi, ShapeInfo& info) const override { + reference_elem::kernels::Q2QuadKernel::values(xi.x, xi.y, xi.z, info.values()); + } - for(CfemInt i=0; i<4; ++i) { - d2N_dxi2[i] = -c_dx2; - d2N_deta2[i] = -c_dy2; - d2N_dzeta2[i] = 0.0; - d2N_dxidzeta[i] = 0.0; - d2N_detadzeta[i] = 0.0; - } + void evaluateShapesDerivatives(const Vector3D &xi, ShapeInfo& info) const override { + reference_elem::kernels::Q2QuadKernel::grads(xi.x, xi.y, xi.z, + info.dN_dxi(), info.dN_deta(), info.dN_dzeta()); + } - d2N_dxideta[0] = o4 - c_dxy; - d2N_dxideta[1] = -o4 - c_dxy; - d2N_dxideta[2] = o4 - c_dxy; - d2N_dxideta[3] = -o4 - c_dxy; + void evaluateShapesSecondDerivatives(const Vector3D &xi, ShapeInfo& info) const override { + reference_elem::kernels::Q2QuadKernel::hess(xi.x, xi.y, xi.z, + info.d2N_dxi2(), info.d2N_deta2(), info.d2N_dzeta2(), + info.d2N_dxideta(), info.d2N_dxidzeta(), info.d2N_detadzeta()); } - void evaluateShapesValuesBatch(const DynamicVector& xiPoints, - DynamicMatrix& valuesBatch) const override - { - const CfemInt nQp = static_cast(xiPoints.size()); - constexpr CfemReal o4 = 0.25; + // ===================================================================== + // HPC BATCH INTERFACES + // ===================================================================== + void evaluateShapesValuesBatch(const DynamicVector &xiPoints, ShapeInfoBatch& shapeBatch) const override { + const CfemInt nQp = shapeBatch.numQuadraturePoints; for (CfemInt q = 0; q < nQp; ++q) { - const CfemReal x = xiPoints[q].x; - const CfemReal y = xiPoints[q].y; - - // Bulle Centrale - const CfemReal b = (1.0 - x*x) * (1.0 - y*y); - valuesBatch[q][4] = b; - - // Sommets - const CfemReal correction = b * o4; - valuesBatch[q][0] = (o4 * (1.0 - x) * (1.0 - y)) - correction; - valuesBatch[q][1] = (o4 * (1.0 + x) * (1.0 - y)) - correction; - valuesBatch[q][2] = (o4 * (1.0 + x) * (1.0 + y)) - correction; - valuesBatch[q][3] = (o4 * (1.0 - x) * (1.0 + y)) - correction; + reference_elem::kernels::Q2QuadKernel::values(xiPoints[q].x, xiPoints[q].y, xiPoints[q].z, + shapeBatch.values(q)); } } - void evaluateShapesDerivativesBatch(const DynamicVector& xiPoints, - DynamicMatrix& dN_dxi, - DynamicMatrix& dN_deta, - DynamicMatrix& dN_dzeta) const override - { - const CfemInt nQp = static_cast(xiPoints.size()); - constexpr CfemReal o4 = 0.25; + void evaluateShapesDerivativesBatch(const DynamicVector &xiPoints, ShapeInfoBatch& shapeBatch) const override { + const CfemInt nQp = shapeBatch.numQuadraturePoints; + for (CfemInt q = 0; q < nQp; ++q) { + reference_elem::kernels::Q2QuadKernel::grads(xiPoints[q].x, xiPoints[q].y, xiPoints[q].z, + shapeBatch.dN_dxi(q), shapeBatch.dN_deta(q), shapeBatch.dN_dzeta(q)); + } + } + void evaluateShapesSecondDerivativesBatch(const DynamicVector &xiPoints, ShapeInfoBatch& shapeBatch) const override { + const CfemInt nQp = shapeBatch.numQuadraturePoints; for (CfemInt q = 0; q < nQp; ++q) { - const CfemReal x = xiPoints[q].x; - const CfemReal y = xiPoints[q].y; + reference_elem::kernels::Q2QuadKernel::hess(xiPoints[q].x, xiPoints[q].y, xiPoints[q].z, + shapeBatch.d2N_dxi2(q), shapeBatch.d2N_deta2(q), shapeBatch.d2N_dzeta2(q), + shapeBatch.d2N_dxideta(q), shapeBatch.d2N_dxidzeta(q), shapeBatch.d2N_detadzeta(q)); + } + } - const CfemReal db_dx = -2.0 * x * (1.0 - y*y); - const CfemReal db_dy = -2.0 * y * (1.0 - x*x); + Vector3D projectFacetRefCoordToCellRefCoord(CfemInt localFacetId, const Vector3D &xiFacet) const override { + return cfem::reference_elem::utils::projectFacetRefCoordToQuadRefCoord(localFacetId, xiFacet); + } - dN_dxi[q][4] = db_dx; - dN_deta[q][4] = db_dy; - dN_dzeta[q][4] = 0.0; + std::span getFaceNodes(CfemInt iFace) const override { + return cfem::reference_elem::utils::quad9_face_nodes[iFace]; + } + }; - const CfemReal c_dx = db_dx * o4; - const CfemReal c_dy = db_dy * o4; + /** + * @brief Mini Reference Quadrilateral (5 nodes: 4 vertices + 1 bubble) + */ + class MiniReferenceQuad : public ReferenceElement { + public: + MiniReferenceQuad() { + m_numNodes = 5; + m_dimension = 2; + m_nodes.resize(m_numNodes); + m_nodes[0] = {-1.0, -1.0, 0.0}; + m_nodes[1] = { 1.0, -1.0, 0.0}; + m_nodes[2] = { 1.0, 1.0, 0.0}; + m_nodes[3] = {-1.0, 1.0, 0.0}; + m_nodes[4] = { 0.0, 0.0, 0.0}; // Bulle + } - dN_dxi[q][0] = (-o4 * (1.0 - y)) - c_dx; dN_deta[q][0] = (-o4 * (1.0 - x)) - c_dy; dN_dzeta[q][0] = 0.0; - dN_dxi[q][1] = ( o4 * (1.0 - y)) - c_dx; dN_deta[q][1] = (-o4 * (1.0 + x)) - c_dy; dN_dzeta[q][1] = 0.0; - dN_dxi[q][2] = ( o4 * (1.0 + y)) - c_dx; dN_deta[q][2] = ( o4 * (1.0 + x)) - c_dy; dN_dzeta[q][2] = 0.0; - dN_dxi[q][3] = (-o4 * (1.0 + y)) - c_dx; dN_deta[q][3] = ( o4 * (1.0 - x)) - c_dy; dN_dzeta[q][3] = 0.0; - } + // ========================================================================= + // SCALAR INTERFACES + // ========================================================================= + + void evaluateShapesValues(const Vector3D& xi, ShapeInfo& info) const override { + reference_elem::kernels::MiniQuadKernel::values(xi.x, xi.y, xi.z, info.values()); } - void evaluateShapesSecondDerivativesBatch(const DynamicVector& xiPoints, - DynamicMatrix& d2N_dxi2, - DynamicMatrix& d2N_deta2, - DynamicMatrix& d2N_dzeta2, - DynamicMatrix& d2N_dxideta, - DynamicMatrix& d2N_dxidzeta, - DynamicMatrix& d2N_detadzeta) const override - { - const CfemInt nQp = static_cast(xiPoints.size()); - constexpr CfemReal o4 = 0.25; + void evaluateShapesDerivatives(const Vector3D& xi, ShapeInfo& info) const override { + reference_elem::kernels::MiniQuadKernel::grads(xi.x, xi.y, xi.z, + info.dN_dxi(), info.dN_deta(), info.dN_dzeta()); + } + + void evaluateShapesSecondDerivatives(const Vector3D& xi, ShapeInfo& info) const override { + reference_elem::kernels::MiniQuadKernel::hess(xi.x, xi.y, xi.z, + info.d2N_dxi2(), info.d2N_deta2(), info.d2N_dzeta2(), + info.d2N_dxideta(), info.d2N_dxidzeta(), info.d2N_detadzeta()); + } + + // ===================================================================== + // HPC BATCH INTERFACES + // ===================================================================== + void evaluateShapesValuesBatch(const DynamicVector& xiPoints, ShapeInfoBatch& shapeBatch) const override { + const CfemInt nQp = shapeBatch.numQuadraturePoints; for (CfemInt q = 0; q < nQp; ++q) { - const CfemReal x = xiPoints[q].x; - const CfemReal y = xiPoints[q].y; - - // Hessiens de la bulle - const CfemReal d2b_dx2 = -2.0 * (1.0 - y*y); - const CfemReal d2b_dy2 = -2.0 * (1.0 - x*x); - const CfemReal d2b_dxy = 4.0 * x * y; - - d2N_dxi2[q][4] = d2b_dx2; - d2N_deta2[q][4] = d2b_dy2; - d2N_dxideta[q][4] = d2b_dxy; - d2N_dzeta2[q][4] = 0.0; d2N_dxidzeta[q][4] = 0.0; d2N_detadzeta[q][4] = 0.0; - - // Sommets (Correction de la composante purement nulle pour xx et yy du Q1) - const CfemReal c_dx2 = d2b_dx2 * o4; - const CfemReal c_dy2 = d2b_dy2 * o4; - const CfemReal c_dxy = d2b_dxy * o4; - - for(CfemInt i=0; i<4; ++i) { - d2N_dxi2[q][i] = -c_dx2; - d2N_deta2[q][i] = -c_dy2; - d2N_dzeta2[q][i] = 0.0; - d2N_dxidzeta[q][i] = 0.0; - d2N_detadzeta[q][i] = 0.0; - } + reference_elem::kernels::MiniQuadKernel::values(xiPoints[q].x, xiPoints[q].y, xiPoints[q].z, + shapeBatch.values(q)); + } + } - d2N_dxideta[q][0] = o4 - c_dxy; - d2N_dxideta[q][1] = -o4 - c_dxy; - d2N_dxideta[q][2] = o4 - c_dxy; - d2N_dxideta[q][3] = -o4 - c_dxy; + void evaluateShapesDerivativesBatch(const DynamicVector& xiPoints, ShapeInfoBatch& shapeBatch) const override { + const CfemInt nQp = shapeBatch.numQuadraturePoints; + for (CfemInt q = 0; q < nQp; ++q) { + reference_elem::kernels::MiniQuadKernel::grads(xiPoints[q].x, xiPoints[q].y, xiPoints[q].z, + shapeBatch.dN_dxi(q), shapeBatch.dN_deta(q), shapeBatch.dN_dzeta(q)); } } - Vector3D projectFacetRefCoordToCellRefCoord(CfemInt localFacetId, const Vector3D &xiFacet) const override - { + void evaluateShapesSecondDerivativesBatch(const DynamicVector& xiPoints, ShapeInfoBatch& shapeBatch) const override { + const CfemInt nQp = shapeBatch.numQuadraturePoints; + for (CfemInt q = 0; q < nQp; ++q) { + reference_elem::kernels::MiniQuadKernel::hess(xiPoints[q].x, xiPoints[q].y, xiPoints[q].z, + shapeBatch.d2N_dxi2(q), shapeBatch.d2N_deta2(q), shapeBatch.d2N_dzeta2(q), + shapeBatch.d2N_dxideta(q), shapeBatch.d2N_dxidzeta(q), shapeBatch.d2N_detadzeta(q)); + } + } + + Vector3D projectFacetRefCoordToCellRefCoord(CfemInt localFacetId, const Vector3D &xiFacet) const override { return reference_elem::utils::projectFacetRefCoordToQuadRefCoord(localFacetId, xiFacet); } - std::span getFaceNodes(CfemInt iFace) const override - { + + std::span getFaceNodes(CfemInt iFace) const override { return reference_elem::utils::quad4_face_nodes[iFace]; } }; - class Q0ReferenceQuad : public ReferenceElement - { + + /** + * @brief Q0 Reference Quadrilateral (1 node, piecewise constant) + */ + class Q0ReferenceQuad : public ReferenceElement { public: - Q0ReferenceQuad() - { + Q0ReferenceQuad() { m_numNodes = 1; m_dimension = 2; @@ -663,90 +542,54 @@ namespace cfem m_nodes = {Vector3D{0.0, 0.0, 0.0}}; } - void evaluateShapesValues(const Vector3D &, CfemReal *values) const override - { - values[0] = 1.0; + // ========================================================================= + // SCALAR INTERFACES + // ========================================================================= + + void evaluateShapesValues(const Vector3D&, ShapeInfo& info) const override { + reference_elem::kernels::Q0QuadKernel::values(info.values()); } - void evaluateShapesDerivatives(const Vector3D &, - CfemReal *dN_dxi, - CfemReal *dN_deta, - CfemReal *dN_dzeta) const override - { - dN_dxi[0] = 0.0; - dN_deta[0] = 0.0; - dN_dzeta[0] = 0.0; - } - - void evaluateShapesSecondDerivatives(const Vector3D &, - CfemReal *d2N_dxi2, - CfemReal *d2N_deta2, - CfemReal *d2N_dzeta2, - CfemReal *d2N_dxideta, - CfemReal *d2N_dxidzeta, - CfemReal *d2N_detadzeta) const override - { - d2N_dxi2[0] = 0.0; - d2N_deta2[0] = 0.0; - d2N_dzeta2[0] = 0.0; - d2N_dxideta[0] = 0.0; - d2N_dxidzeta[0] = 0.0; - d2N_detadzeta[0] = 0.0; + void evaluateShapesDerivatives(const Vector3D&, ShapeInfo& info) const override { + reference_elem::kernels::Q0QuadKernel::grads(info.dN_dxi(), info.dN_deta(), info.dN_dzeta()); } - void evaluateShapesValuesBatch(const DynamicVector &xiPoints, DynamicMatrix &values) const override - { - // Constant piecewise field value - const CfemInt nQp = static_cast(xiPoints.size()); - for (CfemInt q = 0; q < nQp; ++q) - { - values[q][0] = 1.0; + void evaluateShapesSecondDerivatives(const Vector3D&, ShapeInfo& info) const override { + reference_elem::kernels::Q0QuadKernel::hess(info.d2N_dxi2(), info.d2N_deta2(), info.d2N_dzeta2(), + info.d2N_dxideta(), info.d2N_dxidzeta(), info.d2N_detadzeta()); + } + + // ===================================================================== + // HPC BATCH INTERFACES + // ===================================================================== + + void evaluateShapesValuesBatch(const DynamicVector& xiPoints, ShapeInfoBatch& shapeBatch) const override { + const CfemInt nQp = shapeBatch.numQuadraturePoints; + for (CfemInt q = 0; q < nQp; ++q) { + reference_elem::kernels::Q0QuadKernel::values(shapeBatch.values(q)); } } - void evaluateShapesDerivativesBatch(const DynamicVector &xiPoints, - DynamicMatrix &dN_dxi, - DynamicMatrix &dN_deta, - DynamicMatrix &dN_dzeta) const override - { - // Constant function results in flat zero gradients everywhere - const CfemInt nQp = static_cast(xiPoints.size()); - for (CfemInt q = 0; q < nQp; ++q) - { - dN_dxi[q][0] = 0.0; - dN_deta[q][0] = 0.0; - dN_dzeta[q][0] = 0.0; + void evaluateShapesDerivativesBatch(const DynamicVector& xiPoints, ShapeInfoBatch& shapeBatch) const override { + const CfemInt nQp = shapeBatch.numQuadraturePoints; + for (CfemInt q = 0; q < nQp; ++q) { + reference_elem::kernels::Q0QuadKernel::grads(shapeBatch.dN_dxi(q), shapeBatch.dN_deta(q), shapeBatch.dN_dzeta(q)); } } - void evaluateShapesSecondDerivativesBatch(const DynamicVector &xiPoints, - DynamicMatrix &d2N_dxi2, - DynamicMatrix &d2N_deta2, - DynamicMatrix &d2N_dzeta2, - DynamicMatrix &d2N_dxideta, - DynamicMatrix &d2N_dxidzeta, - DynamicMatrix &d2N_detadzeta) const override - { - // Flat null second derivatives - const CfemInt nQp = static_cast(xiPoints.size()); - for (CfemInt q = 0; q < nQp; ++q) - { - d2N_dxi2[q][0] = 0.0; - d2N_deta2[q][0] = 0.0; - d2N_dzeta2[q][0] = 0.0; - d2N_dxideta[q][0] = 0.0; - d2N_dxidzeta[q][0] = 0.0; - d2N_detadzeta[q][0] = 0.0; + void evaluateShapesSecondDerivativesBatch(const DynamicVector& xiPoints, ShapeInfoBatch& shapeBatch) const override { + const CfemInt nQp = shapeBatch.numQuadraturePoints; + for (CfemInt q = 0; q < nQp; ++q) { + reference_elem::kernels::Q0QuadKernel::hess(shapeBatch.d2N_dxi2(q), shapeBatch.d2N_deta2(q), shapeBatch.d2N_dzeta2(q), + shapeBatch.d2N_dxideta(q), shapeBatch.d2N_dxidzeta(q), shapeBatch.d2N_detadzeta(q)); } } - Vector3D projectFacetRefCoordToCellRefCoord(CfemInt localFacetId, const Vector3D &xiFacet) const override - { + Vector3D projectFacetRefCoordToCellRefCoord(CfemInt localFacetId, const Vector3D &xiFacet) const override { return cfem::reference_elem::utils::projectFacetRefCoordToQuadRefCoord(localFacetId, xiFacet); } - std::span getFaceNodes(CfemInt iFace) const override - { + std::span getFaceNodes(CfemInt iFace) const override { return cfem::reference_elem::utils::quad1_face_nodes[iFace]; } }; diff --git a/libs/fem/reference_element/include/ReferenceSegment.h b/libs/fem/reference_element/include/ReferenceSegment.h index effadb8..532a50b 100644 --- a/libs/fem/reference_element/include/ReferenceSegment.h +++ b/libs/fem/reference_element/include/ReferenceSegment.h @@ -16,392 +16,291 @@ // --- held liable for any damages arising from the use of this software. //************************************************************************ -#ifndef CFEM_REFERENCE_SEGMENT -#define CFEM_REFERENCE_SEGMENT +#ifndef CFEM_REFERENCE_SEGMENT_H +#define CFEM_REFERENCE_SEGMENT_H #include "ReferenceElement.h" +#include +#include -namespace cfem::reference_elem::utils -{ +namespace cfem::reference_elem::utils { - inline Vector3D projectFacetRefCoordToSegmentRefCoord(CfemInt localFacetId, - const Vector3D &xiFacet) - { - // Une facette d'une ligne est un point. xiFacet est ignoré. - // Selon ReferenceSegment.h (P2), le segment de référence est [-1, 1] - // Face 0 = Noeud 0 (-1.0), Face 1 = Noeud 1 (1.0) + inline Vector3D projectFacetRefCoordToSegmentRefCoord(CfemInt localFacetId, const Vector3D &xiFacet) { return Vector3D((localFacetId == 0) ? -1.0 : 1.0, 0.0, 0.0); } - static const std::vector> line_face_nodes = { + static const std::array, 2> line_face_nodes = {{ {0}, // Face 0 {1} // Face 1 + }}; + + static const std::array, 2> line1_face_nodes = {{ + {0}, // Face 0 (x = -1) -> Node 0 + {0} // Face 1 (x = 1) -> Node 1 + }}; +} + +namespace cfem::reference_elem::kernels { + + // ========================================================================= + // P1 SEGMENT KERNEL (2 nodes, linear) + // ========================================================================= + struct P1SegmentKernel { + [[gnu::always_inline]] static inline void values(CfemReal x, CfemReal* __restrict N) noexcept { + N[0] = 0.5 * (1.0 - x); + N[1] = 0.5 * (1.0 + x); + } + + [[gnu::always_inline]] static inline void grads(CfemReal* __restrict dx, + CfemReal* __restrict dy, + CfemReal* __restrict dz) noexcept { + dx[0] = -0.5; + dx[1] = 0.5; + dy[0] = dy[1] = 0.0; + dz[0] = dz[1] = 0.0; + } + + [[gnu::always_inline]] static inline void hess(CfemReal* __restrict d2x, CfemReal* __restrict d2y, CfemReal* __restrict d2z, + CfemReal* __restrict dxy, CfemReal* __restrict dxz, CfemReal* __restrict dyz) noexcept { + for (CfemInt i = 0; i < 2; ++i) { + d2x[i] = 0.0; d2y[i] = 0.0; d2z[i] = 0.0; + dxy[i] = 0.0; dxz[i] = 0.0; dyz[i] = 0.0; + } + } + }; + + // ========================================================================= + // P2 SEGMENT KERNEL (3 nodes, quadratic) + // ========================================================================= + struct P2SegmentKernel { + [[gnu::always_inline]] static inline void values(CfemReal x, CfemReal* __restrict N) noexcept { + const CfemReal x2 = x * x; + N[0] = 0.5 * (x2 - x); + N[1] = 0.5 * (x2 + x); + N[2] = 1.0 - x2; + } + + [[gnu::always_inline]] static inline void grads(CfemReal x, + CfemReal* __restrict dx, + CfemReal* __restrict dy, + CfemReal* __restrict dz) noexcept { + dx[0] = x - 0.5; + dx[1] = x + 0.5; + dx[2] = -2.0 * x; + + for (CfemInt i = 0; i < 3; ++i) { + dy[i] = 0.0; + dz[i] = 0.0; + } + } + + [[gnu::always_inline]] static inline void hess(CfemReal* __restrict d2x, CfemReal* __restrict d2y, CfemReal* __restrict d2z, + CfemReal* __restrict dxy, CfemReal* __restrict dxz, CfemReal* __restrict dyz) noexcept { + d2x[0] = 1.0; + d2x[1] = 1.0; + d2x[2] = -2.0; + + for (CfemInt i = 0; i < 3; ++i) { + d2y[i] = 0.0; d2z[i] = 0.0; + dxy[i] = 0.0; dxz[i] = 0.0; dyz[i] = 0.0; + } + } }; - static const std::vector> line1_face_nodes = { - {0}, // Face 0 (x = -1) -> Noeud 0 - {0} // Face 1 (x = 1) -> Noeud 0 + // ========================================================================= + // KERNEL SEGMENT P0 (1 nœud, constant) + // ========================================================================= + struct P0SegmentKernel { + [[gnu::always_inline]] static inline void values(CfemReal* __restrict N) noexcept { + N[0] = 1.0; + } + + [[gnu::always_inline]] static inline void grads(CfemReal* __restrict dx, + CfemReal* __restrict dy, + CfemReal* __restrict dz) noexcept { + dx[0] = 0.0; dy[0] = 0.0; dz[0] = 0.0; + } + + [[gnu::always_inline]] static inline void hess(CfemReal* __restrict d2x, CfemReal* __restrict d2y, CfemReal* __restrict d2z, + CfemReal* __restrict dxy, CfemReal* __restrict dxz, CfemReal* __restrict dyz) noexcept { + d2x[0] = 0.0; d2y[0] = 0.0; d2z[0] = 0.0; + dxy[0] = 0.0; dxz[0] = 0.0; dyz[0] = 0.0; + } }; } -namespace cfem -{ +namespace cfem { - class P1ReferenceSegment : public ReferenceElement - { + /** + * @brief P1 Reference Segment (2 nodes) + */ + class P1ReferenceSegment : public ReferenceElement { public: - P1ReferenceSegment() - { + P1ReferenceSegment() { m_numNodes = 2; m_dimension = 1; m_nodes.resize(m_numNodes); - // Domaine de référence [-1, 1] m_nodes[0] = Vector3D{-1.0, 0.0, 0.0}; - m_nodes[1] = Vector3D{1.0, 0.0, 0.0}; - } - - void evaluateShapesValues(const Vector3D &xi, - CfemReal *values) const override - { - const CfemReal x = xi.x; - values[0] = 0.5 * (1.0 - x); - values[1] = 0.5 * (1.0 + x); - } - - void evaluateShapesDerivatives(const Vector3D &xi, - CfemReal *dN_dxi, - CfemReal *dN_deta, - CfemReal *dN_dzeta) const override - { - // Dérivées / x - dN_dxi[0] = -0.5; - dN_dxi[1] = 0.5; - - // L'élément est 1D : dérivées y et z sont nulles - dN_deta[0] = 0.0; - dN_deta[1] = 0.0; - dN_dzeta[0] = 0.0; - dN_dzeta[1] = 0.0; - } - - void evaluateShapesSecondDerivatives(const Vector3D &xi, - CfemReal *d2N_dxi2, - CfemReal *d2N_deta2, - CfemReal *d2N_dzeta2, - CfemReal *d2N_dxideta, - CfemReal *d2N_dxidzeta, - CfemReal *d2N_detadzeta) const override - { - for (CfemInt i = 0; i < 2; ++i) - { - d2N_dxi2[i] = 0.0; - d2N_deta2[i] = 0.0; - d2N_dzeta2[i] = 0.0; - d2N_dxideta[i] = 0.0; - d2N_dxidzeta[i] = 0.0; - d2N_detadzeta[i] = 0.0; - } + m_nodes[1] = Vector3D{ 1.0, 0.0, 0.0}; + } + + void evaluateShapesValues(const Vector3D &xi, ShapeInfo& info) const override { + reference_elem::kernels::P1SegmentKernel::values(xi.x, info.values()); + } + + void evaluateShapesDerivatives(const Vector3D &xi, ShapeInfo& info) const override { + reference_elem::kernels::P1SegmentKernel::grads(info.dN_dxi(), info.dN_deta(), info.dN_dzeta()); + } + + void evaluateShapesSecondDerivatives(const Vector3D &xi, ShapeInfo& info) const override { + reference_elem::kernels::P1SegmentKernel::hess(info.d2N_dxi2(), info.d2N_deta2(), info.d2N_dzeta2(), + info.d2N_dxideta(), info.d2N_dxidzeta(), info.d2N_detadzeta()); } - void evaluateShapesValuesBatch(const DynamicVector &xiPoints, - DynamicMatrix &valuesBatch) const override - { - const CfemInt nQp = static_cast(xiPoints.size()); - for (CfemInt q = 0; q < nQp; ++q) - { - const CfemReal x = xiPoints[q].x; - valuesBatch[q][0] = 0.5 * (1.0 - x); - valuesBatch[q][1] = 0.5 * (1.0 + x); + void evaluateShapesValuesBatch(const DynamicVector &xiPoints, ShapeInfoBatch& shapeBatch) const override { + const CfemInt nQp = shapeBatch.numQuadraturePoints; + for (CfemInt q = 0; q < nQp; ++q) { + reference_elem::kernels::P1SegmentKernel::values(xiPoints[q].x, shapeBatch.values(q)); } } - void evaluateShapesDerivativesBatch(const DynamicVector &xiPoints, - DynamicMatrix &dN_dxi, - DynamicMatrix &dN_deta, - DynamicMatrix &dN_dzeta) const override - { - const CfemInt nQp = static_cast(xiPoints.size()); - for (CfemInt q = 0; q < nQp; ++q) - { - // Dérivées / x - dN_dxi[q][0] = -0.5; - dN_dxi[q][1] = 0.5; - - // L'élément est 1D : dérivées y et z sont nulles - dN_deta[q][0] = 0.0; - dN_deta[q][1] = 0.0; - dN_dzeta[q][0] = 0.0; - dN_dzeta[q][1] = 0.0; + void evaluateShapesDerivativesBatch(const DynamicVector &xiPoints, ShapeInfoBatch& shapeBatch) const override { + const CfemInt nQp = shapeBatch.numQuadraturePoints; + for (CfemInt q = 0; q < nQp; ++q) { + reference_elem::kernels::P1SegmentKernel::grads(shapeBatch.dN_dxi(q), shapeBatch.dN_deta(q), shapeBatch.dN_dzeta(q)); } } - void evaluateShapesSecondDerivativesBatch(const DynamicVector &xiPoints, - DynamicMatrix &d2N_dxi2, - DynamicMatrix &d2N_deta2, - DynamicMatrix &d2N_dzeta2, - DynamicMatrix &d2N_dxideta, - DynamicMatrix &d2N_dxidzeta, - DynamicMatrix &d2N_detadzeta) const override - { - const CfemInt nQp = static_cast(xiPoints.size()); - for (CfemInt q = 0; q < nQp; ++q) - { - for (CfemInt i = 0; i < 2; ++i) - { - d2N_dxi2[q][i] = 0.0; - d2N_deta2[q][i] = 0.0; - d2N_dzeta2[q][i] = 0.0; - d2N_dxideta[q][i] = 0.0; - d2N_dxidzeta[q][i] = 0.0; - d2N_detadzeta[q][i] = 0.0; - } + void evaluateShapesSecondDerivativesBatch(const DynamicVector &xiPoints, ShapeInfoBatch& shapeBatch) const override { + const CfemInt nQp = shapeBatch.numQuadraturePoints; + for (CfemInt q = 0; q < nQp; ++q) { + reference_elem::kernels::P1SegmentKernel::hess(shapeBatch.d2N_dxi2(q), shapeBatch.d2N_deta2(q), shapeBatch.d2N_dzeta2(q), + shapeBatch.d2N_dxideta(q), shapeBatch.d2N_dxidzeta(q), shapeBatch.d2N_detadzeta(q)); } } - Vector3D projectFacetRefCoordToCellRefCoord(CfemInt localFacetId, const Vector3D &xiFacet) const override - { - return cfem::reference_elem::utils::projectFacetRefCoordToSegmentRefCoord(localFacetId, xiFacet); // Make the compiler happy + Vector3D projectFacetRefCoordToCellRefCoord(CfemInt localFacetId, const Vector3D &xiFacet) const override { + return cfem::reference_elem::utils::projectFacetRefCoordToSegmentRefCoord(localFacetId, xiFacet); } - std::span getFaceNodes(CfemInt iFace) const override - { + std::span getFaceNodes(CfemInt iFace) const override { return cfem::reference_elem::utils::line_face_nodes[iFace]; } }; - class P2ReferenceSegment : public ReferenceElement - { + + /** + * @brief P2 Reference Segment (3 nodes) + */ + class P2ReferenceSegment : public ReferenceElement { public: - P2ReferenceSegment() - { + P2ReferenceSegment() { m_numNodes = 3; m_dimension = 1; m_nodes.resize(m_numNodes); // Domaine de référence [-1, 1], ordre VTK : extrémités puis milieu m_nodes[0] = Vector3D{-1.0, 0.0, 0.0}; - m_nodes[1] = Vector3D{1.0, 0.0, 0.0}; - m_nodes[2] = Vector3D{0.0, 0.0, 0.0}; // Nœud central en x=0 + m_nodes[1] = Vector3D{ 1.0, 0.0, 0.0}; + m_nodes[2] = Vector3D{ 0.0, 0.0, 0.0}; // Nœud central en x=0 } - void evaluateShapesValues(const Vector3D &xi, - CfemReal *values) const override - { - const CfemReal x = xi.x; - const CfemReal x2 = x * x; - - values[0] = 0.5 * (x2 - x); - values[1] = 0.5 * (x2 + x); - values[2] = 1.0 - x2; + void evaluateShapesValues(const Vector3D &xi, ShapeInfo& info) const override { + reference_elem::kernels::P2SegmentKernel::values(xi.x, info.values()); } - void evaluateShapesDerivatives(const Vector3D &xi, - CfemReal *dN_dxi, - CfemReal *dN_deta, - CfemReal *dN_dzeta) const override - { - const CfemReal x = xi.x; - - dN_dxi[0] = x - 0.5; - dN_dxi[1] = x + 0.5; - dN_dxi[2] = -2.0 * x; - - for (CfemInt i = 0; i < 3; ++i) - { - dN_deta[i] = 0.0; - dN_dzeta[i] = 0.0; - } + void evaluateShapesDerivatives(const Vector3D &xi, ShapeInfo& info) const override { + reference_elem::kernels::P2SegmentKernel::grads(xi.x, info.dN_dxi(), info.dN_deta(), info.dN_dzeta()); } - void evaluateShapesSecondDerivatives(const Vector3D &xiPoints, - CfemReal *d2N_dxi2, - CfemReal *d2N_deta2, - CfemReal *d2N_dzeta2, - CfemReal *d2N_dxideta, - CfemReal *d2N_dxidzeta, - CfemReal *d2N_detadzeta) const override - { - - d2N_dxi2[0] = 1.0; - d2N_dxi2[1] = 1.0; - d2N_dxi2[2] = -2.0; - - for (CfemInt i = 0; i < 3; ++i) - { - d2N_deta2[i] = 0.0; - d2N_dzeta2[i] = 0.0; - d2N_dxideta[i] = 0.0; - d2N_dxidzeta[i] = 0.0; - d2N_detadzeta[i] = 0.0; - } + void evaluateShapesSecondDerivatives(const Vector3D &xi, ShapeInfo& info) const override { + reference_elem::kernels::P2SegmentKernel::hess(info.d2N_dxi2(), info.d2N_deta2(), info.d2N_dzeta2(), + info.d2N_dxideta(), info.d2N_dxidzeta(), info.d2N_detadzeta()); } - void evaluateShapesValuesBatch(const DynamicVector &xiPoints, - DynamicMatrix &valuesBatch) const override - { - const CfemInt nQp = static_cast(xiPoints.size()); - for (CfemInt q = 0; q < nQp; ++q) - { - const CfemReal x = xiPoints[q].x; - const CfemReal x2 = x * x; - - valuesBatch[q][0] = 0.5 * (x2 - x); - valuesBatch[q][1] = 0.5 * (x2 + x); - valuesBatch[q][2] = 1.0 - x2; + void evaluateShapesValuesBatch(const DynamicVector &xiPoints, ShapeInfoBatch& shapeBatch) const override { + const CfemInt nQp = shapeBatch.numQuadraturePoints; + for (CfemInt q = 0; q < nQp; ++q) { + reference_elem::kernels::P2SegmentKernel::values(xiPoints[q].x, shapeBatch.values(q)); } } - void evaluateShapesDerivativesBatch(const DynamicVector &xiPoints, - DynamicMatrix &dN_dxi, - DynamicMatrix &dN_deta, - DynamicMatrix &dN_dzeta) const override - { - const CfemInt nQp = static_cast(xiPoints.size()); - for (CfemInt q = 0; q < nQp; ++q) - { - const CfemReal x = xiPoints[q].x; - - dN_dxi[q][0] = x - 0.5; - dN_dxi[q][1] = x + 0.5; - dN_dxi[q][2] = -2.0 * x; - - for (CfemInt i = 0; i < 3; ++i) - { - dN_deta[q][i] = 0.0; - dN_dzeta[q][i] = 0.0; - } + void evaluateShapesDerivativesBatch(const DynamicVector &xiPoints, ShapeInfoBatch& shapeBatch) const override { + const CfemInt nQp = shapeBatch.numQuadraturePoints; + for (CfemInt q = 0; q < nQp; ++q) { + reference_elem::kernels::P2SegmentKernel::grads(xiPoints[q].x, shapeBatch.dN_dxi(q), shapeBatch.dN_deta(q), shapeBatch.dN_dzeta(q)); } } - void evaluateShapesSecondDerivativesBatch(const DynamicVector &xiPoints, - DynamicMatrix &d2N_dxi2, DynamicMatrix &d2N_deta2, DynamicMatrix &d2N_dzeta2, - DynamicMatrix &d2N_dxideta, DynamicMatrix &d2N_dxidzeta, DynamicMatrix &d2N_detadzeta) const override - { - const CfemInt nQp = static_cast(xiPoints.size()); - for (CfemInt q = 0; q < nQp; ++q) - { - - d2N_dxi2[q][0] = 1.0; - d2N_dxi2[q][1] = 1.0; - d2N_dxi2[q][2] = -2.0; - - for (CfemInt i = 0; i < 3; ++i) - { - d2N_deta2[q][i] = 0.0; - d2N_dzeta2[q][i] = 0.0; - d2N_dxideta[q][i] = 0.0; - d2N_dxidzeta[q][i] = 0.0; - d2N_detadzeta[q][i] = 0.0; - } + void evaluateShapesSecondDerivativesBatch(const DynamicVector &xiPoints, ShapeInfoBatch& shapeBatch) const override { + const CfemInt nQp = shapeBatch.numQuadraturePoints; + for (CfemInt q = 0; q < nQp; ++q) { + reference_elem::kernels::P2SegmentKernel::hess(shapeBatch.d2N_dxi2(q), shapeBatch.d2N_deta2(q), shapeBatch.d2N_dzeta2(q), + shapeBatch.d2N_dxideta(q), shapeBatch.d2N_dxidzeta(q), shapeBatch.d2N_detadzeta(q)); } } - Vector3D projectFacetRefCoordToCellRefCoord(CfemInt localFacetId, const Vector3D &xiFacet) const override - { - return cfem::reference_elem::utils::projectFacetRefCoordToSegmentRefCoord(localFacetId, xiFacet); // Make the compiler happy + Vector3D projectFacetRefCoordToCellRefCoord(CfemInt localFacetId, const Vector3D &xiFacet) const override { + return cfem::reference_elem::utils::projectFacetRefCoordToSegmentRefCoord(localFacetId, xiFacet); } - std::span getFaceNodes(CfemInt iFace) const override - { + std::span getFaceNodes(CfemInt iFace) const override { return cfem::reference_elem::utils::line_face_nodes[iFace]; } }; - class P0ReferenceSegment : public ReferenceElement - { + + /** + * @brief P0 Reference Segment (1 node, constant) + */ + class P0ReferenceSegment : public ReferenceElement { public: - P0ReferenceSegment() - { + P0ReferenceSegment() { m_numNodes = 1; m_dimension = 1; m_nodes.resize(m_numNodes); - // Un seul nœud au centre du segment de référence [-1, 1] - m_nodes = {Vector3D{0.0, 0.0, 0.0}}; - } - - void evaluateShapesValues(const Vector3D &, CfemReal *values) const override - { - values[0] = 1.0; - } - - void evaluateShapesDerivatives(const Vector3D &, - CfemReal *dN_dxi, - CfemReal *dN_deta, - CfemReal *dN_dzeta) const override - { - dN_dxi[0] = 0.0; - dN_deta[0] = 0.0; - dN_dzeta[0] = 0.0; - } - - void evaluateShapesSecondDerivatives(const Vector3D &, - CfemReal *d2N_dxi2, - CfemReal *d2N_deta2, - CfemReal *d2N_dzeta2, - CfemReal *d2N_dxideta, - CfemReal *d2N_dxidzeta, - CfemReal *d2N_detadzeta) const override - { - d2N_dxi2[0] = 0.0; - d2N_deta2[0] = 0.0; - d2N_dzeta2[0] = 0.0; - d2N_dxideta[0] = 0.0; - d2N_dxidzeta[0] = 0.0; - d2N_detadzeta[0] = 0.0; - } - - void evaluateShapesValuesBatch(const DynamicVector &xiPoints, DynamicMatrix &values) const override - { - // Constant piecewise field value - const CfemInt nQp = static_cast(xiPoints.size()); - for (CfemInt q = 0; q < nQp; ++q) - { - values[q][0] = 1.0; + m_nodes[0] = Vector3D{0.0, 0.0, 0.0}; + } + + void evaluateShapesValues(const Vector3D&, ShapeInfo& info) const override { + reference_elem::kernels::P0SegmentKernel::values(info.values()); + } + + void evaluateShapesDerivatives(const Vector3D&, ShapeInfo& info) const override { + reference_elem::kernels::P0SegmentKernel::grads(info.dN_dxi(), info.dN_deta(), info.dN_dzeta()); + } + + void evaluateShapesSecondDerivatives(const Vector3D&, ShapeInfo& info) const override { + reference_elem::kernels::P0SegmentKernel::hess(info.d2N_dxi2(), info.d2N_deta2(), info.d2N_dzeta2(), + info.d2N_dxideta(), info.d2N_dxidzeta(), info.d2N_detadzeta()); + } + + void evaluateShapesValuesBatch(const DynamicVector &xiPoints, ShapeInfoBatch& shapeBatch) const override { + const CfemInt nQp = shapeBatch.numQuadraturePoints; + for (CfemInt q = 0; q < nQp; ++q) { + reference_elem::kernels::P0SegmentKernel::values(shapeBatch.values(q)); } } - void evaluateShapesDerivativesBatch(const DynamicVector &xiPoints, - DynamicMatrix &dN_dxi, - DynamicMatrix &dN_deta, - DynamicMatrix &dN_dzeta) const override - { - // Constant function results in flat zero gradients everywhere - const CfemInt nQp = static_cast(xiPoints.size()); - for (CfemInt q = 0; q < nQp; ++q) - { - dN_dxi[q][0] = 0.0; - dN_deta[q][0] = 0.0; - dN_dzeta[q][0] = 0.0; + void evaluateShapesDerivativesBatch(const DynamicVector &xiPoints, ShapeInfoBatch& shapeBatch) const override { + const CfemInt nQp = shapeBatch.numQuadraturePoints; + for (CfemInt q = 0; q < nQp; ++q) { + reference_elem::kernels::P0SegmentKernel::grads(shapeBatch.dN_dxi(q), shapeBatch.dN_deta(q), shapeBatch.dN_dzeta(q)); } } - void evaluateShapesSecondDerivativesBatch(const DynamicVector &xiPoints, - DynamicMatrix &d2N_dxi2, - DynamicMatrix &d2N_deta2, - DynamicMatrix &d2N_dzeta2, - DynamicMatrix &d2N_dxideta, - DynamicMatrix &d2N_dxidzeta, - DynamicMatrix &d2N_detadzeta) const override - { - // Flat null second derivatives - const CfemInt nQp = static_cast(xiPoints.size()); - for (CfemInt q = 0; q < nQp; ++q) - { - d2N_dxi2[q][0] = 0.0; - d2N_deta2[q][0] = 0.0; - d2N_dzeta2[q][0] = 0.0; - d2N_dxideta[q][0] = 0.0; - d2N_dxidzeta[q][0] = 0.0; - d2N_detadzeta[q][0] = 0.0; + void evaluateShapesSecondDerivativesBatch(const DynamicVector &xiPoints, ShapeInfoBatch& shapeBatch) const override { + const CfemInt nQp = shapeBatch.numQuadraturePoints; + for (CfemInt q = 0; q < nQp; ++q) { + reference_elem::kernels::P0SegmentKernel::hess(shapeBatch.d2N_dxi2(q), shapeBatch.d2N_deta2(q), shapeBatch.d2N_dzeta2(q), + shapeBatch.d2N_dxideta(q), shapeBatch.d2N_dxidzeta(q), shapeBatch.d2N_detadzeta(q)); } } - Vector3D projectFacetRefCoordToCellRefCoord(CfemInt localFacetId, const Vector3D &xiFacet) const override - { + Vector3D projectFacetRefCoordToCellRefCoord(CfemInt localFacetId, const Vector3D &xiFacet) const override { return cfem::reference_elem::utils::projectFacetRefCoordToSegmentRefCoord(localFacetId, xiFacet); } - std::span getFaceNodes(CfemInt iFace) const override - { + std::span getFaceNodes(CfemInt iFace) const override { return cfem::reference_elem::utils::line1_face_nodes[iFace]; } }; diff --git a/libs/fem/reference_element/include/ReferenceTetrahedron.h b/libs/fem/reference_element/include/ReferenceTetrahedron.h index 58f4209..487b7be 100644 --- a/libs/fem/reference_element/include/ReferenceTetrahedron.h +++ b/libs/fem/reference_element/include/ReferenceTetrahedron.h @@ -21,6 +21,8 @@ #include "ReferenceElement.h" #include +#include +#include namespace cfem::reference_elem::utils{ @@ -75,9 +77,26 @@ namespace cfem::reference_elem::utils{ namespace cfem::reference_elem::kernels { - // Note : L'utilisation de __restrict (ou __restrict__ sous GCC/Clang) - // garantit au compilateur qu'il n'y a pas d'aliasing entre les pointeurs. - + struct P0TetraKernel { + [[gnu::always_inline]] static inline void values(CfemReal* __restrict N) noexcept + { + N[0] = 1.0; + } + + [[gnu::always_inline]] static inline void grads(CfemReal* __restrict dx, + CfemReal* __restrict dy, + CfemReal* __restrict dz) noexcept + { + dx[0] = dy[0] = dz[0] = 0.0; + } + + [[gnu::always_inline]] static inline void hess(CfemReal* __restrict d2x, CfemReal* __restrict d2y, CfemReal* __restrict d2z, + CfemReal* __restrict dxy, CfemReal* __restrict dxz, CfemReal* __restrict dyz) noexcept + { + d2x[0] = d2y[0] = d2z[0] = dxy[0] = dxz[0] = dyz[0] = 0.0; + } + }; + struct P1TetraKernel { [[gnu::always_inline]] static inline void values(CfemReal L0, CfemReal L1, CfemReal L2, CfemReal L3, CfemReal* __restrict N) noexcept @@ -103,6 +122,59 @@ namespace cfem::reference_elem::kernels { } }; + struct MiniTetraKernel { + [[gnu::always_inline]] static inline void values(CfemReal L0, CfemReal L1, CfemReal L2, CfemReal L3, + CfemReal* __restrict N) noexcept + { + const CfemReal b = 256.0 * L0 * L1 * L2 * L3; + N[4] = b; + N[0] = L0 - b * 0.25; + N[1] = L1 - b * 0.25; + N[2] = L2 - b * 0.25; + N[3] = L3 - b * 0.25; + } + + [[gnu::always_inline]] static inline void grads(CfemReal L0, CfemReal L1, CfemReal L2, CfemReal L3, + CfemReal* __restrict dx, + CfemReal* __restrict dy, + CfemReal* __restrict dz) noexcept + { + const CfemReal db_dxi = 256.0 * L2 * L3 * (L0 - L1); + const CfemReal db_deta = 256.0 * L1 * L3 * (L0 - L2); + const CfemReal db_dzeta = 256.0 * L1 * L2 * (L0 - L3); + + dx[4] = db_dxi; dy[4] = db_deta; dz[4] = db_dzeta; + dx[0] = -1.0 - db_dxi*0.25; dy[0] = -1.0 - db_deta*0.25; dz[0] = -1.0 - db_dzeta*0.25; + dx[1] = 1.0 - db_dxi*0.25; dy[1] = 0.0 - db_deta*0.25; dz[1] = 0.0 - db_dzeta*0.25; + dx[2] = 0.0 - db_dxi*0.25; dy[2] = 1.0 - db_deta*0.25; dz[2] = 0.0 - db_dzeta*0.25; + dx[3] = 0.0 - db_dxi*0.25; dy[3] = 0.0 - db_deta*0.25; dz[3] = 1.0 - db_dzeta*0.25; + } + + [[gnu::always_inline]] static inline void hess(CfemReal L0, CfemReal L1, CfemReal L2, CfemReal L3, + CfemReal* __restrict d2x, CfemReal* __restrict d2y, CfemReal* __restrict d2z, + CfemReal* __restrict dxy, CfemReal* __restrict dxz, CfemReal* __restrict dyz) noexcept + { + const CfemReal d2b_dxi2 = -512.0 * L2 * L3; + const CfemReal d2b_deta2 = -512.0 * L1 * L3; + const CfemReal d2b_dzeta2 = -512.0 * L1 * L2; + const CfemReal d2b_dxideta = 256.0 * L3 * (L0 - L1 - L2); + const CfemReal d2b_dxidzeta = 256.0 * L2 * (L0 - L1 - L3); + const CfemReal d2b_detadzeta= 256.0 * L1 * (L0 - L2 - L3); + + d2x[4] = d2b_dxi2; d2y[4] = d2b_deta2; d2z[4] = d2b_dzeta2; + dxy[4] = d2b_dxideta; dxz[4] = d2b_dxidzeta; dyz[4] = d2b_detadzeta; + + for (CfemInt i = 0; i < 4; ++i) { + d2x[i] = -d2b_dxi2 * 0.25; + d2y[i] = -d2b_deta2 * 0.25; + d2z[i] = -d2b_dzeta2 * 0.25; + dxy[i] = -d2b_dxideta * 0.25; + dxz[i] = -d2b_dxidzeta * 0.25; + dyz[i] = -d2b_detadzeta * 0.25; + } + } + }; + struct P2TetraKernel { [[gnu::always_inline]] static inline void values(CfemReal L0, CfemReal L1, CfemReal L2, CfemReal L3, CfemReal* __restrict N) noexcept @@ -128,7 +200,7 @@ namespace cfem::reference_elem::kernels { // Lignes = composantes (xx, yy, zz, xy, xz, yz) // Colonnes = nœuds (0 à 9) static constexpr CfemReal H[6][10] = { - {4.0, 4.0, 0.0, 0.0,-8.0, 0.0, 0.0, 0.0, 0.0, 0.0}, // d2x2 (CORRIGÉ: Noeuds 1, 6, 7) + {4.0, 4.0, 0.0, 0.0,-8.0, 0.0, 0.0, 0.0, 0.0, 0.0}, // d2x2 {4.0, 0.0, 4.0, 0.0, 0.0, 0.0,-8.0, 0.0, 0.0, 0.0}, // d2y2 {4.0, 0.0, 0.0, 4.0, 0.0, 0.0, 0.0,-8.0, 0.0, 0.0}, // d2z2 {4.0, 0.0, 0.0, 0.0,-4.0, 4.0,-4.0, 0.0, 0.0, 0.0}, // dxy @@ -146,226 +218,178 @@ namespace cfem::reference_elem::kernels { } } }; + } namespace cfem { /** - * @brief Tétraèdre Linéaire (4 nœuds) + * @brief Constant Reference Tetrahedron (1 node, P0) */ - class P1ReferenceTetrahedron : public ReferenceElement { + class P0ReferenceTetrahedron : public ReferenceElement { public: - P1ReferenceTetrahedron() { - m_numNodes = 4; + P0ReferenceTetrahedron() { + m_numNodes = 1; m_dimension = 3; - - m_nodes = { - Vector3D{0.0, 0.0, 0.0}, - Vector3D{1.0, 0.0, 0.0}, - Vector3D{0.0, 1.0, 0.0}, - Vector3D{0.0, 0.0, 1.0} - }; + m_nodes = { Vector3D{0.25, 0.25, 0.25} }; } - void evaluateShapesValues(const Vector3D& xi, CfemReal* values) const override { - CfemReal L0, L1, L2, L3; - reference_elem::utils::computeTetraBarycentric(xi, L0, L1, L2, L3); - reference_elem::kernels::P1TetraKernel::values(L0, L1, L2, L3, values); + // ========================================================================= + // SCALAR INTERFACES + // ========================================================================= + void evaluateShapesValues(const Vector3D&, ShapeInfo& shape) const override + { + reference_elem::kernels::P0TetraKernel::values(shape.values()); } - void evaluateShapesDerivatives(const Vector3D&, CfemReal* dN_dxi, CfemReal* dN_deta, CfemReal* dN_dzeta) const override { - reference_elem::kernels::P1TetraKernel::grads(dN_dxi, dN_deta, dN_dzeta); + void evaluateShapesDerivatives(const Vector3D&, ShapeInfo& shape) const override + { + reference_elem::kernels::P0TetraKernel::grads(shape.dN_dxi(), shape.dN_deta(), shape.dN_dzeta()); } - void evaluateShapesSecondDerivatives(const Vector3D&, - CfemReal* d2N_dxi2, - CfemReal* d2N_deta2, - CfemReal* d2N_dzeta2, - CfemReal* d2N_dxideta, - CfemReal* d2N_dxidzeta, - CfemReal* d2N_detadzeta) const override + void evaluateShapesSecondDerivatives(const Vector3D&, ShapeInfo& shape) const override { - reference_elem::kernels::P1TetraKernel::hess(d2N_dxi2, d2N_deta2, d2N_dzeta2, - d2N_dxideta, d2N_dxidzeta, d2N_detadzeta); + reference_elem::kernels::P0TetraKernel::hess(shape.d2N_dxi2(), shape.d2N_deta2(), shape.d2N_dzeta2(), + shape.d2N_dxideta(), shape.d2N_dxidzeta(), shape.d2N_detadzeta()); } // ===================================================================== - // INTERFACES BATCH + // BATCH INTERFACES (Full Memcpy broadcasting for everything) // ===================================================================== - void evaluateShapesValuesBatch(const DynamicVector& xiPoints, DynamicMatrix& values) const override { - const CfemInt nQp = static_cast(xiPoints.size()); - for (CfemInt q = 0; q < nQp; ++q) { - const Vector3D& xi = xiPoints[q]; - CfemReal L0, L1, L2, L3; - reference_elem::utils::computeTetraBarycentric(xi, L0, L1, L2, L3); - reference_elem::kernels::P1TetraKernel::values(L0, L1, L2, L3, values[q].data()); + void evaluateShapesValuesBatch(const DynamicVector &xiPoints, ShapeInfoBatch& shapeBatch) const override + { + const CfemInt nQp = shapeBatch.numQuadraturePoints; + if (nQp == 0) return; + + reference_elem::kernels::P0TetraKernel::values(shapeBatch.values(0)); + + const size_t bytesToCopy = m_numNodes * sizeof(CfemReal); + for (CfemInt q = 1; q < nQp; ++q) { + std::memcpy(shapeBatch.values(q), shapeBatch.values(0), bytesToCopy); } } - void evaluateShapesDerivativesBatch(const DynamicVector& xiPoints, - DynamicMatrix& dN_dxi, - DynamicMatrix& dN_deta, - DynamicMatrix& dN_dzeta) const override { - const CfemInt nQp = static_cast(xiPoints.size()); + void evaluateShapesDerivativesBatch(const DynamicVector & xiPoints, + ShapeInfoBatch& shapeBatch) const override + { + const CfemInt nQp = shapeBatch.numQuadraturePoints; + if (nQp == 0) return; - for (CfemInt q = 0; q < nQp; ++q) { - reference_elem::kernels::P1TetraKernel::grads(dN_dxi[q].data(), dN_deta[q].data(), dN_dzeta[q].data()); + reference_elem::kernels::P0TetraKernel::grads(shapeBatch.dN_dxi(0), shapeBatch.dN_deta(0), shapeBatch.dN_dzeta(0)); + + const size_t bytesToCopy = m_numNodes * sizeof(CfemReal); + for (CfemInt q = 1; q < nQp; ++q) { + std::memcpy(shapeBatch.dN_dxi(q), shapeBatch.dN_dxi(0), bytesToCopy); + std::memcpy(shapeBatch.dN_deta(q), shapeBatch.dN_deta(0), bytesToCopy); + std::memcpy(shapeBatch.dN_dzeta(q), shapeBatch.dN_dzeta(0), bytesToCopy); } } - void evaluateShapesSecondDerivativesBatch(const DynamicVector& xiPoints, - DynamicMatrix& d2N_dxi2, - DynamicMatrix& d2N_deta2, - DynamicMatrix& d2N_dzeta2, - DynamicMatrix& d2N_dxideta, - DynamicMatrix& d2N_dxidzeta, - DynamicMatrix& d2N_detadzeta) const override - { - const CfemInt nQp = static_cast(xiPoints.size()); + void evaluateShapesSecondDerivativesBatch(const DynamicVector &xiPoints, + ShapeInfoBatch& shapeBatch) const override + { + const CfemInt nQp = shapeBatch.numQuadraturePoints; if (nQp == 0) return; - reference_elem::kernels::P1TetraKernel::hess( - d2N_dxi2[0].data(), d2N_deta2[0].data(), d2N_dzeta2[0].data(), - d2N_dxideta[0].data(), d2N_dxidzeta[0].data(), d2N_detadzeta[0].data() + reference_elem::kernels::P0TetraKernel::hess( + shapeBatch.d2N_dxi2(0), shapeBatch.d2N_deta2(0), shapeBatch.d2N_dzeta2(0), + shapeBatch.d2N_dxideta(0), shapeBatch.d2N_dxidzeta(0), shapeBatch.d2N_detadzeta(0) ); const size_t bytesToCopy = m_numNodes * sizeof(CfemReal); for (CfemInt q = 1; q < nQp; ++q) { - std::memcpy(d2N_dxi2[q].data(), d2N_dxi2[0].data(), bytesToCopy); - std::memcpy(d2N_deta2[q].data(), d2N_deta2[0].data(), bytesToCopy); - std::memcpy(d2N_dzeta2[q].data(), d2N_dzeta2[0].data(), bytesToCopy); - std::memcpy(d2N_dxideta[q].data(), d2N_dxideta[0].data(), bytesToCopy); - std::memcpy(d2N_dxidzeta[q].data(), d2N_dxidzeta[0].data(), bytesToCopy); - std::memcpy(d2N_detadzeta[q].data(), d2N_detadzeta[0].data(), bytesToCopy); + std::memcpy(shapeBatch.d2N_dxi2(q), shapeBatch.d2N_dxi2(0), bytesToCopy); + std::memcpy(shapeBatch.d2N_deta2(q), shapeBatch.d2N_deta2(0), bytesToCopy); + std::memcpy(shapeBatch.d2N_dzeta2(q), shapeBatch.d2N_dzeta2(0), bytesToCopy); + std::memcpy(shapeBatch.d2N_dxideta(q), shapeBatch.d2N_dxideta(0), bytesToCopy); + std::memcpy(shapeBatch.d2N_dxidzeta(q), shapeBatch.d2N_dxidzeta(0), bytesToCopy); + std::memcpy(shapeBatch.d2N_detadzeta(q), shapeBatch.d2N_detadzeta(0), bytesToCopy); } } - Vector3D projectFacetRefCoordToCellRefCoord(CfemInt localFacetId, const Vector3D& xiFacet) const override{ - return cfem::reference_elem::utils::projectFacetRefCoordToTetraRefCoord(localFacetId, xiFacet); // Make the compiler happy + Vector3D projectFacetRefCoordToCellRefCoord(CfemInt localFacetId, const Vector3D& xiFacet) const override { + return cfem::reference_elem::utils::projectFacetRefCoordToTetraRefCoord(localFacetId, xiFacet); } std::span getFaceNodes(CfemInt iFace) const override { - return cfem::reference_elem::utils::tet4_face_nodes[iFace]; + return cfem::reference_elem::utils::tet1_face_nodes[iFace]; } }; /** - * @brief Tétraèdre Quadratique (10 nœuds) + * @brief Linear TET reference element */ - class P2ReferenceTetrahedron : public ReferenceElement { + class P1ReferenceTetrahedron : public ReferenceElement { public: - P2ReferenceTetrahedron() { - m_numNodes = 10; + P1ReferenceTetrahedron() { + m_numNodes = 4; m_dimension = 3; m_nodes = { - Vector3D{0.0, 0.0 ,0.0}, - Vector3D{1.0, 0.0 ,0.0}, - Vector3D{0.0, 1.0 ,0.0}, - Vector3D{0.0, 0.0 ,1.0}, - // Milieux d'arêtes (Standard convention: 01, 12, 20, 03, 13, 23) - Vector3D{0.5, 0.0, 0.0}, - Vector3D{0.5, 0.5, 0.0}, - Vector3D{0.0, 0.5, 0.0}, - Vector3D{0.0, 0.0, 0.5}, - Vector3D{0.5, 0.0, 0.5}, - Vector3D{0.0, 0.5, 0.5} + Vector3D{0.0, 0.0, 0.0}, + Vector3D{1.0, 0.0, 0.0}, + Vector3D{0.0, 1.0, 0.0}, + Vector3D{0.0, 0.0, 1.0} }; } - // ========================================================================= - // FIRST DERIVATIVES - // ========================================================================= - void evaluateShapesValues(const Vector3D& xi, CfemReal* values) const override - { + void evaluateShapesValues(const Vector3D& xi, ShapeInfo& shape) const override { CfemReal L0, L1, L2, L3; reference_elem::utils::computeTetraBarycentric(xi, L0, L1, L2, L3); - reference_elem::kernels::P2TetraKernel::values(L0, L1, L2, L3, values); + reference_elem::kernels::P1TetraKernel::values(L0, L1, L2, L3, shape.values()); } - void evaluateShapesValuesBatch(const DynamicVector& xiPoints, - DynamicMatrix& values) const override - { - const CfemInt nQp = static_cast(xiPoints.size()); - for (CfemInt q = 0; q < nQp; ++q) { - const Vector3D& xi = xiPoints[q]; - CfemReal L0, L1, L2, L3; - reference_elem::utils::computeTetraBarycentric(xi, L0, L1, L2, L3); - - reference_elem::kernels::P2TetraKernel::values(L0, L1, L2, L3, values[q].data()); - } + void evaluateShapesDerivatives(const Vector3D&, ShapeInfo& shape) const override { + reference_elem::kernels::P1TetraKernel::grads(shape.dN_dxi(), shape.dN_deta(), shape.dN_dzeta()); } - - // ========================================================================= - // FIRST DERIVATIVES - // ========================================================================= - void evaluateShapesDerivatives(const Vector3D& xi, - CfemReal* dN_dxi, - CfemReal* dN_deta, - CfemReal* dN_dzeta) const override + + void evaluateShapesSecondDerivatives(const Vector3D&, ShapeInfo& shape) const override { - CfemReal L0, L1, L2, L3; - reference_elem::utils::computeTetraBarycentric(xi, L0, L1, L2, L3); - reference_elem::kernels::P2TetraKernel::grads(L0, L1, L2, L3, dN_dxi, dN_deta, dN_dzeta); + reference_elem::kernels::P1TetraKernel::hess(shape.d2N_dxi2(), shape.d2N_deta2(), shape.d2N_dzeta2(), + shape.d2N_dxideta(), shape.d2N_dxidzeta(), shape.d2N_detadzeta()); } - void evaluateShapesDerivativesBatch(const DynamicVector& xiPoints, - DynamicMatrix& dN_dxi, - DynamicMatrix& dN_deta, - DynamicMatrix& dN_dzeta) const override - { - const CfemInt nQp = static_cast(xiPoints.size()); + // ===================================================================== + // INTERFACES BATCH + // ===================================================================== + void evaluateShapesValuesBatch(const DynamicVector& xiPoints, ShapeInfoBatch& shapeBatch) const override { + const CfemInt nQp = shapeBatch.numQuadraturePoints; for (CfemInt q = 0; q < nQp; ++q) { - const Vector3D& xi = xiPoints[q]; CfemReal L0, L1, L2, L3; - reference_elem::utils::computeTetraBarycentric(xi, L0, L1, L2, L3); - - reference_elem::kernels::P2TetraKernel::grads(L0, L1, L2, L3, - dN_dxi[q].data(), - dN_deta[q].data(), - dN_dzeta[q].data()); + reference_elem::utils::computeTetraBarycentric(xiPoints[q], L0, L1, L2, L3); + reference_elem::kernels::P1TetraKernel::values(L0, L1, L2, L3, shapeBatch.values(q)); } } - // ========================================================================= - // SECOND DERIVATIVES - // ========================================================================= - void evaluateShapesSecondDerivatives(const Vector3D& /*xi*/, - CfemReal* d2N_dxi2, - CfemReal* d2N_deta2, - CfemReal* d2N_dzeta2, - CfemReal* d2N_dxideta, - CfemReal* d2N_dxidzeta, - CfemReal* d2N_detadzeta) const override - { - reference_elem::kernels::P2TetraKernel::hess(d2N_dxi2, d2N_deta2, d2N_dzeta2, - d2N_dxideta, d2N_dxidzeta, d2N_detadzeta); + void evaluateShapesDerivativesBatch(const DynamicVector& xiPoints, ShapeInfoBatch& shapeBatch) const override { + const CfemInt nQp = shapeBatch.numQuadraturePoints; + for (CfemInt q = 0; q < nQp; ++q) { + reference_elem::kernels::P1TetraKernel::grads( + shapeBatch.dN_dxi(q), + shapeBatch.dN_deta(q), + shapeBatch.dN_dzeta(q) + ); + } } - void evaluateShapesSecondDerivativesBatch(const DynamicVector& xiPoints, - DynamicMatrix& d2N_dxi2, - DynamicMatrix& d2N_deta2, - DynamicMatrix& d2N_dzeta2, - DynamicMatrix& d2N_dxideta, - DynamicMatrix& d2N_dxidzeta, - DynamicMatrix& d2N_detadzeta) const override - { - const CfemInt nQp = static_cast(xiPoints.size()); + void evaluateShapesSecondDerivativesBatch(const DynamicVector& xiPoints, ShapeInfoBatch& shapeBatch) const override + { + const CfemInt nQp = shapeBatch.numQuadraturePoints; if (nQp == 0) return; - reference_elem::kernels::P2TetraKernel::hess( - d2N_dxi2[0].data(), d2N_deta2[0].data(), d2N_dzeta2[0].data(), - d2N_dxideta[0].data(), d2N_dxidzeta[0].data(), d2N_detadzeta[0].data() + reference_elem::kernels::P1TetraKernel::hess( + shapeBatch.d2N_dxi2(0), shapeBatch.d2N_deta2(0), shapeBatch.d2N_dzeta2(0), + shapeBatch.d2N_dxideta(0), shapeBatch.d2N_dxidzeta(0), shapeBatch.d2N_detadzeta(0) ); const size_t bytesToCopy = m_numNodes * sizeof(CfemReal); for (CfemInt q = 1; q < nQp; ++q) { - std::memcpy(d2N_dxi2[q].data(), d2N_dxi2[0].data(), bytesToCopy); - std::memcpy(d2N_deta2[q].data(), d2N_deta2[0].data(), bytesToCopy); - std::memcpy(d2N_dzeta2[q].data(), d2N_dzeta2[0].data(), bytesToCopy); - std::memcpy(d2N_dxideta[q].data(), d2N_dxideta[0].data(), bytesToCopy); - std::memcpy(d2N_dxidzeta[q].data(), d2N_dxidzeta[0].data(), bytesToCopy); - std::memcpy(d2N_detadzeta[q].data(), d2N_detadzeta[0].data(), bytesToCopy); + std::memcpy(shapeBatch.d2N_dxi2(q), shapeBatch.d2N_dxi2(0), bytesToCopy); + std::memcpy(shapeBatch.d2N_deta2(q), shapeBatch.d2N_deta2(0), bytesToCopy); + std::memcpy(shapeBatch.d2N_dzeta2(q), shapeBatch.d2N_dzeta2(0), bytesToCopy); + std::memcpy(shapeBatch.d2N_dxideta(q), shapeBatch.d2N_dxideta(0), bytesToCopy); + std::memcpy(shapeBatch.d2N_dxidzeta(q), shapeBatch.d2N_dxidzeta(0), bytesToCopy); + std::memcpy(shapeBatch.d2N_detadzeta(q), shapeBatch.d2N_detadzeta(0), bytesToCopy); } } @@ -374,14 +398,17 @@ namespace cfem { } std::span getFaceNodes(CfemInt iFace) const override { - return cfem::reference_elem::utils::tet10_face_nodes[iFace]; + return cfem::reference_elem::utils::tet4_face_nodes[iFace]; } }; + /** + * @brief P1-Mini Reference Tetrahedron (5 nodes: 4 vertices + 1 bubble) + */ class MiniReferenceTetrahedron : public ReferenceElement { public: MiniReferenceTetrahedron() { - m_numNodes = 5; // 4 sommets + 1 bulle + m_numNodes = 5; m_dimension = 3; m_nodes = { @@ -393,177 +420,70 @@ namespace cfem { }; } - void evaluateShapesValues(const Vector3D& xi, CfemReal* values) const override + // ========================================================================= + // SCALAR INTERFACES + // ========================================================================= + void evaluateShapesValues(const Vector3D& xi, ShapeInfo& shape) const override { CfemReal L0, L1, L2, L3; reference_elem::utils::computeTetraBarycentric(xi, L0, L1, L2, L3); - - // Bubble function: 256 * L0 * L1 * L2 * L3 - const CfemReal b = 256.0 * L0 * L1 * L2 * L3; - - values[4] = b; - values[0] = L0 - b * 0.25; - values[1] = L1 - b * 0.25; - values[2] = L2 - b * 0.25; - values[3] = L3 - b * 0.25; + reference_elem::kernels::MiniTetraKernel::values(L0, L1, L2, L3, shape.values()); } - void evaluateShapesDerivatives(const Vector3D& xi, - CfemReal* dN_dxi, - CfemReal* dN_deta, - CfemReal* dN_dzeta) const override + void evaluateShapesDerivatives(const Vector3D& xi, ShapeInfo& shape) const override { CfemReal L0, L1, L2, L3; reference_elem::utils::computeTetraBarycentric(xi, L0, L1, L2, L3); - - // Bubble gradients - const CfemReal db_dxi = 256.0 * L2 * L3 * (L0 - L1); - const CfemReal db_deta = 256.0 * L1 * L3 * (L0 - L2); - const CfemReal db_dzeta = 256.0 * L1 * L2 * (L0 - L3); - - // Node 4 (Bubble) - dN_dxi[4] = db_dxi; - dN_deta[4] = db_deta; - dN_dzeta[4] = db_dzeta; - - // Node 0 - dN_dxi[0] = -1.0 - db_dxi * 0.25; - dN_deta[0] = -1.0 - db_deta * 0.25; - dN_dzeta[0] = -1.0 - db_dzeta * 0.25; - - // Node 1 - dN_dxi[1] = 1.0 - db_dxi * 0.25; - dN_deta[1] = 0.0 - db_deta * 0.25; - dN_dzeta[1] = 0.0 - db_dzeta * 0.25; - - // Node 2 - dN_dxi[2] = 0.0 - db_dxi * 0.25; - dN_deta[2] = 1.0 - db_deta * 0.25; - dN_dzeta[2] = 0.0 - db_dzeta * 0.25; - - // Node 3 - dN_dxi[3] = 0.0 - db_dxi * 0.25; - dN_deta[3] = 0.0 - db_deta * 0.25; - dN_dzeta[3] = 1.0 - db_dzeta * 0.25; + reference_elem::kernels::MiniTetraKernel::grads(L0, L1, L2, L3, shape.dN_dxi(), shape.dN_deta(), shape.dN_dzeta()); } - void evaluateShapesSecondDerivatives(const Vector3D& xi, - CfemReal* d2N_dxi2, - CfemReal* d2N_deta2, - CfemReal* d2N_dzeta2, - CfemReal* d2N_dxideta, - CfemReal* d2N_dxidzeta, - CfemReal* d2N_detadzeta) const override + void evaluateShapesSecondDerivatives(const Vector3D& xi, ShapeInfo& shape) const override { CfemReal L0, L1, L2, L3; reference_elem::utils::computeTetraBarycentric(xi, L0, L1, L2, L3); - - // Second derivatives of the bubble function - const CfemReal d2b_dxi2 = -512.0 * L2 * L3; - const CfemReal d2b_deta2 = -512.0 * L1 * L3; - const CfemReal d2b_dzeta2 = -512.0 * L1 * L2; - const CfemReal d2b_dxideta = 256.0 * L3 * (L0 - L1 - L2); - const CfemReal d2b_dxidzeta = 256.0 * L2 * (L0 - L1 - L3); - const CfemReal d2b_detadzeta= 256.0 * L1 * (L0 - L2 - L3); - - // Node 4 (Bubble) - d2N_dxi2[4] = d2b_dxi2; - d2N_deta2[4] = d2b_deta2; - d2N_dzeta2[4] = d2b_dzeta2; - d2N_dxideta[4] = d2b_dxideta; - d2N_dxidzeta[4] = d2b_dxidzeta; - d2N_detadzeta[4] = d2b_detadzeta; - - // Nodes 0 to 3 inherit the bubble's correction divided by -4.0 - for (CfemInt i = 0; i < 4; ++i) { - d2N_dxi2[i] = -d2b_dxi2 * 0.25; - d2N_deta2[i] = -d2b_deta2 * 0.25; - d2N_dzeta2[i] = -d2b_dzeta2 * 0.25; - d2N_dxideta[i] = -d2b_dxideta * 0.25; - d2N_dxidzeta[i] = -d2b_dxidzeta * 0.25; - d2N_detadzeta[i] = -d2b_detadzeta * 0.25; - } + reference_elem::kernels::MiniTetraKernel::hess(L0, L1, L2, L3, + shape.d2N_dxi2(), shape.d2N_deta2(), shape.d2N_dzeta2(), + shape.d2N_dxideta(), shape.d2N_dxidzeta(), shape.d2N_detadzeta()); } - void evaluateShapesValuesBatch(const DynamicVector &xiPoints, DynamicMatrix& values) const override + // ===================================================================== + // HPC BATCH INTERFACES + // ===================================================================== + void evaluateShapesValuesBatch(const DynamicVector &xiPoints, ShapeInfoBatch& shapeBatch) const override { - // Constant piecewise field value - const CfemInt nQp = static_cast(xiPoints.size()); + const CfemInt nQp = shapeBatch.numQuadraturePoints; for (CfemInt q = 0; q < nQp; ++q) { CfemReal L0, L1, L2, L3; reference_elem::utils::computeTetraBarycentric(xiPoints[q], L0, L1, L2, L3); - - // Bubble function: 256 * L0 * L1 * L2 * L3 - const CfemReal b = 256.0 * L0 * L1 * L2 * L3; - - values[q][4] = b; - values[q][0] = L0 - b * 0.25; - values[q][1] = L1 - b * 0.25; - values[q][2] = L2 - b * 0.25; - values[q][3] = L3 - b * 0.25; + reference_elem::kernels::MiniTetraKernel::values(L0, L1, L2, L3, shapeBatch.values(q)); } } void evaluateShapesDerivativesBatch(const DynamicVector & xiPoints, - DynamicMatrix &dN_dxi, - DynamicMatrix &dN_deta, - DynamicMatrix &dN_dzeta) const override + ShapeInfoBatch& shapeBatch) const override { - // Constant function results in flat zero gradients everywhere - const CfemInt nQp = static_cast(xiPoints.size()); + const CfemInt nQp = shapeBatch.numQuadraturePoints; for (CfemInt q = 0; q < nQp; ++q) { CfemReal L0, L1, L2, L3; reference_elem::utils::computeTetraBarycentric(xiPoints[q], L0, L1, L2, L3); - - // Bubble gradients - const CfemReal db_dxi = 256.0 * L2 * L3 * (L0 - L1); - const CfemReal db_deta = 256.0 * L1 * L3 * (L0 - L2); - const CfemReal db_dzeta = 256.0 * L1 * L2 * (L0 - L3); - - // Node 4 (Bubble) - dN_dxi[q][4] = db_dxi; - dN_deta[q][4] = db_deta; - dN_dzeta[q][4] = db_dzeta; - - // Node 0 - dN_dxi[q][0] = -1.0 - db_dxi * 0.25; - dN_deta[q][0] = -1.0 - db_deta * 0.25; - dN_dzeta[q][0] = -1.0 - db_dzeta * 0.25; - - // Node 1 - dN_dxi[q][1] = 1.0 - db_dxi * 0.25; - dN_deta[q][1] = 0.0 - db_deta * 0.25; - dN_dzeta[q][1] = 0.0 - db_dzeta * 0.25; - - // Node 2 - dN_dxi[q][2] = 0.0 - db_dxi * 0.25; - dN_deta[q][2] = 1.0 - db_deta * 0.25; - dN_dzeta[q][2] = 0.0 - db_dzeta * 0.25; - - // Node 3 - dN_dxi[q][3] = 0.0 - db_dxi * 0.25; - dN_deta[q][3] = 0.0 - db_deta * 0.25; - dN_dzeta[q][3] = 1.0 - db_dzeta * 0.25; + reference_elem::kernels::MiniTetraKernel::grads(L0, L1, L2, L3, + shapeBatch.dN_dxi(q), + shapeBatch.dN_deta(q), + shapeBatch.dN_dzeta(q)); } } void evaluateShapesSecondDerivativesBatch(const DynamicVector &xiPoints, - DynamicMatrix &d2N_dxi2, - DynamicMatrix &d2N_deta2, - DynamicMatrix &d2N_dzeta2, - DynamicMatrix &d2N_dxideta, - DynamicMatrix &d2N_dxidzeta, - DynamicMatrix &d2N_detadzeta) const override - { - // Flat null second derivatives - const CfemInt nQp = static_cast(xiPoints.size()); + ShapeInfoBatch& shapeBatch) const override + { + const CfemInt nQp = shapeBatch.numQuadraturePoints; + // Note : Contrairement au P1/P2, le hessien dépend de xi. Pas de memcpy possible. for (CfemInt q = 0; q < nQp; ++q) { - d2N_dxi2[q][0] = 0.0; - d2N_deta2[q][0] = 0.0; - d2N_dzeta2[q][0] = 0.0; - d2N_dxideta[q][0] = 0.0; - d2N_dxidzeta[q][0] = 0.0; - d2N_detadzeta[q][0] = 0.0; + CfemReal L0, L1, L2, L3; + reference_elem::utils::computeTetraBarycentric(xiPoints[q], L0, L1, L2, L3); + reference_elem::kernels::MiniTetraKernel::hess(L0, L1, L2, L3, + shapeBatch.d2N_dxi2(q), shapeBatch.d2N_deta2(q), shapeBatch.d2N_dzeta2(q), + shapeBatch.d2N_dxideta(q), shapeBatch.d2N_dxidzeta(q), shapeBatch.d2N_detadzeta(q)); } } @@ -576,95 +496,116 @@ namespace cfem { } }; -class P0ReferenceTetrahedron : public ReferenceElement { + /** + * @brief Tétraèdre Quadratique (10 nœuds) + */ + class P2ReferenceTetrahedron : public ReferenceElement { public: - P0ReferenceTetrahedron() { - m_numNodes = 1; + P2ReferenceTetrahedron() { + m_numNodes = 10; m_dimension = 3; - - m_nodes = { Vector3D{0.25, 0.25, 0.25} }; + + m_nodes = { + Vector3D{0.0, 0.0 ,0.0}, + Vector3D{1.0, 0.0 ,0.0}, + Vector3D{0.0, 1.0 ,0.0}, + Vector3D{0.0, 0.0 ,1.0}, + // Milieux d'arêtes (Standard convention: 01, 12, 20, 03, 13, 23) + Vector3D{0.5, 0.0, 0.0}, + Vector3D{0.5, 0.5, 0.0}, + Vector3D{0.0, 0.5, 0.0}, + Vector3D{0.0, 0.0, 0.5}, + Vector3D{0.5, 0.0, 0.5}, + Vector3D{0.0, 0.5, 0.5} + }; } - void evaluateShapesValues(const Vector3D&, CfemReal* values) const override + // ========================================================================= + // FIRST DERIVATIVES + // ========================================================================= + void evaluateShapesValues(const Vector3D& xi, ShapeInfo& shape) const override { - values[0] = 1.0; + CfemReal L0, L1, L2, L3; + reference_elem::utils::computeTetraBarycentric(xi, L0, L1, L2, L3); + reference_elem::kernels::P2TetraKernel::values(L0, L1, L2, L3, shape.values()); } - void evaluateShapesDerivatives(const Vector3D&, - CfemReal* dN_dxi, - CfemReal* dN_deta, - CfemReal* dN_dzeta) const override + void evaluateShapesValuesBatch(const DynamicVector& xiPoints, + ShapeInfoBatch& shapeBatch) const override { - dN_dxi[0] = 0.0; - dN_deta[0] = 0.0; - dN_dzeta[0] = 0.0; + const CfemInt nQp = shapeBatch.numQuadraturePoints; + for (CfemInt q = 0; q < nQp; ++q) { + const Vector3D& xi = xiPoints[q]; + CfemReal L0, L1, L2, L3; + reference_elem::utils::computeTetraBarycentric(xi, L0, L1, L2, L3); + + reference_elem::kernels::P2TetraKernel::values(L0, L1, L2, L3, shapeBatch.values(q) ); + } } - - void evaluateShapesSecondDerivatives(const Vector3D&, - CfemReal* d2N_dxi2, - CfemReal* d2N_deta2, - CfemReal* d2N_dzeta2, - CfemReal* d2N_dxideta, - CfemReal* d2N_dxidzeta, - CfemReal* d2N_detadzeta) const override + + // ========================================================================= + // FIRST DERIVATIVES + // ========================================================================= + void evaluateShapesDerivatives(const Vector3D& xi, ShapeInfo& shape) const override { - d2N_dxi2[0] = 0.0; - d2N_deta2[0] = 0.0; - d2N_dzeta2[0] = 0.0; - d2N_dxideta[0] = 0.0; - d2N_dxidzeta[0] = 0.0; - d2N_detadzeta[0] = 0.0; + CfemReal L0, L1, L2, L3; + reference_elem::utils::computeTetraBarycentric(xi, L0, L1, L2, L3); + reference_elem::kernels::P2TetraKernel::grads(L0, L1, L2, L3, shape.dN_dxi(), shape.dN_deta(), shape.dN_dzeta()); } - void evaluateShapesValuesBatch(const DynamicVector &xiPoints, DynamicMatrix& values) const override + void evaluateShapesDerivativesBatch(const DynamicVector& xiPoints, + ShapeInfoBatch& shapeBatch) const override { - // Constant piecewise field value - const CfemInt nQp = static_cast(xiPoints.size()); + const CfemInt nQp = shapeBatch.numQuadraturePoints; for (CfemInt q = 0; q < nQp; ++q) { - values[q][0] = 1.0; + const Vector3D& xi = xiPoints[q]; + CfemReal L0, L1, L2, L3; + reference_elem::utils::computeTetraBarycentric(xi, L0, L1, L2, L3); + + reference_elem::kernels::P2TetraKernel::grads(L0, L1, L2, L3, + shapeBatch.dN_dxi(q), + shapeBatch.dN_deta(q), + shapeBatch.dN_dzeta(q)); } } - void evaluateShapesDerivativesBatch(const DynamicVector & xiPoints, - DynamicMatrix &dN_dxi, - DynamicMatrix &dN_deta, - DynamicMatrix &dN_dzeta) const override + // ========================================================================= + // SECOND DERIVATIVES + // ========================================================================= + void evaluateShapesSecondDerivatives(const Vector3D& /*xi*/, ShapeInfo& shape) const override { - // Constant function results in flat zero gradients everywhere - const CfemInt nQp = static_cast(xiPoints.size()); - for (CfemInt q = 0; q < nQp; ++q) { - dN_dxi[q][0] = 0.0; - dN_deta[q][0] = 0.0; - dN_dzeta[q][0] = 0.0; - } + reference_elem::kernels::P2TetraKernel::hess(shape.d2N_dxi2(), shape.d2N_deta2(), shape.d2N_dzeta2(), + shape.d2N_dxideta(), shape.d2N_dxidzeta(), shape.d2N_detadzeta()); } - void evaluateShapesSecondDerivativesBatch(const DynamicVector &xiPoints, - DynamicMatrix &d2N_dxi2, - DynamicMatrix &d2N_deta2, - DynamicMatrix &d2N_dzeta2, - DynamicMatrix &d2N_dxideta, - DynamicMatrix &d2N_dxidzeta, - DynamicMatrix &d2N_detadzeta) const override - { - // Flat null second derivatives - const CfemInt nQp = static_cast(xiPoints.size()); - for (CfemInt q = 0; q < nQp; ++q) { - d2N_dxi2[q][0] = 0.0; - d2N_deta2[q][0] = 0.0; - d2N_dzeta2[q][0] = 0.0; - d2N_dxideta[q][0] = 0.0; - d2N_dxidzeta[q][0] = 0.0; - d2N_detadzeta[q][0] = 0.0; + void evaluateShapesSecondDerivativesBatch(const DynamicVector& xiPoints, + ShapeInfoBatch& shapeBatch) const override + { + const CfemInt nQp = shapeBatch.numQuadraturePoints; + if (nQp == 0) return; + + reference_elem::kernels::P2TetraKernel::hess( + shapeBatch.d2N_dxi2(0), shapeBatch.d2N_deta2(0), shapeBatch.d2N_dzeta2(0), + shapeBatch.d2N_dxideta(0), shapeBatch.d2N_dxidzeta(0), shapeBatch.d2N_detadzeta(0) + ); + + const size_t bytesToCopy = m_numNodes * sizeof(CfemReal); + for (CfemInt q = 1; q < nQp; ++q) { + std::memcpy(shapeBatch.d2N_dxi2(q), shapeBatch.d2N_dxi2(0), bytesToCopy); + std::memcpy(shapeBatch.d2N_deta2(q), shapeBatch.d2N_deta2(0), bytesToCopy); + std::memcpy(shapeBatch.d2N_dzeta2(q), shapeBatch.d2N_dzeta2(0), bytesToCopy); + std::memcpy(shapeBatch.d2N_dxideta(q), shapeBatch.d2N_dxideta(0), bytesToCopy); + std::memcpy(shapeBatch.d2N_dxidzeta(q), shapeBatch.d2N_dxidzeta(0), bytesToCopy); + std::memcpy(shapeBatch.d2N_detadzeta(q), shapeBatch.d2N_detadzeta(0), bytesToCopy); } } - Vector3D projectFacetRefCoordToCellRefCoord(CfemInt localFacetId, const Vector3D& xiFacet) const override { - return cfem::reference_elem::utils::projectFacetRefCoordToTetraRefCoord(localFacetId, xiFacet); + Vector3D projectFacetRefCoordToCellRefCoord(CfemInt localFacetId, const Vector3D& xiFacet) const override{ + return cfem::reference_elem::utils::projectFacetRefCoordToTetraRefCoord(localFacetId, xiFacet); // Make the compiler happy } std::span getFaceNodes(CfemInt iFace) const override { - return cfem::reference_elem::utils::tet1_face_nodes[iFace]; + return cfem::reference_elem::utils::tet10_face_nodes[iFace]; } }; } diff --git a/libs/fem/reference_element/include/ReferenceTriangle.h b/libs/fem/reference_element/include/ReferenceTriangle.h index 4945569..f923fca 100644 --- a/libs/fem/reference_element/include/ReferenceTriangle.h +++ b/libs/fem/reference_element/include/ReferenceTriangle.h @@ -22,6 +22,10 @@ #include "ReferenceElement.h" #include "Vector3D.h" +#include +#include +#include + namespace cfem::reference_elem::utils{ inline Vector3D projectFacetRefCoordToTriRefCoord(CfemInt localFacetId, @@ -40,612 +44,450 @@ namespace cfem::reference_elem::utils{ return Vector3D{}; // Make the compiler happy } - static const std::vector> tri1_face_nodes = { + static constexpr std::array, 3> tri1_face_nodes = {{ {0}, {0}, {0} - }; + }}; - static const std::vector> tri3_face_nodes = { + static constexpr std::array, 3> tri3_face_nodes = {{ {0, 1}, {1, 2}, {2, 0} - }; + }}; - static const std::vector> tri6_face_nodes = { + static constexpr std::array, 3> tri6_face_nodes = {{ {0, 1, 3}, {1, 2, 4}, {2, 0, 5} + }}; + + inline void computeTriBarycentric(const Vector3D& xi, + CfemReal& L0, + CfemReal& L1, + CfemReal& L2) noexcept + { + L1 = xi.x; + L2 = xi.y; + L0 = 1.0 - L1 - L2; + } + +} + +namespace cfem::reference_elem::kernels { + + // ========================================================================= + // P0 TRIANGLE KERNEL (1 node, constant) + // ========================================================================= + struct P0TriKernel { + [[gnu::always_inline]] static inline void values(CfemReal* __restrict N) noexcept { + N[0] = 1.0; + } + + [[gnu::always_inline]] static inline void grads(CfemReal* __restrict dx, + CfemReal* __restrict dy, + CfemReal* __restrict dz) noexcept { + dx[0] = dy[0] = dz[0] = 0.0; + } + + [[gnu::always_inline]] static inline void hess(CfemReal* __restrict d2x, CfemReal* __restrict d2y, CfemReal* __restrict d2z, + CfemReal* __restrict dxy, CfemReal* __restrict dxz, CfemReal* __restrict dyz) noexcept { + d2x[0] = d2y[0] = d2z[0] = dxy[0] = dxz[0] = dyz[0] = 0.0; + } + }; + + // ========================================================================= + // P1 TRIANGLE KERNEL (3 nodes, linear) + // ========================================================================= + struct P1TriKernel { + [[gnu::always_inline]] static inline void values(CfemReal L0, CfemReal L1, CfemReal L2, + CfemReal* __restrict N) noexcept { + N[0] = L0; N[1] = L1; N[2] = L2; + } + + [[gnu::always_inline]] static inline void grads(CfemReal* __restrict dx, + CfemReal* __restrict dy, + CfemReal* __restrict dz) noexcept { + dx[0] = -1.0; dy[0] = -1.0; dz[0] = 0.0; + dx[1] = 1.0; dy[1] = 0.0; dz[1] = 0.0; + dx[2] = 0.0; dy[2] = 1.0; dz[2] = 0.0; + } + + [[gnu::always_inline]] static inline void hess(CfemReal* __restrict d2x, CfemReal* __restrict d2y, CfemReal* __restrict d2z, + CfemReal* __restrict dxy, CfemReal* __restrict dxz, CfemReal* __restrict dyz) noexcept { + for (CfemInt i = 0; i < 3; ++i) { + d2x[i] = d2y[i] = d2z[i] = dxy[i] = dxz[i] = dyz[i] = 0.0; + } + } + }; + + // ========================================================================= + // P1-MINI KERNEL TRIANGLE (4 nodes : 3 vertices + 1 bubble) + // ========================================================================= + struct MiniTriKernel { + [[gnu::always_inline]] static inline void values(CfemReal L0, CfemReal L1, CfemReal L2, + CfemReal* __restrict N) noexcept { + const CfemReal b = 27.0 * L0 * L1 * L2; + N[3] = b; + N[0] = L0 - b / 3.0; + N[1] = L1 - b / 3.0; + N[2] = L2 - b / 3.0; + } + + [[gnu::always_inline]] static inline void grads(CfemReal L0, CfemReal L1, CfemReal L2, + CfemReal* __restrict dx, + CfemReal* __restrict dy, + CfemReal* __restrict dz) noexcept { + const CfemReal db_dxi = 27.0 * L2 * (L0 - L1); + const CfemReal db_deta = 27.0 * L1 * (L0 - L2); + + dx[3] = db_dxi; dy[3] = db_deta; dz[3] = 0.0; + dx[0] = -1.0 - db_dxi / 3.0; dy[0] = -1.0 - db_deta / 3.0; dz[0] = 0.0; + dx[1] = 1.0 - db_dxi / 3.0; dy[1] = 0.0 - db_deta / 3.0; dz[1] = 0.0; + dx[2] = 0.0 - db_dxi / 3.0; dy[2] = 1.0 - db_deta / 3.0; dz[2] = 0.0; + } + + [[gnu::always_inline]] static inline void hess(CfemReal L0, CfemReal L1, CfemReal L2, + CfemReal* __restrict d2x, CfemReal* __restrict d2y, CfemReal* __restrict d2z, + CfemReal* __restrict dxy, CfemReal* __restrict dxz, CfemReal* __restrict dyz) noexcept { + const CfemReal d2b_dxi2 = -54.0 * L2; + const CfemReal d2b_deta2 = -54.0 * L1; + const CfemReal d2b_dxideta = 27.0 * (L0 - L1 - L2); + + d2x[3] = d2b_dxi2; d2y[3] = d2b_deta2; dxy[3] = d2b_dxideta; + d2z[3] = dxz[3] = dyz[3] = 0.0; + + for (CfemInt i = 0; i < 3; ++i) { + d2x[i] = -d2b_dxi2 / 3.0; + d2y[i] = -d2b_deta2 / 3.0; + dxy[i] = -d2b_dxideta / 3.0; + d2z[i] = dxz[i] = dyz[i] = 0.0; + } + } }; + // ========================================================================= + // P2 TRIANGLE KERNEL (6 nodes, quadratic) + // ========================================================================= + struct P2TriKernel { + [[gnu::always_inline]] static inline void values(CfemReal L0, CfemReal L1, CfemReal L2, + CfemReal* __restrict N) noexcept { + N[0] = L0 * (2.0 * L0 - 1.0); + N[1] = L1 * (2.0 * L1 - 1.0); + N[2] = L2 * (2.0 * L2 - 1.0); + N[3] = 4.0 * L0 * L1; + N[4] = 4.0 * L1 * L2; + N[5] = 4.0 * L2 * L0; + } + + [[gnu::always_inline]] static inline void grads(CfemReal L0, CfemReal L1, CfemReal L2, + CfemReal* __restrict dx, + CfemReal* __restrict dy, + CfemReal* __restrict dz) noexcept { + dx[0] = 1.0 - 4.0 * L0; dy[0] = 1.0 - 4.0 * L0; + dx[1] = 4.0 * L1 - 1.0; dy[1] = 0.0; + dx[2] = 0.0; dy[2] = 4.0 * L2 - 1.0; + dx[3] = 4.0 * (L0 - L1); dy[3] = -4.0 * L1; + dx[4] = 4.0 * L2; dy[4] = 4.0 * L1; + dx[5] = -4.0 * L2; dy[5] = 4.0 * (L0 - L2); + + for (CfemInt i=0; i<6; ++i) dz[i] = 0.0; + } + + [[gnu::always_inline]] static inline void hess(CfemReal* __restrict d2x, CfemReal* __restrict d2y, CfemReal* __restrict d2z, + CfemReal* __restrict dxy, CfemReal* __restrict dxz, CfemReal* __restrict dyz) noexcept { + d2x[0] = 4.0; d2y[0] = 4.0; dxy[0] = 4.0; + d2x[1] = 4.0; d2y[1] = 0.0; dxy[1] = 0.0; + d2x[2] = 0.0; d2y[2] = 4.0; dxy[2] = 0.0; + d2x[3] = -8.0; d2y[3] = 0.0; dxy[3] = -4.0; + d2x[4] = 0.0; d2y[4] = 0.0; dxy[4] = 4.0; + d2x[5] = 0.0; d2y[5] = -8.0; dxy[5] = -4.0; + + for (CfemInt i=0; i<6; ++i) { + d2z[i] = dxz[i] = dyz[i] = 0.0; + } + } + }; } namespace cfem { /** - * @class P1ReferenceTriangle - * @brief Linear 3-node reference triangle. + * @brief Constant Reference Triangle (1 node, P0) */ - class P1ReferenceTriangle : public ReferenceElement { - + class P0ReferenceTriangle : public ReferenceElement { public: - - P1ReferenceTriangle() { - m_numNodes = 3; + P0ReferenceTriangle() { + m_numNodes = 1; m_dimension = 2; + m_nodes = { Vector3D{1.0/3.0, 1.0/3.0, 0.0} }; + } - m_nodes = { Vector3D{0.0, 0.0, 0.0}, - Vector3D{1.0, 0.0, 0.0}, - Vector3D{0.0, 1.0, 0.0} }; - } - - void evaluateShapesValues(const Vector3D &xi, CfemReal* values) const override - { - values[0] = 1.0 - xi.x - xi.y; - values[1] = xi.x; - values[2] = xi.y; - } - - void evaluateShapesDerivatives(const Vector3D &, - CfemReal* dN_dxi, - CfemReal* dN_deta, - CfemReal* dN_dzeta) const override - { - dN_dxi[0] = -1.0; dN_deta[0] = -1.0; dN_dzeta[0] = 0.0; - dN_dxi[1] = 1.0; dN_deta[1] = 0.0; dN_dzeta[1] = 0.0; - dN_dxi[2] = 0.0; dN_deta[2] = 1.0; dN_dzeta[2] = 0.0; - } - - void evaluateShapesSecondDerivatives(const Vector3D &, - CfemReal* d2N_dxi2, - CfemReal* d2N_deta2, - CfemReal* d2N_dzeta2, - CfemReal* d2N_dxideta, - CfemReal* d2N_dxidzeta, - CfemReal* d2N_detadzeta) const override - { - for (CfemInt i = 0; i < m_numNodes; ++i) { - d2N_dxi2[i] = 0.0; - d2N_deta2[i] = 0.0; - d2N_dzeta2[i] = 0.0; - d2N_dxideta[i] = 0.0; - d2N_dxidzeta[i] = 0.0; - d2N_detadzeta[i] = 0.0; - } + void evaluateShapesValues(const Vector3D&, ShapeInfo& info) const override { + reference_elem::kernels::P0TriKernel::values(info.values()); } - void evaluateShapesValuesBatch(const DynamicVector &xiPoints, DynamicMatrix& values) const override - { - // Constant piecewise field value - const CfemInt nQp = static_cast(xiPoints.size()); - for (CfemInt q = 0; q < nQp; ++q) { - const Vector3D& xi = xiPoints[q]; - values[q][0] = 1.0 - xi.x - xi.y; - values[q][1] = xi.x; - values[q][2] = xi.y; - } + void evaluateShapesDerivatives(const Vector3D&, ShapeInfo& info) const override { + reference_elem::kernels::P0TriKernel::grads(info.dN_dxi(), info.dN_deta(), info.dN_dzeta()); } - void evaluateShapesDerivativesBatch(const DynamicVector & xiPoints, - DynamicMatrix &dN_dxi, - DynamicMatrix &dN_deta, - DynamicMatrix &dN_dzeta) const override - { - // Constant function results in flat zero gradients everywhere - const CfemInt nQp = static_cast(xiPoints.size()); - for (CfemInt q = 0; q < nQp; ++q) { - const Vector3D& xi = xiPoints[q]; - dN_dxi[q][0] = -1.0; dN_deta[q][0] = -1.0; dN_dzeta[q][0] = 0.0; - dN_dxi[q][1] = 1.0; dN_deta[q][1] = 0.0; dN_dzeta[q][1] = 0.0; - dN_dxi[q][2] = 0.0; dN_deta[q][2] = 1.0; dN_dzeta[q][2] = 0.0; + void evaluateShapesSecondDerivatives(const Vector3D&, ShapeInfo& info) const override { + reference_elem::kernels::P0TriKernel::hess(info.d2N_dxi2(), info.d2N_deta2(), info.d2N_dzeta2(), + info.d2N_dxideta(), info.d2N_dxidzeta(), info.d2N_detadzeta()); + } + + void evaluateShapesValuesBatch(const DynamicVector &xiPoints, ShapeInfoBatch& shapeBatch) const override { + const CfemInt nQp = shapeBatch.numQuadraturePoints; + if (nQp == 0) return; + reference_elem::kernels::P0TriKernel::values(shapeBatch.values(0)); + const size_t bytesToCopy = m_numNodes * sizeof(CfemReal); + for (CfemInt q = 1; q < nQp; ++q) { + std::memcpy(shapeBatch.values(q), shapeBatch.values(0), bytesToCopy); } } - void evaluateShapesSecondDerivativesBatch(const DynamicVector &xiPoints, - DynamicMatrix &d2N_dxi2, - DynamicMatrix &d2N_deta2, - DynamicMatrix &d2N_dzeta2, - DynamicMatrix &d2N_dxideta, - DynamicMatrix &d2N_dxidzeta, - DynamicMatrix &d2N_detadzeta) const override - { - // Flat null second derivatives - const CfemInt nQp = static_cast(xiPoints.size()); - for (CfemInt q = 0; q < nQp; ++q) { - for (CfemInt i = 0; i < m_numNodes; ++i) { - d2N_dxi2[q][i] = 0.0; - d2N_deta2[q][i] = 0.0; - d2N_dzeta2[q][i] = 0.0; - d2N_dxideta[q][i] = 0.0; - d2N_dxidzeta[q][i] = 0.0; - d2N_detadzeta[q][i] = 0.0; - } + void evaluateShapesDerivativesBatch(const DynamicVector & xiPoints, ShapeInfoBatch& shapeBatch) const override { + const CfemInt nQp = shapeBatch.numQuadraturePoints; + if (nQp == 0) return; + reference_elem::kernels::P0TriKernel::grads(shapeBatch.dN_dxi(0), shapeBatch.dN_deta(0), shapeBatch.dN_dzeta(0)); + const size_t bytesToCopy = m_numNodes * sizeof(CfemReal); + for (CfemInt q = 1; q < nQp; ++q) { + std::memcpy(shapeBatch.dN_dxi(q), shapeBatch.dN_dxi(0), bytesToCopy); + std::memcpy(shapeBatch.dN_deta(q), shapeBatch.dN_deta(0), bytesToCopy); + std::memcpy(shapeBatch.dN_dzeta(q), shapeBatch.dN_dzeta(0), bytesToCopy); } } - - Vector3D projectFacetRefCoordToCellRefCoord(CfemInt localFacetId, const Vector3D& xiFacet) const override{ - return cfem::reference_elem::utils::projectFacetRefCoordToTriRefCoord(localFacetId, xiFacet); // Make the compiler happy + + void evaluateShapesSecondDerivativesBatch(const DynamicVector &xiPoints, ShapeInfoBatch& shapeBatch) const override { + const CfemInt nQp = shapeBatch.numQuadraturePoints; + if (nQp == 0) return; + reference_elem::kernels::P0TriKernel::hess(shapeBatch.d2N_dxi2(0), shapeBatch.d2N_deta2(0), shapeBatch.d2N_dzeta2(0), + shapeBatch.d2N_dxideta(0), shapeBatch.d2N_dxidzeta(0), shapeBatch.d2N_detadzeta(0)); + const size_t bytesToCopy = m_numNodes * sizeof(CfemReal); + for (CfemInt q = 1; q < nQp; ++q) { + std::memcpy(shapeBatch.d2N_dxi2(q), shapeBatch.d2N_dxi2(0), bytesToCopy); + std::memcpy(shapeBatch.d2N_deta2(q), shapeBatch.d2N_deta2(0), bytesToCopy); + std::memcpy(shapeBatch.d2N_dzeta2(q), shapeBatch.d2N_dzeta2(0), bytesToCopy); + std::memcpy(shapeBatch.d2N_dxideta(q), shapeBatch.d2N_dxideta(0), bytesToCopy); + std::memcpy(shapeBatch.d2N_dxidzeta(q), shapeBatch.d2N_dxidzeta(0), bytesToCopy); + std::memcpy(shapeBatch.d2N_detadzeta(q), shapeBatch.d2N_detadzeta(0), bytesToCopy); + } } + Vector3D projectFacetRefCoordToCellRefCoord(CfemInt localFacetId, const Vector3D& xiFacet) const override { + return cfem::reference_elem::utils::projectFacetRefCoordToTriRefCoord(localFacetId, xiFacet); + } std::span getFaceNodes(CfemInt iFace) const override { - return cfem::reference_elem::utils::tri3_face_nodes[iFace]; + return cfem::reference_elem::utils::tri1_face_nodes[iFace]; } - }; - /** - * @class P2ReferenceTriangle - * @brief Quadratic 6-node reference triangle. + * @brief Linear Reference Triangle (3 nodes, P1) */ - class P2ReferenceTriangle : public ReferenceElement { + class P1ReferenceTriangle : public ReferenceElement { public: - P2ReferenceTriangle() { - m_numNodes = 6; + P1ReferenceTriangle() { + m_numNodes = 3; m_dimension = 2; + m_nodes = { Vector3D{0.0, 0.0, 0.0}, Vector3D{1.0, 0.0, 0.0}, Vector3D{0.0, 1.0, 0.0} }; + } - m_nodes = { - Vector3D{0.0, 0.0, 0.0}, - Vector3D{1.0, 0.0, 0.0}, - Vector3D{0.0, 1.0, 0.0}, - Vector3D{0.5, 0.0, 0.0}, - Vector3D{0.5, 0.5, 0.0}, - Vector3D{0.0, 0.5, 0.0} - }; + void evaluateShapesValues(const Vector3D& xi, ShapeInfo& info) const override { + CfemReal L0, L1, L2; + reference_elem::utils::computeTriBarycentric(xi, L0, L1, L2); + reference_elem::kernels::P1TriKernel::values(L0, L1, L2, info.values()); } - void evaluateShapesValues(const Vector3D &xi, CfemReal* values) const override - { - const CfemReal L1 = xi.x; - const CfemReal L2 = xi.y; - const CfemReal L0 = 1.0 - L1 - L2; - - values[0] = L0 * (2.0 * L0 - 1.0); - values[1] = L1 * (2.0 * L1 - 1.0); - values[2] = L2 * (2.0 * L2 - 1.0); - values[3] = 4.0 * L0 * L1; - values[4] = 4.0 * L1 * L2; - values[5] = 4.0 * L2 * L0; - } - - void evaluateShapesDerivatives(const Vector3D &xi, - CfemReal* dN_dxi, - CfemReal* dN_deta, - CfemReal* dN_dzeta) const override - { - const CfemReal L1 = xi.x; - const CfemReal L2 = xi.y; - - // Derivatives with respect to xi (Local x direction) - dN_dxi[0] =-3.0 + 4.0 * L1 + 4.0 * L2; - dN_dxi[1] = 4.0 * L1 - 1.0; - dN_dxi[2] = 0.0; - dN_dxi[3] = 4.0 - 8.0 * L1 - 4.0 * L2; - dN_dxi[4] = 4.0 * L2; - dN_dxi[5] =-4.0 * L2; - - // Derivatives with respect to eta (Local y direction) - dN_deta[0] = -3.0 + 4.0 * L1 + 4.0 * L2; - dN_deta[1] = 0.0; - dN_deta[2] = 4.0 * L2 - 1.0; - dN_deta[3] = -4.0 * L1; - dN_deta[4] = 4.0 * L1; - dN_deta[5] = 4.0 - 4.0 * L1 - 8.0 * L2; - - // Derivatives with respect to zeta (Z-direction is zero for a 2D planar element) - for (CfemInt i = 0; i < 6; ++i) { - dN_dzeta[i] = 0.0; - } + void evaluateShapesDerivatives(const Vector3D&, ShapeInfo& info) const override { + reference_elem::kernels::P1TriKernel::grads(info.dN_dxi(), info.dN_deta(), info.dN_dzeta()); } - void evaluateShapesSecondDerivatives(const Vector3D &, - CfemReal* d2N_dxi2, - CfemReal* d2N_deta2, - CfemReal* d2N_dzeta2, - CfemReal* d2N_dxideta, - CfemReal* d2N_dxidzeta, - CfemReal* d2N_detadzeta) const override - { - // Quadratic shape function second derivatives are constants. - d2N_dxi2[0] = 4.0; d2N_deta2[0] = 4.0; d2N_dxideta[0] = 4.0; - d2N_dxi2[1] = 4.0; d2N_deta2[1] = 0.0; d2N_dxideta[1] = 0.0; - d2N_dxi2[2] = 0.0; d2N_deta2[2] = 4.0; d2N_dxideta[2] = 0.0; - d2N_dxi2[3] =-8.0; d2N_deta2[3] = 0.0; d2N_dxideta[3] =-4.0; - d2N_dxi2[4] = 0.0; d2N_deta2[4] = 0.0; d2N_dxideta[4] = 4.0; - d2N_dxi2[5] = 0.0; d2N_deta2[5] =-8.0; d2N_dxideta[5] =-4.0; - - // All derivatives involving the out-of-plane reference coordinate zeta are strictly zero - for (CfemInt i = 0; i < 6; ++i) { - d2N_dzeta2[i] = 0.0; - d2N_dxidzeta[i] = 0.0; - d2N_detadzeta[i] = 0.0; - } + void evaluateShapesSecondDerivatives(const Vector3D&, ShapeInfo& info) const override { + reference_elem::kernels::P1TriKernel::hess(info.d2N_dxi2(), info.d2N_deta2(), info.d2N_dzeta2(), + info.d2N_dxideta(), info.d2N_dxidzeta(), info.d2N_detadzeta()); } - void evaluateShapesValuesBatch(const DynamicVector &xiPoints, DynamicMatrix& values) const override - { - // Constant piecewise field value - const CfemInt nQp = static_cast(xiPoints.size()); + void evaluateShapesValuesBatch(const DynamicVector& xiPoints, ShapeInfoBatch& shapeBatch) const override { + const CfemInt nQp = shapeBatch.numQuadraturePoints; for (CfemInt q = 0; q < nQp; ++q) { - const Vector3D& xi = xiPoints[q]; - const CfemReal L1 = xi.x; - const CfemReal L2 = xi.y; - const CfemReal L0 = 1.0 - L1 - L2; - - values[q][0] = L0 * (2.0 * L0 - 1.0); - values[q][1] = L1 * (2.0 * L1 - 1.0); - values[q][2] = L2 * (2.0 * L2 - 1.0); - values[q][3] = 4.0 * L0 * L1; - values[q][4] = 4.0 * L1 * L2; - values[q][5] = 4.0 * L2 * L0; + CfemReal L0, L1, L2; + reference_elem::utils::computeTriBarycentric(xiPoints[q], L0, L1, L2); + reference_elem::kernels::P1TriKernel::values(L0, L1, L2, shapeBatch.values(q)); } } - void evaluateShapesDerivativesBatch(const DynamicVector & xiPoints, - DynamicMatrix &dN_dxi, - DynamicMatrix &dN_deta, - DynamicMatrix &dN_dzeta) const override - { - // Constant function results in flat zero gradients everywhere - const CfemInt nQp = static_cast(xiPoints.size()); + void evaluateShapesDerivativesBatch(const DynamicVector& xiPoints, ShapeInfoBatch& shapeBatch) const override { + const CfemInt nQp = shapeBatch.numQuadraturePoints; for (CfemInt q = 0; q < nQp; ++q) { - const Vector3D& xi = xiPoints[q]; - const CfemReal L1 = xi.x; - const CfemReal L2 = xi.y; - - // Derivatives with respect to xi (Local x direction) - dN_dxi[q][0] =-3.0 + 4.0 * L1 + 4.0 * L2; - dN_dxi[q][1] = 4.0 * L1 - 1.0; - dN_dxi[q][2] = 0.0; - dN_dxi[q][3] = 4.0 - 8.0 * L1 - 4.0 * L2; - dN_dxi[q][4] = 4.0 * L2; - dN_dxi[q][5] =-4.0 * L2; - - // Derivatives with respect to eta (Local y direction) - dN_deta[q][0] = -3.0 + 4.0 * L1 + 4.0 * L2; - dN_deta[q][1] = 0.0; - dN_deta[q][2] = 4.0 * L2 - 1.0; - dN_deta[q][3] = -4.0 * L1; - dN_deta[q][4] = 4.0 * L1; - dN_deta[q][5] = 4.0 - 4.0 * L1 - 8.0 * L2; - - // Derivatives with respect to zeta (Z-direction is zero for a 2D planar element) - for (CfemInt i = 0; i < 6; ++i) { - dN_dzeta[q][i] = 0.0; - } + reference_elem::kernels::P1TriKernel::grads(shapeBatch.dN_dxi(q), shapeBatch.dN_deta(q), shapeBatch.dN_dzeta(q)); } } - void evaluateShapesSecondDerivativesBatch(const DynamicVector &xiPoints, - DynamicMatrix &d2N_dxi2, - DynamicMatrix &d2N_deta2, - DynamicMatrix &d2N_dzeta2, - DynamicMatrix &d2N_dxideta, - DynamicMatrix &d2N_dxidzeta, - DynamicMatrix &d2N_detadzeta) const override - { - // Flat null second derivatives - const CfemInt nQp = static_cast(xiPoints.size()); - for (CfemInt q = 0; q < nQp; ++q) { - // Quadratic shape function second derivatives are constants. - d2N_dxi2[q][0] = 4.0; d2N_deta2[q][0] = 4.0; d2N_dxideta[q][0] = 4.0; - d2N_dxi2[q][1] = 4.0; d2N_deta2[q][1] = 0.0; d2N_dxideta[q][1] = 0.0; - d2N_dxi2[q][2] = 0.0; d2N_deta2[q][2] = 4.0; d2N_dxideta[q][2] = 0.0; - d2N_dxi2[q][3] =-8.0; d2N_deta2[q][3] = 0.0; d2N_dxideta[q][3] =-4.0; - d2N_dxi2[q][4] = 0.0; d2N_deta2[q][4] = 0.0; d2N_dxideta[q][4] = 4.0; - d2N_dxi2[q][5] = 0.0; d2N_deta2[q][5] =-8.0; d2N_dxideta[q][5] =-4.0; - - // All derivatives involving the out-of-plane reference coordinate zeta are strictly zero - for (CfemInt i = 0; i < 6; ++i) { - d2N_dzeta2[q][i] = 0.0; - d2N_dxidzeta[q][i] = 0.0; - d2N_detadzeta[q][i] = 0.0; - } + void evaluateShapesSecondDerivativesBatch(const DynamicVector& xiPoints, ShapeInfoBatch& shapeBatch) const override { + const CfemInt nQp = shapeBatch.numQuadraturePoints; + if (nQp == 0) return; + reference_elem::kernels::P1TriKernel::hess(shapeBatch.d2N_dxi2(0), shapeBatch.d2N_deta2(0), shapeBatch.d2N_dzeta2(0), + shapeBatch.d2N_dxideta(0), shapeBatch.d2N_dxidzeta(0), shapeBatch.d2N_detadzeta(0)); + const size_t bytesToCopy = m_numNodes * sizeof(CfemReal); + for (CfemInt q = 1; q < nQp; ++q) { + std::memcpy(shapeBatch.d2N_dxi2(q), shapeBatch.d2N_dxi2(0), bytesToCopy); + std::memcpy(shapeBatch.d2N_deta2(q), shapeBatch.d2N_deta2(0), bytesToCopy); + std::memcpy(shapeBatch.d2N_dzeta2(q), shapeBatch.d2N_dzeta2(0), bytesToCopy); + std::memcpy(shapeBatch.d2N_dxideta(q), shapeBatch.d2N_dxideta(0), bytesToCopy); + std::memcpy(shapeBatch.d2N_dxidzeta(q), shapeBatch.d2N_dxidzeta(0), bytesToCopy); + std::memcpy(shapeBatch.d2N_detadzeta(q), shapeBatch.d2N_detadzeta(0), bytesToCopy); } } - Vector3D projectFacetRefCoordToCellRefCoord(CfemInt localFacetId, const Vector3D& xiFacet) const override{ - return cfem::reference_elem::utils::projectFacetRefCoordToTriRefCoord(localFacetId, xiFacet); // Make the compiler happy + Vector3D projectFacetRefCoordToCellRefCoord(CfemInt localFacetId, const Vector3D& xiFacet) const override { + return cfem::reference_elem::utils::projectFacetRefCoordToTriRefCoord(localFacetId, xiFacet); } - std::span getFaceNodes(CfemInt iFace) const override { - return cfem::reference_elem::utils::tri6_face_nodes[iFace]; + return cfem::reference_elem::utils::tri3_face_nodes[iFace]; } }; + /** + * @brief P1-Mini Reference Triangle (4 nodes: 3 vertices + 1 bubble) + */ class MiniReferenceTriangle : public ReferenceElement { - private: - static constexpr inline CfemReal m_1_3 {1.0 / 3.0}; public: MiniReferenceTriangle() { - m_numNodes = 4; // 3 sommets + 1 bulle + m_numNodes = 4; m_dimension = 2; - - m_nodes = { - Vector3D{0.0, 0.0, 0.0}, - Vector3D{1.0, 0.0, 0.0}, - Vector3D{0.0, 1.0, 0.0}, - Vector3D{m_1_3, m_1_3, 0.0} + m_nodes = { + Vector3D{0.0, 0.0, 0.0}, Vector3D{1.0, 0.0, 0.0}, Vector3D{0.0, 1.0, 0.0}, + Vector3D{1.0/3.0, 1.0/3.0, 0.0} }; } - void evaluateShapesValues(const Vector3D &xi, CfemReal* values) const override - { - const CfemReal L1 = xi.x; - const CfemReal L2 = xi.y; - const CfemReal L0 = 1.0 - L1 - L2; - - const CfemReal b = 27.0 * L0 * L1 * L2; - const CfemReal b_x_m_1_3 = b * m_1_3; - - values[3] = b; - values[0] = L0 - b_x_m_1_3; - values[1] = L1 - b_x_m_1_3; - values[2] = L2 - b_x_m_1_3; - } - - void evaluateShapesDerivatives(const Vector3D &xi, - CfemReal* dN_dxi, - CfemReal* dN_deta, - CfemReal* dN_dzeta) const override - { - const CfemReal L1 = xi.x; - const CfemReal L2 = xi.y; - - // Evaluate bubble gradients - const CfemReal db_dxi = 27.0 * L2 * (1.0 - 2.0 * L1 - L2); - const CfemReal db_deta = 27.0 * L1 * (1.0 - L1 - 2.0 * L2); - - // Derivatives with respect to xi - dN_dxi[3] = db_dxi; - dN_dxi[0] = -1.0 - db_dxi * m_1_3; - dN_dxi[1] = 1.0 - db_dxi * m_1_3; - dN_dxi[2] = 0.0 - db_dxi * m_1_3; - - // Derivatives with respect to eta - dN_deta[3] = db_deta; - dN_deta[0] = -1.0 - db_deta * m_1_3; - dN_deta[1] = 0.0 - db_deta * m_1_3; - dN_deta[2] = 1.0 - db_deta * m_1_3; - - // Out-of-plane derivatives are zero - for (CfemInt i = 0; i < 4; ++i) { - dN_dzeta[i] = 0.0; - } + void evaluateShapesValues(const Vector3D& xi, ShapeInfo& info) const override { + CfemReal L0, L1, L2; + reference_elem::utils::computeTriBarycentric(xi, L0, L1, L2); + reference_elem::kernels::MiniTriKernel::values(L0, L1, L2, info.values()); } - void evaluateShapesSecondDerivatives(const Vector3D &xi, - CfemReal* d2N_dxi2, - CfemReal* d2N_deta2, - CfemReal* d2N_dzeta2, - CfemReal* d2N_dxideta, - CfemReal* d2N_dxidzeta, - CfemReal* d2N_detadzeta) const override - { - const CfemReal L1 = xi.x; - const CfemReal L2 = xi.y; - - // Second derivatives of the bubble function - const CfemReal d2b_dxi2 = -54.0 * L2; - const CfemReal d2b_deta2 = -54.0 * L1; - const CfemReal d2b_dxideta = 27.0 * (1.0 - 2.0 * L1 - 2.0 * L2); - - // Node 3 (Bubble) - d2N_dxi2[3] = d2b_dxi2; - d2N_deta2[3] = d2b_deta2; - d2N_dxideta[3] = d2b_dxideta; - - // Nodes 0, 1, 2 inherit the bubble's linear correction term - for (CfemInt i = 0; i < 3; ++i) { - d2N_dxi2[i] = -d2b_dxi2 * m_1_3; - d2N_deta2[i] = -d2b_deta2 * m_1_3; - d2N_dxideta[i] = -d2b_dxideta * m_1_3; - } + void evaluateShapesDerivatives(const Vector3D& xi, ShapeInfo& info) const override { + CfemReal L0, L1, L2; + reference_elem::utils::computeTriBarycentric(xi, L0, L1, L2); + reference_elem::kernels::MiniTriKernel::grads(L0, L1, L2, info.dN_dxi(), info.dN_deta(), info.dN_dzeta()); + } - // Initialize all remaining out-of-plane and zeta cross-derivatives to zero - for (CfemInt i = 0; i < 4; ++i) { - d2N_dzeta2[i] = 0.0; - d2N_dxidzeta[i] = 0.0; - d2N_detadzeta[i] = 0.0; - } + void evaluateShapesSecondDerivatives(const Vector3D& xi, ShapeInfo& info) const override { + CfemReal L0, L1, L2; + reference_elem::utils::computeTriBarycentric(xi, L0, L1, L2); + reference_elem::kernels::MiniTriKernel::hess(L0, L1, L2, info.d2N_dxi2(), info.d2N_deta2(), info.d2N_dzeta2(), + info.d2N_dxideta(), info.d2N_dxidzeta(), info.d2N_detadzeta()); } - void evaluateShapesValuesBatch(const DynamicVector &xiPoints, DynamicMatrix& values) const override - { - // Constant piecewise field value - const CfemInt nQp = static_cast(xiPoints.size()); + void evaluateShapesValuesBatch(const DynamicVector& xiPoints, ShapeInfoBatch& shapeBatch) const override { + const CfemInt nQp = shapeBatch.numQuadraturePoints; for (CfemInt q = 0; q < nQp; ++q) { - const Vector3D& xi = xiPoints[q]; - const CfemReal L1 = xi.x; - const CfemReal L2 = xi.y; - const CfemReal L0 = 1.0 - L1 - L2; - - const CfemReal b = 27.0 * L0 * L1 * L2; - - values[q][3] = b; - values[q][0] = L0 - b * m_1_3; - values[q][1] = L1 - b * m_1_3; - values[q][2] = L2 - b * m_1_3; + CfemReal L0, L1, L2; + reference_elem::utils::computeTriBarycentric(xiPoints[q], L0, L1, L2); + reference_elem::kernels::MiniTriKernel::values(L0, L1, L2, shapeBatch.values(q)); } } - - void evaluateShapesDerivativesBatch(const DynamicVector & xiPoints, - DynamicMatrix &dN_dxi, - DynamicMatrix &dN_deta, - DynamicMatrix &dN_dzeta) const override - { - // Constant function results in flat zero gradients everywhere - const CfemInt nQp = static_cast(xiPoints.size()); + + void evaluateShapesDerivativesBatch(const DynamicVector& xiPoints, ShapeInfoBatch& shapeBatch) const override { + const CfemInt nQp = shapeBatch.numQuadraturePoints; for (CfemInt q = 0; q < nQp; ++q) { - const Vector3D& xi = xiPoints[q]; - const CfemReal L1 = xi.x; - const CfemReal L2 = xi.y; - - // Evaluate bubble gradients - const CfemReal db_dxi = 27.0 * L2 * (1.0 - 2.0 * L1 - L2); - const CfemReal db_deta = 27.0 * L1 * (1.0 - L1 - 2.0 * L2); - - // Derivatives with respect to xi - dN_dxi[q][3] = db_dxi; - dN_dxi[q][0] = -1.0 - db_dxi * m_1_3; - dN_dxi[q][1] = 1.0 - db_dxi * m_1_3; - dN_dxi[q][2] = 0.0 - db_dxi * m_1_3; - - // Derivatives with respect to eta - dN_deta[q][3] = db_deta; - dN_deta[q][0] = -1.0 - db_deta * m_1_3; - dN_deta[q][1] = 0.0 - db_deta * m_1_3; - dN_deta[q][2] = 1.0 - db_deta * m_1_3; - - // Out-of-plane derivatives are zero - for (CfemInt i = 0; i < 4; ++i) { - dN_dzeta[q][i] = 0.0; - } + CfemReal L0, L1, L2; + reference_elem::utils::computeTriBarycentric(xiPoints[q], L0, L1, L2); + reference_elem::kernels::MiniTriKernel::grads(L0, L1, L2, shapeBatch.dN_dxi(q), shapeBatch.dN_deta(q), shapeBatch.dN_dzeta(q)); } } - void evaluateShapesSecondDerivativesBatch(const DynamicVector &xiPoints, - DynamicMatrix &d2N_dxi2, - DynamicMatrix &d2N_deta2, - DynamicMatrix &d2N_dzeta2, - DynamicMatrix &d2N_dxideta, - DynamicMatrix &d2N_dxidzeta, - DynamicMatrix &d2N_detadzeta) const override - { - // Flat null second derivatives - const CfemInt nQp = static_cast(xiPoints.size()); + void evaluateShapesSecondDerivativesBatch(const DynamicVector& xiPoints, ShapeInfoBatch& shapeBatch) const override { + const CfemInt nQp = shapeBatch.numQuadraturePoints; + // Dépendance de \xi pour le hessien de la bulle (pas de memcpy) for (CfemInt q = 0; q < nQp; ++q) { - const Vector3D& xi = xiPoints[q]; - const CfemReal L1 = xi.x; - const CfemReal L2 = xi.y; - - // Second derivatives of the bubble function - const CfemReal d2b_dxi2 = -54.0 * L2; - const CfemReal d2b_deta2 = -54.0 * L1; - const CfemReal d2b_dxideta = 27.0 * (1.0 - 2.0 * L1 - 2.0 * L2); - - // Node 3 (Bubble) - d2N_dxi2[q][3] = d2b_dxi2; - d2N_deta2[q][3] = d2b_deta2; - d2N_dxideta[q][3] = d2b_dxideta; - - // Nodes 0, 1, 2 inherit the bubble's linear correction term - for (CfemInt i = 0; i < 3; ++i) { - d2N_dxi2[q][i] = -d2b_dxi2 * m_1_3; - d2N_deta2[q][i] = -d2b_deta2 * m_1_3; - d2N_dxideta[q][i] = -d2b_dxideta * m_1_3; - } - - // Initialize all remaining out-of-plane and zeta cross-derivatives to zero - for (CfemInt i = 0; i < 4; ++i) { - d2N_dzeta2[q][i] = 0.0; - d2N_dxidzeta[q][i] = 0.0; - d2N_detadzeta[q][i] = 0.0; - } + CfemReal L0, L1, L2; + reference_elem::utils::computeTriBarycentric(xiPoints[q], L0, L1, L2); + reference_elem::kernels::MiniTriKernel::hess(L0, L1, L2, shapeBatch.d2N_dxi2(q), shapeBatch.d2N_deta2(q), shapeBatch.d2N_dzeta2(q), + shapeBatch.d2N_dxideta(q), shapeBatch.d2N_dxidzeta(q), shapeBatch.d2N_detadzeta(q)); } } Vector3D projectFacetRefCoordToCellRefCoord(CfemInt localFacetId, const Vector3D& xiFacet) const override { - return cfem::reference_elem::utils::projectFacetRefCoordToTriRefCoord(localFacetId, xiFacet); + return cfem::reference_elem::utils::projectFacetRefCoordToTriRefCoord(localFacetId, xiFacet); } - - // On réutilise tri3_face_nodes car la bulle n'apparaît pas sur les faces ! std::span getFaceNodes(CfemInt iFace) const override { - return cfem::reference_elem::utils::tri3_face_nodes[iFace]; + return cfem::reference_elem::utils::tri3_face_nodes[iFace]; } }; - class P0ReferenceTriangle : public ReferenceElement { + /** + * @brief Quadratic Reference Triangle (6 nodes, P2) + */ + class P2ReferenceTriangle : public ReferenceElement { public: - P0ReferenceTriangle() { - m_numNodes = 1; + P2ReferenceTriangle() { + m_numNodes = 6; m_dimension = 2; + m_nodes = { + Vector3D{0.0, 0.0, 0.0}, Vector3D{1.0, 0.0, 0.0}, Vector3D{0.0, 1.0, 0.0}, + Vector3D{0.5, 0.0, 0.0}, Vector3D{0.5, 0.5, 0.0}, Vector3D{0.0, 0.5, 0.0} + }; + } - m_nodes = { Vector3D{1.0/3.0, 1.0/3.0, 0.0} }; + void evaluateShapesValues(const Vector3D& xi, ShapeInfo& info) const override { + CfemReal L0, L1, L2; + reference_elem::utils::computeTriBarycentric(xi, L0, L1, L2); + reference_elem::kernels::P2TriKernel::values(L0, L1, L2, info.values()); + } + + void evaluateShapesDerivatives(const Vector3D& xi, ShapeInfo& info) const override { + CfemReal L0, L1, L2; + reference_elem::utils::computeTriBarycentric(xi, L0, L1, L2); + reference_elem::kernels::P2TriKernel::grads(L0, L1, L2, info.dN_dxi(), info.dN_deta(), info.dN_dzeta()); } - void evaluateShapesValues(const Vector3D &, CfemReal* values) const override - { - // Constant piecewise field value - values[0] = 1.0; - } - - void evaluateShapesDerivatives(const Vector3D &, - CfemReal* dN_dxi, - CfemReal* dN_deta, - CfemReal* dN_dzeta) const override - { - // Constant function results in flat zero gradients everywhere - dN_dxi[0] = 0.0; - dN_deta[0] = 0.0; - dN_dzeta[0] = 0.0; - } - - void evaluateShapesSecondDerivatives(const Vector3D &, - CfemReal* d2N_dxi2, - CfemReal* d2N_deta2, - CfemReal* d2N_dzeta2, - CfemReal* d2N_dxideta, - CfemReal* d2N_dxidzeta, - CfemReal* d2N_detadzeta) const override - { - // Flat null second derivatives - d2N_dxi2[0] = 0.0; - d2N_deta2[0] = 0.0; - d2N_dzeta2[0] = 0.0; - d2N_dxideta[0] = 0.0; - d2N_dxidzeta[0] = 0.0; - d2N_detadzeta[0] = 0.0; - } - - void evaluateShapesValuesBatch(const DynamicVector &xiPoints, DynamicMatrix& values) const override - { - // Constant piecewise field value - const CfemInt nQp = static_cast(xiPoints.size()); + void evaluateShapesSecondDerivatives(const Vector3D&, ShapeInfo& info) const override { + reference_elem::kernels::P2TriKernel::hess(info.d2N_dxi2(), info.d2N_deta2(), info.d2N_dzeta2(), + info.d2N_dxideta(), info.d2N_dxidzeta(), info.d2N_detadzeta()); + } + + void evaluateShapesValuesBatch(const DynamicVector& xiPoints, ShapeInfoBatch& shapeBatch) const override { + const CfemInt nQp = shapeBatch.numQuadraturePoints; for (CfemInt q = 0; q < nQp; ++q) { - values[q][0] = 1.0; + CfemReal L0, L1, L2; + reference_elem::utils::computeTriBarycentric(xiPoints[q], L0, L1, L2); + reference_elem::kernels::P2TriKernel::values(L0, L1, L2, shapeBatch.values(q)); } } - void evaluateShapesDerivativesBatch(const DynamicVector & xiPoints, - DynamicMatrix &dN_dxi, - DynamicMatrix &dN_deta, - DynamicMatrix &dN_dzeta) const override - { - // Constant function results in flat zero gradients everywhere - const CfemInt nQp = static_cast(xiPoints.size()); + void evaluateShapesDerivativesBatch(const DynamicVector& xiPoints, ShapeInfoBatch& shapeBatch) const override { + const CfemInt nQp = shapeBatch.numQuadraturePoints; for (CfemInt q = 0; q < nQp; ++q) { - dN_dxi[q][0] = 0.0; - dN_deta[q][0] = 0.0; - dN_dzeta[q][0] = 0.0; + CfemReal L0, L1, L2; + reference_elem::utils::computeTriBarycentric(xiPoints[q], L0, L1, L2); + reference_elem::kernels::P2TriKernel::grads(L0, L1, L2, shapeBatch.dN_dxi(q), shapeBatch.dN_deta(q), shapeBatch.dN_dzeta(q)); } } - void evaluateShapesSecondDerivativesBatch(const DynamicVector &xiPoints, - DynamicMatrix &d2N_dxi2, - DynamicMatrix &d2N_deta2, - DynamicMatrix &d2N_dzeta2, - DynamicMatrix &d2N_dxideta, - DynamicMatrix &d2N_dxidzeta, - DynamicMatrix &d2N_detadzeta) const override - { - // Flat null second derivatives - const CfemInt nQp = static_cast(xiPoints.size()); - for (CfemInt q = 0; q < nQp; ++q) { - d2N_dxi2[q][0] = 0.0; - d2N_deta2[q][0] = 0.0; - d2N_dzeta2[q][0] = 0.0; - d2N_dxideta[q][0] = 0.0; - d2N_dxidzeta[q][0] = 0.0; - d2N_detadzeta[q][0] = 0.0; + void evaluateShapesSecondDerivativesBatch(const DynamicVector& xiPoints, ShapeInfoBatch& shapeBatch) const override { + const CfemInt nQp = shapeBatch.numQuadraturePoints; + if (nQp == 0) return; + + // Hessien constant (indépendant de xi), on utilise le broadcast Memcpy + reference_elem::kernels::P2TriKernel::hess(shapeBatch.d2N_dxi2(0), shapeBatch.d2N_deta2(0), shapeBatch.d2N_dzeta2(0), + shapeBatch.d2N_dxideta(0), shapeBatch.d2N_dxidzeta(0), shapeBatch.d2N_detadzeta(0)); + + const size_t bytesToCopy = m_numNodes * sizeof(CfemReal); + for (CfemInt q = 1; q < nQp; ++q) { + std::memcpy(shapeBatch.d2N_dxi2(q), shapeBatch.d2N_dxi2(0), bytesToCopy); + std::memcpy(shapeBatch.d2N_deta2(q), shapeBatch.d2N_deta2(0), bytesToCopy); + std::memcpy(shapeBatch.d2N_dzeta2(q), shapeBatch.d2N_dzeta2(0), bytesToCopy); + std::memcpy(shapeBatch.d2N_dxideta(q), shapeBatch.d2N_dxideta(0), bytesToCopy); + std::memcpy(shapeBatch.d2N_dxidzeta(q), shapeBatch.d2N_dxidzeta(0), bytesToCopy); + std::memcpy(shapeBatch.d2N_detadzeta(q), shapeBatch.d2N_detadzeta(0), bytesToCopy); } } Vector3D projectFacetRefCoordToCellRefCoord(CfemInt localFacetId, const Vector3D& xiFacet) const override { - return cfem::reference_elem::utils::projectFacetRefCoordToTriRefCoord(localFacetId, xiFacet); + return cfem::reference_elem::utils::projectFacetRefCoordToTriRefCoord(localFacetId, xiFacet); } - std::span getFaceNodes(CfemInt iFace) const override { - return cfem::reference_elem::utils::tri1_face_nodes[iFace]; + return cfem::reference_elem::utils::tri6_face_nodes[iFace]; } }; - - -} // namespace +} #endif \ No newline at end of file diff --git a/libs/fem/reference_element/include/ShapeInfo.h b/libs/fem/reference_element/include/ShapeInfo.h index b228c78..9a69b49 100644 --- a/libs/fem/reference_element/include/ShapeInfo.h +++ b/libs/fem/reference_element/include/ShapeInfo.h @@ -10,185 +10,476 @@ namespace cfem { + // ======================================================================== + // Paramètres matériels (Hardware-aware) + // ======================================================================== + + static constexpr size_t CACHELINE_SIZE = 64; ///< Required alignment for SIMD operations (cache-line aware). + + /** + * @brief Calcule un "stride" (pas) garantissant l'alignement sur une ligne de cache. + * Essentiel pour l'auto-vectorisation AVX2/AVX-512/SVE et pour éviter + * que des variables indépendantes ne partagent la même cacheline. + */ + inline constexpr CfemInt computePaddedStride(CfemInt nNodes) noexcept + { + constexpr CfemInt alignment = CACHELINE_SIZE / sizeof(CfemReal); + return ((nNodes + alignment - 1) / alignment) * alignment; + } + + // ======================================================================== + // ShapeData : POD HPC Pur (Aucun pointeur stocké, calculs à la volée) + // ======================================================================== + /** * @struct ShapeInfo - * @brief Storage buffer for shape function evaluations at a specific quadrature point. - * @note Re-use this object across multiple integration points to avoid frequent heap allocations. + * @brief Contiguous, cache-aligned storage buffer for element shape function evaluations. + * + * This structure implements a Structure-of-Arrays (SoA) layout to store values, gradients, + * and Hessians for a set of quadrature points. + * + * ### Performance Design + * - **Contiguous Memory:** All fields are stored in a single `DynamicVector` allocation. + * - **Cache Locality:** Optimized for L1/L2 cache prefetching by using padded strides. + * - **Zero Overhead:** Accessors provide lightweight views (raw pointers) to data without + * requiring heavy abstractions in hot loops. + * - **Memory Safety:** Designed as a move-only type (Rule of 0) to prevent dangling + * pointers resulting from accidental object cloning. */ struct ShapeInfo { - CfemInt numNodes = 0; - bool shapeMustBeUpdated = true; - ShapeEvaluationFlags validFlags = ShapeEvaluationFlags::None; - - DynamicVector values; ///< Shape values (N_i) + static constexpr CfemInt NUM_FIRST_ORDER_FIELDS = 4; ///< Number of fields for first-order derivatives including the shapes values: \f$ \left\{N, \frac{\partial N}{\partial \xi}, \frac{\partial N}{\partial \eta}, \frac{\partial N}{\partial \zeta} \right} \f$ + static constexpr CfemInt NUM_SECOND_ORDER_FIELDS = 6; ///< Number of fields for second-order derivatives: \f$ \left\{\frac{\partial^2 N}{\partial \xi^2}, \frac{\partial^2 N}{\partial \eta^2}, \frac{\partial^2 N}{\partial \zeta^2}, \frac{\partial^2 N}{\partial \xi \partial \eta}, \frac{\partial^2 N}{\partial \xi \partial \zeta}, \frac{\partial^2 N}{\partial \eta\partial \zeta} \right} \f$ - DynamicVector dN_dxi; ///< First order shape derivatives: \$ \frac{dN_i}{d\xi} \$ - DynamicVector dN_deta; ///< First order shape derivatives: \$ \frac{dN_i}{d\eta} \$ - DynamicVector dN_dzeta; ///< First order shape derivatives: \$ \frac{dN_i}{d\zeta} \$ + DynamicVector storage; ///< Centralized contiguous buffer - DynamicVector d2N_dxi2; ///< Second order local shape derivatives: \$ \frac{\partial^2N_i}{\partial\xi^2} \$ - DynamicVector d2N_deta2; ///< Second order local shape derivatives: \$ \frac{\partial^2N_i}{\partial\eta^2} \$ - DynamicVector d2N_dzeta2; ///< Second order local shape derivatives: \$ \frac{\partial^2N_i}{\partial\zeta^2} \$ + CfemInt numNodes = 0; ///< Number of nodes per element + CfemInt stride = 0; ///< Padded stride to ensure SIMD alignment + bool hasSecondDerivatives = false; - DynamicVector d2N_dxideta; ///< Second order local shape derivatives: \$ \frac{\partial^2N_i}{\partial\xi \partial\eta} \$ - DynamicVector d2N_dxidzeta; ///< Second order local shape derivatives: \$ \frac{\partial^2N_i}{\partial\xi \partial\zeta} \$ - DynamicVector d2N_detadzeta; ///< Second order local shape derivatives: \$ \frac{\partial^2N_i}{\partial\eta \partial\zeta} \$ + bool shapeMustBeUpdated = true; + ShapeEvaluationFlags validFlags = ShapeEvaluationFlags::None; /** - * @brief Prepares buffer sizes to prevent runtime reallocations. - * @param nNodes Number of nodes in the element. - * @param includeSecondDerivatives If true, allocates memory for the second order derivatives + * @brief Allocates and configures the internal storage layout. + * + * @param nNodes Number of interpolation nodes in the reference element. + * @param includeSecondDerivatives If true, memory for Hessian-related quantities is allocated. + * + * @note + * Calling this function invalidates all previously acquired raw field pointers. */ - void setup(CfemInt nNodes, bool includeSecondDerivatives = false) + inline void setup(CfemInt nNodes, bool includeSecondDerivatives = false) { numNodes = nNodes; + hasSecondDerivatives = includeSecondDerivatives; + stride = computePaddedStride(numNodes); - if (values.size() != static_cast(numNodes)) { - values.resize(numNodes); - dN_dxi.resize(numNodes); - dN_deta.resize(numNodes); - dN_dzeta.resize(numNodes); - } + const CfemInt nFields = NUM_FIRST_ORDER_FIELDS + + (includeSecondDerivatives ? NUM_SECOND_ORDER_FIELDS : 0); - if (includeSecondDerivatives && d2N_dxi2.size() != static_cast(numNodes)){ - d2N_dxi2.resize(numNodes); - d2N_deta2.resize(numNodes); - d2N_dzeta2.resize(numNodes); + const size_t totalSize = static_cast(nFields) * static_cast(stride); - d2N_dxideta.resize(numNodes); - d2N_dxidzeta.resize(numNodes); - d2N_detadzeta.resize(numNodes); + if (storage.size() != totalSize) + { + storage.resize(totalSize); } validFlags = ShapeEvaluationFlags::None; } - void resizeFirstDerivativeBuffers(CfemInt nNodes) - { - numNodes = nNodes; - dN_dxi.resize(numNodes); - dN_deta.resize(numNodes); - dN_dzeta.resize(numNodes); + // ================================================================================================ + // SHAPES VALUES + // ================================================================================================ + /** @brief Returns a writable pointer to shape function values. */ + [[nodiscard]] inline CfemReal* values() { + CFEM_ASSERT(has(ShapeEvaluationFlags::Value), "Shape values not computed?"); + return storage.data(); } - void resizeSecondDerivativeBuffers(CfemInt nNodes) - { - numNodes = nNodes; - d2N_dxi2.resize(numNodes); - d2N_deta2.resize(numNodes); - d2N_dzeta2.resize(numNodes); + // ================================================================================================ + // SHAPES FIRST DERIVATIVES + // ================================================================================================ - d2N_dxideta.resize(numNodes); - d2N_dxidzeta.resize(numNodes); - d2N_detadzeta.resize(numNodes); + /** @brief Returns a writable pointer to shape derivatives along the reference \f$\xi\f$ direction. */ + [[nodiscard]] inline CfemReal* dN_dxi() { + CFEM_ASSERT(has(ShapeEvaluationFlags::FirstDerivatives), "Shape first derivatives not computed?"); + return storage.data() + 1 * stride; + } + /** @brief Returns a writable pointer to shape derivatives along the reference \f$\eta\f$ direction. */ + [[nodiscard]] inline CfemReal* dN_deta() { + CFEM_ASSERT(has(ShapeEvaluationFlags::FirstDerivatives), "Shape first derivatives not computed?"); + return storage.data() + 2 * stride; } - inline Vector3D gradient(CfemInt node) const - { - CFEM_ASSERT(node >= 0 && node < numNodes); - return Vector3D(dN_dxi[node], dN_deta[node], dN_dzeta[node]); + /** @brief Returns a writable pointer to shape derivatives along the reference \f$\zeta\f$ direction. */ + [[nodiscard]] inline CfemReal* dN_dzeta() { + CFEM_ASSERT(has(ShapeEvaluationFlags::FirstDerivatives), "Shape first derivatives not computed?"); + return storage.data() + 3 * stride; + } + + // ================================================================================================ + // SHAPES SECOND DERIVATIVES + // ================================================================================================ + + /** @brief Returns a writable pointer to shape second derivatives \f$\partial^2 N \partial \zeta^2\f$ direction. */ + [[nodiscard]] inline CfemReal* d2N_dxi2() { + CFEM_ASSERT(hasSecondDerivatives, "Second derivative not computed?"); + return storage.data() + 4 * stride; + } + + [[nodiscard]] inline CfemReal* d2N_deta2() { + CFEM_ASSERT(hasSecondDerivatives, "Second derivative not computed?"); + return storage.data() + 5 * stride; + } + + [[nodiscard]] inline CfemReal* d2N_dzeta2() { + CFEM_ASSERT(hasSecondDerivatives, "Second derivative not computed?"); + return storage.data() + 6 * stride; + } + + [[nodiscard]] inline CfemReal* d2N_dxideta() { + CFEM_ASSERT(hasSecondDerivatives, "Second derivative not computed?"); + return storage.data() + 7 * stride; + } + + [[nodiscard]] inline CfemReal* d2N_dxidzeta() { + CFEM_ASSERT(hasSecondDerivatives, "Second derivative not computed?"); + return storage.data() + 8 * stride; + } + + [[nodiscard]] inline CfemReal* d2N_detadzeta() { + CFEM_ASSERT(hasSecondDerivatives, "Second derivative not computed?"); + return storage.data() + 9 * stride; + } + + + // Versions const (pour la lecture) + [[nodiscard]] inline const CfemReal* values() const { + CFEM_ASSERT(has(ShapeEvaluationFlags::Value), "Shape values not computed?"); + return storage.data(); + } + + [[nodiscard]] inline const CfemReal* dN_dxi() const { + CFEM_ASSERT( has(ShapeEvaluationFlags::FirstDerivatives), "Shape first derivatives not computed?"); + return storage.data() + 1 * stride; + } + + [[nodiscard]] inline const CfemReal* dN_deta() const { + CFEM_ASSERT( has(ShapeEvaluationFlags::FirstDerivatives), "Shape first derivatives not computed?"); + return storage.data() + 2 * stride; + } + + [[nodiscard]] inline const CfemReal* dN_dzeta() const { + CFEM_ASSERT( has(ShapeEvaluationFlags::FirstDerivatives), "Shape first derivatives not computed?"); + return storage.data() + 3 * stride; + } + + + [[nodiscard]] inline const CfemReal* d2N_dxi2() const { + CFEM_ASSERT(hasSecondDerivatives, "Second derivatives not computed?"); + return storage.data() + 4 * stride; + } + + [[nodiscard]] inline const CfemReal* d2N_deta2() const { + CFEM_ASSERT(hasSecondDerivatives, "Second derivatives not computed?"); + return storage.data() + 5 * stride; + } + + [[nodiscard]] inline const CfemReal* d2N_dzeta2() const { + CFEM_ASSERT(hasSecondDerivatives, "Second derivatives not computed?"); + return storage.data() + 6 * stride; + } + + [[nodiscard]] inline const CfemReal* d2N_dxideta() const { + CFEM_ASSERT(hasSecondDerivatives, "Second derivatives not computed?"); + return storage.data() + 7 * stride; + } + + [[nodiscard]] inline const CfemReal* d2N_dxidzeta() const { + CFEM_ASSERT(hasSecondDerivatives, "Second derivatives not computed?"); + return storage.data() + 8 * stride; + } + + [[nodiscard]] inline const CfemReal* d2N_detadzeta() const { + CFEM_ASSERT(hasSecondDerivatives, "Second derivatives not computed?"); + return storage.data() + 9 * stride; } - inline SymTensor3D hessian(CfemInt node) const + + // ============================================================ + // Getters + // ============================================================ + + /** + * @brief Returns a reference-space gradient vector for a specific node index. + * @param i Node index (0 to numNodes-1). + */ + inline Vector3D gradient(CfemInt i) const { - CFEM_ASSERT(node >= 0 && node < numNodes); - return SymTensor3D(d2N_dxi2[node], d2N_deta2[node], d2N_dzeta2[node], // The diagonal entries - d2N_detadzeta[node], d2N_dxidzeta[node], d2N_dxideta[node]); + CFEM_ASSERT(i >= 0 && i < numNodes); + return Vector3D(dN_dxi()[i], dN_deta()[i], dN_dzeta()[i]); } /** - * @brief Checks if a specific field (Value/Gradient/Hessian) is marked as valid. + * @brief Returns the symmetric Hessian tensor for a specific node index. + * @param i Node index (0 to numNodes-1). */ - bool has(ShapeEvaluationFlags field) const + inline SymTensor3D hessian(CfemInt i) const + { + CFEM_ASSERT(i >= 0 && i < numNodes); + CFEM_ASSERT(hasSecondDerivatives); + return SymTensor3D( + d2N_dxi2()[i], d2N_deta2()[i], d2N_dzeta2()[i], + d2N_detadzeta()[i], d2N_dxidzeta()[i], d2N_dxideta()[i] + ); + } + + inline bool has(ShapeEvaluationFlags field) const { return hasFlag(validFlags, field); } + + inline size_t storageSize() const + { + return storage.size(); + } + + inline size_t storageBytes() const + { + return storage.size() * sizeof(CfemReal); + } }; + /** * @struct ShapeInfoBatch - * @brief Storage buffer for shape function evaluations at a collection of quadrature points. - * @note Optimized for HPC using a Structure of Arrays (SoA) layout via DynamicMatrix. + * @brief Contiguous, cache-aligned storage buffer for shape function evaluations across a batch of quadrature points. + * + * ### HPC Design (Structure of Arrays - SoA) + * To maximize SIMD auto-vectorization and cache hits, all evaluations (values, gradients, Hessians) + * for all quadrature points and all nodes are flattened into a single 1D `DynamicVector`. + * * Memory Layout for a given quadrature point `q`: + * `[ N_nodes... | dxi_nodes... | deta_nodes... | dzeta_nodes... | (Hessians...) ]` + * * Each field block is padded to ensure it starts on a SIMD-friendly memory boundary (e.g., 64 bytes). + * Lightweight view accessors (e.g., `dN_dxi(q)`) return raw pointers to these aligned blocks. */ struct ShapeInfoBatch { + /** @brief Number of fields for first-order data: {N, dN/dxi, dN/deta, dN/dzeta} */ + static constexpr CfemInt NUM_FIRST_ORDER_FIELDS = 4; + + /** @brief Number of fields for second-order data: {d2N/dxi2, d2N/deta2, d2N/dzeta2, d2N/dxideta, d2N/dxidzeta, d2N/detadzeta} */ + static constexpr CfemInt NUM_SECOND_ORDER_FIELDS = 6; + CfemInt numQuadraturePoints = 0; CfemInt numNodes = 0; + bool shapeMustBeUpdated = true; ShapeEvaluationFlags validFlags = ShapeEvaluationFlags::None; - DynamicMatrix values; // Shape values: values[qp][i] - - DynamicMatrix dN_dxi; // First order local shapes' derivatives at a collection of quadrature points: \$ \frac{\partial N_i}{\partial\xi}[q] \$ - DynamicMatrix dN_deta; // First order local shapes' derivatives at a collection of quadrature points: \$ \frac{\partial N_i}{\partial\eta}[q] \$ - DynamicMatrix dN_dzeta; // First order local shapes' derivatives at a collection of quadrature points: \$ \frac{\partial N_i}{\partial\xi}[q] \$ + // ==================================================================== + // HPC Contiguous Storage + // ==================================================================== + DynamicVector storage; ///< Centralized contiguous buffer for all QPs and all fields - DynamicMatrix d2N_dxi2; // Second order local shapes' derivatives at a collection of quadrature points \$ \frac{\partial^2N_i}{\partial\xi^2}[q] \$ - DynamicMatrix d2N_deta2; // Second order local shapes' derivatives at a collection of quadrature points \$ \frac{\partial^2N_i}{\partial\eta^2}[q] \$ - DynamicMatrix d2N_dzeta2; // Second order local shapes' derivatives at a collection of quadrature points \$ \frac{\partial^2N_i}{\partial\zeta}[q] \$ - - DynamicMatrix d2N_dxideta; // Second order local shapes' derivatives at a collection of quadrature points \$ \frac{\partial^2N_i}{\partial\xi\partial\eta}[q] \$ - DynamicMatrix d2N_dxidzeta; // Second order local shapes' derivatives at a collection of quadrature points \$ \frac{\partial^2N_i}{\partial\xi\partial\zeta}[q] \$ - DynamicMatrix d2N_detadzeta; // Second order local shapes' derivatives at a collection of quadrature points \$ \frac{\partial^2N_i}{\partial\eta\partial\zeta}[q] \$ - - /** - * @brief Prepares buffer sizes to prevent runtime reallocations. + CfemInt paddedNodeStride = 0; ///< Padded stride per field to ensure SIMD alignment + CfemInt qpStride = 0; ///< Total stride per quadrature point (all fields combined) + bool hasSecondDerivatives = false; + + /** * @brief Prepares buffer sizes and strides to prevent runtime reallocations. + * * @param nQp Number of quadrature points in the batch. + * @param nNodes Number of interpolation nodes in the reference element. + * @param includeSecondDerivatives If true, memory for Hessian-related quantities is allocated. */ - void setup(CfemInt nQp, CfemInt nNodes, bool includeSecondDerivatives = false) + inline void setup(CfemInt nQp, CfemInt nNodes, bool includeSecondDerivatives = false) { numQuadraturePoints = nQp; numNodes = nNodes; + hasSecondDerivatives = includeSecondDerivatives; - if (values.size() != nQp * nNodes) - { - values.resize(nQp, nNodes); - dN_dxi.resize(nQp, nNodes); - dN_deta.resize(nQp, nNodes); - dN_dzeta.resize(nQp, nNodes); - } + paddedNodeStride = computePaddedStride(numNodes); + const CfemInt nFields = NUM_FIRST_ORDER_FIELDS + + (includeSecondDerivatives ? NUM_SECOND_ORDER_FIELDS : 0); + + qpStride = nFields * paddedNodeStride; + const size_t totalStorageSize = static_cast(nQp) * static_cast(qpStride); - if (includeSecondDerivatives && d2N_dxi2.size() != nQp * nNodes) + if (storage.size() != totalStorageSize) { - d2N_dxi2.resize(nQp, nNodes); - d2N_deta2.resize(nQp, nNodes); - d2N_dzeta2.resize(nQp, nNodes); - - d2N_dxideta.resize(nQp, nNodes); - d2N_dxidzeta.resize(nQp, nNodes); - d2N_detadzeta.resize(nQp, nNodes); + storage.resize(totalStorageSize); } validFlags = ShapeEvaluationFlags::None; } + // ================================================================================================ + // LIGHTWEIGHT VIEWS (Writable) - Return raw pointers for a specific Quadrature Point 'q' + // ================================================================================================ + + [[nodiscard]] inline CfemReal* values(CfemInt q) { + CFEM_ASSERT(q >= 0 && q < numQuadraturePoints, "Invalid QP index"); + CFEM_ASSERT(has(ShapeEvaluationFlags::Value), "Shape values not computed?"); + return storage.data() + q * qpStride + 0 * paddedNodeStride; + } + + [[nodiscard]] inline CfemReal* dN_dxi(CfemInt q) { + CFEM_ASSERT(q >= 0 && q < numQuadraturePoints, "Invalid QP index"); + CFEM_ASSERT(has(ShapeEvaluationFlags::FirstDerivatives), "Shape first derivatives not computed?"); + return storage.data() + q * qpStride + 1 * paddedNodeStride; + } + + [[nodiscard]] inline CfemReal* dN_deta(CfemInt q) { + CFEM_ASSERT(q >= 0 && q < numQuadraturePoints, "Invalid QP index"); + CFEM_ASSERT(has(ShapeEvaluationFlags::FirstDerivatives), "Shape first derivatives not computed?"); + return storage.data() + q * qpStride + 2 * paddedNodeStride; + } + + [[nodiscard]] inline CfemReal* dN_dzeta(CfemInt q) { + CFEM_ASSERT(q >= 0 && q < numQuadraturePoints, "Invalid QP index"); + CFEM_ASSERT(has(ShapeEvaluationFlags::FirstDerivatives), "Shape first derivatives not computed?"); + return storage.data() + q * qpStride + 3 * paddedNodeStride; + } + + [[nodiscard]] inline CfemReal* d2N_dxi2(CfemInt q) { + CFEM_ASSERT(q >= 0 && q < numQuadraturePoints, "Invalid QP index"); + CFEM_ASSERT(hasSecondDerivatives, "Second derivatives not allocated"); + return storage.data() + q * qpStride + 4 * paddedNodeStride; + } + + [[nodiscard]] inline CfemReal* d2N_deta2(CfemInt q) { + CFEM_ASSERT(q >= 0 && q < numQuadraturePoints, "Invalid QP index"); + CFEM_ASSERT(hasSecondDerivatives, "Second derivatives not allocated"); + return storage.data() + q * qpStride + 5 * paddedNodeStride; + } + + [[nodiscard]] inline CfemReal* d2N_dzeta2(CfemInt q) { + CFEM_ASSERT(q >= 0 && q < numQuadraturePoints, "Invalid QP index"); + CFEM_ASSERT(hasSecondDerivatives, "Second derivatives not allocated"); + return storage.data() + q * qpStride + 6 * paddedNodeStride; + } + + [[nodiscard]] inline CfemReal* d2N_dxideta(CfemInt q) { + CFEM_ASSERT(q >= 0 && q < numQuadraturePoints, "Invalid QP index"); + CFEM_ASSERT(hasSecondDerivatives, "Second derivatives not allocated"); + return storage.data() + q * qpStride + 7 * paddedNodeStride; + } + + [[nodiscard]] inline CfemReal* d2N_dxidzeta(CfemInt q) { + CFEM_ASSERT(q >= 0 && q < numQuadraturePoints, "Invalid QP index"); + CFEM_ASSERT(hasSecondDerivatives, "Second derivatives not allocated"); + return storage.data() + q * qpStride + 8 * paddedNodeStride; + } + + [[nodiscard]] inline CfemReal* d2N_detadzeta(CfemInt q) { + CFEM_ASSERT(q >= 0 && q < numQuadraturePoints, "Invalid QP index"); + CFEM_ASSERT(hasSecondDerivatives, "Second derivatives not allocated"); + return storage.data() + q * qpStride + 9 * paddedNodeStride; + } + + // ================================================================================================ + // LIGHTWEIGHT VIEWS (Read-Only) - Include validFlags safety checks + // ================================================================================================ + + [[nodiscard]] inline const CfemReal* values(CfemInt q) const { + CFEM_ASSERT(q >= 0 && q < numQuadraturePoints, "Invalid QP index"); + CFEM_ASSERT(has(ShapeEvaluationFlags::Value), "Shape values not computed"); + return storage.data() + q * qpStride + 0 * paddedNodeStride; + } + + [[nodiscard]] inline const CfemReal* dN_dxi(CfemInt q) const { + CFEM_ASSERT(q >= 0 && q < numQuadraturePoints, "Invalid QP index"); + CFEM_ASSERT(has(ShapeEvaluationFlags::FirstDerivatives), "First derivatives not computed"); + return storage.data() + q * qpStride + 1 * paddedNodeStride; + } + + [[nodiscard]] inline const CfemReal* dN_deta(CfemInt q) const { + CFEM_ASSERT(q >= 0 && q < numQuadraturePoints, "Invalid QP index"); + CFEM_ASSERT(has(ShapeEvaluationFlags::FirstDerivatives), "First derivatives not computed"); + return storage.data() + q * qpStride + 2 * paddedNodeStride; + } + + [[nodiscard]] inline const CfemReal* dN_dzeta(CfemInt q) const { + CFEM_ASSERT(q >= 0 && q < numQuadraturePoints, "Invalid QP index"); + CFEM_ASSERT(has(ShapeEvaluationFlags::FirstDerivatives), "First derivatives not computed"); + return storage.data() + q * qpStride + 3 * paddedNodeStride; + } + + [[nodiscard]] inline const CfemReal* d2N_dxi2(CfemInt q) const { + CFEM_ASSERT(q >= 0 && q < numQuadraturePoints, "Invalid QP index"); + CFEM_ASSERT(hasSecondDerivatives && has(ShapeEvaluationFlags::SecondDerivatives), "Second derivatives not computed"); + return storage.data() + q * qpStride + 4 * paddedNodeStride; + } + + [[nodiscard]] inline const CfemReal* d2N_deta2(CfemInt q) const { + CFEM_ASSERT(q >= 0 && q < numQuadraturePoints, "Invalid QP index"); + CFEM_ASSERT(hasSecondDerivatives && has(ShapeEvaluationFlags::SecondDerivatives), "Second derivatives not computed"); + return storage.data() + q * qpStride + 5 * paddedNodeStride; + } + + [[nodiscard]] inline const CfemReal* d2N_dzeta2(CfemInt q) const { + CFEM_ASSERT(q >= 0 && q < numQuadraturePoints, "Invalid QP index"); + CFEM_ASSERT(hasSecondDerivatives && has(ShapeEvaluationFlags::SecondDerivatives), "Second derivatives not computed"); + return storage.data() + q * qpStride + 6 * paddedNodeStride; + } + + [[nodiscard]] inline const CfemReal* d2N_dxideta(CfemInt q) const { + CFEM_ASSERT(q >= 0 && q < numQuadraturePoints, "Invalid QP index"); + CFEM_ASSERT(hasSecondDerivatives && has(ShapeEvaluationFlags::SecondDerivatives), "Second derivatives not computed"); + return storage.data() + q * qpStride + 7 * paddedNodeStride; + } + + [[nodiscard]] inline const CfemReal* d2N_dxidzeta(CfemInt q) const { + CFEM_ASSERT(q >= 0 && q < numQuadraturePoints, "Invalid QP index"); + CFEM_ASSERT(hasSecondDerivatives && has(ShapeEvaluationFlags::SecondDerivatives), "Second derivatives not computed"); + return storage.data() + q * qpStride + 8 * paddedNodeStride; + } + + [[nodiscard]] inline const CfemReal* d2N_detadzeta(CfemInt q) const { + CFEM_ASSERT(q >= 0 && q < numQuadraturePoints, "Invalid QP index"); + CFEM_ASSERT(hasSecondDerivatives && has(ShapeEvaluationFlags::SecondDerivatives), "Second derivatives not computed"); + return storage.data() + q * qpStride + 9 * paddedNodeStride; + } + + // ============================================================ + // Accessors & Getters + // ============================================================ + /** - * @brief Reconstructs the reference gradient vector for a specific qp and node. + * @brief Reconstructs the reference gradient vector for a specific quadrature point and node. + * @param q Quadrature point index. + * @param i Node index. + * @return Vector3D containing the reference gradients. */ inline Vector3D gradient(CfemInt q, CfemInt i) const { - CFEM_ASSERT(q >= 0 && q < numQuadraturePoints); - CFEM_ASSERT(i >= 0 && i < numNodes); - return Vector3D(dN_dxi(q, i), dN_deta(q, i), dN_dzeta(q, i)); + CFEM_ASSERT(i >= 0 && i < numNodes, "Invalid node index"); + return Vector3D(dN_dxi(q)[i], dN_deta(q)[i], dN_dzeta(q)[i]); } /** - * @brief Reconstructs the reference Hessian tensor for a specific qp and node. + * @brief Reconstructs the reference Hessian tensor for a specific quadrature point and node. + * @param q Quadrature point index. + * @param i Node index. + * @return SymTensor3D containing the reference second derivatives. */ inline SymTensor3D hessian(CfemInt q, CfemInt i) const { - CFEM_ASSERT(q >= 0 && q < numQuadraturePoints); - CFEM_ASSERT(i >= 0 && i < numNodes); - return SymTensor3D(d2N_dxi2(q, i), d2N_deta2(q, i), d2N_dzeta2(q, i), - d2N_detadzeta(q, i), d2N_dxidzeta(q, i), d2N_dxideta(q, i)); + CFEM_ASSERT(i >= 0 && i < numNodes, "Invalid node index"); + CFEM_ASSERT(has(ShapeEvaluationFlags::SecondDerivatives), "Shape second derivatives not computed?"); + return SymTensor3D(d2N_dxi2(q)[i], d2N_deta2(q)[i], d2N_dzeta2(q)[i], + d2N_detadzeta(q)[i], d2N_dxidzeta(q)[i], d2N_dxideta(q)[i]); } - bool has(ShapeEvaluationFlags field) const + /** + * @brief Checks if a specific evaluation field is marked as valid. + */ + inline bool has(ShapeEvaluationFlags field) const { return hasFlag(validFlags, field); } }; - + } \ No newline at end of file diff --git a/libs/prepostprocessing/numerical_integration/src/FunctionIntegrator.cpp b/libs/prepostprocessing/numerical_integration/src/FunctionIntegrator.cpp index e9c0b33..1571dee 100644 --- a/libs/prepostprocessing/numerical_integration/src/FunctionIntegrator.cpp +++ b/libs/prepostprocessing/numerical_integration/src/FunctionIntegrator.cpp @@ -336,7 +336,7 @@ namespace cfem mapGeo.setup(refElGeo->getNumNodes()); for (size_t sIdx = 0; sIdx < idToSpace.size(); ++sIdx) { - CfemInt nNodesSpace = static_cast(cache->spaceCaches[sIdx].shapes[0].values.size()); + CfemInt nNodesSpace = static_cast(cache->spaceCaches[sIdx].shapes[0].numNodes); spaceMappings[sIdx].setup(nNodesSpace); } } @@ -608,7 +608,7 @@ namespace cfem mapParentGeo.setup(static_cast(parentNodes.size())); for (size_t sIdx = 0; sIdx < idToSpace.size(); ++sIdx) { - spaceMappings[sIdx].setup(static_cast(cache.spaceCaches[sIdx].shapes[0].values.size())); + spaceMappings[sIdx].setup(static_cast(cache.spaceCaches[sIdx].shapes[0].numNodes)); } const auto &refElFacet = ReferenceElementFactory::getReferenceElement(facet.type, geoOrder); diff --git a/tests/fem/test_quadrature.cpp b/tests/fem/test_quadrature.cpp index f066ed7..4e8176a 100644 --- a/tests/fem/test_quadrature.cpp +++ b/tests/fem/test_quadrature.cpp @@ -191,7 +191,7 @@ TEST_F(MappingAccuracyTest, TransformedTriangle3) { TEST_F(MappingAccuracyTest, InterpolationConsistency) { Q1ReferenceQuad ref; ShapeInfo shape; - shape.values.resize(ref.getNumNodes()); + shape.setup(ref.getNumNodes()); //shape.setup(ref.getNumNodes()); @@ -220,8 +220,8 @@ TEST_F(MappingAccuracyTest, PhysicalGradientLinearField) { const QuadratureRule& q = scheme.getRule(CellType::TRI3, 1); ShapeInfo shape; MappingInfo map; - shape.setup(3); - map.setup(3); + shape.setup(ref.getNumNodes()); + map.setup(ref.getNumNodes()); // Triangle : (0,0), (1,0), (0,1) DynamicVector nodes = {{0,0,0}, {1,0,0}, {0,1,0}}; diff --git a/tests/fem/test_reference_element.cpp b/tests/fem/test_reference_element.cpp index defab83..ae0b9aa 100644 --- a/tests/fem/test_reference_element.cpp +++ b/tests/fem/test_reference_element.cpp @@ -99,9 +99,10 @@ TEST_P(ReferenceElementTest, KroneckerDeltaProperty) { for (CfemInt j = 0; j < n; ++j) { el->evaluateShapes(nodes[j], ShapeEvaluationFlags::Value, info); + const auto& values = info.values(); for (CfemInt i = 0; i < n; ++i) { CfemReal expected = (i == j) ? 1.0 : 0.0; - EXPECT_NEAR(info.values[i], expected, tol) + EXPECT_NEAR(values[i], expected, tol) << "Element: " << typeid(*el).name() << " - Node: " << j << " - ShapeFn: " << i; } } @@ -115,10 +116,11 @@ TEST_P(ReferenceElementTest, PartitionOfUnity) { for (const auto& xi : test_points) { el->evaluateShapes(xi, ShapeEvaluationFlags::Value, info); - + const auto& values = info.values(); + CfemReal sum = 0.0; for (CfemInt i = 0; i < el->getNumNodes(); ++i) { - sum += info.values[i]; + sum += values[i]; } EXPECT_NEAR(sum, 1.0, tol) @@ -224,7 +226,7 @@ TEST_F(MappingTest, QuadPlanarMapping) { // dN/dz doit être nul car l'élément est dans le plan XY for(CfemInt i=0; i<4; ++i) { - EXPECT_NEAR(map.dN_dz[i], 0.0, tol); + EXPECT_NEAR(map.dN_dz()[i], 0.0, tol); } } @@ -312,9 +314,9 @@ TEST_F(MappingTest, P2TriangleCurvedHessians) { // Check that the gradient components are reasonable (not NaN) for (CfemInt i=0; i<6; ++i) { - EXPECT_FALSE(std::isnan(map.dN_dx[i])); - EXPECT_FALSE(std::isnan(map.dN_dy[i])); - EXPECT_FALSE(std::isnan(map.dN_dz[i])); + EXPECT_FALSE(std::isnan(map.dN_dx()[i])); + EXPECT_FALSE(std::isnan(map.dN_dy()[i])); + EXPECT_FALSE(std::isnan(map.dN_dz()[i])); } } From 86fc16605b31e860f7cfdc20823274b301b7ce2b Mon Sep 17 00:00:00 2001 From: itchinda Date: Wed, 27 May 2026 08:57:37 -0400 Subject: [PATCH 6/9] code refactoring: batch evaluation --- apps/main.cpp | 44 +- libs/expressions/include/BinaryExpression.h | 1 + libs/expressions/include/BinaryExpression.tpp | 227 +++++ libs/expressions/include/CachedExpression.h | 14 + libs/expressions/include/CfemExpression.h | 11 + libs/expressions/include/ConstantExpression.h | 20 + .../include/CoordinateExpression.h | 26 + .../include/DivergenceExpression.h | 44 + libs/expressions/include/EvalContext.h | 19 +- .../include/GeometricNormalExpression.h | 22 + libs/expressions/include/GradExpression.h | 13 +- libs/expressions/include/LambdaExpression.h | 54 ++ .../include/MagnitudeSqExpression.h | 1 + .../include/MagnitudeSqExpression.tpp | 35 + libs/expressions/include/ScaledExpression.h | 19 + libs/expressions/include/TensorExpression.h | 16 + libs/expressions/include/UnaryExpression.h | 4 +- libs/expressions/include/UnaryExpression.tpp | 117 +++ libs/expressions/include/VariableExpression.h | 5 + libs/expressions/include/VectorExpression.h | 16 +- libs/expressions/include/ZeroExpression.h | 8 + .../symengine/ScalarSymbolicExpression.h | 17 + .../symengine/TensorSymbolicExpression.h | 18 +- .../symengine/VectorSymbolicExpression.h | 18 + libs/expressions/src/GradExpression.cpp | 55 -- libs/fem/fefield/include/Argument.h | 29 +- libs/fem/fefield/include/FEField.h | 41 +- libs/fem/fefield/src/FEField.cpp | 241 +++-- libs/fem/mesh/src/GmshParser.cpp | 2 +- .../reference_element/include/MappingInfo.h | 121 +-- .../include/ReferenceElement.h | 43 +- .../include/ReferenceElement.tpp | 834 +++++++++++------- .../reference_element/include/ReferenceQuad.h | 2 +- .../include/ReferenceTetrahedron.h | 45 +- .../include/ReferenceTriangle.h | 8 +- .../fem/reference_element/include/ShapeInfo.h | 40 +- .../src/FunctionIntegrator.cpp | 493 ++++++++--- 37 files changed, 1892 insertions(+), 831 deletions(-) diff --git a/apps/main.cpp b/apps/main.cpp index 9c58b3d..55bbcbb 100644 --- a/apps/main.cpp +++ b/apps/main.cpp @@ -116,14 +116,6 @@ int main(int argc, char **argv) { PROFILE_SCOPE("MeshBuilding"); - SymTensor3D A; - SymTensor3D B; - auto ApB = A + B; - auto AmB = A - B; - auto AfB = A * B; - - - // mesh = Mesh::quad( // MeshOrder::Linear, // Mesh::QuadSubdivisions{1, 1}, // Mesh::BlockSubdivisions{5*(2< out, EvalContext &ctx) const override; + void evaluateBatch(CfemInt cellId, CfemInt nQp, std::span out, EvalContext &ctx) const override; std::shared_ptr getLHS() const { return m_lhs; } std::shared_ptr getRHS() const { return m_rhs; } diff --git a/libs/expressions/include/BinaryExpression.tpp b/libs/expressions/include/BinaryExpression.tpp index 938de70..a04aaed 100644 --- a/libs/expressions/include/BinaryExpression.tpp +++ b/libs/expressions/include/BinaryExpression.tpp @@ -165,6 +165,233 @@ namespace cfem::sym } } + inline void BinaryExpression::evaluateBatch(CfemInt cellId, CfemInt nQp, std::span out, EvalContext &ctx) const + { + // On sauvegarde l'état de l'Arena avant d'allouer les buffers temporaires par lot + auto bookmark = ctx.getBookmark(); + + // Allocation des buffers SoA pour les nQp points : taille = dimension * nQp + std::span spanL = ctx.getBuffer(m_dimL * nQp); + std::span spanR = ctx.getBuffer(m_dimR * nQp); + + // Évaluation en cascade de l'arbre d'expression + m_lhs->evaluateBatch(cellId, nQp, spanL, ctx); + m_rhs->evaluateBatch(cellId, nQp, spanR, ctx); + + // ========================================================================= + // RAPPEL FORMAT SoA: span[comp * nQp + q] + // Les boucles internes sur `q` assurent une lecture/écriture séquentielle pure + // ========================================================================= + + switch (m_op) + { + case MathOp::Add: { + for (CfemInt i = 0; i < m_dimOut; ++i) { + for (CfemInt q = 0; q < nQp; ++q) { + out[i * nQp + q] = spanL[i * nQp + q] + spanR[i * nQp + q]; + } + } + break; + } + case MathOp::Sub: { + for (CfemInt i = 0; i < m_dimOut; ++i) { + for (CfemInt q = 0; q < nQp; ++q) { + out[i * nQp + q] = spanL[i * nQp + q] - spanR[i * nQp + q]; + } + } + break; + } + case MathOp::Mul: { + if (m_dimL == 1) { // Scalaire * (Scalaire/Vecteur/Tenseur) + for (CfemInt i = 0; i < m_dimOut; ++i) { + for (CfemInt q = 0; q < nQp; ++q) { + out[i * nQp + q] = spanL[q] * spanR[i * nQp + q]; // spanL[0*nQp + q] == spanL[q] + } + } + } + else if (m_dimR == 1) { // (Vecteur/Tenseur) * Scalaire + for (CfemInt i = 0; i < m_dimOut; ++i) { + for (CfemInt q = 0; q < nQp; ++q) { + out[i * nQp + q] = spanL[i * nQp + q] * spanR[q]; + } + } + } + else if (m_dimL == 9 && m_dimR == 9) { // Tenseur * Tenseur + for (CfemInt i = 0; i < 3; ++i) { + for (CfemInt j = 0; j < 3; ++j) { + CfemInt outIdx = i * 3 + j; + CfemInt L0 = i * 3 + 0, L1 = i * 3 + 1, L2 = i * 3 + 2; + CfemInt R0 = 0 * 3 + j, R1 = 1 * 3 + j, R2 = 2 * 3 + j; + for (CfemInt q = 0; q < nQp; ++q) { + out[outIdx * nQp + q] = spanL[L0 * nQp + q] * spanR[R0 * nQp + q] + + spanL[L1 * nQp + q] * spanR[R1 * nQp + q] + + spanL[L2 * nQp + q] * spanR[R2 * nQp + q]; + } + } + } + } + else if (m_dimL == 9 && m_dimR == 3) { // Tenseur * Vecteur + for (CfemInt i = 0; i < 3; ++i) { + CfemInt L0 = i * 3 + 0, L1 = i * 3 + 1, L2 = i * 3 + 2; + for (CfemInt q = 0; q < nQp; ++q) { + out[i * nQp + q] = spanL[L0 * nQp + q] * spanR[0 * nQp + q] + + spanL[L1 * nQp + q] * spanR[1 * nQp + q] + + spanL[L2 * nQp + q] * spanR[2 * nQp + q]; + } + } + } + else { // Élément par élément (Hadamard) + for (CfemInt i = 0; i < m_dimOut; ++i) { + for (CfemInt q = 0; q < nQp; ++q) { + out[i * nQp + q] = spanL[i * nQp + q] * spanR[i * nQp + q]; + } + } + } + break; + } + case MathOp::Div: { + if (m_dimR == 1) { // (Vecteur/Tenseur) / Scalaire + for (CfemInt i = 0; i < m_dimOut; ++i) { + for (CfemInt q = 0; q < nQp; ++q) { + out[i * nQp + q] = spanL[i * nQp + q] / spanR[q]; + } + } + } else { // Élément par élément + for (CfemInt i = 0; i < m_dimOut; ++i) { + for (CfemInt q = 0; q < nQp; ++q) { + out[i * nQp + q] = spanL[i * nQp + q] / spanR[i * nQp + q]; + } + } + } + break; + } + case MathOp::Dot: { + if (m_dimL == 3 && m_dimR == 3) { // Vecteur . Vecteur -> Scalaire + for (CfemInt q = 0; q < nQp; ++q) { + out[q] = spanL[0 * nQp + q] * spanR[0 * nQp + q] + + spanL[1 * nQp + q] * spanR[1 * nQp + q] + + spanL[2 * nQp + q] * spanR[2 * nQp + q]; + } + } + else if (m_dimL == 9 && m_dimR == 3) { // Tenseur . Vecteur -> Vecteur + for (CfemInt i = 0; i < 3; ++i) { + CfemInt L0 = i * 3 + 0, L1 = i * 3 + 1, L2 = i * 3 + 2; + for (CfemInt q = 0; q < nQp; ++q) { + out[i * nQp + q] = spanL[L0 * nQp + q] * spanR[0 * nQp + q] + + spanL[L1 * nQp + q] * spanR[1 * nQp + q] + + spanL[L2 * nQp + q] * spanR[2 * nQp + q]; + } + } + } + else if (m_dimL == 3 && m_dimR == 9) { // Vecteur . Tenseur -> Vecteur + for (CfemInt j = 0; j < 3; ++j) { + CfemInt R0 = 0 * 3 + j, R1 = 1 * 3 + j, R2 = 2 * 3 + j; + for (CfemInt q = 0; q < nQp; ++q) { + out[j * nQp + q] = spanL[0 * nQp + q] * spanR[R0 * nQp + q] + + spanL[1 * nQp + q] * spanR[R1 * nQp + q] + + spanL[2 * nQp + q] * spanR[R2 * nQp + q]; + } + } + } + else if (m_dimL == 9 && m_dimR == 9) { // Tenseur . Tenseur -> Tenseur + for (CfemInt i = 0; i < 3; ++i) { + for (CfemInt j = 0; j < 3; ++j) { + CfemInt outIdx = i * 3 + j; + CfemInt L0 = i * 3 + 0, L1 = i * 3 + 1, L2 = i * 3 + 2; + CfemInt R0 = 0 * 3 + j, R1 = 1 * 3 + j, R2 = 2 * 3 + j; + for (CfemInt q = 0; q < nQp; ++q) { + out[outIdx * nQp + q] = spanL[L0 * nQp + q] * spanR[R0 * nQp + q] + + spanL[L1 * nQp + q] * spanR[R1 * nQp + q] + + spanL[L2 * nQp + q] * spanR[R2 * nQp + q]; + } + } + } + } + else if (m_dimL == 1) { // Scalaire . (Vecteur/Tenseur) + for (CfemInt i = 0; i < m_dimOut; ++i) { + for (CfemInt q = 0; q < nQp; ++q) { + out[i * nQp + q] = spanL[q] * spanR[i * nQp + q]; + } + } + } + else if (m_dimR == 1) { // (Vecteur/Tenseur) . Scalaire + for (CfemInt i = 0; i < m_dimOut; ++i) { + for (CfemInt q = 0; q < nQp; ++q) { + out[i * nQp + q] = spanL[i * nQp + q] * spanR[q]; + } + } + } + break; + } + case MathOp::Inner: { // Double contraction sigma : epsilon -> Scalaire + for (CfemInt q = 0; q < nQp; ++q) out[q] = 0.0; + // Boucle externe sur les composantes pour vectorisation parfaite + for (CfemInt i = 0; i < 9; ++i) { + for (CfemInt q = 0; q < nQp; ++q) { + out[q] += spanL[i * nQp + q] * spanR[i * nQp + q]; + } + } + break; + } + case MathOp::Cross: { // Produit vectoriel 3D + for (CfemInt q = 0; q < nQp; ++q) { + out[0 * nQp + q] = spanL[1 * nQp + q] * spanR[2 * nQp + q] - spanL[2 * nQp + q] * spanR[1 * nQp + q]; + out[1 * nQp + q] = spanL[2 * nQp + q] * spanR[0 * nQp + q] - spanL[0 * nQp + q] * spanR[2 * nQp + q]; + out[2 * nQp + q] = spanL[0 * nQp + q] * spanR[1 * nQp + q] - spanL[1 * nQp + q] * spanR[0 * nQp + q]; + } + break; + } + case MathOp::Outer: { // Produit dyadique (Vecteur X Vecteur -> Tenseur) + for (CfemInt i = 0; i < 3; ++i) { + for (CfemInt j = 0; j < 3; ++j) { + CfemInt outIdx = i * 3 + j; + for (CfemInt q = 0; q < nQp; ++q) { + out[outIdx * nQp + q] = spanL[i * nQp + q] * spanR[j * nQp + q]; + } + } + } + break; + } + case MathOp::Pow: { + for (CfemInt i = 0; i < m_dimOut; ++i) { + for (CfemInt q = 0; q < nQp; ++q) { + out[i * nQp + q] = std::pow(spanL[i * nQp + q], spanR[i * nQp + q]); + } + } + break; + } + case MathOp::Max: { + for (CfemInt i = 0; i < m_dimOut; ++i) { + for (CfemInt q = 0; q < nQp; ++q) { + out[i * nQp + q] = std::max(spanL[i * nQp + q], spanR[i * nQp + q]); + } + } + break; + } + case MathOp::Min: { + for (CfemInt i = 0; i < m_dimOut; ++i) { + for (CfemInt q = 0; q < nQp; ++q) { + out[i * nQp + q] = std::min(spanL[i * nQp + q], spanR[i * nQp + q]); + } + } + break; + } + case MathOp::Atan2: { + for (CfemInt i = 0; i < m_dimOut; ++i) { + for (CfemInt q = 0; q < nQp; ++q) { + out[i * nQp + q] = std::atan2(spanL[i * nQp + q], spanR[i * nQp + q]); + } + } + break; + } + default: + break; + } + + // Libération de la mémoire de l'Arena + ctx.releaseToBookmark(bookmark); + } + inline CfemInt getPrecedence(MathOp op) { switch (op) diff --git a/libs/expressions/include/CachedExpression.h b/libs/expressions/include/CachedExpression.h index 3f92e18..cf41f9e 100644 --- a/libs/expressions/include/CachedExpression.h +++ b/libs/expressions/include/CachedExpression.h @@ -59,6 +59,20 @@ namespace cfem::sym { ctx.storeInCache(this, out); } + inline void evaluateBatch(CfemInt cellId, CfemInt nQp, std::span outValues, EvalContext& ctx) const override + { + + CFEM_THROW("CachedExpression::evaluateBatch is explicitly not supported. " + "Remove cache() wrappers when running full-batch integration, " + "or implement an Arena-safe Batch-level caching strategy."); + } + + inline void evaluateGradientBatch(CfemInt cellId, CfemInt nQp, std::span outValues, EvalContext &ctx) const override + { + CFEM_THROW("CachedExpression::evaluateGradientBatch is explicitly not supported."); + } + + bool isConstant() const override { return m_inner->isConstant(); } bool isContinuous() const override { return m_inner->isContinuous(); diff --git a/libs/expressions/include/CfemExpression.h b/libs/expressions/include/CfemExpression.h index 42dd4d5..7e1474f 100644 --- a/libs/expressions/include/CfemExpression.h +++ b/libs/expressions/include/CfemExpression.h @@ -106,6 +106,8 @@ namespace cfem::sym virtual ExpressionType getType() const = 0; virtual void evaluate(CfemInt cellId, const Vector3D &xi, std::span out, EvalContext &ctx) const = 0; + virtual void evaluateBatch(CfemInt cellId, CfemInt nQp, std::span outValues, EvalContext& ctx) const = 0; + virtual void evaluateGradient(CfemInt cellId, const Vector3D &xi, std::span out, EvalContext &ctx) const { auto g = this->grad(); if (g && g.get() != this) { @@ -115,6 +117,15 @@ namespace cfem::sym CFEM_THROW("Gradient evaluation failed."); } + virtual void evaluateGradientBatch(CfemInt cellId, CfemInt nQp, std::span outValues, EvalContext &ctx) const { + auto g = this->grad(); + if (g && g.get() != this) { + g->evaluateBatch(cellId, nQp, outValues, ctx); + return; + } + CFEM_THROW("Gradient batch evaluation failed."); + } + virtual void prepare() const {}; virtual bool isPointwise() const { return false; } diff --git a/libs/expressions/include/ConstantExpression.h b/libs/expressions/include/ConstantExpression.h index b0ed75a..be3a0c4 100644 --- a/libs/expressions/include/ConstantExpression.h +++ b/libs/expressions/include/ConstantExpression.h @@ -78,6 +78,26 @@ namespace cfem::sym std::copy(m_value.begin(), m_value.end(), out.begin()); } + inline void evaluateBatch(CfemInt cellId, CfemInt nQp, std::span outValues, EvalContext& ctx) const override + { + CfemInt nComp = m_value.size(); + + CFEM_ASSERT(outValues.size() >= static_cast(nComp * nQp), + "Batch evaluation output buffer too small for ConstantExpression"); + + // ========================================================================= + // FORMAT SoA: outValues[composante * nQp + q] + // Pour une constante, la même valeur est répétée sur tous les points q + // ========================================================================= + for (CfemInt i = 0; i < nComp; ++i) { + const CfemReal val = m_value[i]; + + for (CfemInt q = 0; q < nQp; ++q) { + outValues[i * nQp + q] = val; + } + } + } + std::shared_ptr diff(CfemInt var_idx) const override { return std::make_shared(this->getNumComponents()); } diff --git a/libs/expressions/include/CoordinateExpression.h b/libs/expressions/include/CoordinateExpression.h index ee5f872..6877af9 100644 --- a/libs/expressions/include/CoordinateExpression.h +++ b/libs/expressions/include/CoordinateExpression.h @@ -64,6 +64,32 @@ namespace cfem::sym out[0] = globalX[m_dimIndex]; } + inline void evaluateBatch(CfemInt cellId, CfemInt nQp, std::span outValues, EvalContext& ctx) const override + { + CFEM_ASSERT(outValues.size() >= static_cast(nQp), + "CoordinateExpression: Batch output buffer too small."); + + if (m_dimIndex == 3) + { + const CfemReal t = ctx.currentTime; + for (CfemInt q = 0; q < nQp; ++q) { + outValues[q] = t; + } + return; + } + + // Récupération du tableau contigu des positions physiques (généré à l'Étape 3.1) + const auto* physPos_ptr = ctx.batchWS.physPos; + CFEM_ASSERT(physPos_ptr != nullptr && (*physPos_ptr).size() >= nQp, "CoordinateExpression requires physical positions in batchWS."); + + const auto& physPos = *physPos_ptr; + + + for (CfemInt q = 0; q < nQp; ++q) { + outValues[q] = physPos[q][m_dimIndex]; + } + } + CfemInt getNumComponents() const override { return 1;} CfemInt getIndex() const { return m_dimIndex; } diff --git a/libs/expressions/include/DivergenceExpression.h b/libs/expressions/include/DivergenceExpression.h index ca3edc8..fcbc43d 100644 --- a/libs/expressions/include/DivergenceExpression.h +++ b/libs/expressions/include/DivergenceExpression.h @@ -152,6 +152,50 @@ namespace cfem::sym } } + inline void evaluateBatch(CfemInt cellId, CfemInt nQp, std::span outValues, EvalContext& ctx) const override + { + CFEM_ASSERT(outValues.size() >= static_cast(m_dimOut * nQp), + "DivergenceExpression: Batch output buffer too small!"); + + // 1. Sauvegarde de la mémoire et allocation des buffers SoA via l'Arena (coût = 0) + auto bookmark = ctx.getBookmark(); + + std::span span_dx = ctx.getBuffer(m_dimIn * nQp); + std::span span_dy = ctx.getBuffer(m_dimIn * nQp); + std::span span_dz = ctx.getBuffer(m_dimIn * nQp); + + // 2. Évaluation des sous-arbres dérivés + m_dx->evaluateBatch(cellId, nQp, span_dx, ctx); + m_dy->evaluateBatch(cellId, nQp, span_dy, ctx); + m_dz->evaluateBatch(cellId, nQp, span_dz, ctx); + + // ========================================================================= + // 3. FORMAT SoA : Contraction (Divergence) + // L'accès séquentiel span[composante * nQp + q] garantit le SIMD + // ========================================================================= + if (m_dimIn == 3) { + // div(Vecteur) = Scalaire + // outValues a une taille de 1 * nQp + for (CfemInt q = 0; q < nQp; ++q) { + outValues[q] = span_dx[0 * nQp + q] + + span_dy[1 * nQp + q] + + span_dz[2 * nQp + q]; + } + } + else if (m_dimIn == 9) { + // div(Tenseur) = Vecteur + // outValues a une taille de 3 * nQp + for (CfemInt q = 0; q < nQp; ++q) { + outValues[0 * nQp + q] = span_dx[0 * nQp + q] + span_dy[1 * nQp + q] + span_dz[2 * nQp + q]; // Ligne 1 + outValues[1 * nQp + q] = span_dx[3 * nQp + q] + span_dy[4 * nQp + q] + span_dz[5 * nQp + q]; // Ligne 2 + outValues[2 * nQp + q] = span_dx[6 * nQp + q] + span_dy[7 * nQp + q] + span_dz[8 * nQp + q]; // Ligne 3 + } + } + + // 4. Libération instantanée de la mémoire temporaire + ctx.releaseToBookmark(bookmark); + } + std::shared_ptr diff(CfemInt var_idx) const override { // By Clairaut's theorem (symmetry of second derivatives): // d(div(u)) / dx_i == div( du / dx_i ) diff --git a/libs/expressions/include/EvalContext.h b/libs/expressions/include/EvalContext.h index 1ab44ca..058d45e 100644 --- a/libs/expressions/include/EvalContext.h +++ b/libs/expressions/include/EvalContext.h @@ -75,6 +75,7 @@ namespace cfem { // ==================================================================== CfemReal currentTime = 0.0; /**< Current simulation time (for transient problems). */ CfemInt currentCellId = -1; /**< Index of the cell currently being integrated. */ + CfemInt currentQpIndex = 0; // ==================================================================== // SINGLE-POINT WORKSPACE (Legacy / Probing) @@ -90,8 +91,8 @@ namespace cfem { // BATCH WORKSPACE (Vectorized Element Assembly) // ==================================================================== struct BatchWorkspace { - DynamicVector physPos; /**< Physical coordinates for all QPs. */ - DynamicVector xi; /**< Reference coordinates for all QPs. */ + DynamicVector* physPos; /**< Physical coordinates for all QPs. */ + DynamicVector* xi; /**< Reference coordinates for all QPs. */ ShapeInfoBatch* shape = nullptr; /**< Pointer to allocated batch shape memory. */ MappingInfoBatch* mapping = nullptr; /**< Pointer to allocated batch metric memory. */ } batchWS; @@ -167,7 +168,7 @@ namespace cfem { return m_spaceDataCache[index]; } - inline void setSpaceDataBatch(CfemInt index, const ShapeInfoBatch* shape, const MappingInfoBatch* mapping) + inline void setSpaceData(CfemInt index, const ShapeInfo* shape, const MappingInfo* mapping) { CFEM_ASSERT(index >= 0, "Negative space index"); #ifdef CFEM_USE_DYNAMIC_SPACE_CACHE @@ -176,11 +177,11 @@ namespace cfem { CFEM_ASSERT(index < MAX_CACHED_SPACES, "Space index exceeds MAX_CACHED_SPACES"); #endif auto& entry = m_spaceDataCache[index]; - entry.shapeBatch = shape; - entry.mappingBatch = mapping; + entry.shapeSingle = shape; + entry.mappingSingle = mapping; } - inline void setSpaceData(CfemInt index, const ShapeInfo* shape, const MappingInfo* mapping) + inline void setSpaceDataBatch(CfemInt index, const ShapeInfoBatch* shape, const MappingInfoBatch* mapping) { CFEM_ASSERT(index >= 0, "Negative space index"); #ifdef CFEM_USE_DYNAMIC_SPACE_CACHE @@ -189,8 +190,8 @@ namespace cfem { CFEM_ASSERT(index < MAX_CACHED_SPACES, "Space index exceeds MAX_CACHED_SPACES"); #endif auto& entry = m_spaceDataCache[index]; - entry.shapeSingle = shape; - entry.mappingSingle = mapping; + entry.shapeBatch = shape; + entry.mappingBatch = mapping; } inline const MappingInfoBatch* getMappingBatch(CfemInt index) const @@ -241,7 +242,7 @@ namespace cfem { * @return Pointer to ShapeInfo (Legacy/Probing). * @pre index is valid and single shape has been set. */ - inline const ShapeInfo* getShapeSingle(CfemInt index) const + inline const ShapeInfo* getShape(CfemInt index) const { #ifdef CFEM_USE_DYNAMIC_SPACE_CACHE CFEM_ASSERT(index >= 0 && index < static_cast(m_spaceDataCache.size()), "EvalContext::getShapeSingle index out of bounds"); diff --git a/libs/expressions/include/GeometricNormalExpression.h b/libs/expressions/include/GeometricNormalExpression.h index 8d71304..a5a8472 100644 --- a/libs/expressions/include/GeometricNormalExpression.h +++ b/libs/expressions/include/GeometricNormalExpression.h @@ -87,6 +87,28 @@ namespace cfem::sym out[2] = n.z; } + inline void evaluateBatch(CfemInt cellId, CfemInt nQp, std::span outValues, EvalContext& ctx) const override + { + CFEM_ASSERT(outValues.size() >= static_cast(3 * nQp), + "GeometricNormalExpression: Batch output buffer too small for 3D normal vectors."); + + const auto* geoBatch = ctx.geometricMappingBatch; + CFEM_ASSERT(geoBatch != nullptr, "Geometric mapping batch must be set in EvalContext to evaluate normal."); + + // ========================================================================= + // FORMAT SoA: [n_x0..n_xn, n_y0..n_yn, n_z0..n_zn] + // Extraction directe depuis le tableau pré-calculé geoBatch->normals + // ========================================================================= + for (CfemInt q = 0; q < nQp; ++q) + { + const Vector3D& n = geoBatch->normals[q]; + + outValues[0 * nQp + q] = n.x; + outValues[1 * nQp + q] = n.y; + outValues[2 * nQp + q] = n.z; + } + } + protected: /** * @brief Computes the gradient of the normal vector. diff --git a/libs/expressions/include/GradExpression.h b/libs/expressions/include/GradExpression.h index a395a97..abb5d02 100644 --- a/libs/expressions/include/GradExpression.h +++ b/libs/expressions/include/GradExpression.h @@ -83,7 +83,18 @@ namespace cfem::sym } CfemInt getNumComponents() const override { return m_expr->getNumComponents() * 3; } void prepare() const override { m_expr->prepare(); } - void evaluate(CfemInt cellId, const Vector3D &xi, std::span out, EvalContext &ctx) const override; + + + void evaluate(CfemInt cellId, const Vector3D &xi, std::span out, EvalContext &ctx) const override + { + m_expr->evaluateGradient(cellId, xi, out, ctx); + } + + void evaluateBatch(CfemInt cellId, CfemInt nQp, std::span outValues, EvalContext &ctx) const override + { + m_expr->evaluateGradientBatch(cellId, nQp, outValues, ctx); + } + ExpressionType getType() const override { return ExpressionType::Gradient; } bool dependsOnFEField() const override { return m_expr->dependsOnFEField(); } bool dependsOnShape() const override { return m_expr->dependsOnShape(); } diff --git a/libs/expressions/include/LambdaExpression.h b/libs/expressions/include/LambdaExpression.h index e4eb014..0384a21 100644 --- a/libs/expressions/include/LambdaExpression.h +++ b/libs/expressions/include/LambdaExpression.h @@ -79,6 +79,60 @@ namespace cfem::sym } } + // ==================================================================== + // --- Vectorized Batch (SoA) Evaluation + // ==================================================================== + void evaluateBatch(CfemInt cellId, CfemInt nQp, std::span outValues, EvalContext& ctx) const override + { + CFEM_ASSERT(outValues.size() >= static_cast(m_dimension * nQp), + "LambdaExpression: Batch buffer too small."); + + // Accès direct au tableau des positions physiques + const auto* physPos_ptr = ctx.batchWS.physPos; + CFEM_ASSERT(physPos_ptr != nullptr && (*physPos_ptr).size() >= nQp, "LambdaExpression requires physical positions in batchWS."); + + const auto& physPos = *physPos_ptr; + + + const CfemReal t = ctx.currentTime; + + // Boucle sur les points de quadrature + for (CfemInt q = 0; q < nQp; ++q) + { + const Vector3D& pos = physPos[q]; + + // 1. Appel du functor à la compilation + auto result = [&]() { + if constexpr (std::is_invocable_v) { + return m_f(pos, t); + } + else if constexpr (std::is_invocable_v) { + return m_f(pos); + } + else { + static_assert(sizeof(F) == 0, "Invalid Lambda signature."); + } + }(); + + // 2. Écriture au format SoA: out[composante * nQp + q] + using ResultType = decltype(result); + if constexpr (std::is_arithmetic_v) { + outValues[q] = static_cast(result); + } + else if constexpr (std::is_same_v) { + outValues[0 * nQp + q] = result.x; + outValues[1 * nQp + q] = result.y; + outValues[2 * nQp + q] = result.z; + } + else if constexpr (std::is_same_v) { + // Le compilateur va dérouler (unroll) cette petite boucle + for(int i = 0; i < 9; ++i) { + outValues[i * nQp + q] = result.data[i]; + } + } + } + } + // --- Static Factories --- static auto fromScalar(F&& f, std::shared_ptr g = nullptr, std::shared_ptr t = nullptr) { diff --git a/libs/expressions/include/MagnitudeSqExpression.h b/libs/expressions/include/MagnitudeSqExpression.h index 5d7329b..0688eb0 100644 --- a/libs/expressions/include/MagnitudeSqExpression.h +++ b/libs/expressions/include/MagnitudeSqExpression.h @@ -81,6 +81,7 @@ namespace cfem::sym void prepare() const override { m_expr->prepare(); } void evaluate(CfemInt cellId, const Vector3D &xi, std::span out, EvalContext &ctx) const override; + inline void evaluateBatch(CfemInt cellId, CfemInt nQp, std::span outValues, EvalContext& ctx) const override; std::shared_ptr getInner() const { return m_expr; } diff --git a/libs/expressions/include/MagnitudeSqExpression.tpp b/libs/expressions/include/MagnitudeSqExpression.tpp index 910d213..9ebb759 100644 --- a/libs/expressions/include/MagnitudeSqExpression.tpp +++ b/libs/expressions/include/MagnitudeSqExpression.tpp @@ -65,6 +65,41 @@ namespace cfem::sym ctx.releaseToBookmark(bookmark); } } + + inline void MagnitudeSqExpression::evaluateBatch(CfemInt cellId, CfemInt nQp, std::span outValues, EvalContext& ctx) const + { + // La magnitude au carré produit un scalaire. Le buffer de sortie doit faire au moins nQp. + CFEM_ASSERT(outValues.size() >= static_cast(nQp), "Batch evaluation output buffer too small for scalar result"); + + // Initialisation de la somme à zéro pour tous les points de quadrature + for (CfemInt q = 0; q < nQp; ++q) { + outValues[q] = 0.0; + } + + // Sauvegarde de l'Arena et allocation du buffer SoA pour l'expression interne + auto bookmark = ctx.getBookmark(); + std::span in_buf = ctx.getBuffer(m_innerDim * nQp); + + m_expr->evaluateBatch(cellId, nQp, in_buf, ctx); + + // ========================================================================= + // FORMAT SoA: in_buf[composante * nQp + q] + // MagnitudeSq = Somme des carrés des composantes. + // La boucle externe sur les composantes (i) assure la vectorisation SIMD + // de la boucle interne (q). + // ========================================================================= + for (CfemInt i = 0; i < m_innerDim; ++i) { + CfemInt i_x_nQp = i * nQp; + for (CfemInt q = 0; q < nQp; ++q) { + CfemReal val = in_buf[i_x_nQp + q]; + outValues[q] += val * val; + } + } + + // Libération de la mémoire + ctx.releaseToBookmark(bookmark); + } + /** * @brief Computes 2 * u * grad(u). * Since u is dim N and grad(u) is dim N*3, the dot product results in dim 3. diff --git a/libs/expressions/include/ScaledExpression.h b/libs/expressions/include/ScaledExpression.h index 5df4f05..64e8dc3 100644 --- a/libs/expressions/include/ScaledExpression.h +++ b/libs/expressions/include/ScaledExpression.h @@ -43,6 +43,25 @@ namespace cfem::sym { } } + inline void evaluateBatch(CfemInt cellId, CfemInt nQp, std::span outValues, EvalContext& ctx) const override + { + + CfemInt dim = nQp * m_base->getNumComponents(); + // Un tenseur 3x3 évalué sur nQp points nécessite 9 * nQp espaces mémoire + CFEM_ASSERT(outValues.size() >= dim, "Batch evaluation output buffer too small for 3D Tensor"); + + // ========================================================================= + // FORMAT SoA (Structure of Arrays) - Optimisé pour le cache et le SIMD + // Le buffer de sortie sera organisé ainsi : + // [xx_0..xx_n, xy_0..xy_n, xz_0..xz_n, yx_0..yx_n, yy_0..yy_n, yz_0..yz_n, zx_0..zx_n, zy_0..zy_n, zz_0..zz_n] + // ========================================================================= + + m_base->evaluateBatch(cellId, nQp, outValues, ctx); + for (CfemInt i = 0; i < dim; ++i) { + outValues[i] *= m_coeff; + } + } + CfemInt getNumComponents() const override { return m_base->getNumComponents(); } bool isContinuous() const override { diff --git a/libs/expressions/include/TensorExpression.h b/libs/expressions/include/TensorExpression.h index 78dd52f..b7fab9d 100644 --- a/libs/expressions/include/TensorExpression.h +++ b/libs/expressions/include/TensorExpression.h @@ -109,6 +109,22 @@ namespace cfem::sym } } + inline void evaluateBatch(CfemInt cellId, CfemInt nQp, std::span outValues, EvalContext& ctx) const override + { + // Un tenseur 3x3 évalué sur nQp points nécessite 9 * nQp espaces mémoire + CFEM_ASSERT(outValues.size() >= 9 * nQp, "Batch evaluation output buffer too small for 3D Tensor"); + + // ========================================================================= + // FORMAT SoA (Structure of Arrays) - Optimisé pour le cache et le SIMD + // Le buffer de sortie sera organisé ainsi : + // [xx_0..xx_n, xy_0..xy_n, xz_0..xz_n, yx_0..yx_n, yy_0..yy_n, yz_0..yz_n, zx_0..zx_n, zy_0..zy_n, zz_0..zz_n] + // ========================================================================= + + for (CfemInt i=0; i<9; ++i){ + m_components[i]->evaluateBatch(cellId, nQp, outValues.subspan(i * nQp, nQp), ctx); + } + } + inline std::shared_ptr getComponent(int i) const { CFEM_ASSERT(i < 9 && i >= 0, "Index out of bounds."); diff --git a/libs/expressions/include/UnaryExpression.h b/libs/expressions/include/UnaryExpression.h index 19c83ab..3b89590 100644 --- a/libs/expressions/include/UnaryExpression.h +++ b/libs/expressions/include/UnaryExpression.h @@ -61,8 +61,8 @@ namespace cfem::sym if (m_expr) m_expr->collectRequiredSpaces(spaces); } - void evaluate(CfemInt cellIdx, const Vector3D& point, - std::span out, EvalContext& ctx) const override; + void evaluate(CfemInt cellIdx, const Vector3D& point, std::span out, EvalContext& ctx) const override; + void evaluateBatch(CfemInt cellId, CfemInt nQp, std::span out, EvalContext &ctx) const override; std::shared_ptr diff(CfemInt var_idx) const override; std::shared_ptr div() const override; diff --git a/libs/expressions/include/UnaryExpression.tpp b/libs/expressions/include/UnaryExpression.tpp index e6a5f73..8b96216 100644 --- a/libs/expressions/include/UnaryExpression.tpp +++ b/libs/expressions/include/UnaryExpression.tpp @@ -151,6 +151,123 @@ namespace cfem::sym } } + inline void UnaryExpression::evaluateBatch(CfemInt cellId, CfemInt nQp, std::span out, EvalContext& ctx) const + { + // Allocation du buffer temporaire SoA pour le nœud enfant + auto bookmark = ctx.getBookmark(); + std::span in_buf = ctx.getBuffer(m_dimIn * nQp); + + // Évaluation en lot de l'expression enfant + m_expr->evaluateBatch(cellId, nQp, in_buf, ctx); + + // ========================================================================= + // FORMAT SoA: buffer[composante * nQp + q] + // Les boucles `q` internes garantissent la vectorisation SIMD maximale. + // ========================================================================= + + switch (m_op) { + // --- Opérations Matricielles (dimIn == 9) --- + case MathOp::Transpose: { + for (CfemInt q = 0; q < nQp; ++q) { + out[0 * nQp + q] = in_buf[0 * nQp + q]; out[1 * nQp + q] = in_buf[3 * nQp + q]; out[2 * nQp + q] = in_buf[6 * nQp + q]; + out[3 * nQp + q] = in_buf[1 * nQp + q]; out[4 * nQp + q] = in_buf[4 * nQp + q]; out[5 * nQp + q] = in_buf[7 * nQp + q]; + out[6 * nQp + q] = in_buf[2 * nQp + q]; out[7 * nQp + q] = in_buf[5 * nQp + q]; out[8 * nQp + q] = in_buf[8 * nQp + q]; + } + break; + } + case MathOp::Trace: { + for (CfemInt q = 0; q < nQp; ++q) { + out[q] = in_buf[0 * nQp + q] + in_buf[4 * nQp + q] + in_buf[8 * nQp + q]; + } + break; + } + case MathOp::Det: { + for (CfemInt q = 0; q < nQp; ++q) { + out[q] = in_buf[0 * nQp + q] * (in_buf[4 * nQp + q] * in_buf[8 * nQp + q] - in_buf[5 * nQp + q] * in_buf[7 * nQp + q]) - + in_buf[1 * nQp + q] * (in_buf[3 * nQp + q] * in_buf[8 * nQp + q] - in_buf[5 * nQp + q] * in_buf[6 * nQp + q]) + + in_buf[2 * nQp + q] * (in_buf[3 * nQp + q] * in_buf[7 * nQp + q] - in_buf[4 * nQp + q] * in_buf[6 * nQp + q]); + } + break; + } + case MathOp::Inv: { + for (CfemInt q = 0; q < nQp; ++q) { + CfemReal det = in_buf[0 * nQp + q] * (in_buf[4 * nQp + q] * in_buf[8 * nQp + q] - in_buf[5 * nQp + q] * in_buf[7 * nQp + q]) - + in_buf[1 * nQp + q] * (in_buf[3 * nQp + q] * in_buf[8 * nQp + q] - in_buf[5 * nQp + q] * in_buf[6 * nQp + q]) + + in_buf[2 * nQp + q] * (in_buf[3 * nQp + q] * in_buf[7 * nQp + q] - in_buf[4 * nQp + q] * in_buf[6 * nQp + q]); + + if (NumericPolicy::isZero(std::abs(det))) { + for(int i = 0; i < 9; ++i) out[i * nQp + q] = 0.0; + continue; // On passe au point de quadrature suivant + } + + CfemReal invDet = 1.0 / det; + + out[0 * nQp + q] = (in_buf[4 * nQp + q] * in_buf[8 * nQp + q] - in_buf[5 * nQp + q] * in_buf[7 * nQp + q]) * invDet; + out[1 * nQp + q] = (in_buf[2 * nQp + q] * in_buf[7 * nQp + q] - in_buf[1 * nQp + q] * in_buf[8 * nQp + q]) * invDet; + out[2 * nQp + q] = (in_buf[1 * nQp + q] * in_buf[5 * nQp + q] - in_buf[2 * nQp + q] * in_buf[4 * nQp + q]) * invDet; + + out[3 * nQp + q] = (in_buf[5 * nQp + q] * in_buf[6 * nQp + q] - in_buf[3 * nQp + q] * in_buf[8 * nQp + q]) * invDet; + out[4 * nQp + q] = (in_buf[0 * nQp + q] * in_buf[8 * nQp + q] - in_buf[2 * nQp + q] * in_buf[6 * nQp + q]) * invDet; + out[5 * nQp + q] = (in_buf[2 * nQp + q] * in_buf[3 * nQp + q] - in_buf[0 * nQp + q] * in_buf[5 * nQp + q]) * invDet; + + out[6 * nQp + q] = (in_buf[3 * nQp + q] * in_buf[7 * nQp + q] - in_buf[4 * nQp + q] * in_buf[6 * nQp + q]) * invDet; + out[7 * nQp + q] = (in_buf[1 * nQp + q] * in_buf[6 * nQp + q] - in_buf[0 * nQp + q] * in_buf[7 * nQp + q]) * invDet; + out[8 * nQp + q] = (in_buf[0 * nQp + q] * in_buf[4 * nQp + q] - in_buf[1 * nQp + q] * in_buf[3 * nQp + q]) * invDet; + } + break; + } + + // --- Mathématiques de base (Élément par élément) --- + case MathOp::Negate: for(int i=0; i 0) ? std::sqrt(in_buf[i*nQp+q]) : 0.0; } break; + case MathOp::Cbrt: for(int i=0; i 0) ? 1.0 : ((in_buf[i*nQp+q] < 0) ? -1.0 : 0.0); } break; + case MathOp::Heaviside: for(int i=0; i= 0) ? 1.0 : 0.0; } break; + + // --- Trigonométrie --- + case MathOp::Sin: for(int i=0; i 0) ? std::log(in_buf[i*nQp+q]) : -1e20; } break; + case MathOp::Gamma: for(int i=0; i outValues, EvalContext& ctx) const override + { + CFEM_THROW("Batch evaluation not allowed for VariableExpression"); + } + // std::shared_ptr diff(CfemInt var_idx) const override { // if (var_idx == 3) { // return timeDerivative(); diff --git a/libs/expressions/include/VectorExpression.h b/libs/expressions/include/VectorExpression.h index b4d685e..3b5f6d1 100644 --- a/libs/expressions/include/VectorExpression.h +++ b/libs/expressions/include/VectorExpression.h @@ -81,13 +81,27 @@ namespace cfem::sym inline void evaluate(CfemInt cellId, const Vector3D &xi, std::span out, EvalContext &ctx) const override { - CFEM_ASSERT(out.size() >= 3, "Expected a buffer of size >=9."); + CFEM_ASSERT(out.size() >= 3, "VectorExpression: Expected a buffer of size >= 3."); for (CfemInt i = 0; i < 3; ++i){ m_components[i]->evaluate(cellId, xi, out.subspan(i, 1), ctx); } } + inline void evaluateBatch(CfemInt cellId, CfemInt nQp, std::span outValues, EvalContext& ctx) const override + { + CFEM_ASSERT(outValues.size() >= 3 * nQp, "Batch evaluation output buffer too small for 3D Vector"); + + // ========================================================================= + // FORMAT SoA (Structure of Arrays) - Optimisé pour le cache et le SIMD + // [x_0, x_1, ..., x_n, y_0, y_1, ..., y_n, z_0, z_1, ..., z_n] + // ========================================================================= + + m_components[0]->evaluateBatch(cellId, nQp, outValues.subspan(0 * nQp, nQp), ctx); + m_components[1]->evaluateBatch(cellId, nQp, outValues.subspan(1 * nQp, nQp), ctx); + m_components[2]->evaluateBatch(cellId, nQp, outValues.subspan(2 * nQp, nQp), ctx); + } + inline std::shared_ptr getComponent(int i) const { CFEM_ASSERT(i < 3 && i >= 0, "Index out of bounds."); diff --git a/libs/expressions/include/ZeroExpression.h b/libs/expressions/include/ZeroExpression.h index ab49a8f..56fe191 100644 --- a/libs/expressions/include/ZeroExpression.h +++ b/libs/expressions/include/ZeroExpression.h @@ -57,6 +57,14 @@ namespace cfem::sym std::ranges::fill(out, 0.0); } + inline void evaluateBatch(CfemInt cellId, CfemInt nQp, std::span outValues, EvalContext& ctx) const override + { + CFEM_ASSERT(outValues.size() >= m_dim * nQp, "Batch evaluation output buffer too small for 3D Vector"); + + std::ranges::fill(outValues, 0.0); + } + + std::shared_ptr diff(CfemInt var_idx) const override { return std::make_shared(this->getNumComponents()); } diff --git a/libs/expressions/include/symengine/ScalarSymbolicExpression.h b/libs/expressions/include/symengine/ScalarSymbolicExpression.h index 42ee92e..1caa92d 100644 --- a/libs/expressions/include/symengine/ScalarSymbolicExpression.h +++ b/libs/expressions/include/symengine/ScalarSymbolicExpression.h @@ -67,6 +67,23 @@ namespace cfem::sym out[0] = static_cast(m_evaluator->evaluate(input)); } + + void evaluateBatch(CfemInt cellId, CfemInt nQp, std::span outValues, EvalContext& ctx) const override + { + const CfemReal t = ctx.currentTime; + const auto* physPos_ptr = ctx.batchWS.physPos; + + CFEM_ASSERT(physPos_ptr != nullptr && (*physPos_ptr).size() >= nQp, "Batch physical positions not properly sized in EvalContext"); + + const auto& physPos = *physPos_ptr; + + for (CfemInt q = 0; q < nQp; ++q) { + const CfemReal input[4] = { physPos[q].x, physPos[q].y, physPos[q].z, t }; + + outValues[q] = m_evaluator->evaluate(input); + } + } + std::shared_ptr diff( const SymEngine::RCP &var) const { return std::make_shared(m_expression.diff(var), m_vars); } diff --git a/libs/expressions/include/symengine/TensorSymbolicExpression.h b/libs/expressions/include/symengine/TensorSymbolicExpression.h index 91c1299..edad3a8 100644 --- a/libs/expressions/include/symengine/TensorSymbolicExpression.h +++ b/libs/expressions/include/symengine/TensorSymbolicExpression.h @@ -56,6 +56,22 @@ namespace cfem::sym for (CfemInt i = 0; i < 9; ++i) m_components[i]->evaluate(cellId, xi, out.subspan(i, 1), ctx); } + inline void evaluateBatch(CfemInt cellId, CfemInt nQp, std::span outValues, EvalContext& ctx) const override + { + // Un tenseur 3x3 évalué sur nQp points nécessite 9 * nQp espaces mémoire + CFEM_ASSERT(outValues.size() >= 9 * nQp, "Batch evaluation output buffer too small for 3D Tensor"); + + // ========================================================================= + // FORMAT SoA (Structure of Arrays) - Optimisé pour le cache et le SIMD + // Le buffer de sortie sera organisé ainsi : + // [xx_0..xx_n, xy_0..xy_n, xz_0..xz_n, yx_0..yx_n, yy_0..yy_n, yz_0..yz_n, zx_0..zx_n, zy_0..zy_n, zz_0..zz_n] + // ========================================================================= + + for (CfemInt i=0; i<9; ++i){ + m_components[i]->evaluateBatch(cellId, nQp, outValues.subspan(i * nQp, nQp), ctx); + } + } + std::shared_ptr diff(CfemInt var_idx) const override; // TODO Implement grad() @@ -80,7 +96,7 @@ namespace cfem::sym private: std::shared_ptr m_components[9]; std::shared_ptr createGradient() const override { - CFEM_ERROR << "Gradient is not yet supported for tensor expressions. It would required a 3rd order tensor which is not implemented."; + CFEM_THROW("Gradient is not yet supported for tensor expressions. It would required a 3rd order tensor which is not implemented."); return nullptr; } std::shared_ptr createTimeDerivative() const override; diff --git a/libs/expressions/include/symengine/VectorSymbolicExpression.h b/libs/expressions/include/symengine/VectorSymbolicExpression.h index b095daa..d0dbe2b 100644 --- a/libs/expressions/include/symengine/VectorSymbolicExpression.h +++ b/libs/expressions/include/symengine/VectorSymbolicExpression.h @@ -55,6 +55,24 @@ namespace cfem::sym m_components[2]->evaluate(cellId, xi, out.subspan(2, 1), ctx); } + inline void evaluateBatch(CfemInt cellId, CfemInt nQp, std::span outValues, EvalContext& ctx) const override + { + // Un vecteur 3D évalué sur nQp points nécessite 3 * nQp espaces mémoire + CFEM_ASSERT(outValues.size() >= 3 * nQp, "Batch evaluation output buffer too small for 3D vector"); + + // ========================================================================= + // FORMAT SoA (Structure of Arrays) - Optimisé pour le cache et le SIMD + // Le buffer de sortie sera organisé ainsi : + // [x_0, x_1, ..., x_n, y_0, y_1, ..., y_n, z_0, z_1, ..., z_n] + // ========================================================================= + + m_components[0]->evaluateBatch(cellId, nQp, outValues.subspan(0, nQp), ctx); + m_components[1]->evaluateBatch(cellId, nQp, outValues.subspan(nQp, nQp), ctx); + m_components[2]->evaluateBatch(cellId, nQp, outValues.subspan(2 * nQp, nQp), ctx); + } + + + inline std::shared_ptr getComponent(CfemInt i) const { CFEM_ASSERT(i < 3 && i >= 0, "Index out of bounds."); diff --git a/libs/expressions/src/GradExpression.cpp b/libs/expressions/src/GradExpression.cpp index f50ce6a..ecd3c6b 100644 --- a/libs/expressions/src/GradExpression.cpp +++ b/libs/expressions/src/GradExpression.cpp @@ -15,58 +15,3 @@ // --- any express or implied warranty. In no event shall they be // --- held liable for any damages arising from the use of this software. //************************************************************************ - -#include "GradExpression.h" -#include "BinaryExpression.h" -#include "BinaryExpression.h" -#include "MagnitudeSqExpression.h" - -namespace cfem::sym -{ - - void GradExpression::evaluate(CfemInt cellId, const Vector3D &xi, std::span out, EvalContext &ctx) const - { - m_expr->evaluateGradient(cellId, xi, out, ctx); - // const auto type = m_expr->getType(); - - // // // 1. Chemin Analytique (Symbolique) - // // // On ne le fait que si ce n'est pas un champ/trial/test pour éviter les boucles infinies - // if (type != ExpressionType::FEField && type != ExpressionType::Trial && type != ExpressionType::Test) - // { - // if (auto analytical = m_expr->grad()) { - // if (analytical->getType() != ExpressionType::Gradient) { - // analytical->evaluate(cellId, xi, out, ctx); - // return; - // } - // } - // } - - // if (type == ExpressionType::FEField) { - // static_cast(m_expr.get())->evaluateGradient(cellId, xi, out, ctx); - // return; - // } - - // 5. Cas Trial / Test (Fonctions de forme) - // if (type == ExpressionType::Trial || type == ExpressionType::Test) { - // const bool isTrial = (type == ExpressionType::Trial); - // const auto* shape = isTrial ? ctx.trialShape : ctx.testShape; - // const auto* map = isTrial ? ctx.trialMapping : ctx.testMapping; - // const CfemInt dof = isTrial ? ctx.trialDof : ctx.testDof; - - // const CfemInt numNodes = shape->values.size(); - // const CfemInt comp = dof / numNodes; - // const CfemInt node = dof % numNodes; - - // const auto& g = map->physicalShapeGradients[node]; - - // std::fill(out.begin(), out.end(), 0.0); - // out[comp*3 + 0] = g.x; - // out[comp*3 + 1] = g.y; - // out[comp*3 + 2] = g.z; - // return; - // } - - // CFEM_ERROR << "GradExpression: Failed to differentiate expression tree."; - } - -} \ No newline at end of file diff --git a/libs/fem/fefield/include/Argument.h b/libs/fem/fefield/include/Argument.h index 20c0bf3..153fcf5 100644 --- a/libs/fem/fefield/include/Argument.h +++ b/libs/fem/fefield/include/Argument.h @@ -167,10 +167,35 @@ class Argument : public CfemExpression { */ virtual void evaluate(CfemInt cellId, const Vector3D& xi, std::span out, EvalContext& ctx) const override { - CFEM_ERROR << "Invalid call: Argument '" << m_name + CFEM_THROW("Invalid call: Argument '" << m_name << "' cannot be evaluated as a field. " << "Arguments are placeholders for basis functions and can only be " - << "evaluated via evaluateBasis() during assembly."; + << "evaluated via evaluateBasis() during assembly."); + } + + void evaluateBatch(CfemInt cellId, CfemInt nQp, std::span outValues, EvalContext& ctx) const override + { + CFEM_THROW("Invalid call: Argument '" << m_name + << "' cannot be evaluated as a field. " + << "Arguments are placeholders for basis functions and can only be " + << "evaluated via evaluateBasis() during assembly."); + } + + void evaluateGradient(CfemInt cellId, const Vector3D& xi, std::span out, EvalContext& ctx) const override + { + CFEM_THROW("Invalid call: Argument '" << m_name + << "' cannot be evaluated as a field. " + << "Arguments are placeholders for basis functions and can only be " + << "evaluated via evaluateBasis() OR evaluateBasisGradients() during assembly."); + } + + + void evaluateGradientBatch(CfemInt cellId, CfemInt nQp, std::span outValues, EvalContext& ctx) const override + { + CFEM_THROW("Invalid call: Argument '" << m_name + << "' cannot be evaluated as a field. " + << "Arguments are placeholders for basis functions and can only be " + << "evaluated via evaluateBasis() OR evaluateBasisGradients() during assembly."); } /** diff --git a/libs/fem/fefield/include/FEField.h b/libs/fem/fefield/include/FEField.h index 6fa03fc..3dcb49c 100644 --- a/libs/fem/fefield/include/FEField.h +++ b/libs/fem/fefield/include/FEField.h @@ -57,6 +57,17 @@ namespace cfem return std::make_shared(shared_from_this()); } + void evaluateWithShape(CfemInt cellId, const ShapeInfo& shape, std::span out) const; + void evaluateWithoutShape(CfemInt cellId, const Vector3D& xi, std::span out, EvalContext &ctx) const; + void evaluateBatchWithShape(CfemInt cellId, CfemInt nQp, const ShapeInfoBatch& shapeBatch, std::span outValues, EvalContext& ctx) const; + void evaluateBatchWithoutShape(CfemInt cellId, CfemInt nQp, std::span outValues, EvalContext& ctx) const; + + + void evaluateGradientWithCachedMapping(CfemInt cellId, const MappingInfo& mapping, std::span out) const; + void evaluateGradientWithoutCachedMapping(CfemInt cellId, const Vector3D &xi, std::span out, EvalContext &ctx) const; + void evaluateGradientBatchWithCachedMapping(CfemInt cellId, CfemInt nQp, const MappingInfoBatch& mappingBatch, std::span outValues, EvalContext& ctx) const; + void evaluateGradientBatchWithoutCachedMapping(CfemInt cellId, CfemInt nQp, std::span outValues, EvalContext& ctx) const; + public: FEField(ConstructorToken, std::shared_ptr space, const std::string &name); virtual ~FEField() = default; @@ -103,39 +114,13 @@ namespace cfem SkewSymTensor3D get3DSkewSymTensorFieldAtNode(CfemInt meshNodeId) const; void extractCellDofs(CfemInt cellId, DynamicMatrix& localDofs, EvalContext& ctx) const; - void evaluateWithoutShape(CfemInt cellId, const Vector3D& xi, std::span out, EvalContext &ctx) const; - void evaluateWithShape(CfemInt cellId, const ShapeInfo& shape, std::span out) const; void evaluateGradient(CfemInt cellId, const Vector3D &xi, std::span out, EvalContext &ctx) const override; - void evaluateGradientWithoutCachedMapping(CfemInt cellId, const Vector3D &xi, std::span out, EvalContext &ctx) const; - void evaluateGradientWithCachedMapping(CfemInt cellId, const MappingInfo& mapping, std::span out) const; // ==================================================================== // --- Vectorized Batch (SoA) Evaluation Extensions // ==================================================================== - - /** - * @brief Evaluates the finite element field values across a whole batch of quadrature points. - * @param cellId Local index of the computational cell. - * @param shapeBatch Contiguous batch structure holding precomputed shape functions [nQp x nNodes]. - * @param outValuesBatch Destination matrix to store the physical field results [nQp x m_dim]. - * @param ctx Unified high-performance evaluation context. - */ - void evaluateBatch(CfemInt cellId, - const ShapeInfoBatch& shapeBatch, - DynamicMatrix& outValuesBatch, - EvalContext& ctx) const; - - /** - * @brief Evaluates the physical spatial gradients (du/dx, du/dy, du/dz) across a batch of quadrature points. - * @param cellId Local index of the computational cell. - * @param mappingBatch Contiguous metric batch structure holding physical derivatives (dN/dx, dN/dy, dN/dz). - * @param outGradientsBatch Destination matrix to store the spatial physical gradients [nQp x (m_dim * spaceDim)]. - * @param ctx Unified high-performance evaluation context. - */ - void evaluateGradientBatch(CfemInt cellId, - const MappingInfoBatch& mappingBatch, - DynamicMatrix& outGradientsBatch, - EvalContext& ctx) const; + void evaluateBatch(CfemInt cellId, CfemInt nQp, std::span outValues, EvalContext& ctx) const override; + void evaluateGradientBatch(CfemInt cellId, CfemInt nQp, std::span outValues, EvalContext& ctx) const override; void interpolate(const sym::CfemExpression& recipe); void interpolate(std::shared_ptr recipe) { diff --git a/libs/fem/fefield/src/FEField.cpp b/libs/fem/fefield/src/FEField.cpp index 6750abd..6ddecab 100644 --- a/libs/fem/fefield/src/FEField.cpp +++ b/libs/fem/fefield/src/FEField.cpp @@ -253,40 +253,25 @@ namespace cfem } } + /** - * @brief Evaluates finite element field values across a whole batch of quadrature points using dense matrix contraction. - * @param cellId Local index of the computational cell. - * @param shapeBatch Contiguous batch structure holding precomputed shape functions [nQp x nNodes]. - * @param outValuesBatch Destination matrix to store the physical field results [nQp x m_dim]. - * @param ctx Unified high-performance evaluation context. + * @brief Implementation of the CfemExpression evaluation interface. */ - void FEField::evaluateBatch(CfemInt cellId, - const ShapeInfoBatch& shapeBatch, - DynamicMatrix& outValuesBatch, - EvalContext& ctx) const + void FEField::evaluate(CfemInt cellId, const Vector3D &xi, std::span out, EvalContext &ctx) const { - // Gather all local cell coefficients into cache-friendly dense layout - this->extractCellDofs(cellId, ctx.scratchCellDofs, ctx); - - const CfemInt nQp = shapeBatch.numQuadraturePoints; - const CfemInt nNodes = shapeBatch.numNodes; - const CfemInt fDim = this->getNumComponents(); - - outValuesBatch.resize(nQp, fDim); - outValuesBatch.setZero(); - - // Vectorized dense matrix contraction: Out[q][d] = Sum_i ( Shape[q][i] * CellDofs[i][d] ) - for (CfemInt q = 0; q < nQp; ++q) { - const auto* values = shapeBatch.values(q); - for (CfemInt i = 0; i < nNodes; ++i) { - const CfemReal Ni = values[i]; - for (CfemInt d = 0; d < fDim; ++d) { - outValuesBatch[q][d] += Ni * ctx.scratchCellDofs[i][d]; - } + if (m_boundIndex >= 0) { + const auto& data = ctx.getSpaceData(m_boundIndex); + + if (data.shapeSingle != nullptr) { + this->evaluateWithShape(cellId, *(data.shapeSingle), out); + return; } } + + this->evaluateWithoutShape(cellId, xi, out, ctx); } + /** * @brief Evaluates the standard field value at a single local coordinate xi without pre-computed shapes. * @param cellId Local index of the computational cell. @@ -446,81 +431,169 @@ namespace cfem } } - /** - * @brief Interpolates physical spatial derivatives of the field for the entire batch of integration points. - * @param cellId Local index of the computational cell. - * @param mappingBatch Contiguous metric batch structure holding physical derivatives (dN/dx, dN/dy, dN/dz). - * @param outGradientsBatch Destination matrix to store the spatial physical gradients [nQp x (m_dim * spatialDimension)]. - * @param ctx Unified high-performance evaluation context. - */ - void FEField::evaluateGradientBatch(CfemInt cellId, - const MappingInfoBatch& mappingBatch, - DynamicMatrix& outGradientsBatch, - EvalContext& ctx) const + + // ======================================================================== + // BATCH EVALUATION (VALEURS) + // ======================================================================== + + void FEField::evaluateBatch(CfemInt cellId, CfemInt nQp, std::span outValues, EvalContext& ctx) const { - // Gather all local cell coefficients into the memory arena scratchpad matrix - this->extractCellDofs(cellId, ctx.scratchCellDofs, ctx); + if (m_boundIndex >= 0) { + const auto* shapeBatch = ctx.getShapeBatch(m_boundIndex); + + if (shapeBatch != nullptr) { + this->evaluateBatchWithShape(cellId, nQp, *shapeBatch, outValues, ctx); + return; + } + } - const CfemInt nQp = mappingBatch.numQuadraturePoints; - const CfemInt nNodes = mappingBatch.numNodes; - const CfemInt fDim = this->getNumComponents(); - const CfemInt spatialDimension = m_space->getMesh()->getSpatialDimension(); + this->evaluateBatchWithoutShape(cellId, nQp, outValues, ctx); + } - // Allocate output footprint: Rows = Quadrature Points, Columns = Combined Gradient Matrix Components - outGradientsBatch.resize(nQp, fDim * spatialDimension); - outGradientsBatch.setZero(); + void FEField::evaluateBatchWithShape(CfemInt cellId, CfemInt nQp, const ShapeInfoBatch& shapeBatch, std::span outValues, EvalContext& ctx) const + { + const CfemInt nComp = this->getNumComponents(); + const CfemInt nNodes = shapeBatch.numNodes; - // Optimized Multi-Component Contraction Loop - for (CfemInt q = 0; q < nQp; ++q) - { - // Loop over field dimensions (components) first to establish continuous row offsets - const auto* dN_dx = mappingBatch.dN_dx(q); - const auto* dN_dy = mappingBatch.dN_dy(q); - const auto* dN_dz = mappingBatch.dN_dz(q); - for (CfemInt d = 0; d < fDim; ++d) - { - const CfemInt rowOffset = d * spatialDimension; - CfemReal grad_x = 0.0; - CfemReal grad_y = 0.0; - CfemReal grad_z = 0.0; + // On s'assure que le buffer de sortie a la bonne taille (SoA) + CFEM_ASSERT(outValues.size() >= nComp * nQp, "FEField::evaluateBatchWithShape - Output buffer too small"); + std::fill_n(outValues.data(), nComp * nQp, 0.0); - // Deepest loop operates over element nodes: perfectly contiguous column memory read - for (CfemInt i = 0; i < nNodes; ++i) - { - const CfemReal u_nodal = ctx.scratchCellDofs[i][d]; - - grad_x += dN_dx[i] * u_nodal; - grad_y += dN_dy[i] * u_nodal; - if (spatialDimension == 3) { - grad_z += dN_dz[i] * u_nodal; - } - } + auto bookmark = ctx.getBookmark(); + this->extractCellDofs(cellId, ctx.scratchCellDofs, ctx); + + for (CfemInt c = 0; c < nComp; ++c) { + for (CfemInt q = 0; q < nQp; ++q) { + const CfemReal* N = shapeBatch.values(q); + + CfemReal sum = 0.0; - // Continuous sequential store inside the active row segment - outGradientsBatch[q][rowOffset + 0] = grad_x; - outGradientsBatch[q][rowOffset + 1] = grad_y; - if (spatialDimension == 3) { - outGradientsBatch[q][rowOffset + 2] = grad_z; + for (CfemInt i = 0; i < nNodes; ++i) { + sum += N[i] * ctx.scratchCellDofs[i][c]; } + outValues[c * nQp + q] = sum; } } + + ctx.releaseToBookmark(bookmark); } - /** - * @brief Implementation of the CfemExpression evaluation interface. - */ - void FEField::evaluate(CfemInt cellId, const Vector3D &xi, std::span out, EvalContext &ctx) const + void FEField::evaluateBatchWithoutShape(CfemInt cellId, CfemInt nQp, std::span outValues, EvalContext& ctx) const + { + // Récupération sécurisée des coordonnées xiBatch depuis le contexte (doit être configuré par l'intégrateur) + const auto* xiBatch = ctx.batchWS.xi; + + CFEM_ASSERT(xiBatch != nullptr && xiBatch->size() >= static_cast(nQp), "FEField::evaluateBatchWithoutShape requires xiBatch to be populated in EvalContext."); + + const Cell &cell = m_space->getMesh()->getCell(cellId); + const auto &refEl = ReferenceElementFactory::getReferenceElement(cell.type, m_space->getOrderInt()); + + // Configuration temporaire du ShapeInfoBatch dans le contexte + ctx.batchWS.shape->setup(nQp, refEl.getNumNodes(), false); + ctx.batchWS.shape->validFlags |= ShapeEvaluationFlags::Value; + + refEl.evaluateShapesValuesBatch(*xiBatch, *(ctx.batchWS.shape)); + + this->evaluateBatchWithShape(cellId, nQp, *(ctx.batchWS.shape), outValues, ctx); + } + + + // ======================================================================== + // BATCH EVALUATION (GRADIENTS) + // ======================================================================== + + void FEField::evaluateGradientBatch(CfemInt cellId, CfemInt nQp, std::span outValues, EvalContext& ctx) const { if (m_boundIndex >= 0) { - const auto& data = ctx.getSpaceData(m_boundIndex); + const auto* mappingBatch = ctx.getMappingBatch(m_boundIndex); - if (data.shapeSingle != nullptr) { - this->evaluateWithShape(cellId, *(data.shapeSingle), out); + if (mappingBatch != nullptr) { + this->evaluateGradientBatchWithCachedMapping(cellId, nQp, *mappingBatch, outValues, ctx); return; } + CFEM_INFO << "WARNING: Bound index present but no MappingBatch cached for FEField::evaluateGradientBatch"; } - this->evaluateWithoutShape(cellId, xi, out, ctx); + this->evaluateGradientBatchWithoutCachedMapping(cellId, nQp, outValues, ctx); + } + + void FEField::evaluateGradientBatchWithCachedMapping(CfemInt cellId, CfemInt nQp, const MappingInfoBatch& mappingBatch, std::span outValues, EvalContext& ctx) const + { + const CfemInt nComp = this->getNumComponents(); + const CfemInt spatialDim = m_space->getMesh()->getSpatialDimension(); + const CfemInt nNodes = mappingBatch.numNodes; + + // Le format de sortie pour un tenseur (ou gradient de vecteur) en SoA: + // Taille : (nComp * spatialDim) * nQp + const CfemInt expectedSize = nComp * spatialDim * nQp; + CFEM_ASSERT(outValues.size() >= static_cast(expectedSize), "FEField::evaluateGradientBatchWithCachedMapping - Output buffer too small"); + std::fill_n(outValues.data(), expectedSize, 0.0); + + auto bookmark = ctx.getBookmark(); + this->extractCellDofs(cellId, ctx.scratchCellDofs, ctx); + + // Boucle sur les composantes du champ (ex: u_x, u_y, u_z) + for (CfemInt c = 0; c < nComp; ++c) + { + const CfemInt baseRow = c * spatialDim; // Offset pour la composante c + + // Boucle interne sur les QP : parfait pour la prédiction de branchement et le chargement cache ! + for (CfemInt q = 0; q < nQp; ++q) + { + const CfemReal* gx = mappingBatch.dN_dx(q); + const CfemReal* gy = (spatialDim >= 2) ? mappingBatch.dN_dy(q) : nullptr; + const CfemReal* gz = (spatialDim == 3) ? mappingBatch.dN_dz(q) : nullptr; + + CfemReal sumX = 0.0, sumY = 0.0, sumZ = 0.0; + + // Contraction locale (nœuds) + for (CfemInt i = 0; i < nNodes; ++i) + { + const CfemReal dofVal = ctx.scratchCellDofs[i][c]; + sumX += dofVal * gx[i]; + if (spatialDim >= 2) sumY += dofVal * gy[i]; + if (spatialDim == 3) sumZ += dofVal * gz[i]; + } + + // Écriture respectant le layout SoA : out[(composante_tensorielle) * nQp + q] + outValues[(baseRow + 0) * nQp + q] = sumX; + if (spatialDim >= 2) outValues[(baseRow + 1) * nQp + q] = sumY; + if (spatialDim == 3) outValues[(baseRow + 2) * nQp + q] = sumZ; + } + } + + ctx.releaseToBookmark(bookmark); + } + + void FEField::evaluateGradientBatchWithoutCachedMapping(CfemInt cellId, CfemInt nQp, std::span outValues, EvalContext& ctx) const + { + // Pareil que pour la valeur batch : on a besoin de xiBatch (via EvalContext) + const auto& xiBatch = ctx.batchWS.xi; + CFEM_ASSERT(xiBatch != nullptr && xiBatch->size() >= static_cast(nQp), "FEField::evaluateGradientBatchWithoutCachedMapping requires xiBatch"); + + const auto &refEl = m_space->getReferenceElement(cellId); + + m_space->getMesh()->getCellVerticesCoords(cellId, ctx.scratchCoords); + const CfemInt expectedNodes = refEl.getNumNodes(); + if (ctx.scratchCoords.size() > static_cast(expectedNodes)) { + ctx.scratchCoords.resize(expectedNodes); + } + + // On demande à l'élément de référence de construire la totale pour nQp points + ctx.batchWS.shape->shapeMustBeUpdated = false; + + const bool isAffine = (m_space->getMesh()->getOrderInt() == 1) || m_space->getMesh()->isCellAffine(cellId); + + refEl.computeMappingBatch( + *xiBatch, + ctx.scratchCoords, + ShapeEvaluationFlags::FirstDerivatives, + MappingEvaluationFlags::PhysicalFirstDerivatives, + *(ctx.batchWS.shape), + *(ctx.batchWS.mapping) + ); + + this->evaluateGradientBatchWithCachedMapping(cellId, nQp, *(ctx.batchWS.mapping), outValues, ctx); } /** diff --git a/libs/fem/mesh/src/GmshParser.cpp b/libs/fem/mesh/src/GmshParser.cpp index 2cf9410..7746d3d 100644 --- a/libs/fem/mesh/src/GmshParser.cpp +++ b/libs/fem/mesh/src/GmshParser.cpp @@ -253,7 +253,7 @@ std::shared_ptr GmshParser::load(const std::string &filename, std::optiona } std::ifstream file(filename); if (!file.is_open()) { - CFEM_ERROR << "Failed to open " << filename; + CFEM_THROW("Failed to open " << filename); return nullptr; } diff --git a/libs/fem/reference_element/include/MappingInfo.h b/libs/fem/reference_element/include/MappingInfo.h index 343c801..6970766 100644 --- a/libs/fem/reference_element/include/MappingInfo.h +++ b/libs/fem/reference_element/include/MappingInfo.h @@ -86,17 +86,14 @@ namespace cfem // ================================================================================================ [[nodiscard]] inline CfemReal* dN_dx() { - CFEM_ASSERT(hasFlag(validFlags, MappingEvaluationFlags::PhysicalFirstDerivatives), "Physical first derivatives not computed?"); return storage.data() + 0 * stride; } [[nodiscard]] inline CfemReal* dN_dy() { - CFEM_ASSERT(hasFlag(validFlags, MappingEvaluationFlags::PhysicalFirstDerivatives), "Physical first derivatives not computed?"); return storage.data() + 1 * stride; } [[nodiscard]] inline CfemReal* dN_dz() { - CFEM_ASSERT(hasFlag(validFlags, MappingEvaluationFlags::PhysicalFirstDerivatives), "Physical first derivatives not computed?"); return storage.data() + 2 * stride; } @@ -120,33 +117,27 @@ namespace cfem // SHAPES SECOND DERIVATIVES (Physical Space) // ================================================================================================ - [[nodiscard]] inline CfemReal* d2N_dx2() { - CFEM_ASSERT(hasSecondDerivatives, "Second derivatives not allocated?"); + [[nodiscard]] inline CfemReal* d2N_dx2() noexcept { return storage.data() + 3 * stride; } - [[nodiscard]] inline CfemReal* d2N_dy2() { - CFEM_ASSERT(hasSecondDerivatives, "Second derivatives not allocated?"); + [[nodiscard]] inline CfemReal* d2N_dy2() noexcept { return storage.data() + 4 * stride; } - [[nodiscard]] inline CfemReal* d2N_dz2() { - CFEM_ASSERT(hasSecondDerivatives, "Second derivatives not allocated?"); + [[nodiscard]] inline CfemReal* d2N_dz2() noexcept { return storage.data() + 5 * stride; } - [[nodiscard]] inline CfemReal* d2N_dxdy() { - CFEM_ASSERT(hasSecondDerivatives, "Second derivatives not allocated?"); + [[nodiscard]] inline CfemReal* d2N_dxdy() noexcept { return storage.data() + 6 * stride; } - [[nodiscard]] inline CfemReal* d2N_dxdz() { - CFEM_ASSERT(hasSecondDerivatives, "Second derivatives not allocated?"); + [[nodiscard]] inline CfemReal* d2N_dxdz() noexcept { return storage.data() + 7 * stride; } - [[nodiscard]] inline CfemReal* d2N_dydz() { - CFEM_ASSERT(hasSecondDerivatives, "Second derivatives not allocated?"); + [[nodiscard]] inline CfemReal* d2N_dydz() noexcept { return storage.data() + 8 * stride; } @@ -199,48 +190,12 @@ namespace cfem d2N_dydz()[node], d2N_dxdz()[node], d2N_dxdy()[node]); } - // ============================================================ - // Calcul (Optimisé HPC) - // ============================================================ - - void computePhysicalFirstDerivativesOnly(const ShapeInfo &fieldShape) + /** + * @brief Checks if a specific evaluation field is marked as valid. + */ + inline bool has(MappingEvaluationFlags field) const { - const CfemInt n = fieldShape.numNodes; - CFEM_ASSERT(n == numNodes); - - // Hoisting (mise en cache des variables invariantes de la boucle) - const CfemReal i00 = invJacobian.xx(); - const CfemReal i01 = invJacobian.xy(); - const CfemReal i02 = invJacobian.xz(); - - const CfemReal i10 = invJacobian.yx(); - const CfemReal i11 = invJacobian.yy(); - const CfemReal i12 = invJacobian.yz(); - - const CfemReal i20 = invJacobian.zx(); - const CfemReal i21 = invJacobian.zy(); - const CfemReal i22 = invJacobian.zz(); - - // Pointeurs en lecture (restrict = promesse de non-aliasing au compilateur) - const CfemReal* __restrict dxi = fieldShape.dN_dxi(); - const CfemReal* __restrict deta = fieldShape.dN_deta(); - const CfemReal* __restrict dzeta = fieldShape.dN_dzeta(); - - // L'astuce : On contourne temporairement l'ASSERT de validFlags pour l'écriture - // (car ils ne seront valides qu'à la fin de cette fonction) - CfemReal* __restrict dx = storage.data() + 0 * stride; - CfemReal* __restrict dy = storage.data() + 1 * stride; - CfemReal* __restrict dz = storage.data() + 2 * stride; - - // Boucle principale vectorisable par le compilateur - for (CfemInt i = 0; i < n; ++i) - { - dx[i] = i00 * dxi[i] + i10 * deta[i] + i20 * dzeta[i]; - dy[i] = i01 * dxi[i] + i11 * deta[i] + i21 * dzeta[i]; - dz[i] = i02 * dxi[i] + i12 * deta[i] + i22 * dzeta[i]; - } - - validFlags |= MappingEvaluationFlags::PhysicalFirstDerivatives; + return hasFlag(validFlags, field); } }; @@ -260,6 +215,7 @@ namespace cfem CfemInt numQuadraturePoints = 0; CfemInt numNodes = 0; MappingEvaluationFlags validFlags = MappingEvaluationFlags::None; + bool isCellAffine = false; // --- Global quantities per quadrature point [qp] --- // Ces éléments restent dans leurs propres DynamicVector car ils sont @@ -326,55 +282,46 @@ namespace cfem [[nodiscard]] inline CfemReal* dN_dx(CfemInt q) { CFEM_ASSERT(q >= 0 && q < numQuadraturePoints, "Invalid QP index"); - CFEM_ASSERT(hasFlag(validFlags, MappingEvaluationFlags::PhysicalFirstDerivatives), "Derivatives not computed"); return storage.data() + q * qpStride + 0 * paddedNodeStride; } [[nodiscard]] inline CfemReal* dN_dy(CfemInt q) { CFEM_ASSERT(q >= 0 && q < numQuadraturePoints, "Invalid QP index"); - CFEM_ASSERT(hasFlag(validFlags, MappingEvaluationFlags::PhysicalFirstDerivatives), "Derivatives not computed"); return storage.data() + q * qpStride + 1 * paddedNodeStride; } [[nodiscard]] inline CfemReal* dN_dz(CfemInt q) { CFEM_ASSERT(q >= 0 && q < numQuadraturePoints, "Invalid QP index"); - CFEM_ASSERT(hasFlag(validFlags, MappingEvaluationFlags::PhysicalFirstDerivatives), "Derivatives not computed"); return storage.data() + q * qpStride + 2 * paddedNodeStride; } [[nodiscard]] inline CfemReal* d2N_dx2(CfemInt q) { CFEM_ASSERT(q >= 0 && q < numQuadraturePoints, "Invalid QP index"); - CFEM_ASSERT(hasFlag(validFlags, MappingEvaluationFlags::PhysicalSecondDerivatives), "Second derivatives not allocated"); return storage.data() + q * qpStride + 3 * paddedNodeStride; } [[nodiscard]] inline CfemReal* d2N_dy2(CfemInt q) { CFEM_ASSERT(q >= 0 && q < numQuadraturePoints, "Invalid QP index"); - CFEM_ASSERT(hasFlag(validFlags, MappingEvaluationFlags::PhysicalSecondDerivatives), "Second derivatives not allocated"); return storage.data() + q * qpStride + 4 * paddedNodeStride; } [[nodiscard]] inline CfemReal* d2N_dz2(CfemInt q) { CFEM_ASSERT(q >= 0 && q < numQuadraturePoints, "Invalid QP index"); - CFEM_ASSERT(hasFlag(validFlags, MappingEvaluationFlags::PhysicalSecondDerivatives), "Second derivatives not allocated"); return storage.data() + q * qpStride + 5 * paddedNodeStride; } [[nodiscard]] inline CfemReal* d2N_dxdy(CfemInt q) { CFEM_ASSERT(q >= 0 && q < numQuadraturePoints, "Invalid QP index"); - CFEM_ASSERT(hasFlag(validFlags, MappingEvaluationFlags::PhysicalSecondDerivatives), "Second derivatives not allocated"); return storage.data() + q * qpStride + 6 * paddedNodeStride; } [[nodiscard]] inline CfemReal* d2N_dxdz(CfemInt q) { CFEM_ASSERT(q >= 0 && q < numQuadraturePoints, "Invalid QP index"); - CFEM_ASSERT(hasFlag(validFlags, MappingEvaluationFlags::PhysicalSecondDerivatives), "Second derivatives not allocated"); return storage.data() + q * qpStride + 7 * paddedNodeStride; } [[nodiscard]] inline CfemReal* d2N_dydz(CfemInt q) { CFEM_ASSERT(q >= 0 && q < numQuadraturePoints, "Invalid QP index"); - CFEM_ASSERT(hasFlag(validFlags, MappingEvaluationFlags::PhysicalSecondDerivatives), "Second derivatives not allocated"); return storage.data() + q * qpStride + 8 * paddedNodeStride; } @@ -452,48 +399,14 @@ namespace cfem d2N_dydz(q)[node], d2N_dxdz(q)[node], d2N_dxdy(q)[node]); } - // ============================================================ - // Calcul (Optimisé HPC) - // ============================================================ - /** - * @brief Computes physical gradients for the entire batch. - * @note Hyper-optimized utilizing SIMD friendly contiguous memory rows. + * @brief Checks if a specific evaluation field is marked as valid. */ - void computePhysicalFirstDerivativesOnly(const ShapeInfoBatch& fieldShape) { - CFEM_ASSERT(fieldShape.numQuadraturePoints == numQuadraturePoints, "Mismatch QP"); - CFEM_ASSERT(fieldShape.numNodes == numNodes, "Mismatch Nodes"); - - for (CfemInt q = 0; q < numQuadraturePoints; ++q) - { - // Cache invariant Jacobian terms in registers for this quadrature point - const CfemReal i00 = invJacobians[q].data[Tensor3D::idx_xx], i01 = invJacobians[q].data[Tensor3D::idx_xy], i02 = invJacobians[q].data[Tensor3D::idx_xz]; - const CfemReal i10 = invJacobians[q].data[Tensor3D::idx_yx], i11 = invJacobians[q].data[Tensor3D::idx_yy], i12 = invJacobians[q].data[Tensor3D::idx_yz]; - const CfemReal i20 = invJacobians[q].data[Tensor3D::idx_zx], i21 = invJacobians[q].data[Tensor3D::idx_zy], i22 = invJacobians[q].data[Tensor3D::idx_zz]; - - // Remarque : Si vous avez appliqué la même logique à ShapeInfoBatch, - // ceci devrait être `fieldShape.dN_dxi(q);`. - // Si ShapeInfoBatch utilise encore l'ancienne méthode (DynamicMatrix), laissez `.data()`. - const CfemReal* __restrict dxi = fieldShape.dN_dxi(q); - const CfemReal* __restrict deta = fieldShape.dN_deta(q); - const CfemReal* __restrict dzeta = fieldShape.dN_dzeta(q); - - // Extraction des pointeurs en écriture (on contourne les ASSERT temporairement) - CfemReal* __restrict dx = storage.data() + q * qpStride + 0 * paddedNodeStride; - CfemReal* __restrict dy = storage.data() + q * qpStride + 1 * paddedNodeStride; - CfemReal* __restrict dz = storage.data() + q * qpStride + 2 * paddedNodeStride; - - // Inner loop over nodes: perfectly contiguous, ripe for auto-vectorization - for (CfemInt i = 0; i < numNodes; ++i) { - dx[i] = i00 * dxi[i] + i10 * deta[i] + i20 * dzeta[i]; - dy[i] = i01 * dxi[i] + i11 * deta[i] + i21 * dzeta[i]; - dz[i] = i02 * dxi[i] + i12 * deta[i] + i22 * dzeta[i]; - } - } - - // Marquer comme valide une fois la boucle de calcul terminée - validFlags |= MappingEvaluationFlags::PhysicalFirstDerivatives; + inline bool has(MappingEvaluationFlags field) const + { + return hasFlag(validFlags, field); } + }; } \ No newline at end of file diff --git a/libs/fem/reference_element/include/ReferenceElement.h b/libs/fem/reference_element/include/ReferenceElement.h index 954c225..14af22a 100644 --- a/libs/fem/reference_element/include/ReferenceElement.h +++ b/libs/fem/reference_element/include/ReferenceElement.h @@ -52,7 +52,6 @@ class ReferenceElement { CfemInt m_numNodes; CfemInt m_dimension; - public: virtual ~ReferenceElement() = default; @@ -64,8 +63,8 @@ class ReferenceElement { void evaluateShapes(const Vector3D &xi, ShapeEvaluationFlags requestedShape, ShapeInfo& out) const; void evaluateShapesBatch(const DynamicVector &xiPoints, ShapeEvaluationFlags requestedShape, ShapeInfoBatch& outBatch) const; /** - * @brief Evaluates the shape function values ($N_i$) at a given reference point. - * @param xi The coordinates of the evaluation point in the reference element ($\xi, \eta, \zeta$). + * @brief Evaluates the shape function values (\f$ N_i \f$) at a given reference point. + * @param xi The coordinates of the evaluation point in the reference element (\f$\xi, \eta, \zeta \f$). * @param values Raw pointer to a pre-allocated array of size [numNodes] to be populated. */ virtual void evaluateShapesValues(const Vector3D& xi, ShapeInfo& info) const = 0; @@ -75,10 +74,10 @@ class ReferenceElement { /** * @brief Evaluates the first-order derivatives of the shape functions (local gradients) * with respect to the reference coordinate system. - * @param xi The coordinates of the evaluation point in the reference element ($\xi, \eta, \zeta$). - * @param dN_dxi Raw pointer to the reference coordinate $\xi$ derivative array (size [numNodes]). - * @param dN_deta Raw pointer to the reference coordinate $\eta$ derivative array (size [numNodes]). - * @param dN_dzeta Raw pointer to the reference coordinate $\zeta$ derivative array (size [numNodes]). + * @param xi The coordinates of the evaluation point in the reference element (\f$\xi, \eta, \zeta \f$). + * @param dN_dxi Raw pointer to the reference coordinate \f$ \xi \f$ derivative array (size [numNodes]). + * @param dN_deta Raw pointer to the reference coordinate \f$ \eta \f$ derivative array (size [numNodes]). + * @param dN_dzeta Raw pointer to the reference coordinate \f$ \zeta \f$ derivative array (size [numNodes]). */ virtual void evaluateShapesDerivatives(const Vector3D& xi, ShapeInfo& info) const = 0; virtual void evaluateShapesDerivativesBatch(const DynamicVector &xi, @@ -146,14 +145,14 @@ class ReferenceElement { MappingInfo& out) const; void computePhysicalFirstDerivativesBatch(const ShapeInfoBatch &shape, - MappingInfoBatch &out) const; + MappingInfoBatch &out) const; /** * @brief Computes physical Hessians including the geometric curvature term. * @param physicalNodesCoords Actual physical coordinates of the element's nodes. * @param shape Work buffer for reference shape functions. * @param out MappingInfo structure to be populated. - * @note Reference: \$ H_{phys} = J^{-T} * [H_{ref} - \nabla_{phys}(N) * H_{geom}] * J^{-1} \$ + * @note Reference: \f$ H_{phys} = J^{-T} * [H_{ref} - \nabla_{phys}(N) * H_{geom}] * J^{-1} \f$ */ void computePhysicalSecondDerivatives(const DynamicVector& physicalNodesCoords, const ShapeInfo& shape, @@ -163,9 +162,6 @@ class ReferenceElement { const ShapeInfoBatch& shapeBatch, MappingInfoBatch& outBatch) const; - void computeGeometricNormal(const ShapeInfo &shape, MappingInfo &out) const; - void computeGeometricNormalBatch(const ShapeInfoBatch &shapeBatch, MappingInfoBatch &outBatch) const; - /** * @brief Interpolates a field (scalar or vector) at a point evaluated in ShapeInfo. * @tparam T The field type (e.g., CfemReal, Vector3D, or even Tensor types). @@ -200,28 +196,19 @@ class ReferenceElement { inline void computeJacobian(const ShapeInfo& shape, const DynamicVector& physicalNodesCoords, MappingInfo& out) const; - inline void computeJacobianBatch(const ShapeInfoBatch& shape, const DynamicVector& physicalNodesCoords, MappingInfoBatch& out) const; - inline void computeDeterminant(const ShapeInfo &shape, - const DynamicVector &physicalNodesCoords, - MappingInfo &out) const; + inline void computeDeterminant(MappingInfo &out) const; + inline void computeDeterminantBatch(MappingInfoBatch &out) const; - inline void computeDeterminantBatch(const ShapeInfoBatch &shape, - const DynamicVector &physicalNodesCoords, - MappingInfoBatch &out) const; + inline void computeInverseJacobian(MappingInfo &out) const; + inline void computeInverseJacobianBatch(MappingInfoBatch &out) const; - inline void computeInverseJacobian(const ShapeInfo &shape, - const DynamicVector &physicalNodesCoords, - MappingInfo &out) const; - - inline void computeInverseJacobianBatch(const ShapeInfoBatch &shape, - const DynamicVector &physicalNodesCoords, - MappingInfoBatch &out) const; - -}; + inline void computeGeometricNormal(MappingInfo &out) const; + inline void computeGeometricNormalBatch(MappingInfoBatch &outBatch) const; + }; } diff --git a/libs/fem/reference_element/include/ReferenceElement.tpp b/libs/fem/reference_element/include/ReferenceElement.tpp index 2367272..219c1ae 100644 --- a/libs/fem/reference_element/include/ReferenceElement.tpp +++ b/libs/fem/reference_element/include/ReferenceElement.tpp @@ -6,57 +6,60 @@ // --- // --- This software is protected by international copyright laws. // --- Use, distribution, or modification of this software in any form, -// --- source or binary, for personal, academic, or non-commercial -// --- purposes is permitted free of charge, provided that this -// --- copyright notice and this permission notice appear in all +// --- source or binary, for personal, academic, or non-commercial +// --- purposes is permitted free of charge, provided that this +// --- copyright notice and this permission notice appear in all // --- copies and supporting documentation. // --- -// --- The authors and contributors provide this code "as is" without -// --- any express or implied warranty. In no event shall they be +// --- The authors and contributors provide this code "as is" without +// --- any express or implied warranty. In no event shall they be // --- held liable for any damages arising from the use of this software. //************************************************************************ namespace cfem { - inline void ReferenceElement::evaluateShapes(const Vector3D &xi, ShapeEvaluationFlags requestedShape, ShapeInfo& shape) const + inline void ReferenceElement::evaluateShapes(const Vector3D &xi, ShapeEvaluationFlags requestedShape, ShapeInfo &shape) const { const bool includeSecondDerivatives = hasFlag(requestedShape, ShapeEvaluationFlags::SecondDerivatives); shape.setup(m_numNodes, includeSecondDerivatives); - if (hasFlag(requestedShape, ShapeEvaluationFlags::Value)) { - shape.validFlags |= ShapeEvaluationFlags::Value; + if (hasFlag(requestedShape, ShapeEvaluationFlags::Value)) + { evaluateShapesValues(xi, shape); } - if (hasFlag(requestedShape, ShapeEvaluationFlags::FirstDerivatives)) { - shape.validFlags |= ShapeEvaluationFlags::FirstDerivatives; + if (hasFlag(requestedShape, ShapeEvaluationFlags::FirstDerivatives)) + { evaluateShapesDerivatives(xi, shape); } - if (includeSecondDerivatives) { - shape.validFlags |= ShapeEvaluationFlags::SecondDerivatives; + if (includeSecondDerivatives) + { evaluateShapesSecondDerivatives(xi, shape); } + shape.validFlags |= requestedShape; } - inline void ReferenceElement::evaluateShapesBatch(const DynamicVector &xiPoints, ShapeEvaluationFlags requestedShape, ShapeInfoBatch& shapeBatch) const + inline void ReferenceElement::evaluateShapesBatch(const DynamicVector &xiPoints, ShapeEvaluationFlags requestedShape, ShapeInfoBatch &shapeBatch) const { const CfemInt nQp = static_cast(xiPoints.size()); const bool includeSecondDerivatives = hasFlag(requestedShape, ShapeEvaluationFlags::SecondDerivatives); shapeBatch.setup(nQp, m_numNodes, includeSecondDerivatives); - if (hasFlag(requestedShape, ShapeEvaluationFlags::Value)) { + if (hasFlag(requestedShape, ShapeEvaluationFlags::Value)) + { evaluateShapesValuesBatch(xiPoints, shapeBatch); } - if (hasFlag(requestedShape, ShapeEvaluationFlags::FirstDerivatives)) { + if (hasFlag(requestedShape, ShapeEvaluationFlags::FirstDerivatives)) + { evaluateShapesDerivativesBatch(xiPoints, shapeBatch); } - if (includeSecondDerivatives) { + if (includeSecondDerivatives) + { evaluateShapesSecondDerivativesBatch(xiPoints, shapeBatch); } - shapeBatch.validFlags = requestedShape; - + shapeBatch.validFlags |= requestedShape; } // ========================================================================= // ÉVALUATIONS PONCTUELLES (SINGLE POINT) @@ -70,10 +73,11 @@ namespace cfem MappingInfo &out) const { // Évaluation polymorphe des fonctions de forme sur l'élément de référence - if (shape.shapeMustBeUpdated){ + if (shape.shapeMustBeUpdated) + { evaluateShapes(xi, requestedShape, shape); } - + // Passage direct à la variante géométrique passive via const ShapeInfo& computeMapping(xi, physicalNodesCoords, requestedMapping, shape, out); } @@ -86,11 +90,13 @@ namespace cfem { out.setup(m_numNodes, hasFlag(requestedMapping, MappingEvaluationFlags::PhysicalSecondDerivatives)); - if (hasFlag(requestedMapping, MappingEvaluationFlags::PhysicalPosition)) { + if (hasFlag(requestedMapping, MappingEvaluationFlags::PhysicalPosition)) + { CfemReal px = 0.0, py = 0.0, pz = 0.0; - const CfemReal* N = shape.values(); - for (CfemInt i = 0; i < m_numNodes; ++i) { - const Vector3D& pos = physicalNodesCoords[i]; + const CfemReal *N = shape.values(); + for (CfemInt i = 0; i < m_numNodes; ++i) + { + const Vector3D &pos = physicalNodesCoords[i]; const CfemReal ni = N[i]; px += pos.x * ni; py += pos.y * ni; @@ -99,37 +105,42 @@ namespace cfem out.physicalPosition = Vector3D(px, py, pz); } - if (hasFlag(requestedMapping, MappingEvaluationFlags::Jacobian)) { - out.validFlags |= MappingEvaluationFlags::Jacobian; + if (hasFlag(requestedMapping, MappingEvaluationFlags::Jacobian)) + { computeJacobian(shape, physicalNodesCoords, out); + out.validFlags |= MappingEvaluationFlags::Jacobian; } - if (hasFlag(requestedMapping, MappingEvaluationFlags::Determinant)) { + if (hasFlag(requestedMapping, MappingEvaluationFlags::Determinant)) + { + computeDeterminant(out); out.validFlags |= MappingEvaluationFlags::Determinant; - computeDeterminant(shape, physicalNodesCoords, out); } - if (hasFlag(requestedMapping, MappingEvaluationFlags::InverseJacobian)) { + if (hasFlag(requestedMapping, MappingEvaluationFlags::InverseJacobian)) + { + computeInverseJacobian(out); out.validFlags |= MappingEvaluationFlags::InverseJacobian; - computeInverseJacobian(shape, physicalNodesCoords, out); } - if (hasFlag(requestedMapping, MappingEvaluationFlags::PhysicalFirstDerivatives)) { - out.validFlags |= MappingEvaluationFlags::PhysicalFirstDerivatives; + if (hasFlag(requestedMapping, MappingEvaluationFlags::PhysicalFirstDerivatives)) + { computePhysicalFirstDerivatives(shape, out); + out.validFlags |= MappingEvaluationFlags::PhysicalFirstDerivatives; } - if (hasFlag(requestedMapping, MappingEvaluationFlags::PhysicalSecondDerivatives)) { - out.validFlags |= MappingEvaluationFlags::PhysicalSecondDerivatives; + if (hasFlag(requestedMapping, MappingEvaluationFlags::PhysicalSecondDerivatives)) + { computePhysicalSecondDerivatives(physicalNodesCoords, shape, out); + out.validFlags |= MappingEvaluationFlags::PhysicalSecondDerivatives; } - if (hasFlag(requestedMapping, MappingEvaluationFlags::Normal)){ + if (hasFlag(requestedMapping, MappingEvaluationFlags::Normal)) + { + computeGeometricNormal(out); out.validFlags |= MappingEvaluationFlags::Normal; - computeGeometricNormal(shape, out); } - out.validFlags = requestedMapping; } // ========================================================================= @@ -142,9 +153,10 @@ namespace cfem MappingEvaluationFlags requestedMapping, ShapeInfoBatch &shapeBatch, MappingInfoBatch &outBatch) const - { + { // Évaluation par lignes contiguës dans les DynamicMatrix - if (shapeBatch.shapeMustBeUpdated){ + if (shapeBatch.shapeMustBeUpdated) + { evaluateShapesBatch(xiPoints, requestedShape, shapeBatch); } @@ -159,18 +171,21 @@ namespace cfem MappingInfoBatch &outBatch) const { const CfemInt nQp = static_cast(xiPoints.size()); - + // Initialisation du lot physique outBatch.setup(nQp, m_numNodes, m_dimension, hasFlag(requestedMapping, MappingEvaluationFlags::PhysicalSecondDerivatives)); - if (hasFlag(requestedMapping, MappingEvaluationFlags::PhysicalPosition)) { - for (CfemInt q = 0; q < nQp; ++q) { + if (hasFlag(requestedMapping, MappingEvaluationFlags::PhysicalPosition)) + { + for (CfemInt q = 0; q < nQp; ++q) + { CfemReal px = 0.0, py = 0.0, pz = 0.0; - - const CfemReal* N = shapeBatch.values(q); - - for (CfemInt i = 0; i < m_numNodes; ++i) { - const Vector3D& pos = physicalNodesCoords[i]; + + const CfemReal *N = shapeBatch.values(q); + + for (CfemInt i = 0; i < m_numNodes; ++i) + { + const Vector3D &pos = physicalNodesCoords[i]; const CfemReal ni = N[i]; px += pos.x * ni; py += pos.y * ni; @@ -181,36 +196,48 @@ namespace cfem } // Évaluation des opérateurs physiques par lot (Chaque fonction traite l'ensemble du vecteur de points qp) - if (hasFlag(requestedMapping, MappingEvaluationFlags::Jacobian)) { + if (hasFlag(requestedMapping, MappingEvaluationFlags::Jacobian)) + { computeJacobianBatch(shapeBatch, physicalNodesCoords, outBatch); + outBatch.validFlags |= MappingEvaluationFlags::Jacobian; } - if (hasFlag(requestedMapping, MappingEvaluationFlags::Determinant)) { - computeDeterminantBatch(shapeBatch, physicalNodesCoords, outBatch); + if (hasFlag(requestedMapping, MappingEvaluationFlags::Determinant)) + { + + computeDeterminantBatch(outBatch); + outBatch.validFlags |= MappingEvaluationFlags::Determinant; } - if (hasFlag(requestedMapping, MappingEvaluationFlags::InverseJacobian)) { - computeInverseJacobianBatch(shapeBatch, physicalNodesCoords, outBatch); + if (hasFlag(requestedMapping, MappingEvaluationFlags::InverseJacobian)) + { + computeInverseJacobianBatch(outBatch); + outBatch.validFlags |= MappingEvaluationFlags::InverseJacobian; } - if (hasFlag(requestedMapping, MappingEvaluationFlags::PhysicalFirstDerivatives)) { + if (hasFlag(requestedMapping, MappingEvaluationFlags::PhysicalFirstDerivatives)) + { computePhysicalFirstDerivativesBatch(shapeBatch, outBatch); + outBatch.validFlags |= MappingEvaluationFlags::PhysicalFirstDerivatives; } - if (hasFlag(requestedMapping, MappingEvaluationFlags::PhysicalSecondDerivatives)) { + if (hasFlag(requestedMapping, MappingEvaluationFlags::PhysicalSecondDerivatives)) + { computePhysicalSecondDerivativesBatch(physicalNodesCoords, shapeBatch, outBatch); + outBatch.validFlags |= MappingEvaluationFlags::PhysicalSecondDerivatives; } - if (hasFlag(requestedMapping, MappingEvaluationFlags::Normal)){ - computeGeometricNormalBatch(shapeBatch, outBatch); + if (hasFlag(requestedMapping, MappingEvaluationFlags::Normal)) + { + computeGeometricNormalBatch(outBatch); + outBatch.validFlags |= MappingEvaluationFlags::Normal; } - outBatch.validFlags = requestedMapping; } - inline void ReferenceElement::computeJacobian(const ShapeInfo& shape, - const DynamicVector& physicalNodesCoords, - MappingInfo& out) const + inline void ReferenceElement::computeJacobian(const ShapeInfo &shape, + const DynamicVector &physicalNodesCoords, + MappingInfo &out) const { const CfemInt n = shape.numNodes; @@ -219,123 +246,188 @@ namespace cfem CfemReal j20 = 0.0, j21 = 0.0, j22 = 0.0; // Pointeurs SoA pour ce point de quadrature précis - const CfemReal* dxi = shape.dN_dxi(); - const CfemReal* deta = shape.dN_deta(); - const CfemReal* dzeta = shape.dN_dzeta(); - + const CfemReal *dxi = shape.dN_dxi(); + const CfemReal *deta = shape.dN_deta(); + const CfemReal *dzeta = shape.dN_dzeta(); + for (CfemInt i = 0; i < n; ++i) { const Vector3D &X = physicalNodesCoords[i]; - j00 += X.x * dxi[i]; j10 += X.y * dxi[i]; j20 += X.z * dxi[i]; - j01 += X.x * deta[i]; j11 += X.y * deta[i]; j21 += X.z * deta[i]; - j02 += X.x * dzeta[i]; j12 += X.y * dzeta[i]; j22 += X.z * dzeta[i]; + j00 += X.x * dxi[i]; + j10 += X.y * dxi[i]; + j20 += X.z * dxi[i]; + j01 += X.x * deta[i]; + j11 += X.y * deta[i]; + j21 += X.z * deta[i]; + j02 += X.x * dzeta[i]; + j12 += X.y * dzeta[i]; + j22 += X.z * dzeta[i]; } - out.jacobian.data[Tensor3D::idx_xx] = j00; out.jacobian.data[Tensor3D::idx_xy] = j01; out.jacobian.data[Tensor3D::idx_xz] = j02; - out.jacobian.data[Tensor3D::idx_yx] = j10; out.jacobian.data[Tensor3D::idx_yy] = j11; out.jacobian.data[Tensor3D::idx_yz] = j12; - out.jacobian.data[Tensor3D::idx_zx] = j20; out.jacobian.data[Tensor3D::idx_zy] = j21; out.jacobian.data[Tensor3D::idx_zz] = j22; + out.jacobian.data[Tensor3D::idx_xx] = j00; + out.jacobian.data[Tensor3D::idx_xy] = j01; + out.jacobian.data[Tensor3D::idx_xz] = j02; + out.jacobian.data[Tensor3D::idx_yx] = j10; + out.jacobian.data[Tensor3D::idx_yy] = j11; + out.jacobian.data[Tensor3D::idx_yz] = j12; + out.jacobian.data[Tensor3D::idx_zx] = j20; + out.jacobian.data[Tensor3D::idx_zy] = j21; + out.jacobian.data[Tensor3D::idx_zz] = j22; } - - inline void ReferenceElement::computeJacobianBatch(const ShapeInfoBatch& shape, - const DynamicVector& physicalNodesCoords, - MappingInfoBatch& out) const + inline void ReferenceElement::computeJacobianBatch(const ShapeInfoBatch &shape, + const DynamicVector &physicalNodesCoords, + MappingInfoBatch &out) const { + CFEM_ASSERT(shape.numQuadraturePoints == out.numQuadraturePoints && shape.has(ShapeEvaluationFlags::FirstDerivatives), "Shapes derivatives not precomputed?"); const CfemInt nQp = shape.numQuadraturePoints; const CfemInt n = shape.numNodes; - for (CfemInt q = 0; q < nQp; ++q) + const CfemInt qEnd = out.isCellAffine ? 1 : nQp; + + for (CfemInt q = 0; q < qEnd; ++q) { CfemReal j00 = 0.0, j01 = 0.0, j02 = 0.0; CfemReal j10 = 0.0, j11 = 0.0, j12 = 0.0; CfemReal j20 = 0.0, j21 = 0.0, j22 = 0.0; // Pointeurs SoA pour ce point de quadrature précis - const CfemReal* dxi = shape.dN_dxi(q); - const CfemReal* deta = shape.dN_deta(q); - const CfemReal* dzeta = shape.dN_dzeta(q); + const CfemReal *dxi = shape.dN_dxi(q); + const CfemReal *deta = shape.dN_deta(q); + const CfemReal *dzeta = shape.dN_dzeta(q); for (CfemInt i = 0; i < n; ++i) { const Vector3D &X = physicalNodesCoords[i]; - j00 += X.x * dxi[i]; j10 += X.y * dxi[i]; j20 += X.z * dxi[i]; - j01 += X.x * deta[i]; j11 += X.y * deta[i]; j21 += X.z * deta[i]; - j02 += X.x * dzeta[i]; j12 += X.y * dzeta[i]; j22 += X.z * dzeta[i]; + j00 += X.x * dxi[i]; + j10 += X.y * dxi[i]; + j20 += X.z * dxi[i]; + j01 += X.x * deta[i]; + j11 += X.y * deta[i]; + j21 += X.z * deta[i]; + j02 += X.x * dzeta[i]; + j12 += X.y * dzeta[i]; + j22 += X.z * dzeta[i]; } - out.jacobians[q].data[Tensor3D::idx_xx] = j00; out.jacobians[q].data[Tensor3D::idx_xy] = j01; out.jacobians[q].data[Tensor3D::idx_xz] = j02; - out.jacobians[q].data[Tensor3D::idx_yx] = j10; out.jacobians[q].data[Tensor3D::idx_yy] = j11; out.jacobians[q].data[Tensor3D::idx_yz] = j12; - out.jacobians[q].data[Tensor3D::idx_zx] = j20; out.jacobians[q].data[Tensor3D::idx_zy] = j21; out.jacobians[q].data[Tensor3D::idx_zz] = j22; + out.jacobians[q].xx() = j00; + out.jacobians[q].xy() = j01; + out.jacobians[q].xz() = j02; + out.jacobians[q].yx() = j10; + out.jacobians[q].yy() = j11; + out.jacobians[q].yz() = j12; + out.jacobians[q].zx() = j20; + out.jacobians[q].zy() = j21; + out.jacobians[q].zz() = j22; + } + + if (out.isCellAffine) + { + for (CfemInt q = 1; q < nQp; ++q) + { + out.jacobians[q] = out.jacobians[0]; + } } } - inline void ReferenceElement::computeDeterminant(const ShapeInfo &shape, - const DynamicVector &physicalNodesCoords, - MappingInfo &out) const + inline void ReferenceElement::computeDeterminant(MappingInfo &out) const { - if (m_dimension == 3) { - out.detJ =det(out.jacobian); - } - else if (m_dimension == 2) { + if (m_dimension == 3) + { + out.detJ = det(out.jacobian); + } + else if (m_dimension == 2) + { // Métrique de surface const CfemReal j00 = out.jacobian.data[Tensor3D::idx_xx], j01 = out.jacobian.data[Tensor3D::idx_xy]; const CfemReal j10 = out.jacobian.data[Tensor3D::idx_yx], j11 = out.jacobian.data[Tensor3D::idx_yy]; const CfemReal j20 = out.jacobian.data[Tensor3D::idx_zx], j21 = out.jacobian.data[Tensor3D::idx_zy]; - CfemReal g11 = j00*j00 + j10*j10 + j20*j20; - CfemReal g22 = j01*j01 + j11*j11 + j21*j21; - CfemReal g12 = j00*j01 + j10*j11 + j20*j21; + CfemReal g11 = j00 * j00 + j10 * j10 + j20 * j20; + CfemReal g22 = j01 * j01 + j11 * j11 + j21 * j21; + CfemReal g12 = j00 * j01 + j10 * j11 + j20 * j21; out.metric2D = SymTensor2D(g11, g22, g12); out.detJ = std::sqrt(std::max(0.0, g11 * g22 - g12 * g12)); } - else if (m_dimension == 1) { + else if (m_dimension == 1) + { const CfemReal j00 = out.jacobian.data[Tensor3D::idx_xx]; const CfemReal j10 = out.jacobian.data[Tensor3D::idx_yx]; const CfemReal j20 = out.jacobian.data[Tensor3D::idx_zx]; - out.metric1D = j00*j00 + j10*j10 + j20*j20; + out.metric1D = j00 * j00 + j10 * j10 + j20 * j20; out.detJ = std::sqrt(out.metric1D); } } - inline void ReferenceElement::computeDeterminantBatch(const ShapeInfoBatch &shape, - const DynamicVector &physicalNodesCoords, - MappingInfoBatch &out) const + inline void ReferenceElement::computeDeterminantBatch(MappingInfoBatch &out) const { - const CfemInt nQp = shape.numQuadraturePoints; + - if (m_dimension == 3) { - for (CfemInt q = 0; q < nQp; ++q){ + const CfemInt nQp = out.numQuadraturePoints; + const CfemInt qEnd = out.isCellAffine ? 1 : nQp; + + if (m_dimension == 3) + { + for (CfemInt q = 0; q < qEnd; ++q) + { out.detJs[q] = det(out.jacobians[q]); } - } - else if (m_dimension == 2) { + + if (out.isCellAffine) + { + for (CfemInt q = 1; q < nQp; ++q) + out.detJs[q] = out.detJs[0]; + } + } + else if (m_dimension == 2) + { // Métrique de surface - for (CfemInt q = 0; q < nQp; ++q){ + for (CfemInt q = 0; q < qEnd; ++q) + { const CfemReal j00 = out.jacobians[q].data[Tensor3D::idx_xx], j01 = out.jacobians[q].data[Tensor3D::idx_xy]; const CfemReal j10 = out.jacobians[q].data[Tensor3D::idx_yx], j11 = out.jacobians[q].data[Tensor3D::idx_yy]; const CfemReal j20 = out.jacobians[q].data[Tensor3D::idx_zx], j21 = out.jacobians[q].data[Tensor3D::idx_zy]; - CfemReal g11 = j00*j00 + j10*j10 + j20*j20; - CfemReal g22 = j01*j01 + j11*j11 + j21*j21; - CfemReal g12 = j00*j01 + j10*j11 + j20*j21; + CfemReal g11 = j00 * j00 + j10 * j10 + j20 * j20; + CfemReal g22 = j01 * j01 + j11 * j11 + j21 * j21; + CfemReal g12 = j00 * j01 + j10 * j11 + j20 * j21; out.metric2Ds[q] = SymTensor2D{g11, g22, g12}; out.detJs[q] = std::sqrt(std::max(0.0, g11 * g22 - g12 * g12)); } + + if (out.isCellAffine) + { + for (CfemInt q = 1; q < nQp; ++q) + { + out.metric2Ds[q] = out.metric2Ds[0]; + out.detJs[q] = out.detJs[0]; + } + } } - else if (m_dimension == 1) { - for (CfemInt q = 0; q < nQp; ++q){ + else if (m_dimension == 1) + { + for (CfemInt q = 0; q < qEnd; ++q) + { const CfemReal j00 = out.jacobians[q].data[Tensor3D::idx_xx]; const CfemReal j10 = out.jacobians[q].data[Tensor3D::idx_yx]; const CfemReal j20 = out.jacobians[q].data[Tensor3D::idx_zx]; - CfemReal g11 = j00*j00 + j10*j10 + j20*j20; - + CfemReal g11 = j00 * j00 + j10 * j10 + j20 * j20; + out.metric1Ds[q] = g11; out.detJs[q] = std::sqrt(g11); } - } + if (out.isCellAffine) + { + for (CfemInt q = 1; q < nQp; ++q) + { + out.metric1Ds[q] = out.metric1Ds[0]; + out.detJs[q] = out.detJs[0]; + } + } + } } /** @@ -344,13 +436,13 @@ namespace cfem * Maps reference-space derivatives to physical-space derivatives: * \$ \nabla_x(u) = G^T \nabla_{\xi}(u) \$ * - * G is defined as the Moore-Penrose pseudo-inverse of the Jacobian J, + * G is defined as the Moore-Penrose pseudo-inverse of the Jacobian J, * depending on the manifold dimension: * - 3D (Volume) : \$ G = J^{-1} \$ * - 2D (Surface) : \$ G = (J^T J)^{-1} J^T \$ * - 1D (Curve) : \$ G = J^T / ||J||^2 \$ * - * @pre `out.jacobian`, `out.detJ`, and the appropriate metric tensor + * @pre `out.jacobian`, `out.detJ`, and the appropriate metric tensor * (`metric2D` or `metric1D`) must be precomputed. * @note Stored in `out.invJacobian` for historical compatibility. * @@ -358,13 +450,11 @@ namespace cfem * @param[in] physicalNodesCoords Physical node coordinates (unused). * @param[out] out Mapping structure receiving the operator. */ - inline void ReferenceElement::computeInverseJacobian(const ShapeInfo &shape, - const DynamicVector &physicalNodesCoords, - MappingInfo &out) const + inline void ReferenceElement::computeInverseJacobian(MappingInfo &out) const { - CFEM_ASSERT( std::abs(out.detJ) > cfem::ZERO_TOLERANCE, "Degenerate Jacobian detected" ); + CFEM_ASSERT(std::abs(out.detJ) > cfem::ZERO_TOLERANCE, "Degenerate Jacobian detected"); - Tensor3D& G = out.invJacobian; + Tensor3D &G = out.invJacobian; // ================================================================ // 3D volumetric element G = J^{-1} @@ -388,8 +478,8 @@ namespace cfem const CfemReal invDetMetric = 1.0 / (out.detJ * out.detJ); // Inverse metric tensor coefficients - const CfemReal m11 = g22 * invDetMetric; - const CfemReal m22 = g11 * invDetMetric; + const CfemReal m11 = g22 * invDetMetric; + const CfemReal m22 = g11 * invDetMetric; const CfemReal m12 = -g12 * invDetMetric; // Jacobian columns (surface tangents) @@ -402,14 +492,14 @@ namespace cfem const CfemReal j21 = out.jacobian.zy(); // First contravariant basis vector - G.xx() = m11*j00 + m12*j01; - G.xy() = m11*j10 + m12*j11; - G.xz() = m11*j20 + m12*j21; + G.xx() = m11 * j00 + m12 * j01; + G.xy() = m11 * j10 + m12 * j11; + G.xz() = m11 * j20 + m12 * j21; // Second contravariant basis vector - G.yx() = m12*j00 + m22*j01; - G.yy() = m12*j10 + m22*j11; - G.yz() = m12*j20 + m22*j21; + G.yx() = m12 * j00 + m22 * j01; + G.yy() = m12 * j10 + m22 * j11; + G.yz() = m12 * j20 + m22 * j21; // Third row unused for 2D manifolds G.zx() = 0.0; @@ -426,17 +516,17 @@ namespace cfem { const CfemReal invMetric = 1.0 / out.metric1D; - G(0,0) = out.jacobian(0,0) * invMetric; - G(0,1) = out.jacobian(1,0) * invMetric; - G(0,2) = out.jacobian(2,0) * invMetric; + G(0, 0) = out.jacobian(0, 0) * invMetric; + G(0, 1) = out.jacobian(1, 0) * invMetric; + G(0, 2) = out.jacobian(2, 0) * invMetric; - G(1,0) = 0.0; - G(1,1) = 0.0; - G(1,2) = 0.0; + G(1, 0) = 0.0; + G(1, 1) = 0.0; + G(1, 2) = 0.0; - G(2,0) = 0.0; - G(2,1) = 0.0; - G(2,2) = 0.0; + G(2, 0) = 0.0; + G(2, 1) = 0.0; + G(2, 2) = 0.0; return; } @@ -444,21 +534,24 @@ namespace cfem CFEM_ASSERT(false, "Unsupported manifold dimension"); } - inline void ReferenceElement::computeInverseJacobianBatch(const ShapeInfoBatch &shape, - const DynamicVector &physicalNodesCoords, - MappingInfoBatch &out) const + inline void ReferenceElement::computeInverseJacobianBatch(MappingInfoBatch &out) const { - const CfemInt nQp = shape.numQuadraturePoints; - CFEM_ASSERT(std::abs(out.detJs[0]) > cfem::ZERO_TOLERANCE, "Degenerate Jacobian detected"); + const CfemInt nQp = out.numQuadraturePoints; + const CfemInt qEnd = out.isCellAffine ? 1 : nQp; - - if (m_dimension == 3) { - for (CfemInt q = 0; q < nQp; ++q){ + if (m_dimension == 3) + { + for (CfemInt q = 0; q < qEnd; ++q) + { + CFEM_ASSERT(std::abs(out.detJs[q]) > cfem::ZERO_TOLERANCE, "Degenerate Jacobian detected"); out.invJacobians[q] = inverse(out.jacobians[q], out.detJs[q]); } - } - else if (m_dimension == 2) { - for (CfemInt q = 0; q < nQp; ++q){ + } + else if (m_dimension == 2) + { + for (CfemInt q = 0; q < qEnd; ++q) + { + CFEM_ASSERT(std::abs(out.detJs[q]) > cfem::ZERO_TOLERANCE, "Degenerate Jacobian detected"); const CfemReal g11 = out.metric2Ds[q].xx(); const CfemReal g22 = out.metric2Ds[q].yy(); const CfemReal g12 = out.metric2Ds[q].xy(); @@ -466,54 +559,78 @@ namespace cfem // OPTIMISATION : Réutilisation du déterminant (sauve 2 multiplications, 1 soustraction) const CfemReal invDetMetric = 1.0 / (out.detJs[q] * out.detJs[q]); - const CfemReal m11 = g22 * invDetMetric; - const CfemReal m22 = g11 * invDetMetric; + const CfemReal m11 = g22 * invDetMetric; + const CfemReal m22 = g11 * invDetMetric; const CfemReal m12 = -g12 * invDetMetric; - const auto& jac_q = out.jacobians[q]; + const auto &jac_q = out.jacobians[q]; const CfemReal j00 = jac_q.xx(), j10 = jac_q.yx(), j20 = jac_q.zx(); const CfemReal j01 = jac_q.xy(), j11 = jac_q.yy(), j21 = jac_q.zy(); // OPTIMISATION : Déroulage manuel total (zéro boucle) - auto& G = out.invJacobians[q]; - G.xx() = m11*j00 + m12*j01; G.xy() = m11*j10 + m12*j11; G.xz() = m11*j20 + m12*j21; - G.yx() = m12*j00 + m22*j01; G.yy() = m12*j10 + m22*j11; G.yz() = m12*j20 + m22*j21; - G.zx() = 0.0; G.zy() = 0.0; G.zz() = 0.0; + auto &G = out.invJacobians[q]; + G.xx() = m11 * j00 + m12 * j01; + G.xy() = m11 * j10 + m12 * j11; + G.xz() = m11 * j20 + m12 * j21; + G.yx() = m12 * j00 + m22 * j01; + G.yy() = m12 * j10 + m22 * j11; + G.yz() = m12 * j20 + m22 * j21; + G.zx() = 0.0; + G.zy() = 0.0; + G.zz() = 0.0; } } - else if (m_dimension == 1) { - for (CfemInt q = 0; q < nQp; ++q){ + else if (m_dimension == 1) + { + for (CfemInt q = 0; q < qEnd; ++q) + { + CFEM_ASSERT(std::abs(out.detJs[q]) > cfem::ZERO_TOLERANCE, "Degenerate Jacobian detected"); const CfemReal invMetric = 1.0 / out.metric1Ds[q]; - const auto& jac_q = out.jacobians[q]; - auto& G = out.invJacobians[q]; + const auto &jac_q = out.jacobians[q]; + auto &G = out.invJacobians[q]; + + G.xx() = jac_q.xx() * invMetric; + G.xy() = jac_q.yx() * invMetric; + G.xz() = jac_q.zx() * invMetric; + G.yx() = 0.0; + G.yy() = 0.0; + G.yz() = 0.0; + G.zx() = 0.0; + G.zy() = 0.0; + G.zz() = 0.0; + } + } - G.xx() = jac_q.xx() * invMetric; G.xy() = jac_q.yx() * invMetric; G.xz() = jac_q.zx() * invMetric; - G.yx() = 0.0; G.yy() = 0.0; G.yz() = 0.0; - G.zx() = 0.0; G.zy() = 0.0; G.zz() = 0.0; + if (out.isCellAffine) + { + for (CfemInt q = 1; q < nQp; ++q) + { + out.invJacobians[q] = out.invJacobians[0]; } } - } inline void ReferenceElement::computePhysicalFirstDerivatives(const ShapeInfo &shape, MappingInfo &out) const { + CFEM_ASSERT(shape.has(ShapeEvaluationFlags::FirstDerivatives), "Shapes derivatives not pre-computed"); + CFEM_ASSERT(out.has(MappingEvaluationFlags::InverseJacobian), "Inverse jacobian not pre-computed"); const CfemInt n = shape.numNodes; - + // J^{-T} appliqué au vecteur local. const CfemReal i00 = out.invJacobian.xx(), i01 = out.invJacobian.xy(), i02 = out.invJacobian.xz(); const CfemReal i10 = out.invJacobian.yx(), i11 = out.invJacobian.yy(), i12 = out.invJacobian.yz(); const CfemReal i20 = out.invJacobian.zx(), i21 = out.invJacobian.zy(), i22 = out.invJacobian.zz(); // Pointeurs SoA (lecture) - const CfemReal* __restrict dxi = shape.dN_dxi(); - const CfemReal* __restrict deta = shape.dN_deta(); - const CfemReal* __restrict dzeta = shape.dN_dzeta(); + const CfemReal *__restrict dxi = shape.dN_dxi(); + const CfemReal *__restrict deta = shape.dN_deta(); + const CfemReal *__restrict dzeta = shape.dN_dzeta(); // Pointeurs SoA (écriture) - CfemReal* __restrict dx = out.dN_dx(); - CfemReal* __restrict dy = out.dN_dy(); - CfemReal* __restrict dz = out.dN_dz(); + CfemReal *__restrict dx = out.dN_dx(); + CfemReal *__restrict dy = out.dN_dy(); + CfemReal *__restrict dz = out.dN_dz(); // Boucle SIMD ultra-rapide for (CfemInt a = 0; a < n; ++a) @@ -526,30 +643,62 @@ namespace cfem dy[a] = i01 * gx + i11 * gy + i21 * gz; dz[a] = i02 * gx + i12 * gy + i22 * gz; } + + out.validFlags |= MappingEvaluationFlags::PhysicalFirstDerivatives; } + /** + * @brief Computes physical gradients for the entire batch. + * @note Hyper-optimized utilizing SIMD friendly contiguous memory rows. + */ inline void ReferenceElement::computePhysicalFirstDerivativesBatch(const ShapeInfoBatch &shape, - MappingInfoBatch &out) const + MappingInfoBatch &out) const { + CFEM_ASSERT(shape.has(ShapeEvaluationFlags::FirstDerivatives), "Shapes derivatives not pre-computed"); + CFEM_ASSERT(out.has(MappingEvaluationFlags::InverseJacobian), "Inverse jacobian not pre-computed"); + const CfemInt nQp = shape.numQuadraturePoints; const CfemInt n = shape.numNodes; - for (CfemInt q = 0; q < nQp; ++q) + CfemReal i00, i01, i02, i10, i11, i12, i20, i21, i22; + if (out.isCellAffine && out.numQuadraturePoints > 0) + { + i00 = out.invJacobians[0].xx(); + i01 = out.invJacobians[0].xy(); + i02 = out.invJacobians[0].xz(); + i10 = out.invJacobians[0].yx(); + i11 = out.invJacobians[0].yy(); + i12 = out.invJacobians[0].yz(); + i20 = out.invJacobians[0].zx(); + i21 = out.invJacobians[0].zy(); + i22 = out.invJacobians[0].zz(); + } + + for (CfemInt q = 0; q < out.numQuadraturePoints; ++q) { - // Cache des composantes de l'inverse du Jacobien dans les registres pour le point q - const CfemReal i00 = out.invJacobians[q].xx(), i01 = out.invJacobians[q].xy(), i02 = out.invJacobians[q].xz(); - const CfemReal i10 = out.invJacobians[q].yx(), i11 = out.invJacobians[q].yy(), i12 = out.invJacobians[q].yz(); - const CfemReal i20 = out.invJacobians[q].zx(), i21 = out.invJacobians[q].zy(), i22 = out.invJacobians[q].zz(); + // Si la cellule n'est PAS affine, on charge le Jacobien spécifique à ce QP + if (!out.isCellAffine) + { + i00 = out.invJacobians[q].xx(); + i01 = out.invJacobians[q].xy(); + i02 = out.invJacobians[q].xz(); + i10 = out.invJacobians[q].yx(); + i11 = out.invJacobians[q].yy(); + i12 = out.invJacobians[q].yz(); + i20 = out.invJacobians[q].zx(); + i21 = out.invJacobians[q].zy(); + i22 = out.invJacobians[q].zz(); + } // Pointeurs SoA vers les lignes de la matrice de référence (Lecture) - const CfemReal* __restrict dxi = shape.dN_dxi(q); - const CfemReal* __restrict deta = shape.dN_deta(q); - const CfemReal* __restrict dzeta = shape.dN_dzeta(q); + const CfemReal *__restrict dxi = shape.dN_dxi(q); + const CfemReal *__restrict deta = shape.dN_deta(q); + const CfemReal *__restrict dzeta = shape.dN_dzeta(q); // Pointeurs SoA vers les lignes de la matrice physique (Écriture) - CfemReal* __restrict dx = out.dN_dx(q); - CfemReal* __restrict dy = out.dN_dy(q); - CfemReal* __restrict dz = out.dN_dz(q); + CfemReal *__restrict dx = out.dN_dx(q); + CfemReal *__restrict dy = out.dN_dy(q); + CfemReal *__restrict dz = out.dN_dz(q); // Boucle sur les nœuds interne : vectorisation SIMD maximale for (CfemInt a = 0; a < n; ++a) @@ -563,31 +712,36 @@ namespace cfem dz[a] = i02 * gx + i12 * gy + i22 * gz; } } + + out.validFlags |= MappingEvaluationFlags::PhysicalFirstDerivatives; } inline void ReferenceElement::computePhysicalSecondDerivatives(const DynamicVector &physicalNodesCoords, const ShapeInfo &shape, MappingInfo &out) const { + CFEM_ASSERT(shape.has(ShapeEvaluationFlags::FirstDerivatives), "Shapes derivatives not pre-computed"); + CFEM_ASSERT(out.has(MappingEvaluationFlags::InverseJacobian), "Inverse jacobian not pre-computed"); + const CfemInt n = shape.numNodes; - // Étape 1 : Calcul de la courbure géométrique (H_geom) + // Étape 1 : Calcul de la courbure géométrique (H_geom) CfemReal hX_xx = 0, hX_yy = 0, hX_zz = 0, hX_xy = 0, hX_xz = 0, hX_yz = 0; CfemReal hY_xx = 0, hY_yy = 0, hY_zz = 0, hY_xy = 0, hY_xz = 0, hY_yz = 0; CfemReal hZ_xx = 0, hZ_yy = 0, hZ_zz = 0, hZ_xy = 0, hZ_xz = 0, hZ_yz = 0; // Pointeurs SoA pour les Hessiens de référence - const CfemReal* __restrict d2xi2 = shape.d2N_dxi2(); - const CfemReal* __restrict d2eta2 = shape.d2N_deta2(); - const CfemReal* __restrict d2zeta2 = shape.d2N_dzeta2(); - const CfemReal* __restrict d2xideta = shape.d2N_dxideta(); - const CfemReal* __restrict d2xidzeta = shape.d2N_dxidzeta(); - const CfemReal* __restrict d2etadzeta = shape.d2N_detadzeta(); + const CfemReal *__restrict d2xi2 = shape.d2N_dxi2(); + const CfemReal *__restrict d2eta2 = shape.d2N_deta2(); + const CfemReal *__restrict d2zeta2 = shape.d2N_dzeta2(); + const CfemReal *__restrict d2xideta = shape.d2N_dxideta(); + const CfemReal *__restrict d2xidzeta = shape.d2N_dxidzeta(); + const CfemReal *__restrict d2etadzeta = shape.d2N_detadzeta(); for (CfemInt a = 0; a < n; ++a) { const Vector3D &pos = physicalNodesCoords[a]; - + // Chargement local depuis les tableaux CfemReal hxx = d2xi2[a]; CfemReal hyy = d2eta2[a]; @@ -596,14 +750,26 @@ namespace cfem CfemReal hxz = d2xidzeta[a]; CfemReal hyz = d2etadzeta[a]; - hX_xx += hxx * pos.x; hX_yy += hyy * pos.x; hX_zz += hzz * pos.x; - hX_xy += hxy * pos.x; hX_xz += hxz * pos.x; hX_yz += hyz * pos.x; - - hY_xx += hxx * pos.y; hY_yy += hyy * pos.y; hY_zz += hzz * pos.y; - hY_xy += hxy * pos.y; hY_xz += hxz * pos.y; hY_yz += hyz * pos.y; - - hZ_xx += hxx * pos.z; hZ_yy += hyy * pos.z; hZ_zz += hzz * pos.z; - hZ_xy += hxy * pos.z; hZ_xz += hxz * pos.z; hZ_yz += hyz * pos.z; + hX_xx += hxx * pos.x; + hX_yy += hyy * pos.x; + hX_zz += hzz * pos.x; + hX_xy += hxy * pos.x; + hX_xz += hxz * pos.x; + hX_yz += hyz * pos.x; + + hY_xx += hxx * pos.y; + hY_yy += hyy * pos.y; + hY_zz += hzz * pos.y; + hY_xy += hxy * pos.y; + hY_xz += hxz * pos.y; + hY_yz += hyz * pos.y; + + hZ_xx += hxx * pos.z; + hZ_yy += hyy * pos.z; + hZ_zz += hzz * pos.z; + hZ_xy += hxy * pos.z; + hZ_xz += hxz * pos.z; + hZ_yz += hyz * pos.z; } const CfemReal i00 = out.invJacobian.xx(), i01 = out.invJacobian.xy(), i02 = out.invJacobian.xz(); @@ -611,17 +777,17 @@ namespace cfem const CfemReal i20 = out.invJacobian.zx(), i21 = out.invJacobian.zy(), i22 = out.invJacobian.zz(); // Pointeurs SoA pour la lecture des gradients physiques - const CfemReal* __restrict dNdx = out.dN_dx(); - const CfemReal* __restrict dNdy = out.dN_dy(); - const CfemReal* __restrict dNdz = out.dN_dz(); + const CfemReal *__restrict dNdx = out.dN_dx(); + const CfemReal *__restrict dNdy = out.dN_dy(); + const CfemReal *__restrict dNdz = out.dN_dz(); // Pointeurs SoA pour l'écriture des Hessiens physiques - CfemReal* __restrict d2x2 = out.d2N_dx2(); - CfemReal* __restrict d2y2 = out.d2N_dy2(); - CfemReal* __restrict d2z2 = out.d2N_dz2(); - CfemReal* __restrict dxdy = out.d2N_dxdy(); - CfemReal* __restrict dxdz = out.d2N_dxdz(); - CfemReal* __restrict dydz = out.d2N_dydz(); + CfemReal *__restrict d2x2 = out.d2N_dx2(); + CfemReal *__restrict d2y2 = out.d2N_dy2(); + CfemReal *__restrict d2z2 = out.d2N_dz2(); + CfemReal *__restrict dxdy = out.d2N_dxdy(); + CfemReal *__restrict dxdz = out.d2N_dxdz(); + CfemReal *__restrict dydz = out.d2N_dydz(); // --- Étape 2 : Matrice R et Transformation vers l'espace physique --- for (CfemInt a = 0; a < n; ++a) @@ -634,40 +800,40 @@ namespace cfem const CfemReal gy = dNdy[a]; const CfemReal gz = dNdz[a]; - const CfemReal rxx = d2xi2[a] - (gx*hX_xx + gy*hY_xx + gz*hZ_xx); - const CfemReal ryy = d2eta2[a] - (gx*hX_yy + gy*hY_yy + gz*hZ_yy); - const CfemReal rzz = d2zeta2[a] - (gx*hX_zz + gy*hY_zz + gz*hZ_zz); - const CfemReal rxy = d2xideta[a] - (gx*hX_xy + gy*hY_xy + gz*hZ_xy); - const CfemReal rxz = d2xidzeta[a] - (gx*hX_xz + gy*hY_xz + gz*hZ_xz); - const CfemReal ryz = d2etadzeta[a] - (gx*hX_yz + gy*hY_yz + gz*hZ_yz); + const CfemReal rxx = d2xi2[a] - (gx * hX_xx + gy * hY_xx + gz * hZ_xx); + const CfemReal ryy = d2eta2[a] - (gx * hX_yy + gy * hY_yy + gz * hZ_yy); + const CfemReal rzz = d2zeta2[a] - (gx * hX_zz + gy * hY_zz + gz * hZ_zz); + const CfemReal rxy = d2xideta[a] - (gx * hX_xy + gy * hY_xy + gz * hZ_xy); + const CfemReal rxz = d2xidzeta[a] - (gx * hX_xz + gy * hY_xz + gz * hZ_xz); + const CfemReal ryz = d2etadzeta[a] - (gx * hX_yz + gy * hY_yz + gz * hZ_yz); // ========================================================= // T = R * invJ // ========================================================= - const CfemReal t00 = rxx*i00 + rxy*i10 + rxz*i20; - const CfemReal t01 = rxx*i01 + rxy*i11 + rxz*i21; - const CfemReal t02 = rxx*i02 + rxy*i12 + rxz*i22; + const CfemReal t00 = rxx * i00 + rxy * i10 + rxz * i20; + const CfemReal t01 = rxx * i01 + rxy * i11 + rxz * i21; + const CfemReal t02 = rxx * i02 + rxy * i12 + rxz * i22; - const CfemReal t10 = rxy*i00 + ryy*i10 + ryz*i20; - const CfemReal t11 = rxy*i01 + ryy*i11 + ryz*i21; - const CfemReal t12 = rxy*i02 + ryy*i12 + ryz*i22; + const CfemReal t10 = rxy * i00 + ryy * i10 + ryz * i20; + const CfemReal t11 = rxy * i01 + ryy * i11 + ryz * i21; + const CfemReal t12 = rxy * i02 + ryy * i12 + ryz * i22; - const CfemReal t20 = rxz*i00 + ryz*i10 + rzz*i20; - const CfemReal t21 = rxz*i01 + ryz*i11 + rzz*i21; - const CfemReal t22 = rxz*i02 + ryz*i12 + rzz*i22; + const CfemReal t20 = rxz * i00 + ryz * i10 + rzz * i20; + const CfemReal t21 = rxz * i01 + ryz * i11 + rzz * i21; + const CfemReal t22 = rxz * i02 + ryz * i12 + rzz * i22; // ========================================================= // H = invJ^T * T // ========================================================= - d2x2[a] = i00*t00 + i10*t10 + i20*t20; - d2y2[a] = i01*t01 + i11*t11 + i21*t21; - d2z2[a] = i02*t02 + i12*t12 + i22*t22; + d2x2[a] = i00 * t00 + i10 * t10 + i20 * t20; + d2y2[a] = i01 * t01 + i11 * t11 + i21 * t21; + d2z2[a] = i02 * t02 + i12 * t12 + i22 * t22; - dxdy[a] = i00*t01 + i10*t11 + i20*t21; - dxdz[a] = i00*t02 + i10*t12 + i20*t22; - dydz[a] = i01*t02 + i11*t12 + i21*t22; + dxdy[a] = i00 * t01 + i10 * t11 + i20 * t21; + dxdz[a] = i00 * t02 + i10 * t12 + i20 * t22; + dydz[a] = i01 * t02 + i11 * t12 + i21 * t22; } } @@ -689,12 +855,12 @@ namespace cfem CfemReal hY_xx = 0, hY_yy = 0, hY_zz = 0, hY_xy = 0, hY_xz = 0, hY_yz = 0; CfemReal hZ_xx = 0, hZ_yy = 0, hZ_zz = 0, hZ_xy = 0, hZ_xz = 0, hZ_yz = 0; - const CfemReal* __restrict d2xi2 = shape.d2N_dxi2(q); - const CfemReal* __restrict d2eta2 = shape.d2N_deta2(q); - const CfemReal* __restrict d2zeta2 = shape.d2N_dzeta2(q); - const CfemReal* __restrict d2xideta = shape.d2N_dxideta(q); - const CfemReal* __restrict d2xidzeta = shape.d2N_dxidzeta(q); - const CfemReal* __restrict d2etadzeta = shape.d2N_detadzeta(q); + const CfemReal *__restrict d2xi2 = shape.d2N_dxi2(q); + const CfemReal *__restrict d2eta2 = shape.d2N_deta2(q); + const CfemReal *__restrict d2zeta2 = shape.d2N_dzeta2(q); + const CfemReal *__restrict d2xideta = shape.d2N_dxideta(q); + const CfemReal *__restrict d2xidzeta = shape.d2N_dxidzeta(q); + const CfemReal *__restrict d2etadzeta = shape.d2N_detadzeta(q); for (CfemInt a = 0; a < n; ++a) { @@ -706,18 +872,30 @@ namespace cfem CfemReal hxz = d2xidzeta[a]; CfemReal hyz = d2etadzeta[a]; - hX_xx += hxx * pos.x; hX_yy += hyy * pos.x; hX_zz += hzz * pos.x; - hX_xy += hxy * pos.x; hX_xz += hxz * pos.x; hX_yz += hyz * pos.x; - - hY_xx += hxx * pos.y; hY_yy += hyy * pos.y; hY_zz += hzz * pos.y; - hY_xy += hxy * pos.y; hY_xz += hxz * pos.y; hY_yz += hyz * pos.y; - - hZ_xx += hxx * pos.z; hZ_yy += hyy * pos.z; hZ_zz += hzz * pos.z; - hZ_xy += hxy * pos.z; hZ_xz += hxz * pos.z; hZ_yz += hyz * pos.z; + hX_xx += hxx * pos.x; + hX_yy += hyy * pos.x; + hX_zz += hzz * pos.x; + hX_xy += hxy * pos.x; + hX_xz += hxz * pos.x; + hX_yz += hyz * pos.x; + + hY_xx += hxx * pos.y; + hY_yy += hyy * pos.y; + hY_zz += hzz * pos.y; + hY_xy += hxy * pos.y; + hY_xz += hxz * pos.y; + hY_yz += hyz * pos.y; + + hZ_xx += hxx * pos.z; + hZ_yy += hyy * pos.z; + hZ_zz += hzz * pos.z; + hZ_xy += hxy * pos.z; + hZ_xz += hxz * pos.z; + hZ_yz += hyz * pos.z; } // Récupération de l'inverse du Jacobien pour le point q - const auto& invJ = out.invJacobians[q]; + const auto &invJ = out.invJacobians[q]; // ========================================================= // PRÉ-CHARGEMENT : Extraction dans les registres locaux @@ -727,16 +905,16 @@ namespace cfem const CfemReal i20 = invJ.zx(), i21 = invJ.zy(), i22 = invJ.zz(); // Recouvrement des pointeurs de lignes physiques (Lecture des gradients, Écriture des Hessiens) - const CfemReal* __restrict dNdx = out.dN_dx(q); - const CfemReal* __restrict dNdy = out.dN_dy(q); - const CfemReal* __restrict dNdz = out.dN_dz(q); + const CfemReal *__restrict dNdx = out.dN_dx(q); + const CfemReal *__restrict dNdy = out.dN_dy(q); + const CfemReal *__restrict dNdz = out.dN_dz(q); - CfemReal* __restrict d2x2 = out.d2N_dx2(q); - CfemReal* __restrict d2y2 = out.d2N_dy2(q); - CfemReal* __restrict d2z2 = out.d2N_dz2(q); - CfemReal* __restrict dxdy = out.d2N_dxdy(q); - CfemReal* __restrict dxdz = out.d2N_dxdz(q); - CfemReal* __restrict dydz = out.d2N_dydz(q); + CfemReal *__restrict d2x2 = out.d2N_dx2(q); + CfemReal *__restrict d2y2 = out.d2N_dy2(q); + CfemReal *__restrict d2z2 = out.d2N_dz2(q); + CfemReal *__restrict dxdy = out.d2N_dxdy(q); + CfemReal *__restrict dxdz = out.d2N_dxdz(q); + CfemReal *__restrict dydz = out.d2N_dydz(q); // Étape 2 : Calcul de R et application de la congruence pour chaque nœud for (CfemInt a = 0; a < n; ++a) @@ -748,133 +926,152 @@ namespace cfem const CfemReal gy = dNdy[a]; const CfemReal gz = dNdz[a]; - const CfemReal rxx = d2xi2[a] - (gx*hX_xx + gy*hY_xx + gz*hZ_xx); - const CfemReal ryy = d2eta2[a] - (gx*hX_yy + gy*hY_yy + gz*hZ_yy); - const CfemReal rzz = d2zeta2[a] - (gx*hX_zz + gy*hY_zz + gz*hZ_zz); - const CfemReal rxy = d2xideta[a] - (gx*hX_xy + gy*hY_xy + gz*hZ_xy); - const CfemReal rxz = d2xidzeta[a] - (gx*hX_xz + gy*hY_xz + gz*hZ_xz); - const CfemReal ryz = d2etadzeta[a] - (gx*hX_yz + gy*hY_yz + gz*hZ_yz); + const CfemReal rxx = d2xi2[a] - (gx * hX_xx + gy * hY_xx + gz * hZ_xx); + const CfemReal ryy = d2eta2[a] - (gx * hX_yy + gy * hY_yy + gz * hZ_yy); + const CfemReal rzz = d2zeta2[a] - (gx * hX_zz + gy * hY_zz + gz * hZ_zz); + const CfemReal rxy = d2xideta[a] - (gx * hX_xy + gy * hY_xy + gz * hZ_xy); + const CfemReal rxz = d2xidzeta[a] - (gx * hX_xz + gy * hY_xz + gz * hZ_xz); + const CfemReal ryz = d2etadzeta[a] - (gx * hX_yz + gy * hY_yz + gz * hZ_yz); // ========================================================= // T = R * invJ // ========================================================= - const CfemReal t00 = rxx*i00 + rxy*i10 + rxz*i20; - const CfemReal t01 = rxx*i01 + rxy*i11 + rxz*i21; - const CfemReal t02 = rxx*i02 + rxy*i12 + rxz*i22; + const CfemReal t00 = rxx * i00 + rxy * i10 + rxz * i20; + const CfemReal t01 = rxx * i01 + rxy * i11 + rxz * i21; + const CfemReal t02 = rxx * i02 + rxy * i12 + rxz * i22; - const CfemReal t10 = rxy*i00 + ryy*i10 + ryz*i20; - const CfemReal t11 = rxy*i01 + ryy*i11 + ryz*i21; - const CfemReal t12 = rxy*i02 + ryy*i12 + ryz*i22; + const CfemReal t10 = rxy * i00 + ryy * i10 + ryz * i20; + const CfemReal t11 = rxy * i01 + ryy * i11 + ryz * i21; + const CfemReal t12 = rxy * i02 + ryy * i12 + ryz * i22; - const CfemReal t20 = rxz*i00 + ryz*i10 + rzz*i20; - const CfemReal t21 = rxz*i01 + ryz*i11 + rzz*i21; - const CfemReal t22 = rxz*i02 + ryz*i12 + rzz*i22; + const CfemReal t20 = rxz * i00 + ryz * i10 + rzz * i20; + const CfemReal t21 = rxz * i01 + ryz * i11 + rzz * i21; + const CfemReal t22 = rxz * i02 + ryz * i12 + rzz * i22; // ========================================================= // H = invJ^T * T // ========================================================= - d2x2[a] = i00*t00 + i10*t10 + i20*t20; - d2y2[a] = i01*t01 + i11*t11 + i21*t21; - d2z2[a] = i02*t02 + i12*t12 + i22*t22; + d2x2[a] = i00 * t00 + i10 * t10 + i20 * t20; + d2y2[a] = i01 * t01 + i11 * t11 + i21 * t21; + d2z2[a] = i02 * t02 + i12 * t12 + i22 * t22; - dxdy[a] = i00*t01 + i10*t11 + i20*t21; - dxdz[a] = i00*t02 + i10*t12 + i20*t22; - dydz[a] = i01*t02 + i11*t12 + i21*t22; + dxdy[a] = i00 * t01 + i10 * t11 + i20 * t21; + dxdz[a] = i00 * t02 + i10 * t12 + i20 * t22; + dydz[a] = i01 * t02 + i11 * t12 + i21 * t22; } } } - - inline void ReferenceElement::computeGeometricNormal(const ShapeInfo &shape, MappingInfo &out) const + inline void ReferenceElement::computeGeometricNormal(MappingInfo &out) const { - if (m_dimension == 2) + CFEM_ASSERT(out.has(MappingEvaluationFlags::Jacobian), "Jacobian not pre-computed"); + + if (m_dimension == 2) { // Variété 2D (Surface, Triangle/Quad) dans l'espace 3D // Les deux vecteurs tangents sont les deux premières colonnes du Jacobien Vector3D t1(out.jacobian.xx(), out.jacobian.yx(), out.jacobian.zx()); Vector3D t2(out.jacobian.xy(), out.jacobian.yy(), out.jacobian.zy()); - - out.normal = (t1.cross(t2)).normalized(); + out.normal = (t1.cross(t2)).normalized(); } - else if (m_dimension == 1) + else if (m_dimension == 1) { // Variété 1D (Ligne) dans un espace 2D (ou 3D z=0) CfemReal dx = out.jacobian.xx(); CfemReal dy = out.jacobian.yx(); CfemReal norm = std::sqrt(dx * dx + dy * dy); - - if (norm > cfem::ZERO_TOLERANCE) { + + if (norm > cfem::ZERO_TOLERANCE) + { // Règle de la main droite (rotation de -90 degrés du vecteur tangent) // L'orientation extérieure dépend de la convention de rotation de ton maillage - out.normal = Vector3D(dy / norm, -dx / norm, 0.0); - } else { + out.normal = Vector3D(dy / norm, -dx / norm, 0.0); + } + else + { out.normal = Vector3D(0.0, 0.0, 0.0); } } - else + else { // Dimension 3 (Volume). Geometric normal undefined inside a 3D cell. out.normal = Vector3D(0.0, 0.0, 0.0); } } - inline void ReferenceElement::computeGeometricNormalBatch(const ShapeInfoBatch &shapeBatch, - MappingInfoBatch &outBatch) const + inline void ReferenceElement::computeGeometricNormalBatch(MappingInfoBatch &outBatch) const { - const CfemInt nQp = shapeBatch.numQuadraturePoints; - CFEM_ASSERT(outBatch.jacobians.size() == nQp, "ReferenceElement::computeGeometricNormalBatch outBatch.jacobians not pre-computed?"); - if (m_dimension == 2) + CFEM_ASSERT(outBatch.has(MappingEvaluationFlags::Jacobian), "Jacobian not pre-computed"); + + const CfemInt nQp = outBatch.numQuadraturePoints; + const CfemInt qEnd = outBatch.isCellAffine ? 1 : nQp; + + if (m_dimension == 2) { // Variété 2D (Surface, Triangle/Quad) dans l'espace 3D // Les deux vecteurs tangents sont les deux premières colonnes du Jacobien - for (CfemInt q = 0; q < nQp; ++q){ + for (CfemInt q = 0; q < qEnd; ++q) + { Vector3D t1(outBatch.jacobians[q].xx(), outBatch.jacobians[q].yx(), outBatch.jacobians[q].zx()); Vector3D t2(outBatch.jacobians[q].xy(), outBatch.jacobians[q].yy(), outBatch.jacobians[q].zy()); - + outBatch.normals[q] = (t1.cross(t2)).normalized(); } - } - else if (m_dimension == 1) + else if (m_dimension == 1) { - for (CfemInt q = 0; q < nQp; ++q){ + for (CfemInt q = 0; q < qEnd; ++q) + { // Variété 1D (Ligne) dans un espace 2D (ou 3D z=0) CfemReal dx = outBatch.jacobians[q].xx(); CfemReal dy = outBatch.jacobians[q].yx(); CfemReal norm = std::sqrt(dx * dx + dy * dy); - - if (norm > cfem::ZERO_TOLERANCE) { + + if (norm > cfem::ZERO_TOLERANCE) + { // Règle de la main droite (rotation de -90 degrés du vecteur tangent) // L'orientation extérieure dépend de la convention de rotation de ton maillage - outBatch.normals[q] = Vector3D(dy / norm, -dx / norm, 0.0); - } else { + outBatch.normals[q] = Vector3D(dy / norm, -dx / norm, 0.0); + } + else + { outBatch.normals[q] = Vector3D(0.0, 0.0, 0.0); } } } - else + else { // Dimension 3 (Volume). Geometric normal undefined inside a 3D cell. - for (CfemInt q = 0; q < nQp; ++q){ + for (CfemInt q = 0; q < qEnd; ++q) + { outBatch.normals[q] = Vector3D(0.0, 0.0, 0.0); } } + + if (outBatch.isCellAffine) + { + for (CfemInt q = 1; q < nQp; ++q) + { + outBatch.normals[q] = outBatch.normals[0]; + } + } } // ---------------------------------------------------------------------- // ------------------------------- interpolation ------------------------ - template - inline T ReferenceElement::interpolate(const ShapeInfo& shape, - const DynamicVector& nodalValues) const + template + inline T ReferenceElement::interpolate(const ShapeInfo &shape, + const DynamicVector &nodalValues) const { const CfemInt n = shape.numNodes; CFEM_ASSERT(shape.has(ShapeEvaluationFlags::Value), "Shape values not computed"); CFEM_ASSERT(nodalValues.size() == n, "Nodal values size mismatch"); T result{}; - const CfemReal* N = shape.values(); + const CfemReal *N = shape.values(); - for (CfemInt i = 0; i < n; ++i){ + for (CfemInt i = 0; i < n; ++i) + { result += nodalValues[i] * N[i]; } return result; @@ -884,7 +1081,7 @@ namespace cfem const DynamicVector &nodalValues) const { const CfemInt n = map.numNodes; - CFEM_ASSERT(hasFlag(map.validFlags, MappingEvaluationFlags::PhysicalFirstDerivatives), + CFEM_ASSERT(hasFlag(map.validFlags, MappingEvaluationFlags::PhysicalFirstDerivatives), "Physical gradients not computed in MappingInfo"); CFEM_ASSERT(nodalValues.size() == n, "Nodal values size mismatch"); @@ -894,11 +1091,12 @@ namespace cfem CfemReal gradZ = 0.0; // Pointeurs vers la mémoire SoA - const CfemReal* dx = map.dN_dx(); - const CfemReal* dy = map.dN_dy(); - const CfemReal* dz = map.dN_dz(); + const CfemReal *dx = map.dN_dx(); + const CfemReal *dy = map.dN_dy(); + const CfemReal *dz = map.dN_dz(); - for (CfemInt i = 0; i < n; ++i) { + for (CfemInt i = 0; i < n; ++i) + { const CfemReal u = nodalValues[i]; gradX += dx[i] * u; gradY += dy[i] * u; @@ -912,19 +1110,19 @@ namespace cfem const DynamicVector &nodalValues) const { const CfemInt n = map.numNodes; - CFEM_ASSERT(hasFlag(map.validFlags, MappingEvaluationFlags::PhysicalSecondDerivatives), + CFEM_ASSERT(hasFlag(map.validFlags, MappingEvaluationFlags::PhysicalSecondDerivatives), "Physical hessians not computed in MappingInfo"); CFEM_ASSERT(nodalValues.size() == n, "Nodal values size mismatch"); CfemReal h11 = 0.0, h22 = 0.0, h33 = 0.0; CfemReal h12 = 0.0, h13 = 0.0, h23 = 0.0; - const CfemReal* d2x = map.d2N_dx2(); - const CfemReal* d2y = map.d2N_dy2(); - const CfemReal* d2z = map.d2N_dz2(); - const CfemReal* dxdy = map.d2N_dxdy(); - const CfemReal* dxdz = map.d2N_dxdz(); - const CfemReal* dydz = map.d2N_dydz(); + const CfemReal *d2x = map.d2N_dx2(); + const CfemReal *d2y = map.d2N_dy2(); + const CfemReal *d2z = map.d2N_dz2(); + const CfemReal *dxdy = map.d2N_dxdy(); + const CfemReal *dxdz = map.d2N_dxdz(); + const CfemReal *dydz = map.d2N_dydz(); for (CfemInt i = 0; i < n; ++i) { @@ -940,4 +1138,4 @@ namespace cfem return SymTensor3D(h11, h22, h33, h23, h13, h12); } -} // namespace cfem \ No newline at end of file +} // namespace cfem \ No newline at end of file diff --git a/libs/fem/reference_element/include/ReferenceQuad.h b/libs/fem/reference_element/include/ReferenceQuad.h index 9d0cb83..3874a1b 100644 --- a/libs/fem/reference_element/include/ReferenceQuad.h +++ b/libs/fem/reference_element/include/ReferenceQuad.h @@ -35,7 +35,7 @@ namespace cfem::reference_elem::utils { case 1: return Vector3D(1.0, u, 0.0); // Face {1,2} : x=1 case 2: return Vector3D(-u, 1.0, 0.0); // Face {2,3} : y=1 case 3: return Vector3D(-1.0, -u, 0.0); // Face {3,0} : x=-1 - default: CFEM_ERROR << "Invalid facet ID for Quad cell"; + default: CFEM_THROW("Invalid facet ID for Quad cell"); } return Vector3D{}; // Make the compiler happy } diff --git a/libs/fem/reference_element/include/ReferenceTetrahedron.h b/libs/fem/reference_element/include/ReferenceTetrahedron.h index 487b7be..166b73b 100644 --- a/libs/fem/reference_element/include/ReferenceTetrahedron.h +++ b/libs/fem/reference_element/include/ReferenceTetrahedron.h @@ -105,8 +105,8 @@ namespace cfem::reference_elem::kernels { } [[gnu::always_inline]] static inline void grads(CfemReal* __restrict dx, - CfemReal* __restrict dy, - CfemReal* __restrict dz) noexcept + CfemReal* __restrict dy, + CfemReal* __restrict dz) noexcept { dx[0] = -1.0; dx[1] = 1.0; dx[2] = 0.0; dx[3] = 0.0; dy[0] = -1.0; dy[1] = 0.0; dy[2] = 1.0; dy[3] = 0.0; @@ -177,7 +177,7 @@ namespace cfem::reference_elem::kernels { struct P2TetraKernel { [[gnu::always_inline]] static inline void values(CfemReal L0, CfemReal L1, CfemReal L2, CfemReal L3, - CfemReal* __restrict N) noexcept + CfemReal* N) noexcept { N[0]=L0*(2*L0-1); N[1]=L1*(2*L1-1); N[2]=L2*(2*L2-1); N[3]=L3*(2*L3-1); N[4]=4*L0*L1; N[5]=4*L1*L2; N[6]=4*L2*L0; @@ -265,7 +265,7 @@ namespace cfem { const size_t bytesToCopy = m_numNodes * sizeof(CfemReal); for (CfemInt q = 1; q < nQp; ++q) { - std::memcpy(shapeBatch.values(q), shapeBatch.values(0), bytesToCopy); + shapeBatch.values(q)[0] = shapeBatch.values(0)[0]; } } @@ -277,11 +277,17 @@ namespace cfem { reference_elem::kernels::P0TetraKernel::grads(shapeBatch.dN_dxi(0), shapeBatch.dN_deta(0), shapeBatch.dN_dzeta(0)); - const size_t bytesToCopy = m_numNodes * sizeof(CfemReal); + // const size_t bytesToCopy = m_numNodes * sizeof(CfemReal); + // for (CfemInt q = 1; q < nQp; ++q) { + // std::memcpy(shapeBatch.dN_dxi(q), shapeBatch.dN_dxi(0), bytesToCopy); + // std::memcpy(shapeBatch.dN_deta(q), shapeBatch.dN_deta(0), bytesToCopy); + // std::memcpy(shapeBatch.dN_dzeta(q), shapeBatch.dN_dzeta(0), bytesToCopy); + // } + for (CfemInt q = 1; q < nQp; ++q) { - std::memcpy(shapeBatch.dN_dxi(q), shapeBatch.dN_dxi(0), bytesToCopy); - std::memcpy(shapeBatch.dN_deta(q), shapeBatch.dN_deta(0), bytesToCopy); - std::memcpy(shapeBatch.dN_dzeta(q), shapeBatch.dN_dzeta(0), bytesToCopy); + shapeBatch.dN_dxi(q)[0] = shapeBatch.dN_dxi(0)[0]; + shapeBatch.dN_deta(q)[0] = shapeBatch.dN_deta(0)[0]; + shapeBatch.dN_dzeta(q)[0] = shapeBatch.dN_dzeta(0)[0]; } } @@ -296,14 +302,23 @@ namespace cfem { shapeBatch.d2N_dxideta(0), shapeBatch.d2N_dxidzeta(0), shapeBatch.d2N_detadzeta(0) ); - const size_t bytesToCopy = m_numNodes * sizeof(CfemReal); + // const size_t bytesToCopy = m_numNodes * sizeof(CfemReal); + // for (CfemInt q = 1; q < nQp; ++q) { + // std::memcpy(shapeBatch.d2N_dxi2(q), shapeBatch.d2N_dxi2(0), bytesToCopy); + // std::memcpy(shapeBatch.d2N_deta2(q), shapeBatch.d2N_deta2(0), bytesToCopy); + // std::memcpy(shapeBatch.d2N_dzeta2(q), shapeBatch.d2N_dzeta2(0), bytesToCopy); + // std::memcpy(shapeBatch.d2N_dxideta(q), shapeBatch.d2N_dxideta(0), bytesToCopy); + // std::memcpy(shapeBatch.d2N_dxidzeta(q), shapeBatch.d2N_dxidzeta(0), bytesToCopy); + // std::memcpy(shapeBatch.d2N_detadzeta(q), shapeBatch.d2N_detadzeta(0), bytesToCopy); + // } + for (CfemInt q = 1; q < nQp; ++q) { - std::memcpy(shapeBatch.d2N_dxi2(q), shapeBatch.d2N_dxi2(0), bytesToCopy); - std::memcpy(shapeBatch.d2N_deta2(q), shapeBatch.d2N_deta2(0), bytesToCopy); - std::memcpy(shapeBatch.d2N_dzeta2(q), shapeBatch.d2N_dzeta2(0), bytesToCopy); - std::memcpy(shapeBatch.d2N_dxideta(q), shapeBatch.d2N_dxideta(0), bytesToCopy); - std::memcpy(shapeBatch.d2N_dxidzeta(q), shapeBatch.d2N_dxidzeta(0), bytesToCopy); - std::memcpy(shapeBatch.d2N_detadzeta(q), shapeBatch.d2N_detadzeta(0), bytesToCopy); + shapeBatch.d2N_dxi2(q)[0] = shapeBatch.d2N_dxi2(0)[0]; + shapeBatch.d2N_deta2(q)[0] = shapeBatch.d2N_deta2(0)[0]; + shapeBatch.d2N_dzeta2(q)[0] = shapeBatch.d2N_dzeta2(0)[0]; + shapeBatch.d2N_dxideta(q)[0] = shapeBatch.d2N_dxideta(0)[0]; + shapeBatch.d2N_dxidzeta(q)[0] = shapeBatch.d2N_dxidzeta(0)[0]; + shapeBatch.d2N_detadzeta(q)[0] = shapeBatch.d2N_detadzeta(0)[0]; } } diff --git a/libs/fem/reference_element/include/ReferenceTriangle.h b/libs/fem/reference_element/include/ReferenceTriangle.h index f923fca..fef8f28 100644 --- a/libs/fem/reference_element/include/ReferenceTriangle.h +++ b/libs/fem/reference_element/include/ReferenceTriangle.h @@ -74,7 +74,7 @@ namespace cfem::reference_elem::kernels { // P0 TRIANGLE KERNEL (1 node, constant) // ========================================================================= struct P0TriKernel { - [[gnu::always_inline]] static inline void values(CfemReal* __restrict N) noexcept { + [[gnu::always_inline]] static inline void values(CfemReal* N) noexcept { N[0] = 1.0; } @@ -95,7 +95,7 @@ namespace cfem::reference_elem::kernels { // ========================================================================= struct P1TriKernel { [[gnu::always_inline]] static inline void values(CfemReal L0, CfemReal L1, CfemReal L2, - CfemReal* __restrict N) noexcept { + CfemReal* N) noexcept { N[0] = L0; N[1] = L1; N[2] = L2; } @@ -120,7 +120,7 @@ namespace cfem::reference_elem::kernels { // ========================================================================= struct MiniTriKernel { [[gnu::always_inline]] static inline void values(CfemReal L0, CfemReal L1, CfemReal L2, - CfemReal* __restrict N) noexcept { + CfemReal* N) noexcept { const CfemReal b = 27.0 * L0 * L1 * L2; N[3] = b; N[0] = L0 - b / 3.0; @@ -165,7 +165,7 @@ namespace cfem::reference_elem::kernels { // ========================================================================= struct P2TriKernel { [[gnu::always_inline]] static inline void values(CfemReal L0, CfemReal L1, CfemReal L2, - CfemReal* __restrict N) noexcept { + CfemReal* N) noexcept { N[0] = L0 * (2.0 * L0 - 1.0); N[1] = L1 * (2.0 * L1 - 1.0); N[2] = L2 * (2.0 * L2 - 1.0); diff --git a/libs/fem/reference_element/include/ShapeInfo.h b/libs/fem/reference_element/include/ShapeInfo.h index 9a69b49..58a044d 100644 --- a/libs/fem/reference_element/include/ShapeInfo.h +++ b/libs/fem/reference_element/include/ShapeInfo.h @@ -93,8 +93,7 @@ namespace cfem // ================================================================================================ /** @brief Returns a writable pointer to shape function values. */ - [[nodiscard]] inline CfemReal* values() { - CFEM_ASSERT(has(ShapeEvaluationFlags::Value), "Shape values not computed?"); + [[nodiscard]] inline CfemReal* values() noexcept { return storage.data(); } @@ -103,20 +102,17 @@ namespace cfem // ================================================================================================ /** @brief Returns a writable pointer to shape derivatives along the reference \f$\xi\f$ direction. */ - [[nodiscard]] inline CfemReal* dN_dxi() { - CFEM_ASSERT(has(ShapeEvaluationFlags::FirstDerivatives), "Shape first derivatives not computed?"); + [[nodiscard]] inline CfemReal* dN_dxi() noexcept { return storage.data() + 1 * stride; } /** @brief Returns a writable pointer to shape derivatives along the reference \f$\eta\f$ direction. */ - [[nodiscard]] inline CfemReal* dN_deta() { - CFEM_ASSERT(has(ShapeEvaluationFlags::FirstDerivatives), "Shape first derivatives not computed?"); + [[nodiscard]] inline CfemReal* dN_deta() noexcept { return storage.data() + 2 * stride; } /** @brief Returns a writable pointer to shape derivatives along the reference \f$\zeta\f$ direction. */ - [[nodiscard]] inline CfemReal* dN_dzeta() { - CFEM_ASSERT(has(ShapeEvaluationFlags::FirstDerivatives), "Shape first derivatives not computed?"); + [[nodiscard]] inline CfemReal* dN_dzeta() noexcept { return storage.data() + 3 * stride; } @@ -125,33 +121,27 @@ namespace cfem // ================================================================================================ /** @brief Returns a writable pointer to shape second derivatives \f$\partial^2 N \partial \zeta^2\f$ direction. */ - [[nodiscard]] inline CfemReal* d2N_dxi2() { - CFEM_ASSERT(hasSecondDerivatives, "Second derivative not computed?"); + [[nodiscard]] inline CfemReal* d2N_dxi2() noexcept { return storage.data() + 4 * stride; } - [[nodiscard]] inline CfemReal* d2N_deta2() { - CFEM_ASSERT(hasSecondDerivatives, "Second derivative not computed?"); + [[nodiscard]] inline CfemReal* d2N_deta2() noexcept { return storage.data() + 5 * stride; } - [[nodiscard]] inline CfemReal* d2N_dzeta2() { - CFEM_ASSERT(hasSecondDerivatives, "Second derivative not computed?"); + [[nodiscard]] inline CfemReal* d2N_dzeta2() noexcept { return storage.data() + 6 * stride; } - [[nodiscard]] inline CfemReal* d2N_dxideta() { - CFEM_ASSERT(hasSecondDerivatives, "Second derivative not computed?"); + [[nodiscard]] inline CfemReal* d2N_dxideta() noexcept { return storage.data() + 7 * stride; } - [[nodiscard]] inline CfemReal* d2N_dxidzeta() { - CFEM_ASSERT(hasSecondDerivatives, "Second derivative not computed?"); + [[nodiscard]] inline CfemReal* d2N_dxidzeta() noexcept { return storage.data() + 8 * stride; } - [[nodiscard]] inline CfemReal* d2N_detadzeta() { - CFEM_ASSERT(hasSecondDerivatives, "Second derivative not computed?"); + [[nodiscard]] inline CfemReal* d2N_detadzeta() noexcept { return storage.data() + 9 * stride; } @@ -321,61 +311,51 @@ namespace cfem [[nodiscard]] inline CfemReal* values(CfemInt q) { CFEM_ASSERT(q >= 0 && q < numQuadraturePoints, "Invalid QP index"); - CFEM_ASSERT(has(ShapeEvaluationFlags::Value), "Shape values not computed?"); return storage.data() + q * qpStride + 0 * paddedNodeStride; } [[nodiscard]] inline CfemReal* dN_dxi(CfemInt q) { CFEM_ASSERT(q >= 0 && q < numQuadraturePoints, "Invalid QP index"); - CFEM_ASSERT(has(ShapeEvaluationFlags::FirstDerivatives), "Shape first derivatives not computed?"); return storage.data() + q * qpStride + 1 * paddedNodeStride; } [[nodiscard]] inline CfemReal* dN_deta(CfemInt q) { CFEM_ASSERT(q >= 0 && q < numQuadraturePoints, "Invalid QP index"); - CFEM_ASSERT(has(ShapeEvaluationFlags::FirstDerivatives), "Shape first derivatives not computed?"); return storage.data() + q * qpStride + 2 * paddedNodeStride; } [[nodiscard]] inline CfemReal* dN_dzeta(CfemInt q) { CFEM_ASSERT(q >= 0 && q < numQuadraturePoints, "Invalid QP index"); - CFEM_ASSERT(has(ShapeEvaluationFlags::FirstDerivatives), "Shape first derivatives not computed?"); return storage.data() + q * qpStride + 3 * paddedNodeStride; } [[nodiscard]] inline CfemReal* d2N_dxi2(CfemInt q) { CFEM_ASSERT(q >= 0 && q < numQuadraturePoints, "Invalid QP index"); - CFEM_ASSERT(hasSecondDerivatives, "Second derivatives not allocated"); return storage.data() + q * qpStride + 4 * paddedNodeStride; } [[nodiscard]] inline CfemReal* d2N_deta2(CfemInt q) { CFEM_ASSERT(q >= 0 && q < numQuadraturePoints, "Invalid QP index"); - CFEM_ASSERT(hasSecondDerivatives, "Second derivatives not allocated"); return storage.data() + q * qpStride + 5 * paddedNodeStride; } [[nodiscard]] inline CfemReal* d2N_dzeta2(CfemInt q) { CFEM_ASSERT(q >= 0 && q < numQuadraturePoints, "Invalid QP index"); - CFEM_ASSERT(hasSecondDerivatives, "Second derivatives not allocated"); return storage.data() + q * qpStride + 6 * paddedNodeStride; } [[nodiscard]] inline CfemReal* d2N_dxideta(CfemInt q) { CFEM_ASSERT(q >= 0 && q < numQuadraturePoints, "Invalid QP index"); - CFEM_ASSERT(hasSecondDerivatives, "Second derivatives not allocated"); return storage.data() + q * qpStride + 7 * paddedNodeStride; } [[nodiscard]] inline CfemReal* d2N_dxidzeta(CfemInt q) { CFEM_ASSERT(q >= 0 && q < numQuadraturePoints, "Invalid QP index"); - CFEM_ASSERT(hasSecondDerivatives, "Second derivatives not allocated"); return storage.data() + q * qpStride + 8 * paddedNodeStride; } [[nodiscard]] inline CfemReal* d2N_detadzeta(CfemInt q) { CFEM_ASSERT(q >= 0 && q < numQuadraturePoints, "Invalid QP index"); - CFEM_ASSERT(hasSecondDerivatives, "Second derivatives not allocated"); return storage.data() + q * qpStride + 9 * paddedNodeStride; } diff --git a/libs/prepostprocessing/numerical_integration/src/FunctionIntegrator.cpp b/libs/prepostprocessing/numerical_integration/src/FunctionIntegrator.cpp index 1571dee..babc06e 100644 --- a/libs/prepostprocessing/numerical_integration/src/FunctionIntegrator.cpp +++ b/libs/prepostprocessing/numerical_integration/src/FunctionIntegrator.cpp @@ -193,6 +193,254 @@ namespace cfem else for (const auto &target : targets) if (target.second) *(target.second) = 0.0; } +// void FunctionIntegrator::integrate(const std::vector &targets, +// const MeshEntity &entity) +// { +// if (targets.empty()) return; +// CFEM_INFO << "Integrating over entity " << entity.getName(); + +// // ============================================================ +// // ANALYSE ET BINDING O(1) +// // ============================================================ +// std::vector activeIdx; +// ShapeEvaluationFlags fieldShapesFlags = ShapeEvaluationFlags::None; +// bool fieldsDependsOnGeoNormal = false; +// std::set> requiredSpaces; +// CfemInt geoOrder = m_mesh->getOrderInt(); + +// for (size_t i = 0; i < targets.size(); ++i) { +// if (!targets[i].first) continue; + +// if (targets[i].first->getNumComponents() != 1) { +// CFEM_THROW( "FunctionIntegrator only accepts scalar expressions. " +// << "Please pass each component separately."); +// } + +// targets[i].first->prepare(); + +// if (targets[i].second) *(targets[i].second) = 0.0; +// activeIdx.push_back(i); +// fieldShapesFlags |= targets[i].first->requiredShapeFlags(); +// fieldsDependsOnGeoNormal = fieldsDependsOnGeoNormal || targets[i].first->dependsOnGeometricNormal(); + +// targets[i].first->collectRequiredSpaces(requiredSpaces); +// } + +// if (activeIdx.empty()) return; + +// std::map spaceToId; +// std::vector> idToSpace; +// CfemInt nextId = 0; +// for (const auto& sp : requiredSpaces) { +// spaceToId[sp.get()] = nextId++; +// idToSpace.push_back(sp); +// } + + +// for (size_t idx : activeIdx) { +// targets[idx].first->bind(spaceToId); +// } + +// const bool needsFieldFirstDerivatives = hasFlag(fieldShapesFlags, ShapeEvaluationFlags::FirstDerivatives); +// const bool needsFieldSecondDerivatives = hasFlag(fieldShapesFlags, ShapeEvaluationFlags::SecondDerivatives); + +// MappingEvaluationFlags geoMappingFlags = MappingEvaluationFlags::PhysicalPosition| +// MappingEvaluationFlags::Determinant | +// (fieldsDependsOnGeoNormal? MappingEvaluationFlags::Normal : MappingEvaluationFlags::None); +// ShapeEvaluationFlags geoShapeFlags = transformMappingToShapeFlags(geoMappingFlags) ; + +// if (needsFieldFirstDerivatives) { +// geoShapeFlags |= ShapeEvaluationFlags::FirstDerivatives; +// geoMappingFlags |= MappingEvaluationFlags::InverseJacobian; +// } + +// IntegrationScheme scheme; + +// // ============================================================ +// // QUADRATURE ET CACHING DE REFERENCE (Read-Only) +// // ============================================================ +// struct SpaceCache { +// CfemInt contextIndex; +// std::vector shapes; +// }; + +// struct CellCacheEntry { +// const QuadratureRule* quad = nullptr; +// std::vector geoShapes; +// std::vector spaceCaches; +// }; + +// std::vector fastCache(static_cast(CellType::COUNT)); + +// for (CellType cell_type : m_mesh->getCellTypesPresent()) +// { +// CfemInt idx = static_cast(cell_type); +// const auto &quad = scheme.getRule(cell_type, m_schemes_params.getNumIntgPt(cell_type)); +// const auto &lQuadPts = quad.getPoints(); + +// fastCache[idx].quad = &quad; +// fastCache[idx].geoShapes.resize(lQuadPts.size()); + +// const auto &refElGeo = ReferenceElementFactory::getReferenceElement(cell_type, geoOrder); + +// for (const auto& space : idToSpace) { +// fastCache[idx].spaceCaches.push_back({spaceToId[space.get()], std::vector(lQuadPts.size())}); +// } + +// for (size_t q = 0; q < lQuadPts.size(); ++q) { +// refElGeo.evaluateShapes(lQuadPts[q].xi, geoShapeFlags, fastCache[idx].geoShapes[q]); +// fastCache[idx].geoShapes[q].shapeMustBeUpdated = false; + +// for (size_t sIdx = 0; sIdx < idToSpace.size(); ++sIdx) { +// CfemInt spaceOrder = idToSpace[sIdx]->getOrderInt(); +// const auto& refElSpace = ReferenceElementFactory::getReferenceElement(cell_type, spaceOrder); + +// refElSpace.evaluateShapes(lQuadPts[q].xi, fieldShapesFlags, fastCache[idx].spaceCaches[sIdx].shapes[q]); +// fastCache[idx].spaceCaches[sIdx].shapes[q].shapeMustBeUpdated = false; +// } +// } +// } + +// const std::vector &cells_vIds = entity.getCellIds(); + +// // ============================================================ +// // BOUCLE D'INTÉGRATION PARALLÈLE OPTIMISÉE +// // ============================================================ + +// #pragma omp parallel +// { +// std::vector threadTotals(targets.size(), 0.0); +// EvalContext ctx; +// CfemReal outVal[9] = {0.0}; +// std::span spanOut(outVal, 1); + +// MappingInfo mapGeo; +// std::vector spaceMappings(idToSpace.size()); +// DynamicVector physCoords; + +// CellType lastType = CellType::COUNT; +// const ReferenceElement* refElGeo = nullptr; +// const CellCacheEntry* cache = nullptr; + +// #pragma omp for schedule(static) +// for (CfemInt i = 0; i < entity.getNumCells(); ++i) +// { +// CfemInt cellid = cells_vIds[i]; +// const auto &cell = m_mesh->getCell(cellid); + +// // Maintien du cache local O(1) : On ne "setup" que si la topologie change +// if (cell.type != lastType) { +// lastType = cell.type; +// refElGeo = &ReferenceElementFactory::getReferenceElement(lastType, geoOrder); +// cache = &fastCache[static_cast(lastType)]; + +// mapGeo.setup(refElGeo->getNumNodes()); +// for (size_t sIdx = 0; sIdx < idToSpace.size(); ++sIdx) { +// CfemInt nNodesSpace = static_cast(cache->spaceCaches[sIdx].shapes[0].numNodes); +// spaceMappings[sIdx].setup(nNodesSpace); +// } +// } + +// const auto &lQuadPts = cache->quad->getPoints(); +// m_mesh->getCellVerticesCoords(cellid, physCoords); + +// const bool isAffine = (geoOrder == 1) || m_mesh->isCellAffine(cellid); + +// if (isAffine) { +// // CFEM_INFO << cache->geoShapes[0].gradient(2); +// refElGeo->computeMapping( +// lQuadPts[0].xi, +// physCoords, +// geoMappingFlags, +// cache->geoShapes[0], +// mapGeo +// ); +// } + +// for (size_t q = 0; q < lQuadPts.size(); ++q) +// { +// const auto &qp = lQuadPts[q]; + +// if (isAffine) { +// refElGeo->computeMapping( +// qp.xi, +// physCoords, +// MappingEvaluationFlags::PhysicalPosition, +// cache->geoShapes[q], +// mapGeo +// ); +// } else { +// refElGeo->computeMapping( +// qp.xi, +// physCoords, +// geoMappingFlags, +// cache->geoShapes[q], +// mapGeo +// ); +// } + +// const CfemReal wDetJ = mapGeo.detJ * qp.weight; + +// ctx.resetBuffers(); // Vide l'index de cache d'expressions pour ce point +// auto bookmark = ctx.getBookmark(); // Sauvegarde l'état exact de l'Arena mémoire + +// ctx.currentCellId = cellid; +// ctx.singleWS.xi = qp.xi; +// ctx.singleWS.physPos = mapGeo.physicalPosition; +// ctx.geometricMapping = &mapGeo; + +// for (size_t sIdx = 0; sIdx < cache->spaceCaches.size(); ++sIdx) { +// const auto& spCache = cache->spaceCaches[sIdx]; +// MappingInfo& mapSpace = spaceMappings[sIdx]; + +// mapSpace.detJ = mapGeo.detJ; +// mapSpace.invJacobian = mapGeo.invJacobian; +// mapSpace.physicalPosition = mapGeo.physicalPosition; +// mapSpace.validFlags |= MappingEvaluationFlags::PhysicalPosition | MappingEvaluationFlags::InverseJacobian; + +// // CFEM_INFO << "gradient de reference: "; +// // for (CfemInt l = 0; l < 3; ++l) +// // { +// // CFEM_INFO +// // << spCache.shapes[q].gradient(l); +// // } + +// if (needsFieldFirstDerivatives) { +// //refElGeo.computePhysicalFirstDerivativesOnly(spCache.shapes[q]); +// refElGeo->computePhysicalFirstDerivatives(spCache.shapes[q], mapSpace); +// } + +// // CFEM_INFO << "gradient de Physiques: "; + +// // for (CfemInt l = 0; l < 3; ++l) +// // { +// // CFEM_INFO +// // << mapSpace.gradient(l); +// // } + +// ctx.setSpaceData(spCache.contextIndex, &spCache.shapes[q], &mapSpace); +// } + +// for (size_t idx : activeIdx) { +// targets[idx].first->evaluate(cellid, qp.xi, spanOut, ctx); +// threadTotals[idx] += spanOut[0] * wDetJ; +// } + +// // Libère TOUTE la mémoire temporaire allouée par `evaluate` pour ce point +// ctx.releaseToBookmark(bookmark); +// // --------------------------------------------------------- +// } +// } + +// #pragma omp critical +// { +// for (size_t idx : activeIdx) { +// if (targets[idx].second) *(targets[idx].second) += threadTotals[idx]; +// } +// } +// } +// } + void FunctionIntegrator::integrate(const std::vector &targets, const MeshEntity &entity) { @@ -200,11 +448,11 @@ namespace cfem CFEM_INFO << "Integrating over entity " << entity.getName(); // ============================================================ - // ANALYSE ET BINDING O(1) + // PHASE 1 : ANALYSE ET BINDING O(1) // ============================================================ std::vector activeIdx; ShapeEvaluationFlags fieldShapesFlags = ShapeEvaluationFlags::None; - bool fieldsDependsOnGeoNormal = false; + bool fieldsDependsOnGeoNormal = false; std::set> requiredSpaces; CfemInt geoOrder = m_mesh->getOrderInt(); @@ -212,8 +460,8 @@ namespace cfem if (!targets[i].first) continue; if (targets[i].first->getNumComponents() != 1) { - CFEM_THROW( "FunctionIntegrator only accepts scalar expressions. " - << "Please pass each component separately."); + CFEM_THROW("FunctionIntegrator only accepts scalar expressions. " + << "Please pass each component separately."); } targets[i].first->prepare(); @@ -228,6 +476,7 @@ namespace cfem if (activeIdx.empty()) return; + // Association des espaces à des IDs uniques pour le contexte std::map spaceToId; std::vector> idToSpace; CfemInt nextId = 0; @@ -236,7 +485,6 @@ namespace cfem idToSpace.push_back(sp); } - for (size_t idx : activeIdx) { targets[idx].first->bind(spaceToId); } @@ -244,33 +492,35 @@ namespace cfem const bool needsFieldFirstDerivatives = hasFlag(fieldShapesFlags, ShapeEvaluationFlags::FirstDerivatives); const bool needsFieldSecondDerivatives = hasFlag(fieldShapesFlags, ShapeEvaluationFlags::SecondDerivatives); - MappingEvaluationFlags geoMappingFlags = MappingEvaluationFlags::PhysicalPosition| - MappingEvaluationFlags::Determinant | - (fieldsDependsOnGeoNormal? MappingEvaluationFlags::Normal : MappingEvaluationFlags::None); - ShapeEvaluationFlags geoShapeFlags = transformMappingToShapeFlags(geoMappingFlags) ; + // Configuration des flags géométriques requis + MappingEvaluationFlags geoMappingFlags = MappingEvaluationFlags::PhysicalPosition | + MappingEvaluationFlags::Determinant | + (fieldsDependsOnGeoNormal ? MappingEvaluationFlags::Normal : MappingEvaluationFlags::None); + ShapeEvaluationFlags geoShapeFlags = transformMappingToShapeFlags(geoMappingFlags); if (needsFieldFirstDerivatives) { geoShapeFlags |= ShapeEvaluationFlags::FirstDerivatives; - geoMappingFlags |= MappingEvaluationFlags::InverseJacobian; + geoMappingFlags |= MappingEvaluationFlags::InverseJacobian; } IntegrationScheme scheme; // ============================================================ - // QUADRATURE ET CACHING DE REFERENCE (Read-Only) + // PHASE 2 : CACHING DE REFERENCE BATCH (Read-Only) // ============================================================ - struct SpaceCache { + struct SpaceCacheBatch { CfemInt contextIndex; - std::vector shapes; + ShapeInfoBatch shapesBatch; }; - struct CellCacheEntry { + struct CellCacheEntryBatch { const QuadratureRule* quad = nullptr; - std::vector geoShapes; - std::vector spaceCaches; + DynamicVector xiPoints; // Coordonnées de référence pour le Batch + ShapeInfoBatch geoShapesBatch; + std::vector spaceCaches; }; - std::vector fastCache(static_cast(CellType::COUNT)); + std::vector fastCache(static_cast(CellType::COUNT)); for (CellType cell_type : m_mesh->getCellTypesPresent()) { @@ -279,48 +529,55 @@ namespace cfem const auto &lQuadPts = quad.getPoints(); fastCache[idx].quad = &quad; - fastCache[idx].geoShapes.resize(lQuadPts.size()); + + // Extraction des points xi pour l'interface Batch + CfemInt nQp = static_cast(lQuadPts.size()); + fastCache[idx].xiPoints.resize(nQp); + for (CfemInt q = 0; q < nQp; ++q) { + fastCache[idx].xiPoints[q] = lQuadPts[q].xi; + } const auto &refElGeo = ReferenceElementFactory::getReferenceElement(cell_type, geoOrder); + + // Évaluation Géométrique de Référence par Lot + refElGeo.evaluateShapesBatch(fastCache[idx].xiPoints, geoShapeFlags, fastCache[idx].geoShapesBatch); + fastCache[idx].geoShapesBatch.shapeMustBeUpdated = false; + // Évaluation des Espaces de Champs par Lot for (const auto& space : idToSpace) { - fastCache[idx].spaceCaches.push_back({spaceToId[space.get()], std::vector(lQuadPts.size())}); - } - - for (size_t q = 0; q < lQuadPts.size(); ++q) { - refElGeo.evaluateShapes(lQuadPts[q].xi, geoShapeFlags, fastCache[idx].geoShapes[q]); - fastCache[idx].geoShapes[q].shapeMustBeUpdated = false; - - for (size_t sIdx = 0; sIdx < idToSpace.size(); ++sIdx) { - CfemInt spaceOrder = idToSpace[sIdx]->getOrderInt(); - const auto& refElSpace = ReferenceElementFactory::getReferenceElement(cell_type, spaceOrder); - - refElSpace.evaluateShapes(lQuadPts[q].xi, fieldShapesFlags, fastCache[idx].spaceCaches[sIdx].shapes[q]); - fastCache[idx].spaceCaches[sIdx].shapes[q].shapeMustBeUpdated = false; - } + SpaceCacheBatch spCache; + spCache.contextIndex = spaceToId[space.get()]; + + CfemInt spaceOrder = space->getOrderInt(); + const auto& refElSpace = ReferenceElementFactory::getReferenceElement(cell_type, spaceOrder); + + refElSpace.evaluateShapesBatch(fastCache[idx].xiPoints, fieldShapesFlags, spCache.shapesBatch); + spCache.shapesBatch.shapeMustBeUpdated = false; + + fastCache[idx].spaceCaches.push_back(std::move(spCache)); } } const std::vector &cells_vIds = entity.getCellIds(); // ============================================================ - // BOUCLE D'INTÉGRATION PARALLÈLE OPTIMISÉE + // PHASE 3 : BOUCLE D'INTÉGRATION PARALLÈLE (100% BATCH) // ============================================================ - - #pragma omp parallel +#pragma omp parallel { std::vector threadTotals(targets.size(), 0.0); EvalContext ctx; - CfemReal outVal[9] = {0.0}; - std::span spanOut(outVal, 1); + + // --- NOUVEAU : Buffer par lot pour stocker la sortie de evaluateBatch --- + std::vector batchOutVals; - MappingInfo mapGeo; - std::vector spaceMappings(idToSpace.size()); + MappingInfoBatch mapGeoBatch; + std::vector spaceMappingsBatch(idToSpace.size()); DynamicVector physCoords; CellType lastType = CellType::COUNT; const ReferenceElement* refElGeo = nullptr; - const CellCacheEntry* cache = nullptr; + const CellCacheEntryBatch* cache = nullptr; #pragma omp for schedule(static) for (CfemInt i = 0; i < entity.getNumCells(); ++i) @@ -328,105 +585,98 @@ namespace cfem CfemInt cellid = cells_vIds[i]; const auto &cell = m_mesh->getCell(cellid); - // Maintien du cache local O(1) : On ne "setup" que si la topologie change + // Maintien du cache local O(1) if (cell.type != lastType) { lastType = cell.type; refElGeo = &ReferenceElementFactory::getReferenceElement(lastType, geoOrder); cache = &fastCache[static_cast(lastType)]; - mapGeo.setup(refElGeo->getNumNodes()); + CfemInt nQp = cache->xiPoints.size(); + mapGeoBatch.setup(nQp, refElGeo->getNumNodes(), refElGeo->dimension(), false); + for (size_t sIdx = 0; sIdx < idToSpace.size(); ++sIdx) { - CfemInt nNodesSpace = static_cast(cache->spaceCaches[sIdx].shapes[0].numNodes); - spaceMappings[sIdx].setup(nNodesSpace); + CfemInt nNodesSpace = static_cast(cache->spaceCaches[sIdx].shapesBatch.numNodes); + spaceMappingsBatch[sIdx].setup(nQp, nNodesSpace, refElGeo->dimension(), needsFieldSecondDerivatives); + } + + // Redimensionner le buffer de sortie pour qu'il puisse contenir tous les QP + if (batchOutVals.size() < static_cast(nQp)) { + batchOutVals.resize(nQp); } } - const auto &lQuadPts = cache->quad->getPoints(); m_mesh->getCellVerticesCoords(cellid, physCoords); - const bool isAffine = (geoOrder == 1) || m_mesh->isCellAffine(cellid); - if (isAffine) { - // CFEM_INFO << cache->geoShapes[0].gradient(2); - refElGeo->computeMapping( - lQuadPts[0].xi, - physCoords, - geoMappingFlags, - cache->geoShapes[0], - mapGeo - ); - } - - for (size_t q = 0; q < lQuadPts.size(); ++q) - { - const auto &qp = lQuadPts[q]; - - if (isAffine) { - refElGeo->computeMapping( - qp.xi, - physCoords, - MappingEvaluationFlags::PhysicalPosition, - cache->geoShapes[q], - mapGeo - ); - } else { - refElGeo->computeMapping( - qp.xi, - physCoords, - geoMappingFlags, - cache->geoShapes[q], - mapGeo - ); - } - - const CfemReal wDetJ = mapGeo.detJ * qp.weight; - - ctx.resetBuffers(); // Vide l'index de cache d'expressions pour ce point - auto bookmark = ctx.getBookmark(); // Sauvegarde l'état exact de l'Arena mémoire - - ctx.currentCellId = cellid; - ctx.singleWS.xi = qp.xi; - ctx.singleWS.physPos = mapGeo.physicalPosition; - ctx.geometricMapping = &mapGeo; + // ------------------------------------------------------------- + // ÉTAPE 3.1 : CALCULS BATCH GÉOMÉTRIQUES + // ------------------------------------------------------------- + mapGeoBatch.isCellAffine = isAffine; + + // CFEM_INFO << "fieldsDependsOnGeoNormal = " << fieldsDependsOnGeoNormal; + // CFEM_INFO << "HASFLAG NORMALE = " << hasFlag(geoMappingFlags, MappingEvaluationFlags::Normal); + + refElGeo->computeMappingBatch( + cache->xiPoints, + physCoords, + geoMappingFlags, + cache->geoShapesBatch, + mapGeoBatch + ); - for (size_t sIdx = 0; sIdx < cache->spaceCaches.size(); ++sIdx) { - const auto& spCache = cache->spaceCaches[sIdx]; - MappingInfo& mapSpace = spaceMappings[sIdx]; + for (size_t sIdx = 0; sIdx < cache->spaceCaches.size(); ++sIdx) { + const auto& spCache = cache->spaceCaches[sIdx]; + MappingInfoBatch& mapSpace = spaceMappingsBatch[sIdx]; - mapSpace.detJ = mapGeo.detJ; - mapSpace.invJacobian = mapGeo.invJacobian; - mapSpace.physicalPosition = mapGeo.physicalPosition; + mapSpace.detJs = mapGeoBatch.detJs; + mapSpace.invJacobians = mapGeoBatch.invJacobians; + mapSpace.physicalPositions = mapGeoBatch.physicalPositions; + mapSpace.validFlags |= MappingEvaluationFlags::PhysicalPosition | MappingEvaluationFlags::InverseJacobian; - // CFEM_INFO << "gradient de reference: "; - // for (CfemInt l = 0; l < 3; ++l) - // { - // CFEM_INFO - // << spCache.shapes[q].gradient(l); - // } + if (needsFieldFirstDerivatives) { + refElGeo->computePhysicalFirstDerivativesBatch(spCache.shapesBatch, mapSpace); + } + } - if (needsFieldFirstDerivatives) { - mapSpace.computePhysicalFirstDerivativesOnly(spCache.shapes[q]); - } + // ------------------------------------------------------------- + // ÉTAPE 3.2 : CONFIGURATION DU CONTEXTE O(1) + // ------------------------------------------------------------- + ctx.currentCellId = cellid; + ctx.geometricMappingBatch = &mapGeoBatch; + + // On lie les espaces au contexte UNE SEULE FOIS pour toute la cellule + for (size_t sIdx = 0; sIdx < cache->spaceCaches.size(); ++sIdx) { + ctx.setSpaceDataBatch( + cache->spaceCaches[sIdx].contextIndex, + &cache->spaceCaches[sIdx].shapesBatch, + &spaceMappingsBatch[sIdx] + ); + } - // CFEM_INFO << "gradient de Physiques: "; + // ------------------------------------------------------------- + // ÉTAPE 3.3 : ÉVALUATION DES EXPRESSIONS (100% BATCH) + // ------------------------------------------------------------- + const auto &lQuadPts = cache->quad->getPoints(); + const CfemInt nQp = static_cast(lQuadPts.size()); + std::span spanOutBatch(batchOutVals.data(), nQp); + ctx.batchWS.physPos = &mapGeoBatch.physicalPositions; - // for (CfemInt l = 0; l < 3; ++l) - // { - // CFEM_INFO - // << mapSpace.gradient(l); - // } + for (size_t idx : activeIdx) { + ctx.resetBuffers(); + auto bookmark = ctx.getBookmark(); - ctx.setSpaceData(spCache.contextIndex, &spCache.shapes[q], &mapSpace); - } + // 1. Évaluation de l'arbre d'expression complet pour TOUS les QP d'un coup + targets[idx].first->evaluateBatch(cellid, nQp, spanOutBatch, ctx); - for (size_t idx : activeIdx) { - targets[idx].first->evaluate(cellid, qp.xi, spanOut, ctx); - threadTotals[idx] += spanOut[0] * wDetJ; + // 2. Intégration numérique vectorisée (Réduction) + // Cette boucle est extrêmement simple, le compilateur la vectorisera automatiquement. + CfemReal localSum = 0.0; + for (CfemInt q = 0; q < nQp; ++q) { + localSum += spanOutBatch[q] * mapGeoBatch.detJs[q] * lQuadPts[q].weight; } + threadTotals[idx] += localSum; - // Libère TOUTE la mémoire temporaire allouée par `evaluate` pour ce point ctx.releaseToBookmark(bookmark); - // --------------------------------------------------------- } } @@ -439,7 +689,6 @@ namespace cfem } } - void FunctionIntegrator::integrateBoundary(const std::vector &targets, const MeshEntity &boundary, const MeshEntity &volume) @@ -597,6 +846,8 @@ namespace cfem if (parentId == -1) continue; + const bool isAffine = (geoOrder == 1) || m_mesh->isCellAffine(parentId); + const auto &parentCell = m_mesh->getCell(parentId); const auto& cache = parentFaceCache[static_cast(parentCell.type)][localFacetId]; const auto &lQuadPts = cache.quad->getPoints(); @@ -614,8 +865,6 @@ namespace cfem const auto &refElFacet = ReferenceElementFactory::getReferenceElement(facet.type, geoOrder); const auto &refElParentGeo = ReferenceElementFactory::getReferenceElement(parentCell.type, geoOrder); - const bool isAffine = (geoOrder == 1) || m_mesh->isCellAffine(parentId); - if (isAffine) { refElParentGeo.computeMapping( lQuadPts[0].xi, @@ -683,8 +932,10 @@ namespace cfem if (needsFieldFirstDerivatives) { mapSpace.detJ = mapParentGeo.detJ; mapSpace.invJacobian = mapParentGeo.invJacobian; - mapSpace.physicalPosition = mapSurf.physicalPosition; - mapSpace.computePhysicalFirstDerivativesOnly(spCache.shapes[q]); + mapSpace.physicalPosition = mapSurf.physicalPosition; + mapSpace.validFlags |= MappingEvaluationFlags::PhysicalPosition | MappingEvaluationFlags::InverseJacobian; + + refElParentGeo.computePhysicalFirstDerivatives(spCache.shapes[q], mapSpace); } ctx.setSpaceData(spCache.contextIndex, &spCache.shapes[q], &mapSpace); From 9d42c8ad26de4f0fd2330ed63a09e31e072050ce Mon Sep 17 00:00:00 2001 From: itchinda Date: Wed, 27 May 2026 16:03:59 -0400 Subject: [PATCH 7/9] New change in Batch evals --- libs/CMakeLists.txt | 1 + libs/expressions/CMakeLists.txt | 1 + libs/expressions/include/EvalContext.h | 357 --------- libs/fem/assembly/CMakeLists.txt | 8 +- libs/fem/exportation/CMakeLists.txt | 11 +- libs/fem/fefield/CMakeLists.txt | 1 + libs/fem/fefield/include/FEField.h | 2 +- libs/fem/fefield/src/FEField.cpp | 74 +- libs/fem/reference_element/CMakeLists.txt | 5 +- .../include/ReferenceElement.h | 9 +- .../include/ReferenceElement.tpp | 133 ++++ libs/kernel/CMakeLists.txt | 13 + libs/kernel/include/EvalContext.h | 675 ++++++++++++++++++ .../include/EvaluationFlags.h | 0 libs/kernel/include/MappingInfo.h | 203 ++++++ .../include/MappingInfoBatch.h} | 239 ++----- libs/kernel/include/MappingInfoBatchView.h | 158 ++++ libs/kernel/include/ShapeInfo.h | 247 +++++++ .../include/ShapeInfoBatch.h} | 256 +------ libs/kernel/include/kernel_utils.h | 46 ++ libs/kernel/src/EvalContext.cpp | 17 + .../numerical_integration/CMakeLists.txt | 15 +- .../src/FunctionIntegrator.cpp | 347 ++------- libs/utils/include/DynamicVector.h | 4 + tests/CMakeLists.txt | 1 + 25 files changed, 1722 insertions(+), 1101 deletions(-) delete mode 100644 libs/expressions/include/EvalContext.h create mode 100644 libs/kernel/CMakeLists.txt create mode 100644 libs/kernel/include/EvalContext.h rename libs/{fem/reference_element => kernel}/include/EvaluationFlags.h (100%) create mode 100644 libs/kernel/include/MappingInfo.h rename libs/{fem/reference_element/include/MappingInfo.h => kernel/include/MappingInfoBatch.h} (56%) create mode 100644 libs/kernel/include/MappingInfoBatchView.h create mode 100644 libs/kernel/include/ShapeInfo.h rename libs/{fem/reference_element/include/ShapeInfo.h => kernel/include/ShapeInfoBatch.h} (52%) create mode 100644 libs/kernel/include/kernel_utils.h create mode 100644 libs/kernel/src/EvalContext.cpp diff --git a/libs/CMakeLists.txt b/libs/CMakeLists.txt index c12b4bd..97f7687 100644 --- a/libs/CMakeLists.txt +++ b/libs/CMakeLists.txt @@ -1,5 +1,6 @@ add_subdirectory(expressions) add_subdirectory(fem) +add_subdirectory(kernel) add_subdirectory(prepostprocessing) add_subdirectory(solvers) add_subdirectory(utils) diff --git a/libs/expressions/CMakeLists.txt b/libs/expressions/CMakeLists.txt index 5c31762..61f7047 100644 --- a/libs/expressions/CMakeLists.txt +++ b/libs/expressions/CMakeLists.txt @@ -27,6 +27,7 @@ target_include_directories(cfem_expressions target_link_libraries(cfem_expressions PUBLIC cfem_utils + cfem_kernel cfem_mesh cfem_reference_element SymEngine::libsymengine diff --git a/libs/expressions/include/EvalContext.h b/libs/expressions/include/EvalContext.h deleted file mode 100644 index 058d45e..0000000 --- a/libs/expressions/include/EvalContext.h +++ /dev/null @@ -1,357 +0,0 @@ -//************************************************************************ -// --- CFEM++ Library -// --- -// --- Copyright 2024-2026 Ismaël Tchinda Ngueyong et al. -// --- ALL RIGHTS RESERVED -// --- -// --- This software is protected by international copyright laws. -// --- Use, distribution, or modification of this software in any form, -// --- source or binary, for personal, academic, or non-commercial -// --- purposes is permitted free of charge, provided that this -// --- copyright notice and this permission notice appear in all -// --- copies and supporting documentation. -// --- -// --- The authors and contributors provide this code "as is" without -// --- any express or implied warranty. In no event shall they be -// --- held liable for any damages arising from the use of this software. -//************************************************************************ - -#pragma once - -#include "cfemutils.h" -#include "Vector3D.h" -#include "ShapeInfo.h" -#include "MappingInfo.h" -#include "DynamicVector.h" -#include "DynamicMatrix.h" - -#include -#include -#include -#include -#include -#include - -namespace cfem { - - class FESpace; // Forward declaration - - // Forward declarations des structures Batch (définies dans votre architecture SoA) - struct ShapeInfoBatch; - struct MappingInfoBatch; - - /** - * @struct SpaceDataEntry - * @brief Ultra-fast link between an FE Space and its evaluations at the current integration cell. - * Uses explicit pointers to safely separate legacy/single-point data from vectorized batch data. - */ - struct SpaceDataEntry { - const ShapeInfo* shapeSingle = nullptr; - const MappingInfo* mappingSingle = nullptr; - - const ShapeInfoBatch* shapeBatch = nullptr; - const MappingInfoBatch* mappingBatch = nullptr; - }; - - /** - * @struct CacheEntry - * @brief Stores a pointer-based identifier and its associated data span. - */ - struct CacheEntry { - const void* id; - std::span data; - }; - - /** - * @struct EvalContext - * @brief High-performance evaluation context for Finite Element assembly. - * Provides O(1) access to geometric data and an Arena Memory system. - * Structured to support both Single-Point (Probing) and Batch (Assembly) evaluation modes. - */ - struct EvalContext { - - // ==================================================================== - // GLOBAL SIMULATION STATE - // ==================================================================== - CfemReal currentTime = 0.0; /**< Current simulation time (for transient problems). */ - CfemInt currentCellId = -1; /**< Index of the cell currently being integrated. */ - CfemInt currentQpIndex = 0; - - // ==================================================================== - // SINGLE-POINT WORKSPACE (Legacy / Probing) - // ==================================================================== - struct SingleWorkspace { - Vector3D physPos = {}; /**< Physical coordinates. */ - Vector3D xi = {-1e9, -1e9, -1e9};/**< Reference coordinates. */ - ShapeInfo shape; /**< Buffer for base functions. */ - MappingInfo mapping; /**< Buffer for geometric mapping. */ - } singleWS; - - // ==================================================================== - // BATCH WORKSPACE (Vectorized Element Assembly) - // ==================================================================== - struct BatchWorkspace { - DynamicVector* physPos; /**< Physical coordinates for all QPs. */ - DynamicVector* xi; /**< Reference coordinates for all QPs. */ - ShapeInfoBatch* shape = nullptr; /**< Pointer to allocated batch shape memory. */ - MappingInfoBatch* mapping = nullptr; /**< Pointer to allocated batch metric memory. */ - } batchWS; - - // ==================================================================== - // MULTI-SPACE CACHE & SHORTCUTS (O(1) Access) - // ==================================================================== - static constexpr CfemInt MAX_CACHED_SPACES = 16; - - #ifdef CFEM_USE_DYNAMIC_SPACE_CACHE - std::vector m_spaceDataCache; - #else - std::array m_spaceDataCache; - #endif - - const ShapeInfoBatch* trialShapeBatch = nullptr; - const ShapeInfoBatch* testShapeBatch = nullptr; - const MappingInfoBatch* trialMappingBatch = nullptr; - const MappingInfoBatch* testMappingBatch = nullptr; - const MappingInfoBatch* geometricMappingBatch = nullptr; - - - const MappingInfo* geometricMapping = nullptr; - - CfemInt trialDof = -1; - CfemInt testDof = -1; - - // ==================================================================== - // PRE-ALLOCATED SCRATCHPADS - // ==================================================================== - DynamicVector scratchCoords; /**< Buffer for cell vertex coordinates. */ - std::vector scratchDofs; /**< Buffer for gathering local DOF indices. */ - DynamicMatrix scratchCellDofs; /**< Buffer for cell DOF values. */ - MappingInfo scratchMapping; /**< Buffer for cell DOF values. */ - ShapeInfo scratchShape; /**< Buffer for cell DOF values. */ - // ==================================================================== - // ARENA MEMORY SYSTEM - // ==================================================================== - static constexpr size_t PAGE_SIZE = 4096; - - std::deque> m_pages; - size_t m_currentPageIdx = 0; - size_t m_currentOffset = 0; - - std::vector m_expressionCache; - - // ==================================================================== - // API METHODS - // ==================================================================== - - EvalContext() - { - m_pages.emplace_back(PAGE_SIZE); - m_expressionCache.reserve(16); -#ifdef CFEM_USE_DYNAMIC_SPACE_CACHE - m_spaceDataCache.reserve(8); -#endif - } - -#ifdef CFEM_USE_DYNAMIC_SPACE_CACHE - inline void prepareSpaceCache(CfemInt n) { - m_spaceDataCache.resize(n); - } -#endif - - inline const SpaceDataEntry& getSpaceData(CfemInt index) const - { - #ifdef CFEM_USE_DYNAMIC_SPACE_CACHE - CFEM_ASSERT(index >= 0 && index < static_cast(m_spaceDataCache.size()), "EvalContext::getSpaceData index out of bounds"); - #else - CFEM_ASSERT(index >= 0 && index < MAX_CACHED_SPACES, "EvalContext::getSpaceData index out of bounds"); - #endif - return m_spaceDataCache[index]; - } - - inline void setSpaceData(CfemInt index, const ShapeInfo* shape, const MappingInfo* mapping) - { - CFEM_ASSERT(index >= 0, "Negative space index"); -#ifdef CFEM_USE_DYNAMIC_SPACE_CACHE - CFEM_ASSERT(index < static_cast(m_spaceDataCache.size()), "Space cache not pre-sized"); -#else - CFEM_ASSERT(index < MAX_CACHED_SPACES, "Space index exceeds MAX_CACHED_SPACES"); -#endif - auto& entry = m_spaceDataCache[index]; - entry.shapeSingle = shape; - entry.mappingSingle = mapping; - } - - inline void setSpaceDataBatch(CfemInt index, const ShapeInfoBatch* shape, const MappingInfoBatch* mapping) - { - CFEM_ASSERT(index >= 0, "Negative space index"); -#ifdef CFEM_USE_DYNAMIC_SPACE_CACHE - CFEM_ASSERT(index < static_cast(m_spaceDataCache.size()), "Space cache not pre-sized"); -#else - CFEM_ASSERT(index < MAX_CACHED_SPACES, "Space index exceeds MAX_CACHED_SPACES"); -#endif - auto& entry = m_spaceDataCache[index]; - entry.shapeBatch = shape; - entry.mappingBatch = mapping; - } - - inline const MappingInfoBatch* getMappingBatch(CfemInt index) const - { - #ifdef CFEM_USE_DYNAMIC_SPACE_CACHE - CFEM_ASSERT(index >= 0 && index < static_cast(m_spaceDataCache.size()), "EvalContext::getMappingBatch index out of bounds"); - #else - CFEM_ASSERT(index >= 0 && index < MAX_CACHED_SPACES, "EvalContext::getMappingBatch index out of bounds"); - #endif - const auto* mapping = m_spaceDataCache[index].mappingBatch; - CFEM_ASSERT(mapping != nullptr, "MappingInfoBatch not set"); - return mapping; - } - - inline const MappingInfo* getMapping(CfemInt index) const - { - #ifdef CFEM_USE_DYNAMIC_SPACE_CACHE - CFEM_ASSERT(index >= 0 && index < static_cast(m_spaceDataCache.size()), "EvalContext::getMappingSingle index out of bounds"); - #else - CFEM_ASSERT(index >= 0 && index < MAX_CACHED_SPACES, "EvalContext::getMappingSingle index out of bounds"); - #endif - const auto* mapping = m_spaceDataCache[index].mappingSingle; - CFEM_ASSERT(mapping != nullptr, "MappingInfo not set"); - return mapping; - } - - /** - * @brief Retrieves precomputed BATCH shape info for a given space index. - * @param index Space identifier. - * @return Pointer to ShapeInfoBatch. - * @pre index is valid and batch shape has been set. - */ - inline const ShapeInfoBatch* getShapeBatch(CfemInt index) const - { - #ifdef CFEM_USE_DYNAMIC_SPACE_CACHE - CFEM_ASSERT(index >= 0 && index < static_cast(m_spaceDataCache.size()), "EvalContext::getShapeBatch index out of bounds"); - #else - CFEM_ASSERT(index >= 0 && index < MAX_CACHED_SPACES, "EvalContext::getShapeBatch index out of bounds"); - #endif - const auto* shape = m_spaceDataCache[index].shapeBatch; - CFEM_ASSERT(shape != nullptr, "ShapeInfoBatch not set. Did you call setSpaceDataBatch?"); - return shape; - } - - /** - * @brief Retrieves precomputed SINGLE shape info for a given space index. - * @param index Space identifier. - * @return Pointer to ShapeInfo (Legacy/Probing). - * @pre index is valid and single shape has been set. - */ - inline const ShapeInfo* getShape(CfemInt index) const - { - #ifdef CFEM_USE_DYNAMIC_SPACE_CACHE - CFEM_ASSERT(index >= 0 && index < static_cast(m_spaceDataCache.size()), "EvalContext::getShapeSingle index out of bounds"); - #else - CFEM_ASSERT(index >= 0 && index < MAX_CACHED_SPACES, "EvalContext::getShapeSingle index out of bounds"); - #endif - const auto* shape = m_spaceDataCache[index].shapeSingle; - CFEM_ASSERT(shape != nullptr, "ShapeInfo not set. Did you call setSpaceDataSingle?"); - return shape; - } - - void resetBuffers() { - m_currentPageIdx = 0; - m_currentOffset = 0; - m_expressionCache.clear(); - } - - /** - * @brief Allocates a buffer from the Arena memory. - * @param size Number of CfemReal elements needed. - * @return A span pointing to the allocated memory. - */ - std::span getBuffer(CfemInt size) { - CFEM_ASSERT(size >= 0, "Negative buffer size"); - CFEM_ASSERT(m_currentPageIdx < m_pages.size(), - "Invalid arena state: page index out of bounds"); - - size_t usize = static_cast(size); - - if (m_currentOffset + usize > m_pages[m_currentPageIdx].size()){ - // Check if the current page has enough remaining space - m_currentPageIdx++; - m_currentOffset = 0; - - // If we ran out of pages, add a new one - if (m_currentPageIdx >= m_pages.size()) { - m_pages.emplace_back(std::max(usize, PAGE_SIZE)); - } - // If a page exists from a previous run but is too small, resize it - else if (m_pages[m_currentPageIdx].size() < usize) { - m_pages[m_currentPageIdx].resize(usize); - } - } - - auto span = std::span(m_pages[m_currentPageIdx].data() + m_currentOffset, usize); - m_currentOffset += usize; - return span; - } - - bool tryGetCache(const void* exprPtr, std::span out) const { - for (const auto& entry : m_expressionCache) { - if (entry.id == exprPtr) { - CFEM_ASSERT(out.size() >= entry.data.size(), "Output buffer too small in tryGetCache"); - std::copy(entry.data.begin(), entry.data.end(), out.begin()); - return true; - } - } - return false; - } - - void storeInCache(const void* exprPtr, std::span result) { - auto storage = getBuffer(static_cast(result.size())); - std::copy(result.begin(), result.end(), storage.begin()); - m_expressionCache.push_back({exprPtr, storage}); - } - - struct Bookmark { size_t page; size_t offset; size_t cacheSize; }; - - Bookmark getBookmark() const { - return { m_currentPageIdx, m_currentOffset, m_expressionCache.size() }; - } - - void releaseToBookmark(Bookmark bm) { - m_currentPageIdx = bm.page; - m_currentOffset = bm.offset; - m_expressionCache.resize(bm.cacheSize); - } - - /** - * @brief Calculates total memory allocated by the Arena in bytes. - */ - size_t getArenaMemoryBytes() const { - size_t total = 0; - total += sizeof(m_pages); - for (const auto& page : m_pages) { - total += sizeof(page); // objet vector - total += page.capacity() * sizeof(CfemReal); - } - return total; - } - - /** - * @brief Debug print of the Arena Memory state to the standard output. - */ - void printArenaMemory() const { - size_t total = 0; - std::cout << "---- Arena Memory Debug ----\n"; - std::cout << "Number of pages: " << m_pages.size() << "\n"; - - for (size_t i = 0; i < m_pages.size(); ++i) { - const auto& page = m_pages[i]; - size_t mem = sizeof(page) + page.capacity() * sizeof(CfemReal); - std::cout << "Page " << i - << " | size: " << page.size() - << " | capacity: " << page.capacity() - << " | mem: " << mem / 1024.0 << " KB\n"; - total += mem; - } - std::cout << "TOTAL arena memory: " << total / (1024.0 * 1024.0) << " MB\n"; - } - }; -} \ No newline at end of file diff --git a/libs/fem/assembly/CMakeLists.txt b/libs/fem/assembly/CMakeLists.txt index 70a2563..d0947c6 100644 --- a/libs/fem/assembly/CMakeLists.txt +++ b/libs/fem/assembly/CMakeLists.txt @@ -13,7 +13,9 @@ target_include_directories(cfem_assembly ) target_link_libraries(cfem_assembly - PUBLIC cfem_petsc_wrappers - cfem_utils - cfem_openmp_interface + PUBLIC + cfem_petsc_wrappers + cfem_utils + cfem_kernel + cfem_openmp_interface ) diff --git a/libs/fem/exportation/CMakeLists.txt b/libs/fem/exportation/CMakeLists.txt index 7df6e80..ed4e2d5 100644 --- a/libs/fem/exportation/CMakeLists.txt +++ b/libs/fem/exportation/CMakeLists.txt @@ -10,8 +10,11 @@ target_include_directories(cfem_exporter ) target_link_libraries(cfem_exporter - PUBLIC cfem_mesh - cfem_expressions - cfem_fefield - cfem_reference_element + PUBLIC + cfem_utils + cfem_kernel + cfem_mesh + cfem_expressions + cfem_fefield + cfem_reference_element ) \ No newline at end of file diff --git a/libs/fem/fefield/CMakeLists.txt b/libs/fem/fefield/CMakeLists.txt index 4af2806..e2032d8 100644 --- a/libs/fem/fefield/CMakeLists.txt +++ b/libs/fem/fefield/CMakeLists.txt @@ -13,6 +13,7 @@ target_link_libraries(cfem_fefield PUBLIC cfem_openmp_interface cfem_utils + cfem_kernel cfem_petsc_wrappers cfem_reference_element cfem_expressions diff --git a/libs/fem/fefield/include/FEField.h b/libs/fem/fefield/include/FEField.h index 3dcb49c..ae18f2c 100644 --- a/libs/fem/fefield/include/FEField.h +++ b/libs/fem/fefield/include/FEField.h @@ -65,7 +65,7 @@ namespace cfem void evaluateGradientWithCachedMapping(CfemInt cellId, const MappingInfo& mapping, std::span out) const; void evaluateGradientWithoutCachedMapping(CfemInt cellId, const Vector3D &xi, std::span out, EvalContext &ctx) const; - void evaluateGradientBatchWithCachedMapping(CfemInt cellId, CfemInt nQp, const MappingInfoBatch& mappingBatch, std::span outValues, EvalContext& ctx) const; + void evaluateGradientBatchWithCachedMapping(CfemInt cellId, CfemInt nQp, const MappingInfoBatchView& mappingBatch, std::span outValues, EvalContext& ctx) const; void evaluateGradientBatchWithoutCachedMapping(CfemInt cellId, CfemInt nQp, std::span outValues, EvalContext& ctx) const; public: diff --git a/libs/fem/fefield/src/FEField.cpp b/libs/fem/fefield/src/FEField.cpp index 6ddecab..125171d 100644 --- a/libs/fem/fefield/src/FEField.cpp +++ b/libs/fem/fefield/src/FEField.cpp @@ -517,7 +517,7 @@ namespace cfem this->evaluateGradientBatchWithoutCachedMapping(cellId, nQp, outValues, ctx); } - void FEField::evaluateGradientBatchWithCachedMapping(CfemInt cellId, CfemInt nQp, const MappingInfoBatch& mappingBatch, std::span outValues, EvalContext& ctx) const + void FEField::evaluateGradientBatchWithCachedMapping(CfemInt cellId, CfemInt nQp, const MappingInfoBatchView& mappingBatch, std::span outValues, EvalContext& ctx) const { const CfemInt nComp = this->getNumComponents(); const CfemInt spatialDim = m_space->getMesh()->getSpatialDimension(); @@ -565,13 +565,44 @@ namespace cfem ctx.releaseToBookmark(bookmark); } + // void FEField::evaluateGradientBatchWithoutCachedMapping(CfemInt cellId, CfemInt nQp, std::span outValues, EvalContext& ctx) const + // { + // // Pareil que pour la valeur batch : on a besoin de xiBatch (via EvalContext) + // const auto& xiBatch = ctx.batchWS.xi; + // CFEM_ASSERT(xiBatch != nullptr && xiBatch->size() >= static_cast(nQp), "FEField::evaluateGradientBatchWithoutCachedMapping requires xiBatch"); + + // const auto &refEl = m_space->getReferenceElement(cellId); + + // m_space->getMesh()->getCellVerticesCoords(cellId, ctx.scratchCoords); + // const CfemInt expectedNodes = refEl.getNumNodes(); + // if (ctx.scratchCoords.size() > static_cast(expectedNodes)) { + // ctx.scratchCoords.resize(expectedNodes); + // } + + // // On demande à l'élément de référence de construire la totale pour nQp points + // ctx.batchWS.shape->shapeMustBeUpdated = false; + + // const bool isAffine = (m_space->getMesh()->getOrderInt() == 1) || m_space->getMesh()->isCellAffine(cellId); + + // refEl.computeMappingBatch( + // *xiBatch, + // ctx.scratchCoords, + // ShapeEvaluationFlags::FirstDerivatives, + // MappingEvaluationFlags::PhysicalFirstDerivatives, + // *(ctx.batchWS.shape), + // *(ctx.batchWS.mapping) + // ); + + // this->evaluateGradientBatchWithCachedMapping(cellId, nQp, *(ctx.batchWS.mapping), outValues, ctx); + // } + void FEField::evaluateGradientBatchWithoutCachedMapping(CfemInt cellId, CfemInt nQp, std::span outValues, EvalContext& ctx) const { - // Pareil que pour la valeur batch : on a besoin de xiBatch (via EvalContext) const auto& xiBatch = ctx.batchWS.xi; - CFEM_ASSERT(xiBatch != nullptr && xiBatch->size() >= static_cast(nQp), "FEField::evaluateGradientBatchWithoutCachedMapping requires xiBatch"); + CFEM_ASSERT(xiBatch != nullptr && xiBatch->size() >= static_cast(nQp), "FEField requires xiBatch"); const auto &refEl = m_space->getReferenceElement(cellId); + const CfemInt spatialDim = m_space->getMesh()->getSpatialDimension(); m_space->getMesh()->getCellVerticesCoords(cellId, ctx.scratchCoords); const CfemInt expectedNodes = refEl.getNumNodes(); @@ -579,21 +610,38 @@ namespace cfem ctx.scratchCoords.resize(expectedNodes); } - // On demande à l'élément de référence de construire la totale pour nQp points - ctx.batchWS.shape->shapeMustBeUpdated = false; - const bool isAffine = (m_space->getMesh()->getOrderInt() == 1) || m_space->getMesh()->isCellAffine(cellId); - + + // Make sure the temporary buffers have the correct sizes + ctx.scratchShapeBatch.setup(nQp, expectedNodes, false); + ctx.scratchMappingBatch.setup(nQp, expectedNodes, spatialDim, false); + + // Shapes and mapping evaluations + refEl.evaluateShapesBatch(*xiBatch, ShapeEvaluationFlags::FirstDerivatives, ctx.scratchShapeBatch); + + ctx.scratchMappingBatch.isCellAffine = isAffine; // for optimization refEl.computeMappingBatch( *xiBatch, - ctx.scratchCoords, - ShapeEvaluationFlags::FirstDerivatives, - MappingEvaluationFlags::PhysicalFirstDerivatives, - *(ctx.batchWS.shape), - *(ctx.batchWS.mapping) + ctx.scratchCoords, + MappingEvaluationFlags::InverseJacobian, + ctx.scratchShapeBatch, + ctx.scratchMappingBatch ); - this->evaluateGradientBatchWithCachedMapping(cellId, nQp, *(ctx.batchWS.mapping), outValues, ctx); + // Mapping view (zero allocation) + MappingInfoBatchView view = ctx.scratchMappingBatch.createView(); + + CfemInt paddedNodes = kernel_utils::computePaddedStride(expectedNodes); + CfemInt reqSize = spatialDim * nQp * paddedNodes; + std::span arenaStorage = ctx.getBuffer(reqSize); + + view.setupPhysicalDerivativesMemory(arenaStorage, expectedNodes, spatialDim, nQp); + + // Physical First derivatives computation + refEl.computePhysicalFirstDerivativesBatch(ctx.scratchShapeBatch, view); + + // Delegation to the Cached mapping version + this->evaluateGradientBatchWithCachedMapping(cellId, nQp, view, outValues, ctx); } /** diff --git a/libs/fem/reference_element/CMakeLists.txt b/libs/fem/reference_element/CMakeLists.txt index 0ba156e..c4cdb7e 100644 --- a/libs/fem/reference_element/CMakeLists.txt +++ b/libs/fem/reference_element/CMakeLists.txt @@ -7,5 +7,8 @@ target_include_directories(cfem_reference_element ) target_link_libraries(cfem_reference_element - PUBLIC cfem_utils cfem_fespace + PUBLIC + cfem_utils + cfem_kernel + cfem_fespace ) \ No newline at end of file diff --git a/libs/fem/reference_element/include/ReferenceElement.h b/libs/fem/reference_element/include/ReferenceElement.h index 14af22a..ff1cb9e 100644 --- a/libs/fem/reference_element/include/ReferenceElement.h +++ b/libs/fem/reference_element/include/ReferenceElement.h @@ -26,7 +26,11 @@ #include "EvaluationFlags.h" #include "ShapeInfo.h" +#include "ShapeInfoBatch.h" #include "MappingInfo.h" +#include "MappingInfoBatch.h" +#include "MappingInfoBatchView.h" + namespace cfem::reference_elem { @@ -145,7 +149,7 @@ class ReferenceElement { MappingInfo& out) const; void computePhysicalFirstDerivativesBatch(const ShapeInfoBatch &shape, - MappingInfoBatch &out) const; + MappingInfoBatchView &out) const; /** * @brief Computes physical Hessians including the geometric curvature term. @@ -208,6 +212,9 @@ class ReferenceElement { inline void computeGeometricNormal(MappingInfo &out) const; inline void computeGeometricNormalBatch(MappingInfoBatch &outBatch) const; + + void computePhysicalFirstDerivativesBatch(const ShapeInfoBatch &shape, + MappingInfoBatch &out) const; }; } diff --git a/libs/fem/reference_element/include/ReferenceElement.tpp b/libs/fem/reference_element/include/ReferenceElement.tpp index 219c1ae..f96de67 100644 --- a/libs/fem/reference_element/include/ReferenceElement.tpp +++ b/libs/fem/reference_element/include/ReferenceElement.tpp @@ -647,6 +647,139 @@ namespace cfem out.validFlags |= MappingEvaluationFlags::PhysicalFirstDerivatives; } + // /** + // * @brief Computes physical gradients for the entire batch. + // * @note Hyper-optimized utilizing SIMD friendly contiguous memory rows. + // */ + // inline void ReferenceElement::computePhysicalFirstDerivativesBatch(const ShapeInfoBatch &shape, + // MappingInfoBatch &out) const + // { + // CFEM_ASSERT(shape.has(ShapeEvaluationFlags::FirstDerivatives), "Shapes derivatives not pre-computed"); + // CFEM_ASSERT(out.has(MappingEvaluationFlags::InverseJacobian), "Inverse jacobian not pre-computed"); + + // const CfemInt nQp = shape.numQuadraturePoints; + // const CfemInt n = shape.numNodes; + + // CfemReal i00, i01, i02, i10, i11, i12, i20, i21, i22; + // if (out.isCellAffine && out.numQuadraturePoints > 0) + // { + // i00 = out.invJacobians[0].xx(); + // i01 = out.invJacobians[0].xy(); + // i02 = out.invJacobians[0].xz(); + // i10 = out.invJacobians[0].yx(); + // i11 = out.invJacobians[0].yy(); + // i12 = out.invJacobians[0].yz(); + // i20 = out.invJacobians[0].zx(); + // i21 = out.invJacobians[0].zy(); + // i22 = out.invJacobians[0].zz(); + // } + + // for (CfemInt q = 0; q < out.numQuadraturePoints; ++q) + // { + // // Si la cellule n'est PAS affine, on charge le Jacobien spécifique à ce QP + // if (!out.isCellAffine) + // { + // i00 = out.invJacobians[q].xx(); + // i01 = out.invJacobians[q].xy(); + // i02 = out.invJacobians[q].xz(); + // i10 = out.invJacobians[q].yx(); + // i11 = out.invJacobians[q].yy(); + // i12 = out.invJacobians[q].yz(); + // i20 = out.invJacobians[q].zx(); + // i21 = out.invJacobians[q].zy(); + // i22 = out.invJacobians[q].zz(); + // } + + // // Pointeurs SoA vers les lignes de la matrice de référence (Lecture) + // const CfemReal *__restrict dxi = shape.dN_dxi(q); + // const CfemReal *__restrict deta = shape.dN_deta(q); + // const CfemReal *__restrict dzeta = shape.dN_dzeta(q); + + // // Pointeurs SoA vers les lignes de la matrice physique (Écriture) + // CfemReal *__restrict dx = out.dN_dx(q); + // CfemReal *__restrict dy = out.dN_dy(q); + // CfemReal *__restrict dz = out.dN_dz(q); + + // // Boucle sur les nœuds interne : vectorisation SIMD maximale + // for (CfemInt a = 0; a < n; ++a) + // { + // const CfemReal gx = dxi[a]; + // const CfemReal gy = deta[a]; + // const CfemReal gz = dzeta[a]; + + // dx[a] = i00 * gx + i10 * gy + i20 * gz; + // dy[a] = i01 * gx + i11 * gy + i21 * gz; + // dz[a] = i02 * gx + i12 * gy + i22 * gz; + // } + // } + + // out.validFlags |= MappingEvaluationFlags::PhysicalFirstDerivatives; + // } + +/** + * @brief Computes physical gradients for the entire batch. + * @note Hyper-optimized utilizing SIMD friendly contiguous memory rows. + * Correctly handles embedded manifolds (e.g., 1D/2D elements in 3D ambient space). + */ + inline void ReferenceElement::computePhysicalFirstDerivativesBatch(const ShapeInfoBatch &shape, + MappingInfoBatchView &out) const + { + CFEM_ASSERT(shape.has(ShapeEvaluationFlags::FirstDerivatives), "Shapes derivatives not pre-computed"); + + const CfemInt nQp = out.numQuadraturePoints; + const CfemInt n = out.numNodes; + + // La dimension de référence (l'élément lui-même : 1 pour Ligne, 2 pour Triangle/Quad, 3 pour Tet/Hex) + const CfemInt refDim = m_dimension; + + // La dimension de l'espace ambiant déduite intelligemment grâce aux allocations de l'Arena + const CfemInt spatialDim = (out.dN_dz_data != nullptr) ? 3 : ((out.dN_dy_data != nullptr) ? 2 : 1); + + CfemReal i00 = 0, i01 = 0, i02 = 0, i10 = 0, i11 = 0, i12 = 0, i20 = 0, i21 = 0, i22 = 0; + + if (out.isCellAffine && nQp > 0) + { + i00 = out.invJacobians[0].xx(); i01 = out.invJacobians[0].xy(); i02 = out.invJacobians[0].xz(); + i10 = out.invJacobians[0].yx(); i11 = out.invJacobians[0].yy(); i12 = out.invJacobians[0].yz(); + i20 = out.invJacobians[0].zx(); i21 = out.invJacobians[0].zy(); i22 = out.invJacobians[0].zz(); + } + + for (CfemInt q = 0; q < nQp; ++q) + { + if (!out.isCellAffine) + { + i00 = out.invJacobians[q].xx(); i01 = out.invJacobians[q].xy(); i02 = out.invJacobians[q].xz(); + i10 = out.invJacobians[q].yx(); i11 = out.invJacobians[q].yy(); i12 = out.invJacobians[q].yz(); + i20 = out.invJacobians[q].zx(); i21 = out.invJacobians[q].zy(); i22 = out.invJacobians[q].zz(); + } + + // --- LECTURE (Basée sur refDim) --- + const CfemReal *__restrict dxi = shape.dN_dxi(q); + const CfemReal *__restrict deta = (refDim >= 2) ? shape.dN_deta(q) : nullptr; + const CfemReal *__restrict dzeta = (refDim == 3) ? shape.dN_dzeta(q) : nullptr; + + // --- ÉCRITURE (Basée sur spatialDim) --- + CfemReal *__restrict dx = out.dN_dx_mut(q); + CfemReal *__restrict dy = (spatialDim >= 2) ? out.dN_dy_mut(q) : nullptr; + CfemReal *__restrict dz = (spatialDim == 3) ? out.dN_dz_mut(q) : nullptr; + + for (CfemInt a = 0; a < n; ++a) + { + const CfemReal gx = dxi[a]; + const CfemReal gy = (refDim >= 2) ? deta[a] : 0.0; + const CfemReal gz = (refDim == 3) ? dzeta[a] : 0.0; + + // Calcul matriciel optimisé + // Même si refDim=2 (gz=0), si spatialDim=3, on écrit bien dans dz ! + dx[a] = i00 * gx + i10 * gy + i20 * gz; + if (spatialDim >= 2) dy[a] = i01 * gx + i11 * gy + i21 * gz; + if (spatialDim == 3) dz[a] = i02 * gx + i12 * gy + i22 * gz; + } + } + + out.validFlags |= MappingEvaluationFlags::PhysicalFirstDerivatives; + } + /** * @brief Computes physical gradients for the entire batch. * @note Hyper-optimized utilizing SIMD friendly contiguous memory rows. diff --git a/libs/kernel/CMakeLists.txt b/libs/kernel/CMakeLists.txt new file mode 100644 index 0000000..59cec22 --- /dev/null +++ b/libs/kernel/CMakeLists.txt @@ -0,0 +1,13 @@ +add_library(cfem_kernel + src/EvalContext.cpp +) + +target_include_directories(cfem_kernel + PUBLIC + $ + $ +) + +target_link_libraries(cfem_kernel + PUBLIC cfem_utils +) \ No newline at end of file diff --git a/libs/kernel/include/EvalContext.h b/libs/kernel/include/EvalContext.h new file mode 100644 index 0000000..a84157e --- /dev/null +++ b/libs/kernel/include/EvalContext.h @@ -0,0 +1,675 @@ +// //************************************************************************ +// // --- CFEM++ Library +// // --- +// // --- Copyright 2024-2026 Ismaël Tchinda Ngueyong et al. +// // --- ALL RIGHTS RESERVED +// // --- +// // --- This software is protected by international copyright laws. +// // --- Use, distribution, or modification of this software in any form, +// // --- source or binary, for personal, academic, or non-commercial +// // --- purposes is permitted free of charge, provided that this +// // --- copyright notice and this permission notice appear in all +// // --- copies and supporting documentation. +// // --- +// // --- The authors and contributors provide this code "as is" without +// // --- any express or implied warranty. In no event shall they be +// // --- held liable for any damages arising from the use of this software. +// //************************************************************************ + +// #pragma once + +// #include "cfemutils.h" +// #include "Vector3D.h" +// #include "DynamicVector.h" +// #include "DynamicMatrix.h" + +// #include "ShapeInfo.h" +// #include "ShapeInfoBatch.h" +// #include "MappingInfo.h" +// #include "MappingInfoBatchView.h" + +// #include +// #include +// #include +// #include +// #include +// #include + +// namespace cfem { + +// class FESpace; // Forward declaration + +// // Forward declarations des structures Batch (définies dans votre architecture SoA) +// struct ShapeInfoBatch; +// struct MappingInfoBatch; + +// struct MappingInfoBatch; + +// /** +// * @struct SpaceDataEntry +// * @brief Ultra-fast link between an FE Space and its evaluations at the current integration cell. +// * Uses explicit pointers to safely separate legacy/single-point data from vectorized batch data. +// */ +// struct SpaceDataEntry { +// const ShapeInfo* shapeSingle = nullptr; +// const MappingInfo* mappingSingle = nullptr; + +// const ShapeInfoBatch* shapeBatch = nullptr; +// const MappingInfoBatchView* mappingBatch = nullptr; +// }; + +// /** +// * @struct CacheEntry +// * @brief Stores a pointer-based identifier and its associated data span. +// */ +// struct CacheEntry { +// const void* id; +// std::span data; +// }; + +// /** +// * @struct EvalContext +// * @brief High-performance evaluation context for Finite Element assembly. +// * Provides O(1) access to geometric data and an Arena Memory system. +// * Structured to support both Single-Point (Probing) and Batch (Assembly) evaluation modes. +// */ +// struct EvalContext { + +// // ==================================================================== +// // GLOBAL SIMULATION STATE +// // ==================================================================== +// CfemReal currentTime = 0.0; /**< Current simulation time (for transient problems). */ +// CfemInt currentCellId = -1; /**< Index of the cell currently being integrated. */ +// CfemInt currentQpIndex = 0; + +// // ==================================================================== +// // SINGLE-POINT WORKSPACE (Legacy / Probing) +// // ==================================================================== +// struct SingleWorkspace { +// Vector3D physPos = {}; /**< Physical coordinates. */ +// Vector3D xi = {-1e9, -1e9, -1e9};/**< Reference coordinates. */ +// ShapeInfo shape; /**< Buffer for base functions. */ +// MappingInfo mapping; /**< Buffer for geometric mapping. */ +// } singleWS; + +// // ==================================================================== +// // BATCH WORKSPACE (Vectorized Element Assembly) +// // ==================================================================== +// struct BatchWorkspace { +// DynamicVector* physPos; /**< Physical coordinates for all QPs. */ +// DynamicVector* xi; /**< Reference coordinates for all QPs. */ +// ShapeInfoBatch* shape = nullptr; /**< Pointer to allocated batch shape memory. */ +// MappingInfoBatch* mapping = nullptr; /**< Pointer to allocated batch metric memory. */ +// } batchWS; + +// // ==================================================================== +// // MULTI-SPACE CACHE & SHORTCUTS (O(1) Access) +// // ==================================================================== +// static constexpr CfemInt MAX_CACHED_SPACES = 16; + +// #ifdef CFEM_USE_DYNAMIC_SPACE_CACHE +// std::vector m_spaceDataCache; +// #else +// std::array m_spaceDataCache; +// #endif + +// const ShapeInfoBatch* trialShapeBatch = nullptr; +// const ShapeInfoBatch* testShapeBatch = nullptr; +// const MappingInfoBatch* trialMappingBatch = nullptr; +// const MappingInfoBatch* testMappingBatch = nullptr; +// const MappingInfoBatch* geometricMappingBatch = nullptr; + + +// const MappingInfo* geometricMapping = nullptr; + +// CfemInt trialDof = -1; +// CfemInt testDof = -1; + +// // ==================================================================== +// // PRE-ALLOCATED SCRATCHPADS +// // ==================================================================== +// DynamicVector scratchCoords; /**< Buffer for cell vertex coordinates. */ +// std::vector scratchDofs; /**< Buffer for gathering local DOF indices. */ +// DynamicMatrix scratchCellDofs; /**< Buffer for cell DOF values. */ +// MappingInfo scratchMapping; /**< Buffer for cell DOF values. */ +// ShapeInfo scratchShape; /**< Buffer for cell DOF values. */ +// // ==================================================================== +// // ARENA MEMORY SYSTEM +// // ==================================================================== +// static constexpr size_t PAGE_SIZE = 4096; + +// std::deque> m_pages; +// size_t m_currentPageIdx = 0; +// size_t m_currentOffset = 0; + +// std::vector m_expressionCache; + +// // ==================================================================== +// // API METHODS +// // ==================================================================== + +// EvalContext() +// { +// m_pages.emplace_back(PAGE_SIZE); +// m_expressionCache.reserve(16); +// #ifdef CFEM_USE_DYNAMIC_SPACE_CACHE +// m_spaceDataCache.reserve(8); +// #endif +// } + +// #ifdef CFEM_USE_DYNAMIC_SPACE_CACHE +// inline void prepareSpaceCache(CfemInt n) { +// m_spaceDataCache.resize(n); +// } +// #endif + +// inline const SpaceDataEntry& getSpaceData(CfemInt index) const +// { +// #ifdef CFEM_USE_DYNAMIC_SPACE_CACHE +// CFEM_ASSERT(index >= 0 && index < static_cast(m_spaceDataCache.size()), "EvalContext::getSpaceData index out of bounds"); +// #else +// CFEM_ASSERT(index >= 0 && index < MAX_CACHED_SPACES, "EvalContext::getSpaceData index out of bounds"); +// #endif +// return m_spaceDataCache[index]; +// } + +// inline void setSpaceData(CfemInt index, const ShapeInfo* shape, const MappingInfo* mapping) +// { +// CFEM_ASSERT(index >= 0, "Negative space index"); +// #ifdef CFEM_USE_DYNAMIC_SPACE_CACHE +// CFEM_ASSERT(index < static_cast(m_spaceDataCache.size()), "Space cache not pre-sized"); +// #else +// CFEM_ASSERT(index < MAX_CACHED_SPACES, "Space index exceeds MAX_CACHED_SPACES"); +// #endif +// auto& entry = m_spaceDataCache[index]; +// entry.shapeSingle = shape; +// entry.mappingSingle = mapping; +// } + +// inline void setSpaceDataBatch(CfemInt index, const ShapeInfoBatch* shape, const MappingInfoBatch* mapping) +// { +// CFEM_ASSERT(index >= 0, "Negative space index"); +// #ifdef CFEM_USE_DYNAMIC_SPACE_CACHE +// CFEM_ASSERT(index < static_cast(m_spaceDataCache.size()), "Space cache not pre-sized"); +// #else +// CFEM_ASSERT(index < MAX_CACHED_SPACES, "Space index exceeds MAX_CACHED_SPACES"); +// #endif +// auto& entry = m_spaceDataCache[index]; +// entry.shapeBatch = shape; +// entry.mappingBatch = mapping; +// } + +// inline const MappingInfoBatch* getMappingBatch(CfemInt index) const +// { +// #ifdef CFEM_USE_DYNAMIC_SPACE_CACHE +// CFEM_ASSERT(index >= 0 && index < static_cast(m_spaceDataCache.size()), "EvalContext::getMappingBatch index out of bounds"); +// #else +// CFEM_ASSERT(index >= 0 && index < MAX_CACHED_SPACES, "EvalContext::getMappingBatch index out of bounds"); +// #endif +// const auto* mapping = m_spaceDataCache[index].mappingBatch; +// CFEM_ASSERT(mapping != nullptr, "MappingInfoBatch not set"); +// return mapping; +// } + +// inline const MappingInfo* getMapping(CfemInt index) const +// { +// #ifdef CFEM_USE_DYNAMIC_SPACE_CACHE +// CFEM_ASSERT(index >= 0 && index < static_cast(m_spaceDataCache.size()), "EvalContext::getMappingSingle index out of bounds"); +// #else +// CFEM_ASSERT(index >= 0 && index < MAX_CACHED_SPACES, "EvalContext::getMappingSingle index out of bounds"); +// #endif +// const auto* mapping = m_spaceDataCache[index].mappingSingle; +// CFEM_ASSERT(mapping != nullptr, "MappingInfo not set"); +// return mapping; +// } + +// /** +// * @brief Retrieves precomputed BATCH shape info for a given space index. +// * @param index Space identifier. +// * @return Pointer to ShapeInfoBatch. +// * @pre index is valid and batch shape has been set. +// */ +// inline const ShapeInfoBatch* getShapeBatch(CfemInt index) const +// { +// #ifdef CFEM_USE_DYNAMIC_SPACE_CACHE +// CFEM_ASSERT(index >= 0 && index < static_cast(m_spaceDataCache.size()), "EvalContext::getShapeBatch index out of bounds"); +// #else +// CFEM_ASSERT(index >= 0 && index < MAX_CACHED_SPACES, "EvalContext::getShapeBatch index out of bounds"); +// #endif +// const auto* shape = m_spaceDataCache[index].shapeBatch; +// CFEM_ASSERT(shape != nullptr, "ShapeInfoBatch not set. Did you call setSpaceDataBatch?"); +// return shape; +// } + +// /** +// * @brief Retrieves precomputed SINGLE shape info for a given space index. +// * @param index Space identifier. +// * @return Pointer to ShapeInfo (Legacy/Probing). +// * @pre index is valid and single shape has been set. +// */ +// inline const ShapeInfo* getShape(CfemInt index) const +// { +// #ifdef CFEM_USE_DYNAMIC_SPACE_CACHE +// CFEM_ASSERT(index >= 0 && index < static_cast(m_spaceDataCache.size()), "EvalContext::getShapeSingle index out of bounds"); +// #else +// CFEM_ASSERT(index >= 0 && index < MAX_CACHED_SPACES, "EvalContext::getShapeSingle index out of bounds"); +// #endif +// const auto* shape = m_spaceDataCache[index].shapeSingle; +// CFEM_ASSERT(shape != nullptr, "ShapeInfo not set. Did you call setSpaceDataSingle?"); +// return shape; +// } + +// void resetBuffers() { +// m_currentPageIdx = 0; +// m_currentOffset = 0; +// m_expressionCache.clear(); +// } + +// /** +// * @brief Allocates a buffer from the Arena memory. +// * @param size Number of CfemReal elements needed. +// * @return A span pointing to the allocated memory. +// */ +// std::span getBuffer(CfemInt size) { +// CFEM_ASSERT(size >= 0, "Negative buffer size"); +// CFEM_ASSERT(m_currentPageIdx < m_pages.size(), +// "Invalid arena state: page index out of bounds"); + +// size_t usize = static_cast(size); + +// if (m_currentOffset + usize > m_pages[m_currentPageIdx].size()){ +// // Check if the current page has enough remaining space +// m_currentPageIdx++; +// m_currentOffset = 0; + +// // If we ran out of pages, add a new one +// if (m_currentPageIdx >= m_pages.size()) { +// m_pages.emplace_back(std::max(usize, PAGE_SIZE)); +// } +// // If a page exists from a previous run but is too small, resize it +// else if (m_pages[m_currentPageIdx].size() < usize) { +// m_pages[m_currentPageIdx].resize(usize); +// } +// } + +// auto span = std::span(m_pages[m_currentPageIdx].data() + m_currentOffset, usize); +// m_currentOffset += usize; +// return span; +// } + +// bool tryGetCache(const void* exprPtr, std::span out) const { +// for (const auto& entry : m_expressionCache) { +// if (entry.id == exprPtr) { +// CFEM_ASSERT(out.size() >= entry.data.size(), "Output buffer too small in tryGetCache"); +// std::copy(entry.data.begin(), entry.data.end(), out.begin()); +// return true; +// } +// } +// return false; +// } + +// void storeInCache(const void* exprPtr, std::span result) { +// auto storage = getBuffer(static_cast(result.size())); +// std::copy(result.begin(), result.end(), storage.begin()); +// m_expressionCache.push_back({exprPtr, storage}); +// } + +// struct Bookmark { size_t page; size_t offset; size_t cacheSize; }; + +// Bookmark getBookmark() const { +// return { m_currentPageIdx, m_currentOffset, m_expressionCache.size() }; +// } + +// void releaseToBookmark(Bookmark bm) { +// m_currentPageIdx = bm.page; +// m_currentOffset = bm.offset; +// m_expressionCache.resize(bm.cacheSize); +// } + +// /** +// * @brief Calculates total memory allocated by the Arena in bytes. +// */ +// size_t getArenaMemoryBytes() const { +// size_t total = 0; +// total += sizeof(m_pages); +// for (const auto& page : m_pages) { +// total += sizeof(page); // objet vector +// total += page.capacity() * sizeof(CfemReal); +// } +// return total; +// } + +// /** +// * @brief Debug print of the Arena Memory state to the standard output. +// */ +// void printArenaMemory() const { +// size_t total = 0; +// std::cout << "---- Arena Memory Debug ----\n"; +// std::cout << "Number of pages: " << m_pages.size() << "\n"; + +// for (size_t i = 0; i < m_pages.size(); ++i) { +// const auto& page = m_pages[i]; +// size_t mem = sizeof(page) + page.capacity() * sizeof(CfemReal); +// std::cout << "Page " << i +// << " | size: " << page.size() +// << " | capacity: " << page.capacity() +// << " | mem: " << mem / 1024.0 << " KB\n"; +// total += mem; +// } +// std::cout << "TOTAL arena memory: " << total / (1024.0 * 1024.0) << " MB\n"; +// } +// }; +// } + + +#pragma once + +#include "cfemutils.h" +#include "Vector3D.h" +#include "ShapeInfo.h" +#include "ShapeInfoBatch.h" +#include "MappingInfo.h" +#include "MappingInfoBatch.h" +#include "MappingInfoBatchView.h" +#include "DynamicVector.h" +#include "DynamicMatrix.h" + +#include +#include +#include +#include +#include +#include + +namespace cfem { + + class FESpace; // Forward declaration + + /** + * @struct SpaceDataEntry + * @brief Ultra-fast link between an FE Space and its evaluations at the current integration cell. + */ + struct SpaceDataEntry { + const ShapeInfo* shapeSingle = nullptr; + const MappingInfo* mappingSingle = nullptr; + + const ShapeInfoBatch* shapeBatch = nullptr; + const MappingInfoBatchView* mappingBatch = nullptr; // HPC View + }; + + /** + * @struct CacheEntry + * @brief Stores a pointer-based identifier and its associated data span. + */ + struct CacheEntry { + const void* id; + std::span data; + }; + + /** + * @struct EvalContext + * @brief High-performance evaluation context for Finite Element assembly. + */ + struct EvalContext { + + // ==================================================================== + // GLOBAL SIMULATION STATE + // ==================================================================== + CfemReal currentTime = 0.0; + CfemInt currentCellId = -1; + CfemInt currentQpIndex = 0; + + // ==================================================================== + // SINGLE-POINT WORKSPACE (Legacy / Probing) + // ==================================================================== + struct SingleWorkspace { + Vector3D physPos = {}; + Vector3D xi = {-1e9, -1e9, -1e9}; + ShapeInfo shape; + MappingInfo mapping; + } singleWS; + + // ==================================================================== + // BATCH WORKSPACE (Vectorized Element Assembly) + // ==================================================================== + struct BatchWorkspace { + DynamicVector* physPos = nullptr; + DynamicVector* xi = nullptr; + ShapeInfoBatch* shape = nullptr; + MappingInfoBatchView* mapping = nullptr; // HPC View + } batchWS; + + // ==================================================================== + // MULTI-SPACE CACHE & SHORTCUTS (O(1) Access) + // ==================================================================== + static constexpr CfemInt MAX_CACHED_SPACES = 16; + + #ifdef CFEM_USE_DYNAMIC_SPACE_CACHE + std::vector m_spaceDataCache; + #else + std::array m_spaceDataCache; + #endif + + const ShapeInfoBatch* trialShapeBatch = nullptr; + const ShapeInfoBatch* testShapeBatch = nullptr; + const MappingInfoBatchView* trialMappingBatch = nullptr; + const MappingInfoBatchView* testMappingBatch = nullptr; + const MappingInfoBatchView* geometricMappingBatch = nullptr; + + const MappingInfo* geometricMapping = nullptr; + + CfemInt trialDof = -1; + CfemInt testDof = -1; + + // ==================================================================== + // PRE-ALLOCATED SCRATCHPADS + // ==================================================================== + DynamicVector scratchCoords; + std::vector scratchDofs; + DynamicMatrix scratchCellDofs; + MappingInfo scratchMapping; + ShapeInfo scratchShape; + ShapeInfoBatch scratchShapeBatch; + MappingInfoBatch scratchMappingBatch; + + // ==================================================================== + // ARENA MEMORY SYSTEM + // ==================================================================== + static constexpr size_t PAGE_SIZE = 4096; + + std::deque> m_pages; + size_t m_currentPageIdx = 0; + size_t m_currentOffset = 0; + + std::vector m_expressionCache; + + // ==================================================================== + // API METHODS + // ==================================================================== + + EvalContext() + { + m_pages.emplace_back(PAGE_SIZE); + m_expressionCache.reserve(16); +#ifdef CFEM_USE_DYNAMIC_SPACE_CACHE + m_spaceDataCache.reserve(8); +#endif + } + +#ifdef CFEM_USE_DYNAMIC_SPACE_CACHE + inline void prepareSpaceCache(CfemInt n) { + m_spaceDataCache.resize(n); + } +#endif + + inline const SpaceDataEntry& getSpaceData(CfemInt index) const + { + #ifdef CFEM_USE_DYNAMIC_SPACE_CACHE + CFEM_ASSERT(index >= 0 && index < static_cast(m_spaceDataCache.size()), "EvalContext::getSpaceData index out of bounds"); + #else + CFEM_ASSERT(index >= 0 && index < MAX_CACHED_SPACES, "EvalContext::getSpaceData index out of bounds"); + #endif + return m_spaceDataCache[index]; + } + + inline void setSpaceData(CfemInt index, const ShapeInfo* shape, const MappingInfo* mapping) + { + CFEM_ASSERT(index >= 0, "Negative space index"); +#ifdef CFEM_USE_DYNAMIC_SPACE_CACHE + CFEM_ASSERT(index < static_cast(m_spaceDataCache.size()), "Space cache not pre-sized"); +#else + CFEM_ASSERT(index < MAX_CACHED_SPACES, "Space index exceeds MAX_CACHED_SPACES"); +#endif + auto& entry = m_spaceDataCache[index]; + entry.shapeSingle = shape; + entry.mappingSingle = mapping; + } + + inline void setSpaceDataBatch(CfemInt index, const ShapeInfoBatch* shape, const MappingInfoBatchView* mapping) + { + CFEM_ASSERT(index >= 0, "Negative space index"); +#ifdef CFEM_USE_DYNAMIC_SPACE_CACHE + CFEM_ASSERT(index < static_cast(m_spaceDataCache.size()), "Space cache not pre-sized"); +#else + CFEM_ASSERT(index < MAX_CACHED_SPACES, "Space index exceeds MAX_CACHED_SPACES"); +#endif + auto& entry = m_spaceDataCache[index]; + entry.shapeBatch = shape; + entry.mappingBatch = mapping; + } + + inline const MappingInfoBatchView* getMappingBatch(CfemInt index) const + { + #ifdef CFEM_USE_DYNAMIC_SPACE_CACHE + CFEM_ASSERT(index >= 0 && index < static_cast(m_spaceDataCache.size()), "EvalContext::getMappingBatch index out of bounds"); + #else + CFEM_ASSERT(index >= 0 && index < MAX_CACHED_SPACES, "EvalContext::getMappingBatch index out of bounds"); + #endif + const auto* mapping = m_spaceDataCache[index].mappingBatch; + CFEM_ASSERT(mapping != nullptr, "MappingInfoBatchView not set"); + return mapping; + } + + inline const MappingInfo* getMapping(CfemInt index) const + { + #ifdef CFEM_USE_DYNAMIC_SPACE_CACHE + CFEM_ASSERT(index >= 0 && index < static_cast(m_spaceDataCache.size()), "EvalContext::getMappingSingle index out of bounds"); + #else + CFEM_ASSERT(index >= 0 && index < MAX_CACHED_SPACES, "EvalContext::getMappingSingle index out of bounds"); + #endif + const auto* mapping = m_spaceDataCache[index].mappingSingle; + CFEM_ASSERT(mapping != nullptr, "MappingInfo not set"); + return mapping; + } + + inline const ShapeInfoBatch* getShapeBatch(CfemInt index) const + { + #ifdef CFEM_USE_DYNAMIC_SPACE_CACHE + CFEM_ASSERT(index >= 0 && index < static_cast(m_spaceDataCache.size()), "EvalContext::getShapeBatch index out of bounds"); + #else + CFEM_ASSERT(index >= 0 && index < MAX_CACHED_SPACES, "EvalContext::getShapeBatch index out of bounds"); + #endif + const auto* shape = m_spaceDataCache[index].shapeBatch; + CFEM_ASSERT(shape != nullptr, "ShapeInfoBatch not set. Did you call setSpaceDataBatch?"); + return shape; + } + + inline const ShapeInfo* getShape(CfemInt index) const + { + #ifdef CFEM_USE_DYNAMIC_SPACE_CACHE + CFEM_ASSERT(index >= 0 && index < static_cast(m_spaceDataCache.size()), "EvalContext::getShapeSingle index out of bounds"); + #else + CFEM_ASSERT(index >= 0 && index < MAX_CACHED_SPACES, "EvalContext::getShapeSingle index out of bounds"); + #endif + const auto* shape = m_spaceDataCache[index].shapeSingle; + CFEM_ASSERT(shape != nullptr, "ShapeInfo not set. Did you call setSpaceDataSingle?"); + return shape; + } + + void resetBuffers() { + m_currentPageIdx = 0; + m_currentOffset = 0; + m_expressionCache.clear(); + } + + std::span getBuffer(CfemInt size) { + CFEM_ASSERT(size >= 0, "Negative buffer size"); + CFEM_ASSERT(m_currentPageIdx < m_pages.size(), + "Invalid arena state: page index out of bounds"); + + size_t usize = static_cast(size); + + if (m_currentOffset + usize > m_pages[m_currentPageIdx].size()){ + m_currentPageIdx++; + m_currentOffset = 0; + + if (m_currentPageIdx >= m_pages.size()) { + m_pages.emplace_back(std::max(usize, PAGE_SIZE)); + } + else if (m_pages[m_currentPageIdx].size() < usize) { + m_pages[m_currentPageIdx].resize(usize); + } + } + + auto span = std::span(m_pages[m_currentPageIdx].data() + m_currentOffset, usize); + m_currentOffset += usize; + return span; + } + + bool tryGetCache(const void* exprPtr, std::span out) const { + for (const auto& entry : m_expressionCache) { + if (entry.id == exprPtr) { + CFEM_ASSERT(out.size() >= entry.data.size(), "Output buffer too small in tryGetCache"); + std::copy(entry.data.begin(), entry.data.end(), out.begin()); + return true; + } + } + return false; + } + + void storeInCache(const void* exprPtr, std::span result) { + auto storage = getBuffer(static_cast(result.size())); + std::copy(result.begin(), result.end(), storage.begin()); + m_expressionCache.push_back({exprPtr, storage}); + } + + struct Bookmark { size_t page; size_t offset; size_t cacheSize; }; + + Bookmark getBookmark() const { + return { m_currentPageIdx, m_currentOffset, m_expressionCache.size() }; + } + + void releaseToBookmark(Bookmark bm) { + m_currentPageIdx = bm.page; + m_currentOffset = bm.offset; + m_expressionCache.resize(bm.cacheSize); + } + + size_t getArenaMemoryBytes() const { + size_t total = 0; + total += sizeof(m_pages); + for (const auto& page : m_pages) { + total += sizeof(page); + total += page.capacity() * sizeof(CfemReal); + } + return total; + } + + void printArenaMemory() const { + size_t total = 0; + std::cout << "---- Arena Memory Debug ----\n"; + std::cout << "Number of pages: " << m_pages.size() << "\n"; + + for (size_t i = 0; i < m_pages.size(); ++i) { + const auto& page = m_pages[i]; + size_t mem = sizeof(page) + page.capacity() * sizeof(CfemReal); + std::cout << "Page " << i + << " | size: " << page.size() + << " | capacity: " << page.capacity() + << " | mem: " << mem / 1024.0 << " KB\n"; + total += mem; + } + std::cout << "TOTAL arena memory: " << total / (1024.0 * 1024.0) << " MB\n"; + } + }; +} \ No newline at end of file diff --git a/libs/fem/reference_element/include/EvaluationFlags.h b/libs/kernel/include/EvaluationFlags.h similarity index 100% rename from libs/fem/reference_element/include/EvaluationFlags.h rename to libs/kernel/include/EvaluationFlags.h diff --git a/libs/kernel/include/MappingInfo.h b/libs/kernel/include/MappingInfo.h new file mode 100644 index 0000000..f71bc9c --- /dev/null +++ b/libs/kernel/include/MappingInfo.h @@ -0,0 +1,203 @@ +#pragma once + +#include "cfemutils.h" +#include "kernel_utils.h" +#include "EvaluationFlags.h" +#include "DynamicMatrix.h" +#include "DynamicMatrix.h" +#include "Tensor3D.h" +#include "Tensor2D.h" + +#include "ShapeInfo.h" + + +namespace cfem +{ +/** + * @struct MappingInfo + * @brief Contiguous, cache-aligned storage buffer for physical shape function derivatives and mapping data. + * + * This structure implements a Structure-of-Arrays (SoA) layout to store physical gradients + * and Hessians for a set of quadrature points. + * * ### Performance Design + * - **Contiguous Memory:** Derivatives are stored in a single `DynamicVector` allocation. + * - **Cache Locality:** Optimized for L1/L2 cache prefetching by using padded strides. + * - **Zero Overhead:** Accessors provide lightweight views (raw pointers) to data without + * requiring heavy abstractions in hot loops. + */ + struct MappingInfo + { + // ==================================================================== + // Paramètres de taille pour le buffer SoA + // ==================================================================== + static constexpr CfemInt NUM_FIRST_ORDER_FIELDS = 3; ///< \f$ \left\{\frac{\partial N}{\partial x}, \frac{\partial N}{\partial y}, \frac{\partial N}{\partial z}\right\} \f$ + static constexpr CfemInt NUM_SECOND_ORDER_FIELDS = 6; ///< \f$ \left\{\frac{\partial^2 N}{\partial x^2}, \frac{\partial^2 N}{\partial y^2}, \frac{\partial^2 N}{\partial z^2}, \frac{\partial^2 N}{\partial x \partial y}, d2N/dxdz, \frac{\partial^2 N}{\partial y \partial z} \right\} \f$ + + // ==================================================================== + // Données d'état et Géométrie Locale (Small Tensors / Scalars) + // ==================================================================== + CfemInt numNodes = 0; + MappingEvaluationFlags validFlags = MappingEvaluationFlags::None; + + Vector3D physicalPosition; + Tensor3D jacobian; + Tensor3D invJacobian; + CfemReal detJ = 0.0; + Vector3D normal; + + CfemReal metric1D = 0.0; + SymTensor2D metric2D; + + // ==================================================================== + // Stockage Contigu HPC pour les Dérivées Physiques Nodales + // ==================================================================== + DynamicVector storage; ///< Centralized contiguous buffer + CfemInt stride = 0; ///< Padded stride to ensure SIMD alignment + bool hasSecondDerivatives = false; + + /** + * @brief Allocates and configures the internal storage layout. + */ + inline void setup(CfemInt nNodes, bool includeSecondDerivatives = false) + { + numNodes = nNodes; + hasSecondDerivatives = includeSecondDerivatives; + stride = kernel_utils::computePaddedStride(numNodes); + + const CfemInt nFields = NUM_FIRST_ORDER_FIELDS + + (includeSecondDerivatives ? NUM_SECOND_ORDER_FIELDS : 0); + + const size_t totalSize = static_cast(nFields) * static_cast(stride); + + if (storage.size() != totalSize) + { + storage.resize(totalSize); + } + + validFlags = MappingEvaluationFlags::None; + } + + inline void invalidate() + { + validFlags = MappingEvaluationFlags::None; + } + + // ================================================================================================ + // SHAPES FIRST DERIVATIVES (Physical Space) + // ================================================================================================ + + [[nodiscard]] inline CfemReal* dN_dx() { + return storage.data() + 0 * stride; + } + + [[nodiscard]] inline CfemReal* dN_dy() { + return storage.data() + 1 * stride; + } + + [[nodiscard]] inline CfemReal* dN_dz() { + return storage.data() + 2 * stride; + } + + // Versions const + [[nodiscard]] inline const CfemReal* dN_dx() const { + CFEM_ASSERT(hasFlag(validFlags, MappingEvaluationFlags::PhysicalFirstDerivatives), "Physical first derivatives not computed?"); + return storage.data() + 0 * stride; + } + + [[nodiscard]] inline const CfemReal* dN_dy() const { + CFEM_ASSERT(hasFlag(validFlags, MappingEvaluationFlags::PhysicalFirstDerivatives), "Physical first derivatives not computed?"); + return storage.data() + 1 * stride; + } + + [[nodiscard]] inline const CfemReal* dN_dz() const { + CFEM_ASSERT(hasFlag(validFlags, MappingEvaluationFlags::PhysicalFirstDerivatives), "Physical first derivatives not computed?"); + return storage.data() + 2 * stride; + } + + // ================================================================================================ + // SHAPES SECOND DERIVATIVES (Physical Space) + // ================================================================================================ + + [[nodiscard]] inline CfemReal* d2N_dx2() noexcept { + return storage.data() + 3 * stride; + } + + [[nodiscard]] inline CfemReal* d2N_dy2() noexcept { + return storage.data() + 4 * stride; + } + + [[nodiscard]] inline CfemReal* d2N_dz2() noexcept { + return storage.data() + 5 * stride; + } + + [[nodiscard]] inline CfemReal* d2N_dxdy() noexcept { + return storage.data() + 6 * stride; + } + + [[nodiscard]] inline CfemReal* d2N_dxdz() noexcept { + return storage.data() + 7 * stride; + } + + [[nodiscard]] inline CfemReal* d2N_dydz() noexcept { + return storage.data() + 8 * stride; + } + + // Versions const + [[nodiscard]] inline const CfemReal* d2N_dx2() const { + CFEM_ASSERT(hasSecondDerivatives, "Second derivatives not allocated?"); + return storage.data() + 3 * stride; + } + + [[nodiscard]] inline const CfemReal* d2N_dy2() const { + CFEM_ASSERT(hasSecondDerivatives, "Second derivatives not allocated?"); + return storage.data() + 4 * stride; + } + + [[nodiscard]] inline const CfemReal* d2N_dz2() const { + CFEM_ASSERT(hasSecondDerivatives, "Second derivatives not allocated?"); + return storage.data() + 5 * stride; + } + + [[nodiscard]] inline const CfemReal* d2N_dxdy() const { + CFEM_ASSERT(hasSecondDerivatives, "Second derivatives not allocated?"); + return storage.data() + 6 * stride; + } + + [[nodiscard]] inline const CfemReal* d2N_dxdz() const { + CFEM_ASSERT(hasSecondDerivatives, "Second derivatives not allocated?"); + return storage.data() + 7 * stride; + } + + [[nodiscard]] inline const CfemReal* d2N_dydz() const { + CFEM_ASSERT(hasSecondDerivatives, "Second derivatives not allocated?"); + return storage.data() + 8 * stride; + } + + // ============================================================ + // Getters + // ============================================================ + + inline Vector3D gradient(CfemInt node) const + { + CFEM_ASSERT(node >= 0 && node < numNodes); + return Vector3D(dN_dx()[node], dN_dy()[node], dN_dz()[node]); + } + + inline SymTensor3D hessian(CfemInt node) const + { + CFEM_ASSERT(node >= 0 && node < numNodes); + CFEM_ASSERT(hasSecondDerivatives); + return SymTensor3D(d2N_dx2()[node], d2N_dy2()[node], d2N_dz2()[node], + d2N_dydz()[node], d2N_dxdz()[node], d2N_dxdy()[node]); + } + + /** + * @brief Checks if a specific evaluation field is marked as valid. + */ + inline bool has(MappingEvaluationFlags field) const + { + return hasFlag(validFlags, field); + } + }; + +} \ No newline at end of file diff --git a/libs/fem/reference_element/include/MappingInfo.h b/libs/kernel/include/MappingInfoBatch.h similarity index 56% rename from libs/fem/reference_element/include/MappingInfo.h rename to libs/kernel/include/MappingInfoBatch.h index 6970766..761eca0 100644 --- a/libs/fem/reference_element/include/MappingInfo.h +++ b/libs/kernel/include/MappingInfoBatch.h @@ -1,204 +1,35 @@ +//************************************************************************ +// --- CFEM++ Library +// --- +// --- Copyright 2024-2026 Ismaël Tchinda Ngueyong et al. +// --- ALL RIGHTS RESERVED +// --- +// --- This software is protected by international copyright laws. +// --- Use, distribution, or modification of this software in any form, +// --- source or binary, for personal, academic, or non-commercial +// --- purposes is permitted free of charge, provided that this +// --- copyright notice and this permission notice appear in all +// --- copies and supporting documentation. +// --- +// --- The authors and contributors provide this code "as is" without +// --- any express or implied warranty. In no event shall they be +// --- held liable for any damages arising from the use of this software. +//************************************************************************ + #pragma once #include "cfemutils.h" +#include "kernel_utils.h" #include "EvaluationFlags.h" #include "DynamicMatrix.h" #include "DynamicMatrix.h" #include "Tensor3D.h" #include "Tensor2D.h" -#include "ShapeInfo.h" - +#include "MappingInfoBatchView.h" namespace cfem { -/** - * @struct MappingInfo - * @brief Contiguous, cache-aligned storage buffer for physical shape function derivatives and mapping data. - * - * This structure implements a Structure-of-Arrays (SoA) layout to store physical gradients - * and Hessians for a set of quadrature points. - * * ### Performance Design - * - **Contiguous Memory:** Derivatives are stored in a single `DynamicVector` allocation. - * - **Cache Locality:** Optimized for L1/L2 cache prefetching by using padded strides. - * - **Zero Overhead:** Accessors provide lightweight views (raw pointers) to data without - * requiring heavy abstractions in hot loops. - */ - struct MappingInfo - { - // ==================================================================== - // Paramètres de taille pour le buffer SoA - // ==================================================================== - static constexpr CfemInt NUM_FIRST_ORDER_FIELDS = 3; ///< \f$ \left\{\frac{\partial N}{\partial x}, \frac{\partial N}{\partial y}, \frac{\partial N}{\partial z}\right\} \f$ - static constexpr CfemInt NUM_SECOND_ORDER_FIELDS = 6; ///< \f$ \left\{\frac{\partial^2 N}{\partial x^2}, \frac{\partial^2 N}{\partial y^2}, \frac{\partial^2 N}{\partial z^2}, \frac{\partial^2 N}{\partial x \partial y}, d2N/dxdz, \frac{\partial^2 N}{\partial y \partial z} \right\} \f$ - - // ==================================================================== - // Données d'état et Géométrie Locale (Small Tensors / Scalars) - // ==================================================================== - CfemInt numNodes = 0; - MappingEvaluationFlags validFlags = MappingEvaluationFlags::None; - - Vector3D physicalPosition; - Tensor3D jacobian; - Tensor3D invJacobian; - CfemReal detJ = 0.0; - Vector3D normal; - - CfemReal metric1D = 0.0; - SymTensor2D metric2D; - - // ==================================================================== - // Stockage Contigu HPC pour les Dérivées Physiques Nodales - // ==================================================================== - DynamicVector storage; ///< Centralized contiguous buffer - CfemInt stride = 0; ///< Padded stride to ensure SIMD alignment - bool hasSecondDerivatives = false; - - /** - * @brief Allocates and configures the internal storage layout. - */ - inline void setup(CfemInt nNodes, bool includeSecondDerivatives = false) - { - numNodes = nNodes; - hasSecondDerivatives = includeSecondDerivatives; - stride = computePaddedStride(numNodes); - - const CfemInt nFields = NUM_FIRST_ORDER_FIELDS + - (includeSecondDerivatives ? NUM_SECOND_ORDER_FIELDS : 0); - - const size_t totalSize = static_cast(nFields) * static_cast(stride); - - if (storage.size() != totalSize) - { - storage.resize(totalSize); - } - - validFlags = MappingEvaluationFlags::None; - } - - inline void invalidate() - { - validFlags = MappingEvaluationFlags::None; - } - - // ================================================================================================ - // SHAPES FIRST DERIVATIVES (Physical Space) - // ================================================================================================ - - [[nodiscard]] inline CfemReal* dN_dx() { - return storage.data() + 0 * stride; - } - - [[nodiscard]] inline CfemReal* dN_dy() { - return storage.data() + 1 * stride; - } - - [[nodiscard]] inline CfemReal* dN_dz() { - return storage.data() + 2 * stride; - } - - // Versions const - [[nodiscard]] inline const CfemReal* dN_dx() const { - CFEM_ASSERT(hasFlag(validFlags, MappingEvaluationFlags::PhysicalFirstDerivatives), "Physical first derivatives not computed?"); - return storage.data() + 0 * stride; - } - - [[nodiscard]] inline const CfemReal* dN_dy() const { - CFEM_ASSERT(hasFlag(validFlags, MappingEvaluationFlags::PhysicalFirstDerivatives), "Physical first derivatives not computed?"); - return storage.data() + 1 * stride; - } - - [[nodiscard]] inline const CfemReal* dN_dz() const { - CFEM_ASSERT(hasFlag(validFlags, MappingEvaluationFlags::PhysicalFirstDerivatives), "Physical first derivatives not computed?"); - return storage.data() + 2 * stride; - } - - // ================================================================================================ - // SHAPES SECOND DERIVATIVES (Physical Space) - // ================================================================================================ - - [[nodiscard]] inline CfemReal* d2N_dx2() noexcept { - return storage.data() + 3 * stride; - } - - [[nodiscard]] inline CfemReal* d2N_dy2() noexcept { - return storage.data() + 4 * stride; - } - - [[nodiscard]] inline CfemReal* d2N_dz2() noexcept { - return storage.data() + 5 * stride; - } - - [[nodiscard]] inline CfemReal* d2N_dxdy() noexcept { - return storage.data() + 6 * stride; - } - - [[nodiscard]] inline CfemReal* d2N_dxdz() noexcept { - return storage.data() + 7 * stride; - } - - [[nodiscard]] inline CfemReal* d2N_dydz() noexcept { - return storage.data() + 8 * stride; - } - - // Versions const - [[nodiscard]] inline const CfemReal* d2N_dx2() const { - CFEM_ASSERT(hasSecondDerivatives, "Second derivatives not allocated?"); - return storage.data() + 3 * stride; - } - - [[nodiscard]] inline const CfemReal* d2N_dy2() const { - CFEM_ASSERT(hasSecondDerivatives, "Second derivatives not allocated?"); - return storage.data() + 4 * stride; - } - - [[nodiscard]] inline const CfemReal* d2N_dz2() const { - CFEM_ASSERT(hasSecondDerivatives, "Second derivatives not allocated?"); - return storage.data() + 5 * stride; - } - - [[nodiscard]] inline const CfemReal* d2N_dxdy() const { - CFEM_ASSERT(hasSecondDerivatives, "Second derivatives not allocated?"); - return storage.data() + 6 * stride; - } - - [[nodiscard]] inline const CfemReal* d2N_dxdz() const { - CFEM_ASSERT(hasSecondDerivatives, "Second derivatives not allocated?"); - return storage.data() + 7 * stride; - } - - [[nodiscard]] inline const CfemReal* d2N_dydz() const { - CFEM_ASSERT(hasSecondDerivatives, "Second derivatives not allocated?"); - return storage.data() + 8 * stride; - } - - // ============================================================ - // Getters - // ============================================================ - - inline Vector3D gradient(CfemInt node) const - { - CFEM_ASSERT(node >= 0 && node < numNodes); - return Vector3D(dN_dx()[node], dN_dy()[node], dN_dz()[node]); - } - - inline SymTensor3D hessian(CfemInt node) const - { - CFEM_ASSERT(node >= 0 && node < numNodes); - CFEM_ASSERT(hasSecondDerivatives); - return SymTensor3D(d2N_dx2()[node], d2N_dy2()[node], d2N_dz2()[node], - d2N_dydz()[node], d2N_dxdz()[node], d2N_dxdy()[node]); - } - - /** - * @brief Checks if a specific evaluation field is marked as valid. - */ - inline bool has(MappingEvaluationFlags field) const - { - return hasFlag(validFlags, field); - } - }; - /** * @struct MappingInfoBatch * @brief Stores metric data resulting from the geometric transformation for a batch of points. @@ -258,7 +89,7 @@ namespace cfem } // Calcul des strides et de la taille totale du buffer - paddedNodeStride = computePaddedStride(numNodes); + paddedNodeStride = kernel_utils::computePaddedStride(numNodes); const CfemInt nFields = NUM_FIRST_ORDER_FIELDS + (includeSecondDerivatives ? NUM_SECOND_ORDER_FIELDS : 0); @@ -399,6 +230,32 @@ namespace cfem d2N_dydz(q)[node], d2N_dxdz(q)[node], d2N_dxdy(q)[node]); } + [[nodiscard]] MappingInfoBatchView createView() const + { + MappingInfoBatchView view; + + view.numQuadraturePoints = this->numQuadraturePoints; + view.numNodes = this->numNodes; + view.validFlags = this->validFlags; + view.isCellAffine = this->isCellAffine; + + // std::span prend (pointeur_brut, taille). Coût de l'opération : ~16 octets copiés. + view.physicalPositions = std::span(physicalPositions.data(), numQuadraturePoints); + view.invJacobians = std::span(invJacobians.data(), numQuadraturePoints); + view.detJs = std::span(detJs.data(), numQuadraturePoints); + + if (!normals.empty()) { + view.normals = std::span(normals.data(), numQuadraturePoints); + } + + view.paddedNodeStride = this->paddedNodeStride; + + // Note: paddedNodeStride et les dN_dx_data seront remplis + // par l'intégrateur via l'Arena, pas par cette méthode de géométrie globale. + + return view; + } + /** * @brief Checks if a specific evaluation field is marked as valid. */ @@ -406,7 +263,5 @@ namespace cfem { return hasFlag(validFlags, field); } - }; - } \ No newline at end of file diff --git a/libs/kernel/include/MappingInfoBatchView.h b/libs/kernel/include/MappingInfoBatchView.h new file mode 100644 index 0000000..2a6dc8d --- /dev/null +++ b/libs/kernel/include/MappingInfoBatchView.h @@ -0,0 +1,158 @@ +//************************************************************************ +// --- CFEM++ Library +// --- +// --- Copyright 2024-2026 Ismaël Tchinda Ngueyong et al. +// --- ALL RIGHTS RESERVED +// --- +// --- This software is protected by international copyright laws. +// --- Use, distribution, or modification of this software in any form, +// --- source or binary, for personal, academic, or non-commercial +// --- purposes is permitted free of charge, provided that this +// --- copyright notice and this permission notice appear in all +// --- copies and supporting documentation. +// --- +// --- The authors and contributors provide this code "as is" without +// --- any express or implied warranty. In no event shall they be +// --- held liable for any damages arising from the use of this software. +//************************************************************************ + +#pragma once + +#include "cfemutils.h" +#include "kernel_utils.h" +#include "EvaluationFlags.h" +#include + +namespace cfem +{ + /** + * @struct MappingInfoBatchView + * @brief A zero-copy, non-owning view of metric data for a batch of quadrature points. + * + * This structure is designed for High-Performance Computing (HPC). It does not own any + * memory. Instead, it holds lightweight `std::span` objects pointing to the global geometric + * transformation data (owned by a master MappingInfoBatch) and raw pointers to the temporary + * memory arena (`EvalContext`) for space-specific physical derivatives. + * + * By enforcing a Structure of Arrays (SoA) layout and SIMD-aligned memory padding, this view + * ensures optimal L1 cache utilization and compiler auto-vectorization during finite element integration. + */ + struct MappingInfoBatchView + { + // ==================================================================== + // METADATA (POD) + // ==================================================================== + CfemInt numQuadraturePoints = 0; ///< Number of quadrature points in the current batch. + CfemInt numNodes = 0; ///< Number of basis functions (nodes) for the current FE space. + MappingEvaluationFlags validFlags = MappingEvaluationFlags::None; ///< Bitmask indicating which metric fields are currently computed and valid. + bool isCellAffine = false; ///< True if the cell transformation is affine (constant Jacobian). + + // ==================================================================== + // SHARED GEOMETRIC VIEWS (Zero-Copy from MappingInfoBatch) + // ==================================================================== + std::span physicalPositions; ///< Physical coordinates of the quadrature points. + std::span invJacobians; ///< Inverse of the geometric Jacobian matrix per quadrature point. + std::span detJs; ///< Determinant of the geometric Jacobian per quadrature point. + std::span normals; ///< Physical normal vectors (for boundary/surface integration). + + // ==================================================================== + // SPACE-SPECIFIC DERIVATIVES (Pointers to EvalContext Arena) + // ==================================================================== + + // Pointers are mutable here to allow initialization, but exposed as const to the user via getters. + CfemReal* dN_dx_data = nullptr; ///< Base pointer to physical x-derivatives. + CfemReal* dN_dy_data = nullptr; ///< Base pointer to physical y-derivatives. + CfemReal* dN_dz_data = nullptr; ///< Base pointer to physical z-derivatives. + + CfemReal* d2N_dxx_data = nullptr; ///< Base pointer to physical xx-second derivatives. + CfemReal* d2N_dyy_data = nullptr; ///< Base pointer to physical yy-second derivatives. + CfemReal* d2N_dzz_data = nullptr; ///< Base pointer to physical zz-second derivatives. + CfemReal* d2N_dxy_data = nullptr; ///< Base pointer to physical xy-second derivatives. + CfemReal* d2N_dyz_data = nullptr; ///< Base pointer to physical yz-second derivatives. + CfemReal* d2N_dxz_data = nullptr; ///< Base pointer to physical xz-second derivatives. + + CfemInt paddedNodeStride = 0; ///< Memory stride between quadrature points, aligned for SIMD vectorization. + + // ==================================================================== + // ACCESSORS + // ==================================================================== + + /** + * @name Read-Only Accessors + * @brief Safe getters used by FEFields and integration loops to read mathematical derivatives. + * Memory layout: `data[q * paddedNodeStride + i]`. + * @param q The index of the local quadrature point. + * @return A contiguous, SIMD-aligned constant pointer to the derivatives for all nodes at point q. + */ + ///@{ + [[nodiscard]] inline const CfemReal* dN_dx(CfemInt q) const { return dN_dx_data + q * paddedNodeStride; } + [[nodiscard]] inline const CfemReal* dN_dy(CfemInt q) const { return dN_dy_data + q * paddedNodeStride; } + [[nodiscard]] inline const CfemReal* dN_dz(CfemInt q) const { return dN_dz_data + q * paddedNodeStride; } + + [[nodiscard]] inline const CfemReal* d2N_dxx(CfemInt q) const { return d2N_dxx_data + q * paddedNodeStride; } + [[nodiscard]] inline const CfemReal* d2N_dyy(CfemInt q) const { return d2N_dyy_data + q * paddedNodeStride; } + [[nodiscard]] inline const CfemReal* d2N_dzz(CfemInt q) const { return d2N_dzz_data + q * paddedNodeStride; } + [[nodiscard]] inline const CfemReal* d2N_dxy(CfemInt q) const { return d2N_dxy_data + q * paddedNodeStride; } + [[nodiscard]] inline const CfemReal* d2N_dyz(CfemInt q) const { return d2N_dyz_data + q * paddedNodeStride; } + [[nodiscard]] inline const CfemReal* d2N_dxz(CfemInt q) const { return d2N_dxz_data + q * paddedNodeStride; } + ///@} + + /** + * @name Mutable Accessors + * @brief Internal getters used EXCLUSIVELY by mapping routines (e.g., computePhysicalFirstDerivatives) + * to populate the memory arena. + * @param q The index of the local quadrature point. + * @return A contiguous, SIMD-aligned mutable pointer. + */ + ///@{ + [[nodiscard]] inline CfemReal* dN_dx_mut(CfemInt q) { return dN_dx_data + q * paddedNodeStride; } + [[nodiscard]] inline CfemReal* dN_dy_mut(CfemInt q) { return dN_dy_data + q * paddedNodeStride; } + [[nodiscard]] inline CfemReal* dN_dz_mut(CfemInt q) { return dN_dz_data + q * paddedNodeStride; } + ///@} + + // ==================================================================== + // ARENA ALLOCATION + // ==================================================================== + + /** + * @brief Allocates contiguous, SIMD-aligned memory in the temporary EvalContext arena + * for space-specific physical derivatives. + * + * This method handles the offset distribution to enforce a pure Structure of Arrays (SoA) layout. + * It must ONLY be called for FE Space views, never for the pure geometric view. + * + * @param scratchStorage Contiguous memory bloc allocated by the caller. + * @param spaceNumNodes The number of basis functions in the target FE space. + * @param spatialDim The spatial dimension of the mesh (1, 2, or 3). + * @param nQp The number of quadrature points in the current batch. + */ + inline void setupPhysicalDerivativesMemory(std::span scratchStorage, + CfemInt spaceNumNodes, + CfemInt spatialDim, + CfemInt nQp) + { + this->numNodes = spaceNumNodes; + this->paddedNodeStride = kernel_utils::computePaddedStride(spaceNumNodes); + + CfemInt reqSize = spatialDim * nQp * this->paddedNodeStride; + CFEM_ASSERT(scratchStorage.size() >= static_cast(reqSize), + "Insufficient memory provided for physical derivatives"); + + this->dN_dx_data = scratchStorage.data(); + if (spatialDim >= 2) this->dN_dy_data = scratchStorage.data() + (1 * nQp * this->paddedNodeStride); + if (spatialDim == 3) this->dN_dz_data = scratchStorage.data() + (2 * nQp * this->paddedNodeStride); + + this->validFlags |= MappingEvaluationFlags::PhysicalFirstDerivatives; + } + + /** + * @brief Quick check to verify if physical first derivatives have been computed and allocated. + * @return True if valid, false otherwise. + */ + [[nodiscard]] inline bool hasPhysicalFirstDerivatives() const noexcept + { + return hasFlag(validFlags, MappingEvaluationFlags::PhysicalFirstDerivatives); + } + }; + +} // namespace cfem \ No newline at end of file diff --git a/libs/kernel/include/ShapeInfo.h b/libs/kernel/include/ShapeInfo.h new file mode 100644 index 0000000..4333294 --- /dev/null +++ b/libs/kernel/include/ShapeInfo.h @@ -0,0 +1,247 @@ +//************************************************************************ +// --- CFEM++ Library +// --- +// --- Copyright 2024-2026 Ismaël Tchinda Ngueyong et al. +// --- ALL RIGHTS RESERVED +// --- +// --- This software is protected by international copyright laws. +// --- Use, distribution, or modification of this software in any form, +// --- source or binary, for personal, academic, or non-commercial +// --- purposes is permitted free of charge, provided that this +// --- copyright notice and this permission notice appear in all +// --- copies and supporting documentation. +// --- +// --- The authors and contributors provide this code "as is" without +// --- any express or implied warranty. In no event shall they be +// --- held liable for any damages arising from the use of this software. +//************************************************************************ + +#pragma once + +#include "cfemutils.h" +#include "kernel_utils.h" + +#include "EvaluationFlags.h" +#include "Tensor3D.h" +#include "DynamicVector.h" +#include "DynamicMatrix.h" + +namespace cfem +{ + // ======================================================================== + // ShapeData : POD HPC Pur (Aucun pointeur stocké, calculs à la volée) + // ======================================================================== + + /** + * @struct ShapeInfo + * @brief Contiguous, cache-aligned storage buffer for element shape function evaluations. + * + * This structure implements a Structure-of-Arrays (SoA) layout to store values, gradients, + * and Hessians for a set of quadrature points. + * + * ### Performance Design + * - **Contiguous Memory:** All fields are stored in a single `DynamicVector` allocation. + * - **Cache Locality:** Optimized for L1/L2 cache prefetching by using padded strides. + * - **Zero Overhead:** Accessors provide lightweight views (raw pointers) to data without + * requiring heavy abstractions in hot loops. + * - **Memory Safety:** Designed as a move-only type (Rule of 0) to prevent dangling + * pointers resulting from accidental object cloning. + */ + struct ShapeInfo + { + static constexpr CfemInt NUM_FIRST_ORDER_FIELDS = 4; ///< Number of fields for first-order derivatives including the shapes values: \f$ \left\{N, \frac{\partial N}{\partial \xi}, \frac{\partial N}{\partial \eta}, \frac{\partial N}{\partial \zeta} \right} \f$ + static constexpr CfemInt NUM_SECOND_ORDER_FIELDS = 6; ///< Number of fields for second-order derivatives: \f$ \left\{\frac{\partial^2 N}{\partial \xi^2}, \frac{\partial^2 N}{\partial \eta^2}, \frac{\partial^2 N}{\partial \zeta^2}, \frac{\partial^2 N}{\partial \xi \partial \eta}, \frac{\partial^2 N}{\partial \xi \partial \zeta}, \frac{\partial^2 N}{\partial \eta\partial \zeta} \right} \f$ + + DynamicVector storage; ///< Centralized contiguous buffer + + CfemInt numNodes = 0; ///< Number of nodes per element + CfemInt stride = 0; ///< Padded stride to ensure SIMD alignment + bool hasSecondDerivatives = false; + + bool shapeMustBeUpdated = true; + ShapeEvaluationFlags validFlags = ShapeEvaluationFlags::None; + + /** + * @brief Allocates and configures the internal storage layout. + * + * @param nNodes Number of interpolation nodes in the reference element. + * @param includeSecondDerivatives If true, memory for Hessian-related quantities is allocated. + * + * @note + * Calling this function invalidates all previously acquired raw field pointers. + */ + inline void setup(CfemInt nNodes, bool includeSecondDerivatives = false) + { + numNodes = nNodes; + hasSecondDerivatives = includeSecondDerivatives; + stride = kernel_utils::computePaddedStride(numNodes); + + const CfemInt nFields = NUM_FIRST_ORDER_FIELDS + + (includeSecondDerivatives ? NUM_SECOND_ORDER_FIELDS : 0); + + const size_t totalSize = static_cast(nFields) * static_cast(stride); + + if (storage.size() != totalSize) + { + storage.resize(totalSize); + } + + validFlags = ShapeEvaluationFlags::None; + } + + // ================================================================================================ + // SHAPES VALUES + // ================================================================================================ + + /** @brief Returns a writable pointer to shape function values. */ + [[nodiscard]] inline CfemReal* values() noexcept { + return storage.data(); + } + + // ================================================================================================ + // SHAPES FIRST DERIVATIVES + // ================================================================================================ + + /** @brief Returns a writable pointer to shape derivatives along the reference \f$\xi\f$ direction. */ + [[nodiscard]] inline CfemReal* dN_dxi() noexcept { + return storage.data() + 1 * stride; + } + + /** @brief Returns a writable pointer to shape derivatives along the reference \f$\eta\f$ direction. */ + [[nodiscard]] inline CfemReal* dN_deta() noexcept { + return storage.data() + 2 * stride; + } + + /** @brief Returns a writable pointer to shape derivatives along the reference \f$\zeta\f$ direction. */ + [[nodiscard]] inline CfemReal* dN_dzeta() noexcept { + return storage.data() + 3 * stride; + } + + // ================================================================================================ + // SHAPES SECOND DERIVATIVES + // ================================================================================================ + + /** @brief Returns a writable pointer to shape second derivatives \f$\partial^2 N \partial \zeta^2\f$ direction. */ + [[nodiscard]] inline CfemReal* d2N_dxi2() noexcept { + return storage.data() + 4 * stride; + } + + [[nodiscard]] inline CfemReal* d2N_deta2() noexcept { + return storage.data() + 5 * stride; + } + + [[nodiscard]] inline CfemReal* d2N_dzeta2() noexcept { + return storage.data() + 6 * stride; + } + + [[nodiscard]] inline CfemReal* d2N_dxideta() noexcept { + return storage.data() + 7 * stride; + } + + [[nodiscard]] inline CfemReal* d2N_dxidzeta() noexcept { + return storage.data() + 8 * stride; + } + + [[nodiscard]] inline CfemReal* d2N_detadzeta() noexcept { + return storage.data() + 9 * stride; + } + + + // Versions const (pour la lecture) + [[nodiscard]] inline const CfemReal* values() const { + CFEM_ASSERT(has(ShapeEvaluationFlags::Value), "Shape values not computed?"); + return storage.data(); + } + + [[nodiscard]] inline const CfemReal* dN_dxi() const { + CFEM_ASSERT( has(ShapeEvaluationFlags::FirstDerivatives), "Shape first derivatives not computed?"); + return storage.data() + 1 * stride; + } + + [[nodiscard]] inline const CfemReal* dN_deta() const { + CFEM_ASSERT( has(ShapeEvaluationFlags::FirstDerivatives), "Shape first derivatives not computed?"); + return storage.data() + 2 * stride; + } + + [[nodiscard]] inline const CfemReal* dN_dzeta() const { + CFEM_ASSERT( has(ShapeEvaluationFlags::FirstDerivatives), "Shape first derivatives not computed?"); + return storage.data() + 3 * stride; + } + + + [[nodiscard]] inline const CfemReal* d2N_dxi2() const { + CFEM_ASSERT(hasSecondDerivatives, "Second derivatives not computed?"); + return storage.data() + 4 * stride; + } + + [[nodiscard]] inline const CfemReal* d2N_deta2() const { + CFEM_ASSERT(hasSecondDerivatives, "Second derivatives not computed?"); + return storage.data() + 5 * stride; + } + + [[nodiscard]] inline const CfemReal* d2N_dzeta2() const { + CFEM_ASSERT(hasSecondDerivatives, "Second derivatives not computed?"); + return storage.data() + 6 * stride; + } + + [[nodiscard]] inline const CfemReal* d2N_dxideta() const { + CFEM_ASSERT(hasSecondDerivatives, "Second derivatives not computed?"); + return storage.data() + 7 * stride; + } + + [[nodiscard]] inline const CfemReal* d2N_dxidzeta() const { + CFEM_ASSERT(hasSecondDerivatives, "Second derivatives not computed?"); + return storage.data() + 8 * stride; + } + + [[nodiscard]] inline const CfemReal* d2N_detadzeta() const { + CFEM_ASSERT(hasSecondDerivatives, "Second derivatives not computed?"); + return storage.data() + 9 * stride; + } + + + // ============================================================ + // Getters + // ============================================================ + + /** + * @brief Returns a reference-space gradient vector for a specific node index. + * @param i Node index (0 to numNodes-1). + */ + inline Vector3D gradient(CfemInt i) const + { + CFEM_ASSERT(i >= 0 && i < numNodes); + return Vector3D(dN_dxi()[i], dN_deta()[i], dN_dzeta()[i]); + } + + /** + * @brief Returns the symmetric Hessian tensor for a specific node index. + * @param i Node index (0 to numNodes-1). + */ + inline SymTensor3D hessian(CfemInt i) const + { + CFEM_ASSERT(i >= 0 && i < numNodes); + CFEM_ASSERT(hasSecondDerivatives); + return SymTensor3D( + d2N_dxi2()[i], d2N_deta2()[i], d2N_dzeta2()[i], + d2N_detadzeta()[i], d2N_dxidzeta()[i], d2N_dxideta()[i] + ); + } + + inline bool has(ShapeEvaluationFlags field) const + { + return hasFlag(validFlags, field); + } + + inline size_t storageSize() const + { + return storage.size(); + } + + inline size_t storageBytes() const + { + return storage.size() * sizeof(CfemReal); + } + }; + +} \ No newline at end of file diff --git a/libs/fem/reference_element/include/ShapeInfo.h b/libs/kernel/include/ShapeInfoBatch.h similarity index 52% rename from libs/fem/reference_element/include/ShapeInfo.h rename to libs/kernel/include/ShapeInfoBatch.h index 58a044d..95d594c 100644 --- a/libs/fem/reference_element/include/ShapeInfo.h +++ b/libs/kernel/include/ShapeInfoBatch.h @@ -1,6 +1,25 @@ +//************************************************************************ +// --- CFEM++ Library +// --- +// --- Copyright 2024-2026 Ismaël Tchinda Ngueyong et al. +// --- ALL RIGHTS RESERVED +// --- +// --- This software is protected by international copyright laws. +// --- Use, distribution, or modification of this software in any form, +// --- source or binary, for personal, academic, or non-commercial +// --- purposes is permitted free of charge, provided that this +// --- copyright notice and this permission notice appear in all +// --- copies and supporting documentation. +// --- +// --- The authors and contributors provide this code "as is" without +// --- any express or implied warranty. In no event shall they be +// --- held liable for any damages arising from the use of this software. +//************************************************************************ + #pragma once #include "cfemutils.h" +#include "kernel_utils.h" #include "EvaluationFlags.h" #include "Tensor3D.h" @@ -9,241 +28,6 @@ namespace cfem { - - // ======================================================================== - // Paramètres matériels (Hardware-aware) - // ======================================================================== - - static constexpr size_t CACHELINE_SIZE = 64; ///< Required alignment for SIMD operations (cache-line aware). - - /** - * @brief Calcule un "stride" (pas) garantissant l'alignement sur une ligne de cache. - * Essentiel pour l'auto-vectorisation AVX2/AVX-512/SVE et pour éviter - * que des variables indépendantes ne partagent la même cacheline. - */ - inline constexpr CfemInt computePaddedStride(CfemInt nNodes) noexcept - { - constexpr CfemInt alignment = CACHELINE_SIZE / sizeof(CfemReal); - return ((nNodes + alignment - 1) / alignment) * alignment; - } - - // ======================================================================== - // ShapeData : POD HPC Pur (Aucun pointeur stocké, calculs à la volée) - // ======================================================================== - - /** - * @struct ShapeInfo - * @brief Contiguous, cache-aligned storage buffer for element shape function evaluations. - * - * This structure implements a Structure-of-Arrays (SoA) layout to store values, gradients, - * and Hessians for a set of quadrature points. - * - * ### Performance Design - * - **Contiguous Memory:** All fields are stored in a single `DynamicVector` allocation. - * - **Cache Locality:** Optimized for L1/L2 cache prefetching by using padded strides. - * - **Zero Overhead:** Accessors provide lightweight views (raw pointers) to data without - * requiring heavy abstractions in hot loops. - * - **Memory Safety:** Designed as a move-only type (Rule of 0) to prevent dangling - * pointers resulting from accidental object cloning. - */ - struct ShapeInfo - { - static constexpr CfemInt NUM_FIRST_ORDER_FIELDS = 4; ///< Number of fields for first-order derivatives including the shapes values: \f$ \left\{N, \frac{\partial N}{\partial \xi}, \frac{\partial N}{\partial \eta}, \frac{\partial N}{\partial \zeta} \right} \f$ - static constexpr CfemInt NUM_SECOND_ORDER_FIELDS = 6; ///< Number of fields for second-order derivatives: \f$ \left\{\frac{\partial^2 N}{\partial \xi^2}, \frac{\partial^2 N}{\partial \eta^2}, \frac{\partial^2 N}{\partial \zeta^2}, \frac{\partial^2 N}{\partial \xi \partial \eta}, \frac{\partial^2 N}{\partial \xi \partial \zeta}, \frac{\partial^2 N}{\partial \eta\partial \zeta} \right} \f$ - - DynamicVector storage; ///< Centralized contiguous buffer - - CfemInt numNodes = 0; ///< Number of nodes per element - CfemInt stride = 0; ///< Padded stride to ensure SIMD alignment - bool hasSecondDerivatives = false; - - bool shapeMustBeUpdated = true; - ShapeEvaluationFlags validFlags = ShapeEvaluationFlags::None; - - /** - * @brief Allocates and configures the internal storage layout. - * - * @param nNodes Number of interpolation nodes in the reference element. - * @param includeSecondDerivatives If true, memory for Hessian-related quantities is allocated. - * - * @note - * Calling this function invalidates all previously acquired raw field pointers. - */ - inline void setup(CfemInt nNodes, bool includeSecondDerivatives = false) - { - numNodes = nNodes; - hasSecondDerivatives = includeSecondDerivatives; - stride = computePaddedStride(numNodes); - - const CfemInt nFields = NUM_FIRST_ORDER_FIELDS + - (includeSecondDerivatives ? NUM_SECOND_ORDER_FIELDS : 0); - - const size_t totalSize = static_cast(nFields) * static_cast(stride); - - if (storage.size() != totalSize) - { - storage.resize(totalSize); - } - - validFlags = ShapeEvaluationFlags::None; - } - - // ================================================================================================ - // SHAPES VALUES - // ================================================================================================ - - /** @brief Returns a writable pointer to shape function values. */ - [[nodiscard]] inline CfemReal* values() noexcept { - return storage.data(); - } - - // ================================================================================================ - // SHAPES FIRST DERIVATIVES - // ================================================================================================ - - /** @brief Returns a writable pointer to shape derivatives along the reference \f$\xi\f$ direction. */ - [[nodiscard]] inline CfemReal* dN_dxi() noexcept { - return storage.data() + 1 * stride; - } - - /** @brief Returns a writable pointer to shape derivatives along the reference \f$\eta\f$ direction. */ - [[nodiscard]] inline CfemReal* dN_deta() noexcept { - return storage.data() + 2 * stride; - } - - /** @brief Returns a writable pointer to shape derivatives along the reference \f$\zeta\f$ direction. */ - [[nodiscard]] inline CfemReal* dN_dzeta() noexcept { - return storage.data() + 3 * stride; - } - - // ================================================================================================ - // SHAPES SECOND DERIVATIVES - // ================================================================================================ - - /** @brief Returns a writable pointer to shape second derivatives \f$\partial^2 N \partial \zeta^2\f$ direction. */ - [[nodiscard]] inline CfemReal* d2N_dxi2() noexcept { - return storage.data() + 4 * stride; - } - - [[nodiscard]] inline CfemReal* d2N_deta2() noexcept { - return storage.data() + 5 * stride; - } - - [[nodiscard]] inline CfemReal* d2N_dzeta2() noexcept { - return storage.data() + 6 * stride; - } - - [[nodiscard]] inline CfemReal* d2N_dxideta() noexcept { - return storage.data() + 7 * stride; - } - - [[nodiscard]] inline CfemReal* d2N_dxidzeta() noexcept { - return storage.data() + 8 * stride; - } - - [[nodiscard]] inline CfemReal* d2N_detadzeta() noexcept { - return storage.data() + 9 * stride; - } - - - // Versions const (pour la lecture) - [[nodiscard]] inline const CfemReal* values() const { - CFEM_ASSERT(has(ShapeEvaluationFlags::Value), "Shape values not computed?"); - return storage.data(); - } - - [[nodiscard]] inline const CfemReal* dN_dxi() const { - CFEM_ASSERT( has(ShapeEvaluationFlags::FirstDerivatives), "Shape first derivatives not computed?"); - return storage.data() + 1 * stride; - } - - [[nodiscard]] inline const CfemReal* dN_deta() const { - CFEM_ASSERT( has(ShapeEvaluationFlags::FirstDerivatives), "Shape first derivatives not computed?"); - return storage.data() + 2 * stride; - } - - [[nodiscard]] inline const CfemReal* dN_dzeta() const { - CFEM_ASSERT( has(ShapeEvaluationFlags::FirstDerivatives), "Shape first derivatives not computed?"); - return storage.data() + 3 * stride; - } - - - [[nodiscard]] inline const CfemReal* d2N_dxi2() const { - CFEM_ASSERT(hasSecondDerivatives, "Second derivatives not computed?"); - return storage.data() + 4 * stride; - } - - [[nodiscard]] inline const CfemReal* d2N_deta2() const { - CFEM_ASSERT(hasSecondDerivatives, "Second derivatives not computed?"); - return storage.data() + 5 * stride; - } - - [[nodiscard]] inline const CfemReal* d2N_dzeta2() const { - CFEM_ASSERT(hasSecondDerivatives, "Second derivatives not computed?"); - return storage.data() + 6 * stride; - } - - [[nodiscard]] inline const CfemReal* d2N_dxideta() const { - CFEM_ASSERT(hasSecondDerivatives, "Second derivatives not computed?"); - return storage.data() + 7 * stride; - } - - [[nodiscard]] inline const CfemReal* d2N_dxidzeta() const { - CFEM_ASSERT(hasSecondDerivatives, "Second derivatives not computed?"); - return storage.data() + 8 * stride; - } - - [[nodiscard]] inline const CfemReal* d2N_detadzeta() const { - CFEM_ASSERT(hasSecondDerivatives, "Second derivatives not computed?"); - return storage.data() + 9 * stride; - } - - - // ============================================================ - // Getters - // ============================================================ - - /** - * @brief Returns a reference-space gradient vector for a specific node index. - * @param i Node index (0 to numNodes-1). - */ - inline Vector3D gradient(CfemInt i) const - { - CFEM_ASSERT(i >= 0 && i < numNodes); - return Vector3D(dN_dxi()[i], dN_deta()[i], dN_dzeta()[i]); - } - - /** - * @brief Returns the symmetric Hessian tensor for a specific node index. - * @param i Node index (0 to numNodes-1). - */ - inline SymTensor3D hessian(CfemInt i) const - { - CFEM_ASSERT(i >= 0 && i < numNodes); - CFEM_ASSERT(hasSecondDerivatives); - return SymTensor3D( - d2N_dxi2()[i], d2N_deta2()[i], d2N_dzeta2()[i], - d2N_detadzeta()[i], d2N_dxidzeta()[i], d2N_dxideta()[i] - ); - } - - inline bool has(ShapeEvaluationFlags field) const - { - return hasFlag(validFlags, field); - } - - inline size_t storageSize() const - { - return storage.size(); - } - - inline size_t storageBytes() const - { - return storage.size() * sizeof(CfemReal); - } - }; - - /** * @struct ShapeInfoBatch * @brief Contiguous, cache-aligned storage buffer for shape function evaluations across a batch of quadrature points. @@ -290,7 +74,7 @@ namespace cfem numNodes = nNodes; hasSecondDerivatives = includeSecondDerivatives; - paddedNodeStride = computePaddedStride(numNodes); + paddedNodeStride = kernel_utils::computePaddedStride(numNodes); const CfemInt nFields = NUM_FIRST_ORDER_FIELDS + (includeSecondDerivatives ? NUM_SECOND_ORDER_FIELDS : 0); diff --git a/libs/kernel/include/kernel_utils.h b/libs/kernel/include/kernel_utils.h new file mode 100644 index 0000000..cc5c910 --- /dev/null +++ b/libs/kernel/include/kernel_utils.h @@ -0,0 +1,46 @@ +//************************************************************************ +// --- CFEM++ Library +// --- +// --- Copyright 2024-2026 Ismaël Tchinda Ngueyong et al. +// --- ALL RIGHTS RESERVED +// --- +// --- This software is protected by international copyright laws. +// --- Use, distribution, or modification of this software in any form, +// --- source or binary, for personal, academic, or non-commercial +// --- purposes is permitted free of charge, provided that this +// --- copyright notice and this permission notice appear in all +// --- copies and supporting documentation. +// --- +// --- The authors and contributors provide this code "as is" without +// --- any express or implied warranty. In no event shall they be +// --- held liable for any damages arising from the use of this software. +//************************************************************************ + + +#pragma once + +#include "cfemutils.h" + +namespace cfem +{ + namespace kernel_utils + { + // ======================================================================== + // Harware parameters + // ======================================================================== + + static constexpr size_t CACHELINE_SIZE = 64; ///< Required alignment for SIMD operations (cache-line aware). + + /** + * @brief Calcule un "stride" (pas) garantissant l'alignement sur une ligne de cache. + * Essentiel pour l'auto-vectorisation AVX2/AVX-512/SVE et pour éviter + * que des variables indépendantes ne partagent la même cacheline. + */ + inline constexpr CfemInt computePaddedStride(CfemInt nNodes) noexcept + { + constexpr CfemInt alignment = CACHELINE_SIZE / sizeof(CfemReal); + return ((nNodes + alignment - 1) / alignment) * alignment; + } + + } +} \ No newline at end of file diff --git a/libs/kernel/src/EvalContext.cpp b/libs/kernel/src/EvalContext.cpp new file mode 100644 index 0000000..34de07e --- /dev/null +++ b/libs/kernel/src/EvalContext.cpp @@ -0,0 +1,17 @@ +//************************************************************************ +// --- CFEM++ Library +// --- +// --- Copyright 2024-2026 Ismaël Tchinda Ngueyong et al. +// --- ALL RIGHTS RESERVED +// --- +// --- This software is protected by international copyright laws. +// --- Use, distribution, or modification of this software in any form, +// --- source or binary, for personal, academic, or non-commercial +// --- purposes is permitted free of charge, provided that this +// --- copyright notice and this permission notice appear in all +// --- copies and supporting documentation. +// --- +// --- The authors and contributors provide this code "as is" without +// --- any express or implied warranty. In no event shall they be +// --- held liable for any damages arising from the use of this software. +//************************************************************************ \ No newline at end of file diff --git a/libs/prepostprocessing/numerical_integration/CMakeLists.txt b/libs/prepostprocessing/numerical_integration/CMakeLists.txt index 52657b7..8312b01 100644 --- a/libs/prepostprocessing/numerical_integration/CMakeLists.txt +++ b/libs/prepostprocessing/numerical_integration/CMakeLists.txt @@ -7,11 +7,12 @@ target_include_directories(cfem_numerical_integration ) target_link_libraries(cfem_numerical_integration - PUBLIC cfem_utils - cfem_openmp_interface - cfem_reference_element - cfem_quadrature - cfem_mesh - cfem_fespace - cfem_expressions + PUBLIC cfem_utils + cfem_kernel + cfem_openmp_interface + cfem_reference_element + cfem_quadrature + cfem_mesh + cfem_fespace + cfem_expressions ) diff --git a/libs/prepostprocessing/numerical_integration/src/FunctionIntegrator.cpp b/libs/prepostprocessing/numerical_integration/src/FunctionIntegrator.cpp index babc06e..88dde69 100644 --- a/libs/prepostprocessing/numerical_integration/src/FunctionIntegrator.cpp +++ b/libs/prepostprocessing/numerical_integration/src/FunctionIntegrator.cpp @@ -24,6 +24,13 @@ #include "MeshEntity.h" #include "EvalContext.h" +#include "ShapeInfo.h" +#include "ShapeInfoBatch.h" +#include "MappingInfo.h" +#include "MappingInfoBatch.h" +#include "MappingInfoBatchView.h" + + #include #include @@ -193,255 +200,7 @@ namespace cfem else for (const auto &target : targets) if (target.second) *(target.second) = 0.0; } -// void FunctionIntegrator::integrate(const std::vector &targets, -// const MeshEntity &entity) -// { -// if (targets.empty()) return; -// CFEM_INFO << "Integrating over entity " << entity.getName(); - -// // ============================================================ -// // ANALYSE ET BINDING O(1) -// // ============================================================ -// std::vector activeIdx; -// ShapeEvaluationFlags fieldShapesFlags = ShapeEvaluationFlags::None; -// bool fieldsDependsOnGeoNormal = false; -// std::set> requiredSpaces; -// CfemInt geoOrder = m_mesh->getOrderInt(); - -// for (size_t i = 0; i < targets.size(); ++i) { -// if (!targets[i].first) continue; - -// if (targets[i].first->getNumComponents() != 1) { -// CFEM_THROW( "FunctionIntegrator only accepts scalar expressions. " -// << "Please pass each component separately."); -// } - -// targets[i].first->prepare(); - -// if (targets[i].second) *(targets[i].second) = 0.0; -// activeIdx.push_back(i); -// fieldShapesFlags |= targets[i].first->requiredShapeFlags(); -// fieldsDependsOnGeoNormal = fieldsDependsOnGeoNormal || targets[i].first->dependsOnGeometricNormal(); - -// targets[i].first->collectRequiredSpaces(requiredSpaces); -// } - -// if (activeIdx.empty()) return; - -// std::map spaceToId; -// std::vector> idToSpace; -// CfemInt nextId = 0; -// for (const auto& sp : requiredSpaces) { -// spaceToId[sp.get()] = nextId++; -// idToSpace.push_back(sp); -// } - - -// for (size_t idx : activeIdx) { -// targets[idx].first->bind(spaceToId); -// } - -// const bool needsFieldFirstDerivatives = hasFlag(fieldShapesFlags, ShapeEvaluationFlags::FirstDerivatives); -// const bool needsFieldSecondDerivatives = hasFlag(fieldShapesFlags, ShapeEvaluationFlags::SecondDerivatives); - -// MappingEvaluationFlags geoMappingFlags = MappingEvaluationFlags::PhysicalPosition| -// MappingEvaluationFlags::Determinant | -// (fieldsDependsOnGeoNormal? MappingEvaluationFlags::Normal : MappingEvaluationFlags::None); -// ShapeEvaluationFlags geoShapeFlags = transformMappingToShapeFlags(geoMappingFlags) ; - -// if (needsFieldFirstDerivatives) { -// geoShapeFlags |= ShapeEvaluationFlags::FirstDerivatives; -// geoMappingFlags |= MappingEvaluationFlags::InverseJacobian; -// } - -// IntegrationScheme scheme; - -// // ============================================================ -// // QUADRATURE ET CACHING DE REFERENCE (Read-Only) -// // ============================================================ -// struct SpaceCache { -// CfemInt contextIndex; -// std::vector shapes; -// }; - -// struct CellCacheEntry { -// const QuadratureRule* quad = nullptr; -// std::vector geoShapes; -// std::vector spaceCaches; -// }; - -// std::vector fastCache(static_cast(CellType::COUNT)); - -// for (CellType cell_type : m_mesh->getCellTypesPresent()) -// { -// CfemInt idx = static_cast(cell_type); -// const auto &quad = scheme.getRule(cell_type, m_schemes_params.getNumIntgPt(cell_type)); -// const auto &lQuadPts = quad.getPoints(); - -// fastCache[idx].quad = &quad; -// fastCache[idx].geoShapes.resize(lQuadPts.size()); - -// const auto &refElGeo = ReferenceElementFactory::getReferenceElement(cell_type, geoOrder); - -// for (const auto& space : idToSpace) { -// fastCache[idx].spaceCaches.push_back({spaceToId[space.get()], std::vector(lQuadPts.size())}); -// } - -// for (size_t q = 0; q < lQuadPts.size(); ++q) { -// refElGeo.evaluateShapes(lQuadPts[q].xi, geoShapeFlags, fastCache[idx].geoShapes[q]); -// fastCache[idx].geoShapes[q].shapeMustBeUpdated = false; - -// for (size_t sIdx = 0; sIdx < idToSpace.size(); ++sIdx) { -// CfemInt spaceOrder = idToSpace[sIdx]->getOrderInt(); -// const auto& refElSpace = ReferenceElementFactory::getReferenceElement(cell_type, spaceOrder); - -// refElSpace.evaluateShapes(lQuadPts[q].xi, fieldShapesFlags, fastCache[idx].spaceCaches[sIdx].shapes[q]); -// fastCache[idx].spaceCaches[sIdx].shapes[q].shapeMustBeUpdated = false; -// } -// } -// } - -// const std::vector &cells_vIds = entity.getCellIds(); - -// // ============================================================ -// // BOUCLE D'INTÉGRATION PARALLÈLE OPTIMISÉE -// // ============================================================ - -// #pragma omp parallel -// { -// std::vector threadTotals(targets.size(), 0.0); -// EvalContext ctx; -// CfemReal outVal[9] = {0.0}; -// std::span spanOut(outVal, 1); - -// MappingInfo mapGeo; -// std::vector spaceMappings(idToSpace.size()); -// DynamicVector physCoords; - -// CellType lastType = CellType::COUNT; -// const ReferenceElement* refElGeo = nullptr; -// const CellCacheEntry* cache = nullptr; - -// #pragma omp for schedule(static) -// for (CfemInt i = 0; i < entity.getNumCells(); ++i) -// { -// CfemInt cellid = cells_vIds[i]; -// const auto &cell = m_mesh->getCell(cellid); - -// // Maintien du cache local O(1) : On ne "setup" que si la topologie change -// if (cell.type != lastType) { -// lastType = cell.type; -// refElGeo = &ReferenceElementFactory::getReferenceElement(lastType, geoOrder); -// cache = &fastCache[static_cast(lastType)]; - -// mapGeo.setup(refElGeo->getNumNodes()); -// for (size_t sIdx = 0; sIdx < idToSpace.size(); ++sIdx) { -// CfemInt nNodesSpace = static_cast(cache->spaceCaches[sIdx].shapes[0].numNodes); -// spaceMappings[sIdx].setup(nNodesSpace); -// } -// } - -// const auto &lQuadPts = cache->quad->getPoints(); -// m_mesh->getCellVerticesCoords(cellid, physCoords); - -// const bool isAffine = (geoOrder == 1) || m_mesh->isCellAffine(cellid); - -// if (isAffine) { -// // CFEM_INFO << cache->geoShapes[0].gradient(2); -// refElGeo->computeMapping( -// lQuadPts[0].xi, -// physCoords, -// geoMappingFlags, -// cache->geoShapes[0], -// mapGeo -// ); -// } - -// for (size_t q = 0; q < lQuadPts.size(); ++q) -// { -// const auto &qp = lQuadPts[q]; - -// if (isAffine) { -// refElGeo->computeMapping( -// qp.xi, -// physCoords, -// MappingEvaluationFlags::PhysicalPosition, -// cache->geoShapes[q], -// mapGeo -// ); -// } else { -// refElGeo->computeMapping( -// qp.xi, -// physCoords, -// geoMappingFlags, -// cache->geoShapes[q], -// mapGeo -// ); -// } - -// const CfemReal wDetJ = mapGeo.detJ * qp.weight; - -// ctx.resetBuffers(); // Vide l'index de cache d'expressions pour ce point -// auto bookmark = ctx.getBookmark(); // Sauvegarde l'état exact de l'Arena mémoire - -// ctx.currentCellId = cellid; -// ctx.singleWS.xi = qp.xi; -// ctx.singleWS.physPos = mapGeo.physicalPosition; -// ctx.geometricMapping = &mapGeo; - -// for (size_t sIdx = 0; sIdx < cache->spaceCaches.size(); ++sIdx) { -// const auto& spCache = cache->spaceCaches[sIdx]; -// MappingInfo& mapSpace = spaceMappings[sIdx]; - -// mapSpace.detJ = mapGeo.detJ; -// mapSpace.invJacobian = mapGeo.invJacobian; -// mapSpace.physicalPosition = mapGeo.physicalPosition; -// mapSpace.validFlags |= MappingEvaluationFlags::PhysicalPosition | MappingEvaluationFlags::InverseJacobian; - -// // CFEM_INFO << "gradient de reference: "; -// // for (CfemInt l = 0; l < 3; ++l) -// // { -// // CFEM_INFO -// // << spCache.shapes[q].gradient(l); -// // } - -// if (needsFieldFirstDerivatives) { -// //refElGeo.computePhysicalFirstDerivativesOnly(spCache.shapes[q]); -// refElGeo->computePhysicalFirstDerivatives(spCache.shapes[q], mapSpace); -// } - -// // CFEM_INFO << "gradient de Physiques: "; - -// // for (CfemInt l = 0; l < 3; ++l) -// // { -// // CFEM_INFO -// // << mapSpace.gradient(l); -// // } - -// ctx.setSpaceData(spCache.contextIndex, &spCache.shapes[q], &mapSpace); -// } - -// for (size_t idx : activeIdx) { -// targets[idx].first->evaluate(cellid, qp.xi, spanOut, ctx); -// threadTotals[idx] += spanOut[0] * wDetJ; -// } - -// // Libère TOUTE la mémoire temporaire allouée par `evaluate` pour ce point -// ctx.releaseToBookmark(bookmark); -// // --------------------------------------------------------- -// } -// } - -// #pragma omp critical -// { -// for (size_t idx : activeIdx) { -// if (targets[idx].second) *(targets[idx].second) += threadTotals[idx]; -// } -// } -// } -// } - - void FunctionIntegrator::integrate(const std::vector &targets, +void FunctionIntegrator::integrate(const std::vector &targets, const MeshEntity &entity) { if (targets.empty()) return; @@ -455,6 +214,7 @@ namespace cfem bool fieldsDependsOnGeoNormal = false; std::set> requiredSpaces; CfemInt geoOrder = m_mesh->getOrderInt(); + CfemInt spatialDim =m_mesh->getSpatialDimension(); for (size_t i = 0; i < targets.size(); ++i) { if (!targets[i].first) continue; @@ -515,7 +275,7 @@ namespace cfem struct CellCacheEntryBatch { const QuadratureRule* quad = nullptr; - DynamicVector xiPoints; // Coordonnées de référence pour le Batch + DynamicVector xiPoints; ShapeInfoBatch geoShapesBatch; std::vector spaceCaches; }; @@ -530,7 +290,6 @@ namespace cfem fastCache[idx].quad = &quad; - // Extraction des points xi pour l'interface Batch CfemInt nQp = static_cast(lQuadPts.size()); fastCache[idx].xiPoints.resize(nQp); for (CfemInt q = 0; q < nQp; ++q) { @@ -539,11 +298,9 @@ namespace cfem const auto &refElGeo = ReferenceElementFactory::getReferenceElement(cell_type, geoOrder); - // Évaluation Géométrique de Référence par Lot refElGeo.evaluateShapesBatch(fastCache[idx].xiPoints, geoShapeFlags, fastCache[idx].geoShapesBatch); fastCache[idx].geoShapesBatch.shapeMustBeUpdated = false; - // Évaluation des Espaces de Champs par Lot for (const auto& space : idToSpace) { SpaceCacheBatch spCache; spCache.contextIndex = spaceToId[space.get()]; @@ -567,14 +324,18 @@ namespace cfem { std::vector threadTotals(targets.size(), 0.0); EvalContext ctx; - - // --- NOUVEAU : Buffer par lot pour stocker la sortie de evaluateBatch --- std::vector batchOutVals; + // Le SEUL Owner de la mémoire géométrique MappingInfoBatch mapGeoBatch; - std::vector spaceMappingsBatch(idToSpace.size()); - DynamicVector physCoords; + // L'instance locale de la vue géométrique (pour que son adresse survive à la boucle) + MappingInfoBatchView geoView; + + // NOUVEAU : Un vecteur de Vues (Zéro allocation dynamique) + std::vector spaceMappingsViews(idToSpace.size()); + + DynamicVector physCoords; CellType lastType = CellType::COUNT; const ReferenceElement* refElGeo = nullptr; const CellCacheEntryBatch* cache = nullptr; @@ -585,21 +346,16 @@ namespace cfem CfemInt cellid = cells_vIds[i]; const auto &cell = m_mesh->getCell(cellid); - // Maintien du cache local O(1) if (cell.type != lastType) { lastType = cell.type; refElGeo = &ReferenceElementFactory::getReferenceElement(lastType, geoOrder); cache = &fastCache[static_cast(lastType)]; CfemInt nQp = cache->xiPoints.size(); + + // Seul le Owner géométrique nécessite un redimensionnement (setup) mapGeoBatch.setup(nQp, refElGeo->getNumNodes(), refElGeo->dimension(), false); - for (size_t sIdx = 0; sIdx < idToSpace.size(); ++sIdx) { - CfemInt nNodesSpace = static_cast(cache->spaceCaches[sIdx].shapesBatch.numNodes); - spaceMappingsBatch[sIdx].setup(nQp, nNodesSpace, refElGeo->dimension(), needsFieldSecondDerivatives); - } - - // Redimensionner le buffer de sortie pour qu'il puisse contenir tous les QP if (batchOutVals.size() < static_cast(nQp)) { batchOutVals.resize(nQp); } @@ -609,13 +365,10 @@ namespace cfem const bool isAffine = (geoOrder == 1) || m_mesh->isCellAffine(cellid); // ------------------------------------------------------------- - // ÉTAPE 3.1 : CALCULS BATCH GÉOMÉTRIQUES + // ÉTAPE 3.1 : CALCULS BATCH GÉOMÉTRIQUES (Le Owner) // ------------------------------------------------------------- mapGeoBatch.isCellAffine = isAffine; - // CFEM_INFO << "fieldsDependsOnGeoNormal = " << fieldsDependsOnGeoNormal; - // CFEM_INFO << "HASFLAG NORMALE = " << hasFlag(geoMappingFlags, MappingEvaluationFlags::Normal); - refElGeo->computeMappingBatch( cache->xiPoints, physCoords, @@ -624,58 +377,80 @@ namespace cfem mapGeoBatch ); + // On génère la vue géométrique pure (O(1)) + geoView = mapGeoBatch.createView(); + + // ------------------------------------------------------------- + // ÉTAPE 3.2 : CONFIGURATION DES VUES METIERS ET DE L'ARENA + // ------------------------------------------------------------- + // CRITIQUE HPC : On remet l'Arena à zéro au DÉBUT de la cellule. + // Cela libère la mémoire des dérivées physiques de la cellule précédente. + ctx.resetBuffers(); + + CfemInt nQp = cache->xiPoints.size(); + for (size_t sIdx = 0; sIdx < cache->spaceCaches.size(); ++sIdx) { const auto& spCache = cache->spaceCaches[sIdx]; - MappingInfoBatch& mapSpace = spaceMappingsBatch[sIdx]; - - mapSpace.detJs = mapGeoBatch.detJs; - mapSpace.invJacobians = mapGeoBatch.invJacobians; - mapSpace.physicalPositions = mapGeoBatch.physicalPositions; - mapSpace.validFlags |= MappingEvaluationFlags::PhysicalPosition | MappingEvaluationFlags::InverseJacobian; + + // 1. Zéro-Copie : La vue métier hérite de la vue géométrique + spaceMappingsViews[sIdx] = geoView; + // 2. Allocation locale via l'Arena et calcul de la physique if (needsFieldFirstDerivatives) { - refElGeo->computePhysicalFirstDerivativesBatch(spCache.shapesBatch, mapSpace); + + // L'intégrateur demande la mémoire continue à l'Arena + CfemInt paddedNodes = kernel_utils::computePaddedStride(spCache.shapesBatch.numNodes); + std::span arenaStorage = ctx.getBuffer(spatialDim * nQp * paddedNodes); + + // La vue configure ses pointeurs internes + + // CFEM_INFO << "IIIIIIIIIIIIIIIIIIIII"; + spaceMappingsViews[sIdx].setupPhysicalDerivativesMemory(arenaStorage, spCache.shapesBatch.numNodes, spatialDim, nQp); + + + // Le noyau vectorisé calcule la physique + refElGeo->computePhysicalFirstDerivativesBatch(spCache.shapesBatch, spaceMappingsViews[sIdx]); + // CFEM_INFO << "OK"; } } // ------------------------------------------------------------- - // ÉTAPE 3.2 : CONFIGURATION DU CONTEXTE O(1) + // ÉTAPE 3.3 : BINDING AU CONTEXTE O(1) // ------------------------------------------------------------- ctx.currentCellId = cellid; - ctx.geometricMappingBatch = &mapGeoBatch; + ctx.geometricMappingBatch = &geoView; + ctx.batchWS.physPos = &mapGeoBatch.physicalPositions; - // On lie les espaces au contexte UNE SEULE FOIS pour toute la cellule + // On lie les Vues au contexte for (size_t sIdx = 0; sIdx < cache->spaceCaches.size(); ++sIdx) { ctx.setSpaceDataBatch( cache->spaceCaches[sIdx].contextIndex, &cache->spaceCaches[sIdx].shapesBatch, - &spaceMappingsBatch[sIdx] + &spaceMappingsViews[sIdx] ); } // ------------------------------------------------------------- - // ÉTAPE 3.3 : ÉVALUATION DES EXPRESSIONS (100% BATCH) + // ÉTAPE 3.4 : ÉVALUATION DES EXPRESSIONS // ------------------------------------------------------------- const auto &lQuadPts = cache->quad->getPoints(); - const CfemInt nQp = static_cast(lQuadPts.size()); std::span spanOutBatch(batchOutVals.data(), nQp); - ctx.batchWS.physPos = &mapGeoBatch.physicalPositions; for (size_t idx : activeIdx) { - ctx.resetBuffers(); + // On pose un signet POUR PRÉSERVER les dérivées physiques déjà allouées ! auto bookmark = ctx.getBookmark(); - // 1. Évaluation de l'arbre d'expression complet pour TOUS les QP d'un coup + // Évaluation de l'arbre d'expression complet targets[idx].first->evaluateBatch(cellid, nQp, spanOutBatch, ctx); - // 2. Intégration numérique vectorisée (Réduction) - // Cette boucle est extrêmement simple, le compilateur la vectorisera automatiquement. + // Intégration numérique (Réduction vectorisable) CfemReal localSum = 0.0; for (CfemInt q = 0; q < nQp; ++q) { localSum += spanOutBatch[q] * mapGeoBatch.detJs[q] * lQuadPts[q].weight; } threadTotals[idx] += localSum; + // On libère uniquement la mémoire temporaire allouée par evaluateBatch (ex: tenseurs intermédiaires) ctx.releaseToBookmark(bookmark); } } diff --git a/libs/utils/include/DynamicVector.h b/libs/utils/include/DynamicVector.h index 0eed419..2f9b8ff 100644 --- a/libs/utils/include/DynamicVector.h +++ b/libs/utils/include/DynamicVector.h @@ -112,6 +112,10 @@ class DynamicVector { m_data.assign(size, value); } + constexpr bool empty() const noexcept{ + return m_data.empty(); + } + /*! * @brief Resets all elements to zero. */ diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 4d3c953..c7aa0d9 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -28,6 +28,7 @@ target_link_libraries(unit_tests cfem_openmp_interface cfem_solvers cfem_utils + cfem_kernel cfem_quadrature cfem_reference_element cfem_mesh From 2f07431a16de78ea55b568b12f33b75af51799a1 Mon Sep 17 00:00:00 2001 From: itchinda Date: Wed, 27 May 2026 18:01:12 -0400 Subject: [PATCH 8/9] latest fix --- .../include/ReferenceElement.tpp | 71 +------------------ .../src/FunctionIntegrator.cpp | 14 ++-- 2 files changed, 5 insertions(+), 80 deletions(-) diff --git a/libs/fem/reference_element/include/ReferenceElement.tpp b/libs/fem/reference_element/include/ReferenceElement.tpp index f96de67..ab35c8c 100644 --- a/libs/fem/reference_element/include/ReferenceElement.tpp +++ b/libs/fem/reference_element/include/ReferenceElement.tpp @@ -647,76 +647,7 @@ namespace cfem out.validFlags |= MappingEvaluationFlags::PhysicalFirstDerivatives; } - // /** - // * @brief Computes physical gradients for the entire batch. - // * @note Hyper-optimized utilizing SIMD friendly contiguous memory rows. - // */ - // inline void ReferenceElement::computePhysicalFirstDerivativesBatch(const ShapeInfoBatch &shape, - // MappingInfoBatch &out) const - // { - // CFEM_ASSERT(shape.has(ShapeEvaluationFlags::FirstDerivatives), "Shapes derivatives not pre-computed"); - // CFEM_ASSERT(out.has(MappingEvaluationFlags::InverseJacobian), "Inverse jacobian not pre-computed"); - - // const CfemInt nQp = shape.numQuadraturePoints; - // const CfemInt n = shape.numNodes; - - // CfemReal i00, i01, i02, i10, i11, i12, i20, i21, i22; - // if (out.isCellAffine && out.numQuadraturePoints > 0) - // { - // i00 = out.invJacobians[0].xx(); - // i01 = out.invJacobians[0].xy(); - // i02 = out.invJacobians[0].xz(); - // i10 = out.invJacobians[0].yx(); - // i11 = out.invJacobians[0].yy(); - // i12 = out.invJacobians[0].yz(); - // i20 = out.invJacobians[0].zx(); - // i21 = out.invJacobians[0].zy(); - // i22 = out.invJacobians[0].zz(); - // } - - // for (CfemInt q = 0; q < out.numQuadraturePoints; ++q) - // { - // // Si la cellule n'est PAS affine, on charge le Jacobien spécifique à ce QP - // if (!out.isCellAffine) - // { - // i00 = out.invJacobians[q].xx(); - // i01 = out.invJacobians[q].xy(); - // i02 = out.invJacobians[q].xz(); - // i10 = out.invJacobians[q].yx(); - // i11 = out.invJacobians[q].yy(); - // i12 = out.invJacobians[q].yz(); - // i20 = out.invJacobians[q].zx(); - // i21 = out.invJacobians[q].zy(); - // i22 = out.invJacobians[q].zz(); - // } - - // // Pointeurs SoA vers les lignes de la matrice de référence (Lecture) - // const CfemReal *__restrict dxi = shape.dN_dxi(q); - // const CfemReal *__restrict deta = shape.dN_deta(q); - // const CfemReal *__restrict dzeta = shape.dN_dzeta(q); - - // // Pointeurs SoA vers les lignes de la matrice physique (Écriture) - // CfemReal *__restrict dx = out.dN_dx(q); - // CfemReal *__restrict dy = out.dN_dy(q); - // CfemReal *__restrict dz = out.dN_dz(q); - - // // Boucle sur les nœuds interne : vectorisation SIMD maximale - // for (CfemInt a = 0; a < n; ++a) - // { - // const CfemReal gx = dxi[a]; - // const CfemReal gy = deta[a]; - // const CfemReal gz = dzeta[a]; - - // dx[a] = i00 * gx + i10 * gy + i20 * gz; - // dy[a] = i01 * gx + i11 * gy + i21 * gz; - // dz[a] = i02 * gx + i12 * gy + i22 * gz; - // } - // } - - // out.validFlags |= MappingEvaluationFlags::PhysicalFirstDerivatives; - // } - -/** + /** * @brief Computes physical gradients for the entire batch. * @note Hyper-optimized utilizing SIMD friendly contiguous memory rows. * Correctly handles embedded manifolds (e.g., 1D/2D elements in 3D ambient space). diff --git a/libs/prepostprocessing/numerical_integration/src/FunctionIntegrator.cpp b/libs/prepostprocessing/numerical_integration/src/FunctionIntegrator.cpp index 88dde69..9f182a4 100644 --- a/libs/prepostprocessing/numerical_integration/src/FunctionIntegrator.cpp +++ b/libs/prepostprocessing/numerical_integration/src/FunctionIntegrator.cpp @@ -271,6 +271,7 @@ void FunctionIntegrator::integrate(const std::vector &targets, struct SpaceCacheBatch { CfemInt contextIndex; ShapeInfoBatch shapesBatch; + CfemInt paddedNodeStride; }; struct CellCacheEntryBatch { @@ -310,6 +311,8 @@ void FunctionIntegrator::integrate(const std::vector &targets, refElSpace.evaluateShapesBatch(fastCache[idx].xiPoints, fieldShapesFlags, spCache.shapesBatch); spCache.shapesBatch.shapeMustBeUpdated = false; + + spCache.paddedNodeStride = kernel_utils::computePaddedStride(spCache.shapesBatch.numNodes); fastCache[idx].spaceCaches.push_back(std::move(spCache)); } @@ -392,25 +395,16 @@ void FunctionIntegrator::integrate(const std::vector &targets, for (size_t sIdx = 0; sIdx < cache->spaceCaches.size(); ++sIdx) { const auto& spCache = cache->spaceCaches[sIdx]; - // 1. Zéro-Copie : La vue métier hérite de la vue géométrique spaceMappingsViews[sIdx] = geoView; - // 2. Allocation locale via l'Arena et calcul de la physique if (needsFieldFirstDerivatives) { // L'intégrateur demande la mémoire continue à l'Arena - CfemInt paddedNodes = kernel_utils::computePaddedStride(spCache.shapesBatch.numNodes); + CfemInt paddedNodes = spCache.paddedNodeStride; std::span arenaStorage = ctx.getBuffer(spatialDim * nQp * paddedNodes); - // La vue configure ses pointeurs internes - - // CFEM_INFO << "IIIIIIIIIIIIIIIIIIIII"; spaceMappingsViews[sIdx].setupPhysicalDerivativesMemory(arenaStorage, spCache.shapesBatch.numNodes, spatialDim, nQp); - - - // Le noyau vectorisé calcule la physique refElGeo->computePhysicalFirstDerivativesBatch(spCache.shapesBatch, spaceMappingsViews[sIdx]); - // CFEM_INFO << "OK"; } } From 222c31c8cf2255ff9eadcb3239c5057dc081c236 Mon Sep 17 00:00:00 2001 From: itchinda Date: Sat, 30 May 2026 21:46:57 -0400 Subject: [PATCH 9/9] Perf: Implement SIMD batch evaluation and zero-copy Arena architecture This major update transitions the CFEM++ integration engine from a single-point scalar evaluation model to a high-performance, SIMD-friendly batch evaluation architecture using Structure-of-Arrays (SoA). Core Architecture & Reference Element: - Redesigned `ReferenceElement` API to fully support batch processing (`evaluateShapesBatch`, `computeMappingBatch`). - Introduced Strict Separation of Concerns for mappings: `MappingInfoBatch` acts as the geometric data owner (Jacobian, DetJ), while `MappingInfoBatchView` acts as a zero-copy view for physical derivatives. - Implemented dimensional masking in `computePhysicalFirstDerivativesBatch` to correctly handle embedded manifolds (e.g., 2D surface in 3D space) based on `refDim` vs `spatialDim`. Memory Management & Optimization: - Integrated `EvalContext` with a thread-local Arena memory allocator to eliminate dynamic OS allocations (`new`/`malloc`) inside the hot loops. - Implemented a `Bookmark/Release` mechanism to safely manage temporary AST evaluation buffers while preserving physical derivatives. - Applied Loop-Invariant Code Motion by precomputing SIMD padded strides (`kernel_utils::computePaddedStride`) inside the Phase 2 cache build. Volume Integration (`integrate`): - Rewrote the integration loop to utilize OpenMP parallelization over cells. - Hoisted Jacobian and determinant computations out of the quadrature loop for affine elements. Boundary Integration (`integrateBoundary`): - Implemented the "Hybrid View" architecture: successfully combines facet geometry (normals, dS) with parent cell inverse Jacobians (for gradients) in a single zero-copy view. - Added automatic projection of boundary quadrature points into the parent reference space (`projectFacetRefCoordToCellRefCoord`). Bug Fixes & Documentation: - Fixed a Segmentation Fault caused by blind 3D array access during physical derivative computation on 2D elements. - Added comprehensive Doxygen documentation covering thread-safety, Arena memory lifecycles, and the Hybrid Geometry architecture. --- README.md | 4 +- apps/main.cpp | 22 +- libs/expressions/include/CfemExpression.h | 188 ++++++- libs/expressions/include/MathOperators.h | 2 +- .../exportation/include/VTKFamilyExporter.h | 79 ++- .../fem/exportation/src/VTKFamilyExporter.cpp | 316 +---------- libs/fem/fefield/include/FEField.h | 2 +- libs/fem/mesh/include/MeshEntity.h | 4 +- libs/fem/mesh/src/Mesh.cpp | 3 + libs/fem/mesh/src/MeshEntity.cpp | 19 +- .../include/ReferenceElement.h | 509 ++++++++++++------ .../include/ReferenceElement.tpp | 2 +- libs/kernel/include/EvalContext.h | 346 ------------ libs/kernel/include/ShapeInfoBatch.h | 11 +- .../numerical_integration/CMakeLists.txt | 2 +- .../include/FunctionIntegrator.h | 145 ----- .../include/ScalarFunctionalEvaluator.h | 300 +++++++++++ ...ator.cpp => ScalarFunctionalEvaluator.cpp} | 330 +++++++----- tests/fem/test_domain_integrator.cpp | 24 +- tests/fem/test_fefield.cpp | 26 +- tests/fem/test_quadrature.cpp | 2 +- tests/fem/test_reference_element.cpp | 126 ++--- tests/test_main.cpp | 4 +- 23 files changed, 1142 insertions(+), 1324 deletions(-) delete mode 100644 libs/prepostprocessing/numerical_integration/include/FunctionIntegrator.h create mode 100644 libs/prepostprocessing/numerical_integration/include/ScalarFunctionalEvaluator.h rename libs/prepostprocessing/numerical_integration/src/{FunctionIntegrator.cpp => ScalarFunctionalEvaluator.cpp} (67%) diff --git a/README.md b/README.md index 3cd2487..7c9c7a2 100644 --- a/README.md +++ b/README.md @@ -93,7 +93,7 @@ The following example demonstrates how to generate a tetrahedral mesh, interpola #include "Mesh.h" #include "FEField.h" #include "MathOperators.h" -#include "FunctionIntegrator.h" +#include "ScalarFunctionalEvaluator.h" #include "LagrangeSpace.h" #include "VTUExporter.h" @@ -122,7 +122,7 @@ int main(int argc, char **argv) { // Error Analysis (Clean syntax, no explicit scheme params needed) CfemReal l2Norm_U, h1SemiNorm_U; IntegrationSchemeParams schemes_params{}; // Use default number of integration points - FunctionIntegrator integrator(mesh, schemes_params); + ScalarFunctionalEvaluator integrator(mesh, schemes_params); integrator.L2Norm(u - analytical_u, &l2Norm_U, &h1SemiNorm_U); CFEM_INFO << "L2-norm error on u: " << l2Norm_U; CFEM_INFO << "H1-semi norm error on u: " << h1SemiNorm_U; diff --git a/apps/main.cpp b/apps/main.cpp index 55bbcbb..e2070cc 100644 --- a/apps/main.cpp +++ b/apps/main.cpp @@ -33,7 +33,7 @@ // #include "Mesh.h" // #include "FEField.h" // #include "MathOperators.h" -// #include "FunctionIntegrator.h" +// #include "ScalarFunctionalEvaluator.h" // #include "LagrangeSpace.h" // #include "VTUExporter.h" @@ -61,7 +61,7 @@ // // Error Analysis (Clean syntax, no explicit scheme params needed) // CfemReal l2Norm_U, h1SemiNorm_U; // IntegrationSchemeParams schemes_params{}; // Use default number of integration points -// FunctionIntegrator integrator(mesh, schemes_params); +// ScalarFunctionalEvaluator integrator(mesh, schemes_params); // integrator.L2Norm(u - analytical_u, &l2Norm_U, &h1SemiNorm_U); // CFEM_INFO << "L2-norm error on u: " << l2Norm_U; // CFEM_INFO << "H1-semi norm error on u: " << h1SemiNorm_U; @@ -82,7 +82,7 @@ #include "LagrangeSpace.h" #include "DGSpace.h" #include "FEField.h" -#include "FunctionIntegrator.h" +#include "ScalarFunctionalEvaluator.h" #include "VTKExporter.h" #include "VTUExporter.h" #include "MathOperators.h" @@ -112,7 +112,8 @@ int main(int argc, char **argv) CfemReal y0 = 0; CfemReal z0 = 0; CfemReal r0 = 0.5; - CfemReal h = 0.0125; + CfemReal h = 0.0625; + { PROFILE_SCOPE("MeshBuilding"); @@ -141,10 +142,10 @@ int main(int argc, char **argv) // mesh = Mesh::loadFromGmsh("../tests/grenier/stanford_bunny/bunny.msh"); // mesh = Mesh::loadFromGmsh("../tests/grenier/wheel/wheel.msh"); - // mesh = Mesh::sphere( MeshOrder::Quadratic, - // 0.25, + // mesh = Mesh::sphere( MeshOrder::Linear, + // h, // Vector3D{0,0,0}, - // 0.5 + // r0 // ); @@ -205,8 +206,8 @@ int main(int argc, char **argv) { PROFILE_SCOPE("L2Norm"); IntegrationSchemeParams schemes_params{}; - FunctionIntegrator integrator(mesh, schemes_params); - // schemes_params.NumIntgPtTetElem = 1; + // schemes_params.NumIntgPtTetElem = 33; + ScalarFunctionalEvaluator integrator(mesh, schemes_params); CfemReal l2Norm_P, l2Norm_U, h1SemiNorm_P, h1SemiNorm_U; integrator.L2Norm(p - analytical_p, &l2Norm_P, &h1SemiNorm_P); integrator.L2Norm(u - analytical_u, &l2Norm_U, &h1SemiNorm_U); @@ -216,7 +217,8 @@ int main(int argc, char **argv) // auto Proj = tensor(1 -normaleX*normaleX, -normaleX*normaleY, -normaleX*normaleZ, // -normaleY*normaleX, 1 -normaleY*normaleY, -normaleY*normaleZ, // -normaleZ*normaleX, -normaleZ*normaleY, 1 -normaleZ*normaleZ); - // CFEM_INFO << "surface error = " << sqrt( integrator.computeIntegral(mag_sqr( grad(u) - grad(analytical_u)*P ) ) ); + // CfemReal s = integrator.computeIntegral(scalar(1), SYS_BOUNDARY_ID); + // CFEM_INFO << " Boundary surface = " << s << " Exact value: " << 4 * CFEM_PI * r0 * r0 << " Error " << std::abs( s - 4 * CFEM_PI * r0 * r0) ; // auto gradPanal = Proj*grad(analytical_p); //grad(analytical_p) - dot(grad(analytical_p), normale) * normale; // auto gradP = Proj*grad(p); //grad(p) - dot(grad(p), normale) * normale; diff --git a/libs/expressions/include/CfemExpression.h b/libs/expressions/include/CfemExpression.h index 7e1474f..4c97087 100644 --- a/libs/expressions/include/CfemExpression.h +++ b/libs/expressions/include/CfemExpression.h @@ -33,8 +33,11 @@ namespace cfem::sym { /** ************************************************************************* - * @brief + * @enum MathOp + * @brief Defines the mathematical operations supported by the symbolic engine. * + * Includes standard binary operations, vector/tensor operations, and a + * comprehensive suite of unary transcendental and algebraic functions. **************************************************************************** */ enum class MathOp { // Binary @@ -49,35 +52,56 @@ namespace cfem::sym Exp, Log, Gamma, Erf, Erfc, Zeta }; + /** ************************************************************************* + * @enum ExpressionType + * @brief Identifies the specific node type within the Abstract Syntax Tree (AST). + * + * Used for fast Runtime Type Information (RTTI), expression folding, + * and targeted optimizations without relying on dynamic casting. + **************************************************************************** */ enum class ExpressionType { - Constant, /// A literal value or vector of values - ScalarSymbolic, /// A SymEngine/Symbolic variable (x, y, t) - Variable, /// A FEM field (u, v, p) - Zero, /// Specialized zero node for optimization - Unary, /// sin, cos, exp, log, sqrt, abs - Binary, /// +, -, *, /, power - Scaled, /// k * expression (the "optimization" node) - VectorExpression, - VectorSymbolic, /// Container for 3 scalars - TensorExpression, /// Container for 9 scalars (or Voigt) - TensorSymbolic, - Gradient, /// Grad(u) - MagnitudeSq, /// |u|^2 - Divergence, /// div(u) - Lambda, /// Custom user-defined functions - LinearEvaluator, - FEField, - Trial, - Test + Constant, ///< A literal value or vector of values + ScalarSymbolic, ///< A SymEngine/Symbolic spatial or temporal variable (x, y, t) + Variable, ///< A generic unknown variable + Zero, ///< Specialized zero node for rapid algebraic simplification + Unary, ///< Mathematical functions (sin, cos, exp, log, sqrt, abs) + Binary, ///< Arithmetic operators (+, -, *, /, power) + Scaled, ///< Optimized node for scalar multiplication (k * expression) + VectorExpression, ///< A dynamically evaluated vector + VectorSymbolic, ///< Container for 3 scalar symbolic variables + TensorExpression, ///< Container for 9 scalars (or Voigt notation) + TensorSymbolic, ///< Symbolic tensor + Gradient, ///< Spatial gradient of an expression: Grad(u) + MagnitudeSq, ///< Squared magnitude optimization: |u|^2 + Divergence, ///< Divergence of a vector field: div(u) + Lambda, ///< Custom user-defined C++ lambda functions + LinearEvaluator, ///< Optimized linear combinations + FEField, ///< A Finite Element Field bounded to a mesh + Trial, ///< Trial function for bilinear forms + Test ///< Test function for weak formulations }; + /** ************************************************************************* + * @struct SpaceRequirements + * @brief Tracks the Finite Element spaces and shape flags required by an expression. + * + * During the preparation phase, the AST is traversed to collect all `FESpace` + * dependencies. This allows the integrator to pre-allocate caches and only + * compute the strictly necessary shape functions or derivatives. + **************************************************************************** */ struct SpaceRequirements { std::vector> data; + /** + * @brief Registers a dependency on an FE space with specific shape requirements. + * @param space Pointer to the required FE Space. + * @param flags Bitmask of required evaluations (e.g., Value | FirstDerivatives). + * @note If the space is already registered, the flags are merged (bitwise OR). + */ void addRequirement(const FESpace* space, ShapeEvaluationFlags flags) { for (auto& pair : data) { if (pair.first == space) { - pair.second |= flags; // Fusionne les drapeaux si l'espace existe + pair.second |= flags; return; } } @@ -86,28 +110,73 @@ namespace cfem::sym }; /** ************************************************************************* - * @brief + * @class CfemExpression + * @brief Abstract base class for all nodes in the symbolic Abstract Syntax Tree (AST). * + * This class provides the unified interface for evaluating mathematical expressions + * at quadrature points. It supports both single-point scalar evaluation and + * high-performance, SIMD-friendly batch evaluation. It also handles automatic + * symbolic differentiation via lazy evaluation. **************************************************************************** */ - class CfemExpression : public std::enable_shared_from_this + class CfemExpression { protected: mutable std::shared_ptr m_gradient = nullptr; mutable std::shared_ptr m_timeDeriv = nullptr; + + /** + * @brief Factory method to generate the symbolic spatial gradient of this node. + * @return A new expression tree representing the gradient. + */ virtual std::shared_ptr createGradient() const = 0; + + /** + * @brief Factory method to generate the symbolic time derivative of this node. + * @return A new expression tree representing the time derivative. + */ virtual std::shared_ptr createTimeDerivative() const = 0; + + /** + * @brief Internal hook for AST nodes to register their FE space dependencies. + */ virtual void doCollectSpaceRequirements(SpaceRequirements& reqs, ShapeEvaluationFlags inheritedFlags) const {} public: virtual ~CfemExpression() = default; + /** + * @brief Returns the number of scalar components (1 for scalar, 3 for vector, 9 for tensor). + */ virtual CfemInt getNumComponents() const = 0; + + /** + * @brief Returns the AST node type for RTTI and optimizations. + */ virtual ExpressionType getType() const = 0; + /** + * @brief Evaluates the expression at a single reference point. + * @param cellId The global ID of the cell being evaluated. + * @param xi The reference coordinates of the evaluation point. + * @param out Span pointing to the pre-allocated output buffer. + * @param ctx Thread-local evaluation context (Arena allocator). + */ virtual void evaluate(CfemInt cellId, const Vector3D &xi, std::span out, EvalContext &ctx) const = 0; + + /** + * @brief Evaluates the expression for a batch of quadrature points (SIMD-friendly). + * @param cellId The global ID of the cell being evaluated. + * @param nQp The number of quadrature points in the batch. + * @param outValues Span pointing to the pre-allocated SoA output buffer. + * @param ctx Thread-local batch evaluation context. + */ virtual void evaluateBatch(CfemInt cellId, CfemInt nQp, std::span outValues, EvalContext& ctx) const = 0; + /** + * @brief Directly evaluates the spatial gradient at a single point. + * @note Defaults to lazily building the gradient tree and evaluating it. + */ virtual void evaluateGradient(CfemInt cellId, const Vector3D &xi, std::span out, EvalContext &ctx) const { auto g = this->grad(); if (g && g.get() != this) { @@ -117,6 +186,9 @@ namespace cfem::sym CFEM_THROW("Gradient evaluation failed."); } + /** + * @brief Directly evaluates the spatial gradient for a batch of points. + */ virtual void evaluateGradientBatch(CfemInt cellId, CfemInt nQp, std::span outValues, EvalContext &ctx) const { auto g = this->grad(); if (g && g.get() != this) { @@ -126,36 +198,59 @@ namespace cfem::sym CFEM_THROW("Gradient batch evaluation failed."); } + /** + * @brief Performs pre-computation and tree optimization before the integration loop begins. + */ virtual void prepare() const {}; + // --- AST Optimization & Traversal Flags --- virtual bool isPointwise() const { return false; } virtual bool isBinary() const { return false; } virtual bool isZero() const { return false; } virtual bool isContinuous() const { return true; } - virtual bool isConstant() const { return false; } virtual bool dependsOnFEField() const { return false; } virtual bool dependsOnShape() const { return true; } virtual bool dependsOnGeometricNormal() const { return false; } + /** + * @brief Binds required FE Spaces to unique indices used by the EvalContext. + * @param spaceToId Mapping from FESpace pointers to compact integer IDs. + */ virtual void bind(const std::map& spaceToId) const {} + /** + * @brief Returns the minimum shape evaluation flags required by this specific node. + */ virtual ShapeEvaluationFlags requiredShapeFlags() const { return ShapeEvaluationFlags::None; } + /** + * @brief Traverses the AST to collect all unique FESpace instances required for evaluation. + */ virtual void collectRequiredSpaces(std::set>& spaces) const { - // Par défaut, pas de dépendance à un espace FE + // By default, no dependency on an FE space. } + /** + * @brief Recursively collects the specific shape flags required for each FE Space. + */ void collectSpaceRequirements(SpaceRequirements& reqs, ShapeEvaluationFlags inheritedFlags = ShapeEvaluationFlags::Value) const { doCollectSpaceRequirements(reqs, inheritedFlags); } + /** + * @brief Computes the partial derivative with respect to a specific variable index. + */ virtual std::shared_ptr diff(CfemInt var_idx) const {return nullptr; } + /** + * @brief Returns the lazily-evaluated spatial gradient of this expression. + * @return A cached shared pointer to the gradient AST. + */ std::shared_ptr grad() const { if ( !m_gradient ){ m_gradient = createGradient(); @@ -166,6 +261,10 @@ namespace cfem::sym return m_gradient; } + /** + * @brief Returns the lazily-evaluated time derivative of this expression. + * @return A cached shared pointer to the time derivative AST. + */ std::shared_ptr timeDerivative() const { if ( !m_timeDeriv ){ m_timeDeriv = createTimeDerivative(); @@ -176,18 +275,33 @@ namespace cfem::sym return m_timeDeriv; } + /** + * @brief Returns the divergence of the expression (applicable for vector fields). + */ virtual std::shared_ptr div() const { return nullptr; } + /** + * @brief Generates a human-readable string representation of the expression. + */ virtual std::string toString() const { return "???"; } + /** + * @brief Returns the operator precedence (used for safe string formatting with parentheses). + */ virtual CfemInt precedence() const { return 10; } }; + /** + * @brief Stream operator to easily print the symbolic expression. + */ inline std::ostream& operator<< (std::ostream& os, const std::shared_ptr& expr){ return os << expr->toString(); } + /** + * @brief Helper function to convert a MathOp enum to its string symbol (e.g., MathOp::Add -> "+"). + */ std::string opToSym(MathOp op); @@ -198,15 +312,32 @@ namespace cfem::sym #include namespace cfem::sym { + + /** + * @class VariableRegistry + * @brief A global registry to manage symbolic variable names and prevent collisions. + * + * Ensures that standard spatial/temporal coordinates ("x", "y", "z", "t") are + * protected, and prevents the user from accidentally defining overlapping variables + * which would break symbolic differentiation. + */ class VariableRegistry { inline static std::set reserved_names = {"x", "y", "z", "t"}; inline static std::set active_variables; public: + /** + * @brief Checks if a variable name is a reserved system coordinate. + */ static bool isReserved(const std::string& name) { return reserved_names.contains(name); } + /** + * @brief Registers a new variable name in the system. + * @param name The requested variable name. + * @throws CFEM_ERROR If the name is already actively in use by another symbolic object. + */ static void registerVariable(const std::string& name) { // If it's a reserved coordinate name, we allow it (it's "pre-validated") if (isReserved(name)) return; @@ -218,10 +349,11 @@ namespace cfem::sym { active_variables.insert(name); } + /** + * @brief Clears all user-defined active variables from the registry. + */ static void clear() { - // If it's a reserved coordinate name, we allow it (it's "pre-validated") active_variables.clear(); } }; -} - +} \ No newline at end of file diff --git a/libs/expressions/include/MathOperators.h b/libs/expressions/include/MathOperators.h index dee56c3..4e76d44 100644 --- a/libs/expressions/include/MathOperators.h +++ b/libs/expressions/include/MathOperators.h @@ -105,7 +105,7 @@ namespace cfem::sym std::shared_ptr log(std::shared_ptr u); std::shared_ptr erf(std::shared_ptr u); std::shared_ptr erfc(std::shared_ptr u); - std::shared_ptr tgamma(std::shared_ptr u); + std::shared_ptr tgamma(std::shared_ptr u); std::shared_ptr zeta(std::shared_ptr u); /** @} */ diff --git a/libs/fem/exportation/include/VTKFamilyExporter.h b/libs/fem/exportation/include/VTKFamilyExporter.h index 3150ca9..bf5c7f4 100644 --- a/libs/fem/exportation/include/VTKFamilyExporter.h +++ b/libs/fem/exportation/include/VTKFamilyExporter.h @@ -42,17 +42,14 @@ namespace cfem /** * @class VTKFamilyExporter * @brief Intermediate base class for all unstructured grid exporters. - * - * @ingroup MeshExporter - * - * @details + * * @ingroup MeshExporter + * * @details * This class centralizes all the heavy mathematical and topological logic required * for exporting unstructured meshes, including: * - **Entity Filtering:** Extraction of specific sub-domains or boundary manifolds. * - **Discontinuous Topology:** Generation of Discontinuous Galerkin (DG) topology via node duplication. * - **Field Evaluation:** Evaluating expressions using the "Double Mapping" strategy (Volume + Facet) to properly handle exact physical gradients and geometric normals. - * - * Derived classes (such as `VTUExporter` and `VTKExporter`) only need to implement + * * Derived classes (such as `VTUExporter` and `VTKExporter`) only need to implement * the low-level formatting and I/O logic (ASCII, Binary, XML, etc.) by overriding * the specialized protected `write...` methods. */ @@ -66,11 +63,10 @@ namespace cfem /** * @brief Evaluates all attached expressions and writes the mesh file. - * - * @param filename Base name of the output file. Extensions (e.g., `.cont.vtu`, `.disc.vtk`) - * are automatically appended based on field continuity. + * * @param filename Base name of the output file. Extensions (e.g., `.cont.vtu`, `.disc.vtk`) + * are automatically appended based on field continuity. * @param entityId Optional ID of the specific mesh entity to export. Defaults to the - * entire universe (`UNIVERSE_ID`). + * entire universe (`UNIVERSE_ID`). * @throws CFEM_ERROR If the file cannot be created or opened. */ void write(const std::string &filename, CfemInt entityId = UNIVERSE_ID) const override; @@ -83,43 +79,76 @@ namespace cfem protected: - std::string m_outputFileExtension {""}; ///< File extension defined by derived classes (e.g., "vtu", "vtk") - VTKExportFormat m_format{VTKExportFormat::BINARY}; ///< Active export format + std::string m_outputFileExtension {""}; ///< File extension defined by derived classes (e.g., "vtu", "vtk"). + VTKExportFormat m_format{VTKExportFormat::BINARY}; ///< Active export format. + /** + * @brief Constructs a VTKFamilyExporter tied to a specific mesh. + * @param mesh The CFEM++ mesh object to be exported. + */ explicit VTKFamilyExporter(const Mesh &mesh) : MeshExporter(mesh) {} /** * @brief Internal geometry representation decoupled from the CFEM++ core mesh. + * @details This structure holds the flattened arrays strictly required by VTK/VTU formats, + * filtering out inactive elements if a specific sub-domain is being exported. */ struct ExtractedGeometry { - CfemInt numActiveCells = 0; - CfemInt numActiveVerts = 0; - std::vector points; - std::vector connectivity; - std::vector offsets; - std::vector types; + CfemInt numActiveCells = 0; ///< Number of cells actually exported. + CfemInt numActiveVerts = 0; ///< Number of unique vertices actually exported. + + std::vector points; ///< Flattened 3D coordinates [x0, y0, z0, x1, y1, z1, ...]. + std::vector connectivity; ///< Flattened node indices defining each cell's connectivity. + std::vector offsets; ///< Array indicating the end index of each cell within the connectivity array. + std::vector types; ///< VTK cell type identifiers (e.g., VTK_TRIANGLE, VTK_TETRA). - std::vector globalToLocal; - std::vector localToGlobal; + std::vector globalToLocal; ///< Mapping from CFEM++ global node IDs to local export node IDs. + std::vector localToGlobal; ///< Mapping from local export node IDs to CFEM++ global node IDs. - const std::vector* pActiveElements = nullptr; + const std::vector* pActiveElements = nullptr; ///< Pointer to the list of active cell IDs in the mesh. }; /** * @brief Container for evaluated nodal fields and element metadata. */ struct EvaluatedData { - std::vector> fields; - std::vector> nodeMasks; - std::vector cellQuality; - std::vector> cellMasks; + std::vector> fields; ///< Evaluated scalar/vector field values. Outer size: number of fields. Inner size: number of nodes. + std::vector> nodeMasks; ///< Node validity masks (e.g., for thresholding or clipping in ParaView). + std::vector cellQuality; ///< Quality metrics computed per cell. + std::vector> cellMasks; ///< Cell validity masks. }; // --- Core Mathematical Kernels --- + /** + * @brief Builds the topological and geometric data for a standard continuous export. + * @details Nodes shared between adjacent cells are written once, maintaining standard connectivity. + * @param entityId The ID of the mesh entity to extract. + * @return The populated ExtractedGeometry structure. + */ ExtractedGeometry buildContinuousGeometry(CfemInt entityId) const; + + /** + * @brief Builds the topological and geometric data for a discontinuous (DG) export. + * @details Nodes shared between adjacent cells are intentionally duplicated. Each cell receives + * its own unique set of nodes. This prevents ParaView/VisIt from applying nodal interpolation + * smoothing across material interfaces or elements evaluating discontinuous gradients. + * @param entityId The ID of the mesh entity to extract. + * @return The populated ExtractedGeometry structure. + */ ExtractedGeometry buildDiscontinuousGeometry(CfemInt entityId) const; + /** + * @brief Evaluates all requested fields (expressions) on the extracted geometry. + * @details Utilizes the CFEM++ functional evaluator to compute field values at nodes. + * It intelligently handles both standard volume evaluations and boundary evaluations + * (which require projecting facet coordinates into the parent volume). + * @param geo The previously extracted geometry to evaluate fields on. + * @param entries The list of expressions/fields to evaluate. + * @param entityId The ID of the targeted mesh entity. + * @param isDiscontinuous Flag indicating if the geometry is DG (affects node-to-cell mapping logic). + * @return An EvaluatedData structure containing the flattened results ready for VTK serialization. + */ EvaluatedData evaluateFields(const ExtractedGeometry& geo, const std::vector& entries, CfemInt entityId, diff --git a/libs/fem/exportation/src/VTKFamilyExporter.cpp b/libs/fem/exportation/src/VTKFamilyExporter.cpp index 55ebfbb..9df9347 100644 --- a/libs/fem/exportation/src/VTKFamilyExporter.cpp +++ b/libs/fem/exportation/src/VTKFamilyExporter.cpp @@ -138,314 +138,6 @@ namespace cfem return geo; } -// VTKFamilyExporter::EvaluatedData VTKFamilyExporter::evaluateFields(const ExtractedGeometry& geo, - -// const std::vector& entries, - -// CfemInt entityId, - -// bool isDiscontinuous) const - -// { - -// EvaluatedData data; - -// const MeshEntity& entity = m_mesh.getEntityConst(entityId); - -// bool isVolume = (entity.getTopologicalDimension() == m_mesh.getTopologicalDimension()); - -// bool isFacet = (entity.getTopologicalDimension() == m_mesh.getTopologicalDimension() - 1); - - - -// size_t numEntries = entries.size(); - -// ShapeEvaluationFlags shapeEvalFlags = ShapeEvaluationFlags::None; - -// MappingEvaluationFlags mappingEvalFlags = MappingEvaluationFlags::PhysicalPosition; - -// bool needsNormal = false; - -// // if (isDiscontinuous && isFacet) fieldFlags |= ShapeEvaluationFlags::Normal; - - - -// data.fields.resize(numEntries); - -// for (size_t i = 0; i < numEntries; ++i) { - -// entries[i].expr->prepare(); - -// shapeEvalFlags |= entries[i].expr->requiredShapeFlags(); - -// needsNormal = needsNormal || entries[i].expr->dependsOnGeometricNormal(); - -// data.fields[i].resize(geo.numActiveVerts * entries[i].expr->getNumComponents(), 0.0); - -// } - - - -// mappingEvalFlags |= transformShapeToMappingFlags(shapeEvalFlags) | MappingEvaluationFlags::PhysicalPosition | -// (needsNormal? MappingEvaluationFlags::Jacobian : MappingEvaluationFlags::None) ; - - - -// struct TypeCache { std::vector nodeShapes; const ReferenceElement *refEl; }; - -// std::unordered_map geoCache; - -// CfemInt geoOrder = m_mesh.getOrderInt(); - - - -// auto getOrCacheRefEl = [&](CellType type) -> TypeCache& { - -// auto it = geoCache.find(type); - -// if (it == geoCache.end()) { - -// auto &cache = geoCache[type]; - -// cache.refEl = &ReferenceElementFactory::getReferenceElement(type, geoOrder); - -// for (const auto &xi : cache.refEl->referenceNodes()) { - -// ShapeInfo info; - -// cache.refEl->evaluateShapes(xi, shapeEvalFlags, info); - - - -// info.shapeMustBeUpdated = false; - -// cache.nodeShapes.push_back(info); - -// } - -// return cache; - -// } - -// return it->second; - -// }; - - - -// EvalContext ctx; - -// MappingInfo scratchFacetMapping; - -// DynamicVector physCoordsElem, physCoordsParent; - -// std::vector computed(geo.numActiveVerts, 0); - - - -// CfemInt ptIdx = 0; - -// for (CfemInt elemId : *geo.pActiveElements) { - -// CfemInt parentCellId = elemId; - -// const std::vector* localFaceConnectivity = nullptr; - - - -// if (isFacet) { - -// const auto& face = m_mesh.getFacet(elemId); - -// parentCellId = face.left_cell; - -// localFaceConnectivity = &Cell::getLocalFacesIndices(m_mesh.getCell(parentCellId).type)[face.left_cell_facet_id]; - -// } - - - -// auto elemType = isVolume ? m_mesh.getCell(elemId).type : m_mesh.getFacet(elemId).type; - -// auto parentCellType = m_mesh.getCell(parentCellId).type; - - - -// auto &elemCache = getOrCacheRefEl(elemType); - -// auto &parentCache = getOrCacheRefEl(parentCellType); - - - -// if (isFacet) m_mesh.getFacetVerticesCoords(elemId, physCoordsElem); - -// m_mesh.getCellVerticesCoords(parentCellId, physCoordsParent); - - - -// const auto &conn = isVolume ? m_mesh.getCellConnectivity(elemId) : m_mesh.getFacetConnectivity(elemId); - - - -// for (size_t n = 0; n < conn.size(); ++n) { - -// // FIX: Unified index resolution. O(1) for both Continuous and DG - -// CfemInt evalIdx = isDiscontinuous ? ptIdx : geo.globalToLocal[conn[n]]; - - - -// if (!isDiscontinuous && computed[evalIdx]) continue; - - - -// CfemInt parentNodeIdx = isVolume ? n : (*localFaceConnectivity)[n]; - -// ctx.resetBuffers(); - -// MappingInfo& mapParent = ctx.scratchMapping; - -// parentCache.refEl->computeMapping( -// parentCache.refEl->referenceNodes()[parentNodeIdx], -// physCoordsParent, -// mappingEvalFlags, -// parentCache.nodeShapes[parentNodeIdx], -// mapParent - -// ); - -// if (needsNormal) { -// CFEM_INFO << "Hiii"; -// elemCache.refEl->computeMapping( -// elemCache.refEl->referenceNodes()[n], -// physCoordsElem, -// MappingEvaluationFlags::Normal, -// elemCache.nodeShapes[n], -// scratchFacetMapping - -// ); -// mapParent.normal = scratchFacetMapping.normal; -// } - -// ctx.singleWS.physPos = mapParent.physicalPosition; -// ctx.geometricMapping = &mapParent; - -// for (size_t i = 0; i < numEntries; ++i) { - -// CfemInt dim = entries[i].expr->getNumComponents(); - -// std::span outSpan(&data.fields[i][evalIdx * dim], dim); - -// entries[i].expr->evaluate(parentCellId, parentCache.refEl->referenceNodes()[parentNodeIdx], outSpan, ctx); - -// } - - - -// if (!isDiscontinuous) computed[evalIdx] = 1; - -// if (isDiscontinuous) ptIdx++; - -// } - -// } - - - -// for (const auto &name : m_exportedEntityNames) { - - - -// if ( !m_mesh.isPartOfTheMeshEntities(name) ) continue; - - - -// const MeshEntity& ent = m_mesh.getEntityConst(name); - - - -// std::vector isInEntity(m_mesh.getNumVertices(), 0); - -// for (CfemInt vId : ent.getVertexIds()) isInEntity[vId] = 1; - - - -// std::vector mask(geo.numActiveVerts, 0.0f); - -// for (size_t i = 0; i < geo.numActiveVerts; ++i) { - -// if (isInEntity[geo.localToGlobal[i]]) mask[i] = 1.0f; - -// } - -// data.nodeMasks.push_back(std::move(mask)); - -// } - - - -// if (m_exportQuality) { - -// data.cellQuality.reserve(geo.numActiveCells); - -// for (CfemInt elemId : *geo.pActiveElements) { - -// data.cellQuality.push_back(isVolume ? m_mesh.computeCellQuality(elemId) : m_mesh.computeFacetQuality(elemId)); - -// } - -// } - - - -// for (const auto &name : m_exportedEntityNames) { - -// if ( !m_mesh.isPartOfTheMeshEntities(name) ) continue; - - - -// const MeshEntity& ent = m_mesh.getEntityConst(name); - - - -// std::vector isEntityCell(m_mesh.getNumCells(), 0); - -// std::vector isEntityFacet(m_mesh.getNumFacets(), 0); - - - -// if (isVolume) for (CfemInt cId : ent.getCellIds()) isEntityCell[cId] = 1; - -// else for (CfemInt fId : ent.getFacetIds()) isEntityFacet[fId] = 1; - - - -// std::vector mask(geo.numActiveCells, 0.0f); - -// CfemInt idx = 0; - -// for (CfemInt elemId : *geo.pActiveElements) { - -// if ((isVolume && isEntityCell[elemId]) || (!isVolume && isEntityFacet[elemId])) mask[idx] = 1.0f; - -// idx++; - -// } - -// data.cellMasks.push_back(std::move(mask)); - -// } - - - -// return data; - -// } - -// } - - - VTKFamilyExporter::EvaluatedData VTKFamilyExporter::evaluateFields(const ExtractedGeometry& geo, const std::vector& entries, CfemInt entityId, @@ -482,7 +174,7 @@ namespace cfem if (it == geoCache.end()) { auto &cache = geoCache[type]; cache.refEl = &ReferenceElementFactory::getReferenceElement(type, geoOrder); - for (const auto &xi : cache.refEl->referenceNodes()) { + for (const auto &xi : cache.refEl->getReferenceNodes()) { ShapeInfo info; cache.refEl->evaluateShapes(xi, shapeEvalFlags, info); @@ -532,7 +224,7 @@ namespace cfem MappingInfo& mapParent = ctx.scratchMapping; parentCache.refEl->computeMapping( - parentCache.refEl->referenceNodes()[parentNodeIdx], + parentCache.refEl->getReferenceNodes()[parentNodeIdx], physCoordsParent, mappingEvalFlags, parentCache.nodeShapes[parentNodeIdx], @@ -541,7 +233,7 @@ namespace cfem if (needsNormal && (isFacet || mesh_top_dim == 2 ) ) { elemCache.refEl->computeMapping( - elemCache.refEl->referenceNodes()[n], + elemCache.refEl->getReferenceNodes()[n], (isFacet? physCoordsElem : physCoordsParent), MappingEvaluationFlags::Normal, elemCache.nodeShapes[n], @@ -556,7 +248,7 @@ namespace cfem for (size_t i = 0; i < numEntries; ++i) { CfemInt dim = entries[i].expr->getNumComponents(); std::span outSpan(&data.fields[i][evalIdx * dim], dim); - entries[i].expr->evaluate(parentCellId, parentCache.refEl->referenceNodes()[parentNodeIdx], outSpan, ctx); + entries[i].expr->evaluate(parentCellId, parentCache.refEl->getReferenceNodes()[parentNodeIdx], outSpan, ctx); } if (!isDiscontinuous) computed[evalIdx] = 1; diff --git a/libs/fem/fefield/include/FEField.h b/libs/fem/fefield/include/FEField.h index ae18f2c..d6ef00e 100644 --- a/libs/fem/fefield/include/FEField.h +++ b/libs/fem/fefield/include/FEField.h @@ -39,7 +39,7 @@ namespace cfem { - class FEField : public sym::TrialFunction, public FESpaceObserver + class FEField : public sym::TrialFunction, public FESpaceObserver, public std::enable_shared_from_this { private: struct ConstructorToken {}; diff --git a/libs/fem/mesh/include/MeshEntity.h b/libs/fem/mesh/include/MeshEntity.h index 4c22be9..e85eb9d 100644 --- a/libs/fem/mesh/include/MeshEntity.h +++ b/libs/fem/mesh/include/MeshEntity.h @@ -70,7 +70,8 @@ class MeshEntity { } bool containsCell(CfemInt cell_id) const { - return std::binary_search(m_cell_ids.begin(), m_cell_ids.end(), cell_id); + CFEM_ASSERT(0 <= cell_id && cell_id < m_cellMask.size(), "cell_id out of bounds"); + return m_cellMask[cell_id]; } void update(); @@ -103,6 +104,7 @@ class MeshEntity { std::string m_name; CfemInt m_topologicalDimension; std::vector m_cell_ids; + std::vector m_cellMask; std::vector m_facet_ids; std::vector m_vertex_ids; diff --git a/libs/fem/mesh/src/Mesh.cpp b/libs/fem/mesh/src/Mesh.cpp index 4d2baee..2ba9585 100644 --- a/libs/fem/mesh/src/Mesh.cpp +++ b/libs/fem/mesh/src/Mesh.cpp @@ -1583,8 +1583,11 @@ namespace cfem notifyObservers(); +#ifndef CFEM_TESTING + CFEM_INFO << "Mesh successfully upgraded to quadratic."; CFEM_INFO << this->info(); +#endif } /** ************************************************************************************************************************************ diff --git a/libs/fem/mesh/src/MeshEntity.cpp b/libs/fem/mesh/src/MeshEntity.cpp index dc48d1d..63fad6f 100644 --- a/libs/fem/mesh/src/MeshEntity.cpp +++ b/libs/fem/mesh/src/MeshEntity.cpp @@ -161,8 +161,15 @@ namespace cfem } /** - * @brief Ensures all ID vectors are sorted and contain no duplicates. - * Should be called after initial construction or significant changes. + * @brief Finalize and optimize internal storage. + * + * Must be called after modifying the entity topology + * (cells, facets, vertices) + * + * After finalize(): + * - ids are sorted and uniqued + * - lookup acceleration structures are built + * - containsCell()/containsFacet() become O(1) */ void MeshEntity::finalize() { @@ -173,6 +180,14 @@ namespace cfem clean(m_vertex_ids); clean(m_facet_ids); clean(m_cell_ids); + + m_cellMask.assign(m_mesh->getNumCells(), 0); + + if (!m_cell_ids.empty()){ + for (CfemInt cid : m_cell_ids){ + m_cellMask[cid] = 1; + } + } } /** diff --git a/libs/fem/reference_element/include/ReferenceElement.h b/libs/fem/reference_element/include/ReferenceElement.h index ff1cb9e..75dff1c 100644 --- a/libs/fem/reference_element/include/ReferenceElement.h +++ b/libs/fem/reference_element/include/ReferenceElement.h @@ -6,13 +6,13 @@ // --- // --- This software is protected by international copyright laws. // --- Use, distribution, or modification of this software in any form, -// --- source or binary, for personal, academic, or non-commercial -// --- purposes is permitted free of charge, provided that this -// --- copyright notice and this permission notice appear in all +// --- source or binary, for personal, academic, or non-commercial +// --- purposes is permitted free of charge, provided that this +// --- copyright notice and this permission notice appear in all // --- copies and supporting documentation. // --- -// --- The authors and contributors provide this code "as is" without -// --- any express or implied warranty. In no event shall they be +// --- The authors and contributors provide this code "as is" without +// --- any express or implied warranty. In no event shall they be // --- held liable for any damages arising from the use of this software. //************************************************************************ @@ -31,7 +31,6 @@ #include "MappingInfoBatch.h" #include "MappingInfoBatchView.h" - namespace cfem::reference_elem { static const DynamicVector> line_face_nodes = { @@ -40,181 +39,337 @@ namespace cfem::reference_elem }; } -namespace cfem { - enum class Order { P0 = 0, P1 = 1, P2 = 2 , Mini = 101}; - -/** - * @class ReferenceElement - * @brief Abstract base class for Finite Element reference definitions. - * Provides the machinery to compute geometric mappings, Jacobians, and - * physical derivatives from local shape functions. - */ -class ReferenceElement { -protected: - DynamicVector m_nodes; ///< Node coordinates in reference space (xi) - - CfemInt m_numNodes; - CfemInt m_dimension; - - -public: - virtual ~ReferenceElement() = default; - - CfemInt dimension() const { return m_dimension; }; - CfemInt getNumNodes() const { return m_numNodes; }; - virtual std::span getFaceNodes(CfemInt iFace) const = 0; - - void evaluateShapes(const Vector3D &xi, ShapeEvaluationFlags requestedShape, ShapeInfo& out) const; - void evaluateShapesBatch(const DynamicVector &xiPoints, ShapeEvaluationFlags requestedShape, ShapeInfoBatch& outBatch) const; - /** - * @brief Evaluates the shape function values (\f$ N_i \f$) at a given reference point. - * @param xi The coordinates of the evaluation point in the reference element (\f$\xi, \eta, \zeta \f$). - * @param values Raw pointer to a pre-allocated array of size [numNodes] to be populated. - */ - virtual void evaluateShapesValues(const Vector3D& xi, ShapeInfo& info) const = 0; - virtual void evaluateShapesValuesBatch(const DynamicVector &xi, - ShapeInfoBatch& shapeBatch) const = 0; - - /** - * @brief Evaluates the first-order derivatives of the shape functions (local gradients) - * with respect to the reference coordinate system. - * @param xi The coordinates of the evaluation point in the reference element (\f$\xi, \eta, \zeta \f$). - * @param dN_dxi Raw pointer to the reference coordinate \f$ \xi \f$ derivative array (size [numNodes]). - * @param dN_deta Raw pointer to the reference coordinate \f$ \eta \f$ derivative array (size [numNodes]). - * @param dN_dzeta Raw pointer to the reference coordinate \f$ \zeta \f$ derivative array (size [numNodes]). - */ - virtual void evaluateShapesDerivatives(const Vector3D& xi, ShapeInfo& info) const = 0; - virtual void evaluateShapesDerivativesBatch(const DynamicVector &xi, - ShapeInfoBatch& shapeBatch) const = 0; +namespace cfem +{ + enum class Order + { + P0 = 0, + P1 = 1, + P2 = 2, + Mini = 101 + }; /** - * @brief Evaluates the second-order derivatives of the shape functions (local Hessians) - * with respect to the reference coordinate system. + * @class ReferenceElement + * @brief Abstract base class for Finite Element reference definitions. * - * @param info The ShapeInfo object containing buffers to be filled. - */ - virtual void evaluateShapesSecondDerivatives(const Vector3D& xi, ShapeInfo& info) const = 0; - virtual void evaluateShapesSecondDerivativesBatch(const DynamicVector &xi, - ShapeInfoBatch& shapeBatch) const = 0; - - virtual Vector3D projectFacetRefCoordToCellRefCoord(CfemInt localFacetId, const Vector3D& xiFacet) const = 0; - /** - * @brief Returns the coordinates of the nodes in the reference space. - */ - const DynamicVector& referenceNodes() const { return m_nodes; } - const Vector3D& getNodeCoords( CfemInt i) const { CFEM_ASSERT( i < m_nodes.size() && i >= 0 ); return m_nodes[i]; } - - - - /** - * @brief Computes the full geometric mapping at a given point xi. - * @param xi Target poCfemInt in reference space. - * @param physicalNodesCoords Actual physical coordinates of the element's nodes. - * @param requestedFields Bitmask of physical fields required (Gradients, Hessians). - * @param shape Work buffer for reference shape functions. - * @param out MappingInfo structure to be populated. - */ - void computeMapping(const Vector3D &xi, - const DynamicVector &physicalNodesCoords, - ShapeEvaluationFlags requestedShape, - MappingEvaluationFlags requestedMapping, - ShapeInfo &shape, - MappingInfo &out) const; - - void computeMapping(const Vector3D &xi, - const DynamicVector &physicalNodesCoords, - MappingEvaluationFlags requestedMapping, - const ShapeInfo &shape, - MappingInfo &out) const; - - void computeMappingBatch(const DynamicVector &xiPoints, - const DynamicVector &physicalNodesCoords, - ShapeEvaluationFlags requestedShape, - MappingEvaluationFlags requestedMapping, - ShapeInfoBatch &shapeBatch, - MappingInfoBatch &outBatch) const; - - void computeMappingBatch(const DynamicVector &xiPoints, - const DynamicVector &physicalNodesCoords, - MappingEvaluationFlags requestedMapping, - const ShapeInfoBatch &shapeBatch, - MappingInfoBatch &outBatch) const; - - /** - * @brief Transforms reference gradients to physical space using J^-T. - * @param shape Work buffer for reference shape functions. - * @param out MappingInfo structure to be populated. + * Provides the machinery to compute shape functions, geometric mappings, Jacobians, + * and physical derivatives. It supports both traditional single-point evaluation + * and high-performance, SIMD-friendly batch evaluation using Structure-of-Arrays (SoA). */ - void computePhysicalFirstDerivatives(const ShapeInfo& shape, - MappingInfo& out) const; - - void computePhysicalFirstDerivativesBatch(const ShapeInfoBatch &shape, - MappingInfoBatchView &out) const; - - /** - * @brief Computes physical Hessians including the geometric curvature term. - * @param physicalNodesCoords Actual physical coordinates of the element's nodes. - * @param shape Work buffer for reference shape functions. - * @param out MappingInfo structure to be populated. - * @note Reference: \f$ H_{phys} = J^{-T} * [H_{ref} - \nabla_{phys}(N) * H_{geom}] * J^{-1} \f$ - */ - void computePhysicalSecondDerivatives(const DynamicVector& physicalNodesCoords, - const ShapeInfo& shape, - MappingInfo& out) const; - - void computePhysicalSecondDerivativesBatch(const DynamicVector& physicalNodesCoords, - const ShapeInfoBatch& shapeBatch, - MappingInfoBatch& outBatch) const; - - /** - * @brief Interpolates a field (scalar or vector) at a point evaluated in ShapeInfo. - * @tparam T The field type (e.g., CfemReal, Vector3D, or even Tensor types). - * @param shape Pre-evaluated shape functions at point xi. - * @param nodalValues The values of the field at the element nodes. - */ - template - T interpolate(const ShapeInfo& shape, const DynamicVector& nodalValues) const; - - /** - * @brief Interpolates the physical gradient of a scalar field at a point. - * @return Vector3D containing [du/dx, du/dy, du/dz] - */ - Vector3D interpolateGradient(const MappingInfo& map, - const DynamicVector& nodalValues) const; - - - /** - * @brief Interpolates the physical Hessian of a scalar field. - * @return SymTensor3D representing the 2nd derivative matrix. - */ - SymTensor3D interpolateHessian(const MappingInfo& map, - const DynamicVector& nodalValues) const; - -private: - - /** - * @brief Computes the Jacobian, its determinant (metric), and the pseudo-inverse. - * @note This version supports 1D/2D elements embedded in 3D space using - * the Moore-Penrose pseudo-inverse logic. - */ - inline void computeJacobian(const ShapeInfo& shape, - const DynamicVector& physicalNodesCoords, - MappingInfo& out) const; - inline void computeJacobianBatch(const ShapeInfoBatch& shape, - const DynamicVector& physicalNodesCoords, - MappingInfoBatch& out) const; - - inline void computeDeterminant(MappingInfo &out) const; - inline void computeDeterminantBatch(MappingInfoBatch &out) const; - - inline void computeInverseJacobian(MappingInfo &out) const; - inline void computeInverseJacobianBatch(MappingInfoBatch &out) const; - - inline void computeGeometricNormal(MappingInfo &out) const; - inline void computeGeometricNormalBatch(MappingInfoBatch &outBatch) const; - - void computePhysicalFirstDerivativesBatch(const ShapeInfoBatch &shape, - MappingInfoBatch &out) const; + class ReferenceElement + { + protected: + DynamicVector m_nodes; ///< Node coordinates in reference space (\f$ \xi \f$). + + CfemInt m_numNodes; ///< Reference element nodes count + CfemInt m_dimension; ///< Topological dimension of the reference element + + public: + virtual ~ReferenceElement() = default; + + /** + * @brief Returns the topological dimension of the reference element. + * @return 1 for lines, 2 for triangles/quads, 3 for tetrahedrons/hexahedrons. + */ + CfemInt getDimension() const { return m_dimension; }; + + /** + * @brief Returns the total number of nodes defining the reference element. + */ + CfemInt getNumNodes() const { return m_numNodes; }; + + /** + * @brief Retrieves the local node indices that belong to a specific face of the element. + * @param iFace The local index of the face. + * @return A view (span) of the local node indices making up the face. + */ + virtual std::span getFaceNodes(CfemInt iFace) const = 0; + + /** + * @brief Centralized method to evaluate shape functions and/or their reference derivatives at a single point. + * @param xi Target point in reference space. + * @param requestedShape Bitmask indicating which fields to compute (Values, FirstDerivatives, and/or SecondDerivatives.). + * @param out The ShapeInfo structure to populate. + */ + void evaluateShapes(const Vector3D &xi, ShapeEvaluationFlags requestedShape, ShapeInfo &out) const; + + /** + * @brief Centralized method for SIMD-friendly batch evaluation of shape functions and reference derivatives. + * @param xiPoints Array of target points in reference space. + * @param requestedShape Bitmask indicating which fields to compute. + * @param outBatch The ShapeInfoBatch structure (SoA layout) to populate. + */ + void evaluateShapesBatch(const DynamicVector &xiPoints, ShapeEvaluationFlags requestedShape, ShapeInfoBatch &outBatch) const; + + /** + * @brief Evaluates the shape function values (\f$ N_i \f$) at a given reference point. + * @param xi The coordinates of the evaluation point in the reference element (\f$\xi, \eta, \zeta \f$). + * @param info ShapeInfo object whose value buffer will be populated. + */ + virtual void evaluateShapesValues(const Vector3D &xi, ShapeInfo &info) const = 0; + + /** + * @brief Vectorized evaluation of shape function values (\f$ N_i \f$) for a batch of reference points. + * @param xi Array of evaluation points. + * @param shapeBatch SoA structure to store the computed values. + */ + virtual void evaluateShapesValuesBatch(const DynamicVector &xi, + ShapeInfoBatch &shapeBatch) const = 0; + + /** + * @brief Evaluates the first-order derivatives of the shape functions (local gradients) + * with respect to the reference coordinate system. + * @param xi The coordinates of the evaluation point in the reference element (\f$\xi, \eta, \zeta \f$). + * @param info ShapeInfo object whose derivative buffers will be populated. + */ + virtual void evaluateShapesDerivatives(const Vector3D &xi, ShapeInfo &info) const = 0; + + /** + * @brief Vectorized evaluation of first-order reference derivatives for a batch of points. + * @param xi Array of evaluation points. + * @param shapeBatch SoA structure to store the computed reference derivatives. + */ + virtual void evaluateShapesDerivativesBatch(const DynamicVector &xi, + ShapeInfoBatch &shapeBatch) const = 0; + + /** + * @brief Evaluates the second-order derivatives of the shape functions (local Hessians) + * with respect to the reference coordinate system. + * @param xi The coordinates of the evaluation point. + * @param info The ShapeInfo object containing buffers to be filled. + */ + virtual void evaluateShapesSecondDerivatives(const Vector3D &xi, ShapeInfo &info) const = 0; + + /** + * @brief Vectorized evaluation of second-order reference derivatives for a batch of points. + * @param xi Array of evaluation points. + * @param shapeBatch SoA structure to store the computed reference Hessians. + */ + virtual void evaluateShapesSecondDerivativesBatch(const DynamicVector &xi, + ShapeInfoBatch &shapeBatch) const = 0; + + /** + * @brief Projects a reference coordinate from a lower-dimensional boundary facet into the parent cell's reference space. + * @note Crucial for boundary integrations where the surface measure is computed on the facet, but + * physical derivatives require shape evaluations inside the parent volume. + * @param localFacetId The local index of the facet within the parent element. + * @param xiFacet The reference coordinate on the facet. + * @return The projected coordinate \f$ (\xi, \eta, \zeta) \f$ in the parent element's reference space. + */ + virtual Vector3D projectFacetRefCoordToCellRefCoord(CfemInt localFacetId, const Vector3D &xiFacet) const = 0; + + /** + * @brief Returns the coordinates of the nodes defining the element in the reference space. + */ + const DynamicVector &getReferenceNodes() const { return m_nodes; } + + /** + * @brief Returns the reference coordinate of a specific local node. + * @param i Local node index. + */ + const Vector3D &getNodeCoords(CfemInt i) const + { + CFEM_ASSERT(i < m_nodes.size() && i >= 0); + return m_nodes[i]; + } + + /** + * @brief Computes the full geometric mapping at a given point xi, internally evaluating required shape functions. + * @param xi Target point in reference space. + * @param physicalNodesCoords Actual physical coordinates of the element's nodes. + * @param requestedShape Bitmask of reference shape fields required. + * @param requestedMapping Bitmask of physical fields required (Jacobian, Gradients, Hessians). + * @param shape Work buffer for reference shape functions. + * @param out MappingInfo structure to be populated. + */ + void computeMapping(const Vector3D &xi, + const DynamicVector &physicalNodesCoords, + ShapeEvaluationFlags requestedShape, + MappingEvaluationFlags requestedMapping, + ShapeInfo &shape, + MappingInfo &out) const; + + /** + * @brief Computes the full geometric mapping utilizing pre-evaluated reference shape functions. + * @param xi Target point in reference space. + * @param physicalNodesCoords Actual physical coordinates of the element's nodes. + * @param requestedMapping Bitmask of physical fields required. + * @param shape Pre-evaluated shape functions. + * @param out MappingInfo structure to be populated. + */ + void computeMapping(const Vector3D &xi, + const DynamicVector &physicalNodesCoords, + MappingEvaluationFlags requestedMapping, + const ShapeInfo &shape, + MappingInfo &out) const; + + /** + * @brief Computes the full geometric mapping for a batch of points, internally evaluating required shape functions. + * @note Features an affine-bypass optimization: if the element is affine, Jacobians are evaluated only once. + * @param xiPoints Array of target points in reference space. + * @param physicalNodesCoords Actual physical coordinates of the element's nodes. + * @param requestedShape Bitmask of reference shape fields required. + * @param requestedMapping Bitmask of geometric fields required. + * @param shapeBatch SoA work buffer for reference shape functions. + * @param outBatch SoA MappingInfoBatch structure to be populated. + */ + void computeMappingBatch(const DynamicVector &xiPoints, + const DynamicVector &physicalNodesCoords, + ShapeEvaluationFlags requestedShape, + MappingEvaluationFlags requestedMapping, + ShapeInfoBatch &shapeBatch, + MappingInfoBatch &outBatch) const; + + /** + * @brief Computes the geometric mapping for a batch of points utilizing pre-evaluated reference shapes. + * @param xiPoints Array of target points in reference space. + * @param physicalNodesCoords Actual physical coordinates of the element's nodes. + * @param requestedMapping Bitmask of geometric fields required. + * @param shapeBatch Pre-evaluated SoA reference shapes. + * @param outBatch SoA MappingInfoBatch structure to be populated. + */ + void computeMappingBatch(const DynamicVector &xiPoints, + const DynamicVector &physicalNodesCoords, + MappingEvaluationFlags requestedMapping, + const ShapeInfoBatch &shapeBatch, + MappingInfoBatch &outBatch) const; + + /** + * @brief Transforms reference gradients to physical space using \f$ J^{-T} \f$. + * @param shape Pre-evaluated reference shape functions. + * @param out MappingInfo structure (must contain valid inverse Jacobian) to be populated with physical derivatives. + */ + void computePhysicalFirstDerivatives(const ShapeInfo &shape, + MappingInfo &out) const; + + /** + * @brief High-performance SIMD kernel transforming reference gradients to physical space for a batch of points. + * @note Utilizes dimensional masking to avoid unnecessary computations in 2D or 1D spaces, and safely handles + * embedded manifolds (e.g., computing 3D physical gradients for a 2D surface element). Memory is managed via the View. + * @param shape Pre-evaluated reference shape batch. + * @param out The Zero-Copy Hybrid View containing the target Arena memory layout. + */ + void computePhysicalFirstDerivativesBatch(const ShapeInfoBatch &shape, + MappingInfoBatchView &out) const; + + /** + * @brief Computes physical Hessians including the geometric curvature correction term. + * @note Reference: \f$ H_{phys} = J^{-T} * [H_{ref} - \nabla_{phys}(N) * H_{geom}] * J^{-1} \f$ + * @param physicalNodesCoords Actual physical coordinates of the element's nodes. + * @param shape Pre-evaluated reference shape functions. + * @param out MappingInfo structure to be populated. + */ + void computePhysicalSecondDerivatives(const DynamicVector &physicalNodesCoords, + const ShapeInfo &shape, + MappingInfo &out) const; + + /** + * @brief Computes physical Hessians for a batch of points, accounting for geometric curvature. + * @param physicalNodesCoords Actual physical coordinates of the element's nodes. + * @param shapeBatch Pre-evaluated reference shape batch. + * @param outBatch SoA MappingInfoBatch to be populated with physical Hessians. + */ + void computePhysicalSecondDerivativesBatch(const DynamicVector &physicalNodesCoords, + const ShapeInfoBatch &shapeBatch, + MappingInfoBatch &outBatch) const; + + /** + * @brief Interpolates a field (scalar or vector) at a point evaluated in ShapeInfo. + * @tparam T The field type (e.g., CfemReal, Vector3D, or Tensor types). + * @param shape Pre-evaluated shape functions at point xi. + * @param nodalValues The values of the field at the element nodes. + * @return The interpolated value. + */ + template + T interpolate(const ShapeInfo &shape, const DynamicVector &nodalValues) const; + + /** + * @brief Interpolates the physical gradient of a scalar field at a point. + * @param map MappingInfo containing physical first derivatives of the shape functions. + * @param nodalValues The values of the field at the element nodes. + * @return Vector3D containing \f$ [\frac{\partial u}{\partial x}, \frac{\partial u}{\partial y}, \frac{\partial u}{\partial z}] \f$. + */ + Vector3D interpolateGradient(const MappingInfo &map, + const DynamicVector &nodalValues) const; + + /** + * @brief Interpolates the physical Hessian of a scalar field. + * @param map MappingInfo containing physical second derivatives of the shape functions. + * @param nodalValues The values of the field at the element nodes. + * @return SymTensor3D representing the symmetric 2nd derivative matrix. + */ + SymTensor3D interpolateHessian(const MappingInfo &map, + const DynamicVector &nodalValues) const; + + private: + /** + * @brief Computes the Jacobian, its determinant (metric), and the pseudo-inverse for a single point. + * @note This version safely supports 1D/2D elements embedded in 3D space using + * Moore-Penrose pseudo-inverse logic. + * @param shape Pre-evaluated reference shape functions. + * @param physicalNodesCoords Actual physical coordinates of the element's nodes. + * @param out MappingInfo structure to be populated with the Jacobian. + */ + inline void computeJacobian(const ShapeInfo &shape, + const DynamicVector &physicalNodesCoords, + MappingInfo &out) const; + + /** + * @brief Computes the Jacobian matrices for a batch of quadrature points. + * @param shape SoA batch containing pre-evaluated reference shape derivatives. + * @param physicalNodesCoords Actual physical coordinates of the element's nodes. + * @param out MappingInfoBatch structure to store the computed Jacobians. + */ + inline void computeJacobianBatch(const ShapeInfoBatch &shape, + const DynamicVector &physicalNodesCoords, + MappingInfoBatch &out) const; + + /** + * @brief Computes the determinant of the Jacobian (metric / volume or surface element) for a single point. + * @note For embedded manifolds (e.g., 2D surface in 3D space), this evaluates the Gram determinant metric. + * @param out MappingInfo structure containing a valid Jacobian, to be populated with its determinant. + */ + inline void computeDeterminant(MappingInfo &out) const; + + /** + * @brief SIMD-friendly computation of Jacobian determinants for a batch of points. + * @param out SoA MappingInfoBatch structure containing valid Jacobians, to be populated with their determinants. + */ + inline void computeDeterminantBatch(MappingInfoBatch &out) const; + + /** + * @brief Computes the inverse of the Jacobian matrix for a single point. + * @note Utilizes Moore-Penrose pseudo-inversion when the reference dimension is strictly less + * than the spatial dimension (e.g., computing the inverse for a surface element). + * @param out MappingInfo structure containing a valid Jacobian, to be populated with its inverse. + */ + inline void computeInverseJacobian(MappingInfo &out) const; + + /** + * @brief SIMD-friendly computation of inverse Jacobian matrices for a batch of points. + * @param out SoA MappingInfoBatch structure to be populated with the inverses. + */ + inline void computeInverseJacobianBatch(MappingInfoBatch &out) const; + + /** + * @brief Computes the physical geometric normal vector at a single evaluation point. + * @note Primarily applicable for \f$ (N-1) \f$-dimensional boundary or interface elements (facets). + * @param out MappingInfo structure containing a valid Jacobian, to be populated with the normal vector. + */ + inline void computeGeometricNormal(MappingInfo &out) const; + + /** + * @brief SIMD-friendly computation of physical geometric normal vectors for a batch of points. + * @param outBatch SoA MappingInfoBatch structure to be populated with the normal vectors. + */ + inline void computeGeometricNormalBatch(MappingInfoBatch &outBatch) const; + + /** + * @brief Legacy batch derivative computation (Replaced by MappingInfoBatchView architecture). + */ + void computePhysicalFirstDerivativesBatch(const ShapeInfoBatch &shape, + MappingInfoBatch &out) const; }; } diff --git a/libs/fem/reference_element/include/ReferenceElement.tpp b/libs/fem/reference_element/include/ReferenceElement.tpp index ab35c8c..1bf3780 100644 --- a/libs/fem/reference_element/include/ReferenceElement.tpp +++ b/libs/fem/reference_element/include/ReferenceElement.tpp @@ -649,7 +649,7 @@ namespace cfem /** * @brief Computes physical gradients for the entire batch. - * @note Hyper-optimized utilizing SIMD friendly contiguous memory rows. + * @note Optimized utilizing SIMD friendly contiguous memory rows. * Correctly handles embedded manifolds (e.g., 1D/2D elements in 3D ambient space). */ inline void ReferenceElement::computePhysicalFirstDerivativesBatch(const ShapeInfoBatch &shape, diff --git a/libs/kernel/include/EvalContext.h b/libs/kernel/include/EvalContext.h index a84157e..e772549 100644 --- a/libs/kernel/include/EvalContext.h +++ b/libs/kernel/include/EvalContext.h @@ -16,352 +16,6 @@ // // --- held liable for any damages arising from the use of this software. // //************************************************************************ -// #pragma once - -// #include "cfemutils.h" -// #include "Vector3D.h" -// #include "DynamicVector.h" -// #include "DynamicMatrix.h" - -// #include "ShapeInfo.h" -// #include "ShapeInfoBatch.h" -// #include "MappingInfo.h" -// #include "MappingInfoBatchView.h" - -// #include -// #include -// #include -// #include -// #include -// #include - -// namespace cfem { - -// class FESpace; // Forward declaration - -// // Forward declarations des structures Batch (définies dans votre architecture SoA) -// struct ShapeInfoBatch; -// struct MappingInfoBatch; - -// struct MappingInfoBatch; - -// /** -// * @struct SpaceDataEntry -// * @brief Ultra-fast link between an FE Space and its evaluations at the current integration cell. -// * Uses explicit pointers to safely separate legacy/single-point data from vectorized batch data. -// */ -// struct SpaceDataEntry { -// const ShapeInfo* shapeSingle = nullptr; -// const MappingInfo* mappingSingle = nullptr; - -// const ShapeInfoBatch* shapeBatch = nullptr; -// const MappingInfoBatchView* mappingBatch = nullptr; -// }; - -// /** -// * @struct CacheEntry -// * @brief Stores a pointer-based identifier and its associated data span. -// */ -// struct CacheEntry { -// const void* id; -// std::span data; -// }; - -// /** -// * @struct EvalContext -// * @brief High-performance evaluation context for Finite Element assembly. -// * Provides O(1) access to geometric data and an Arena Memory system. -// * Structured to support both Single-Point (Probing) and Batch (Assembly) evaluation modes. -// */ -// struct EvalContext { - -// // ==================================================================== -// // GLOBAL SIMULATION STATE -// // ==================================================================== -// CfemReal currentTime = 0.0; /**< Current simulation time (for transient problems). */ -// CfemInt currentCellId = -1; /**< Index of the cell currently being integrated. */ -// CfemInt currentQpIndex = 0; - -// // ==================================================================== -// // SINGLE-POINT WORKSPACE (Legacy / Probing) -// // ==================================================================== -// struct SingleWorkspace { -// Vector3D physPos = {}; /**< Physical coordinates. */ -// Vector3D xi = {-1e9, -1e9, -1e9};/**< Reference coordinates. */ -// ShapeInfo shape; /**< Buffer for base functions. */ -// MappingInfo mapping; /**< Buffer for geometric mapping. */ -// } singleWS; - -// // ==================================================================== -// // BATCH WORKSPACE (Vectorized Element Assembly) -// // ==================================================================== -// struct BatchWorkspace { -// DynamicVector* physPos; /**< Physical coordinates for all QPs. */ -// DynamicVector* xi; /**< Reference coordinates for all QPs. */ -// ShapeInfoBatch* shape = nullptr; /**< Pointer to allocated batch shape memory. */ -// MappingInfoBatch* mapping = nullptr; /**< Pointer to allocated batch metric memory. */ -// } batchWS; - -// // ==================================================================== -// // MULTI-SPACE CACHE & SHORTCUTS (O(1) Access) -// // ==================================================================== -// static constexpr CfemInt MAX_CACHED_SPACES = 16; - -// #ifdef CFEM_USE_DYNAMIC_SPACE_CACHE -// std::vector m_spaceDataCache; -// #else -// std::array m_spaceDataCache; -// #endif - -// const ShapeInfoBatch* trialShapeBatch = nullptr; -// const ShapeInfoBatch* testShapeBatch = nullptr; -// const MappingInfoBatch* trialMappingBatch = nullptr; -// const MappingInfoBatch* testMappingBatch = nullptr; -// const MappingInfoBatch* geometricMappingBatch = nullptr; - - -// const MappingInfo* geometricMapping = nullptr; - -// CfemInt trialDof = -1; -// CfemInt testDof = -1; - -// // ==================================================================== -// // PRE-ALLOCATED SCRATCHPADS -// // ==================================================================== -// DynamicVector scratchCoords; /**< Buffer for cell vertex coordinates. */ -// std::vector scratchDofs; /**< Buffer for gathering local DOF indices. */ -// DynamicMatrix scratchCellDofs; /**< Buffer for cell DOF values. */ -// MappingInfo scratchMapping; /**< Buffer for cell DOF values. */ -// ShapeInfo scratchShape; /**< Buffer for cell DOF values. */ -// // ==================================================================== -// // ARENA MEMORY SYSTEM -// // ==================================================================== -// static constexpr size_t PAGE_SIZE = 4096; - -// std::deque> m_pages; -// size_t m_currentPageIdx = 0; -// size_t m_currentOffset = 0; - -// std::vector m_expressionCache; - -// // ==================================================================== -// // API METHODS -// // ==================================================================== - -// EvalContext() -// { -// m_pages.emplace_back(PAGE_SIZE); -// m_expressionCache.reserve(16); -// #ifdef CFEM_USE_DYNAMIC_SPACE_CACHE -// m_spaceDataCache.reserve(8); -// #endif -// } - -// #ifdef CFEM_USE_DYNAMIC_SPACE_CACHE -// inline void prepareSpaceCache(CfemInt n) { -// m_spaceDataCache.resize(n); -// } -// #endif - -// inline const SpaceDataEntry& getSpaceData(CfemInt index) const -// { -// #ifdef CFEM_USE_DYNAMIC_SPACE_CACHE -// CFEM_ASSERT(index >= 0 && index < static_cast(m_spaceDataCache.size()), "EvalContext::getSpaceData index out of bounds"); -// #else -// CFEM_ASSERT(index >= 0 && index < MAX_CACHED_SPACES, "EvalContext::getSpaceData index out of bounds"); -// #endif -// return m_spaceDataCache[index]; -// } - -// inline void setSpaceData(CfemInt index, const ShapeInfo* shape, const MappingInfo* mapping) -// { -// CFEM_ASSERT(index >= 0, "Negative space index"); -// #ifdef CFEM_USE_DYNAMIC_SPACE_CACHE -// CFEM_ASSERT(index < static_cast(m_spaceDataCache.size()), "Space cache not pre-sized"); -// #else -// CFEM_ASSERT(index < MAX_CACHED_SPACES, "Space index exceeds MAX_CACHED_SPACES"); -// #endif -// auto& entry = m_spaceDataCache[index]; -// entry.shapeSingle = shape; -// entry.mappingSingle = mapping; -// } - -// inline void setSpaceDataBatch(CfemInt index, const ShapeInfoBatch* shape, const MappingInfoBatch* mapping) -// { -// CFEM_ASSERT(index >= 0, "Negative space index"); -// #ifdef CFEM_USE_DYNAMIC_SPACE_CACHE -// CFEM_ASSERT(index < static_cast(m_spaceDataCache.size()), "Space cache not pre-sized"); -// #else -// CFEM_ASSERT(index < MAX_CACHED_SPACES, "Space index exceeds MAX_CACHED_SPACES"); -// #endif -// auto& entry = m_spaceDataCache[index]; -// entry.shapeBatch = shape; -// entry.mappingBatch = mapping; -// } - -// inline const MappingInfoBatch* getMappingBatch(CfemInt index) const -// { -// #ifdef CFEM_USE_DYNAMIC_SPACE_CACHE -// CFEM_ASSERT(index >= 0 && index < static_cast(m_spaceDataCache.size()), "EvalContext::getMappingBatch index out of bounds"); -// #else -// CFEM_ASSERT(index >= 0 && index < MAX_CACHED_SPACES, "EvalContext::getMappingBatch index out of bounds"); -// #endif -// const auto* mapping = m_spaceDataCache[index].mappingBatch; -// CFEM_ASSERT(mapping != nullptr, "MappingInfoBatch not set"); -// return mapping; -// } - -// inline const MappingInfo* getMapping(CfemInt index) const -// { -// #ifdef CFEM_USE_DYNAMIC_SPACE_CACHE -// CFEM_ASSERT(index >= 0 && index < static_cast(m_spaceDataCache.size()), "EvalContext::getMappingSingle index out of bounds"); -// #else -// CFEM_ASSERT(index >= 0 && index < MAX_CACHED_SPACES, "EvalContext::getMappingSingle index out of bounds"); -// #endif -// const auto* mapping = m_spaceDataCache[index].mappingSingle; -// CFEM_ASSERT(mapping != nullptr, "MappingInfo not set"); -// return mapping; -// } - -// /** -// * @brief Retrieves precomputed BATCH shape info for a given space index. -// * @param index Space identifier. -// * @return Pointer to ShapeInfoBatch. -// * @pre index is valid and batch shape has been set. -// */ -// inline const ShapeInfoBatch* getShapeBatch(CfemInt index) const -// { -// #ifdef CFEM_USE_DYNAMIC_SPACE_CACHE -// CFEM_ASSERT(index >= 0 && index < static_cast(m_spaceDataCache.size()), "EvalContext::getShapeBatch index out of bounds"); -// #else -// CFEM_ASSERT(index >= 0 && index < MAX_CACHED_SPACES, "EvalContext::getShapeBatch index out of bounds"); -// #endif -// const auto* shape = m_spaceDataCache[index].shapeBatch; -// CFEM_ASSERT(shape != nullptr, "ShapeInfoBatch not set. Did you call setSpaceDataBatch?"); -// return shape; -// } - -// /** -// * @brief Retrieves precomputed SINGLE shape info for a given space index. -// * @param index Space identifier. -// * @return Pointer to ShapeInfo (Legacy/Probing). -// * @pre index is valid and single shape has been set. -// */ -// inline const ShapeInfo* getShape(CfemInt index) const -// { -// #ifdef CFEM_USE_DYNAMIC_SPACE_CACHE -// CFEM_ASSERT(index >= 0 && index < static_cast(m_spaceDataCache.size()), "EvalContext::getShapeSingle index out of bounds"); -// #else -// CFEM_ASSERT(index >= 0 && index < MAX_CACHED_SPACES, "EvalContext::getShapeSingle index out of bounds"); -// #endif -// const auto* shape = m_spaceDataCache[index].shapeSingle; -// CFEM_ASSERT(shape != nullptr, "ShapeInfo not set. Did you call setSpaceDataSingle?"); -// return shape; -// } - -// void resetBuffers() { -// m_currentPageIdx = 0; -// m_currentOffset = 0; -// m_expressionCache.clear(); -// } - -// /** -// * @brief Allocates a buffer from the Arena memory. -// * @param size Number of CfemReal elements needed. -// * @return A span pointing to the allocated memory. -// */ -// std::span getBuffer(CfemInt size) { -// CFEM_ASSERT(size >= 0, "Negative buffer size"); -// CFEM_ASSERT(m_currentPageIdx < m_pages.size(), -// "Invalid arena state: page index out of bounds"); - -// size_t usize = static_cast(size); - -// if (m_currentOffset + usize > m_pages[m_currentPageIdx].size()){ -// // Check if the current page has enough remaining space -// m_currentPageIdx++; -// m_currentOffset = 0; - -// // If we ran out of pages, add a new one -// if (m_currentPageIdx >= m_pages.size()) { -// m_pages.emplace_back(std::max(usize, PAGE_SIZE)); -// } -// // If a page exists from a previous run but is too small, resize it -// else if (m_pages[m_currentPageIdx].size() < usize) { -// m_pages[m_currentPageIdx].resize(usize); -// } -// } - -// auto span = std::span(m_pages[m_currentPageIdx].data() + m_currentOffset, usize); -// m_currentOffset += usize; -// return span; -// } - -// bool tryGetCache(const void* exprPtr, std::span out) const { -// for (const auto& entry : m_expressionCache) { -// if (entry.id == exprPtr) { -// CFEM_ASSERT(out.size() >= entry.data.size(), "Output buffer too small in tryGetCache"); -// std::copy(entry.data.begin(), entry.data.end(), out.begin()); -// return true; -// } -// } -// return false; -// } - -// void storeInCache(const void* exprPtr, std::span result) { -// auto storage = getBuffer(static_cast(result.size())); -// std::copy(result.begin(), result.end(), storage.begin()); -// m_expressionCache.push_back({exprPtr, storage}); -// } - -// struct Bookmark { size_t page; size_t offset; size_t cacheSize; }; - -// Bookmark getBookmark() const { -// return { m_currentPageIdx, m_currentOffset, m_expressionCache.size() }; -// } - -// void releaseToBookmark(Bookmark bm) { -// m_currentPageIdx = bm.page; -// m_currentOffset = bm.offset; -// m_expressionCache.resize(bm.cacheSize); -// } - -// /** -// * @brief Calculates total memory allocated by the Arena in bytes. -// */ -// size_t getArenaMemoryBytes() const { -// size_t total = 0; -// total += sizeof(m_pages); -// for (const auto& page : m_pages) { -// total += sizeof(page); // objet vector -// total += page.capacity() * sizeof(CfemReal); -// } -// return total; -// } - -// /** -// * @brief Debug print of the Arena Memory state to the standard output. -// */ -// void printArenaMemory() const { -// size_t total = 0; -// std::cout << "---- Arena Memory Debug ----\n"; -// std::cout << "Number of pages: " << m_pages.size() << "\n"; - -// for (size_t i = 0; i < m_pages.size(); ++i) { -// const auto& page = m_pages[i]; -// size_t mem = sizeof(page) + page.capacity() * sizeof(CfemReal); -// std::cout << "Page " << i -// << " | size: " << page.size() -// << " | capacity: " << page.capacity() -// << " | mem: " << mem / 1024.0 << " KB\n"; -// total += mem; -// } -// std::cout << "TOTAL arena memory: " << total / (1024.0 * 1024.0) << " MB\n"; -// } -// }; -// } - - #pragma once #include "cfemutils.h" diff --git a/libs/kernel/include/ShapeInfoBatch.h b/libs/kernel/include/ShapeInfoBatch.h index 95d594c..74f111b 100644 --- a/libs/kernel/include/ShapeInfoBatch.h +++ b/libs/kernel/include/ShapeInfoBatch.h @@ -35,9 +35,11 @@ namespace cfem * ### HPC Design (Structure of Arrays - SoA) * To maximize SIMD auto-vectorization and cache hits, all evaluations (values, gradients, Hessians) * for all quadrature points and all nodes are flattened into a single 1D `DynamicVector`. - * * Memory Layout for a given quadrature point `q`: + * + * Memory Layout for a given quadrature point `q`: * `[ N_nodes... | dxi_nodes... | deta_nodes... | dzeta_nodes... | (Hessians...) ]` - * * Each field block is padded to ensure it starts on a SIMD-friendly memory boundary (e.g., 64 bytes). + * + * Each field block is padded to ensure it starts on a SIMD-friendly memory boundary (e.g., 64 bytes). * Lightweight view accessors (e.g., `dN_dxi(q)`) return raw pointers to these aligned blocks. */ struct ShapeInfoBatch @@ -63,8 +65,9 @@ namespace cfem CfemInt qpStride = 0; ///< Total stride per quadrature point (all fields combined) bool hasSecondDerivatives = false; - /** * @brief Prepares buffer sizes and strides to prevent runtime reallocations. - * * @param nQp Number of quadrature points in the batch. + /** + * @brief Prepares buffer sizes and strides to prevent runtime reallocations. + * @param nQp Number of quadrature points in the batch. * @param nNodes Number of interpolation nodes in the reference element. * @param includeSecondDerivatives If true, memory for Hessian-related quantities is allocated. */ diff --git a/libs/prepostprocessing/numerical_integration/CMakeLists.txt b/libs/prepostprocessing/numerical_integration/CMakeLists.txt index 8312b01..075b3d0 100644 --- a/libs/prepostprocessing/numerical_integration/CMakeLists.txt +++ b/libs/prepostprocessing/numerical_integration/CMakeLists.txt @@ -1,5 +1,5 @@ add_library(cfem_numerical_integration - src/FunctionIntegrator.cpp + src/ScalarFunctionalEvaluator.cpp ) target_include_directories(cfem_numerical_integration diff --git a/libs/prepostprocessing/numerical_integration/include/FunctionIntegrator.h b/libs/prepostprocessing/numerical_integration/include/FunctionIntegrator.h deleted file mode 100644 index 2f8e4eb..0000000 --- a/libs/prepostprocessing/numerical_integration/include/FunctionIntegrator.h +++ /dev/null @@ -1,145 +0,0 @@ -//************************************************************************ -// --- CFEM++ Library -// --- -// --- Copyright 2024-2026 Ismaël Tchinda Ngueyong et al. -// --- ALL RIGHTS RESERVED -// --- -// --- This software is protected by international copyright laws. -// --- Use, distribution, or modification of this software in any form, -// --- source or binary, for personal, academic, or non-commercial -// --- purposes is permitted free of charge, provided that this -// --- copyright notice and this permission notice appear in all -// --- copies and supporting documentation. -// --- -// --- The authors and contributors provide this code "as is" without -// --- any express or implied warranty. In no event shall they be -// --- held liable for any damages arising from the use of this software. -//************************************************************************ - -#ifndef CFEM_FUNCTIONINTEGRATOR_H -#define CFEM_FUNCTIONINTEGRATOR_H - -#include "CfemExpression.h" -#include "Quadrature.h" -#include "cfem_topology.h" -#include "FESpace.h" -#include -#include -#include -#include - -namespace cfem -{ - class Mesh; - class MeshEntity; - - /** - * @class FunctionIntegrator - * @brief Engine for high-performance numerical integration of FE expressions. - * - * This class handles the reduction of symbolic expressions into scalar values (integrals). - * It supports multi-target integration, allowing the calculation of several values - * (like L2 and H1 norms) in a single mesh sweep to maximize cache efficiency. - * - * Features: - * - Hybrid OpenMP parallelism for large-scale meshes. - * - Multi-level caching of shape functions and geometric mappings. - * - Integration over volumes (Cells) and boundaries (Facets/Physical Groups). - */ - class FunctionIntegrator - { - private: - std::shared_ptr m_mesh; - IntegrationSchemeParams m_schemes_params; - bool m_cacheInitialized = false; - - using EvaluationTarget = std::pair, CfemReal*>; - - /** - * @brief Core volume integration loop (OpenMP accelerated). - */ - void integrate(const std::vector &targets, const MeshEntity &domain); - - /** - * @brief Core boundary integration loop (OpenMP accelerated). - * @details Handles facet-to-parent mapping for field evaluation on surfaces. - */ - void integrateBoundary(const std::vector &targets, - const MeshEntity &boundary, - const MeshEntity &volume); - - /** - * @brief Internal dispatcher that selects the appropriate integration strategy. - * @details Decides whether to use volume integration or boundary integration - * based on the topological dimension of the provided entity. - */ - void dispatchIntegration(const std::vector &targets, - const MeshEntity &entity, - const MeshEntity &); - - // inline bool needsMapping(ShapeEvaluationFlags flags) - // { - // return hasFlag(flags, ShapeEvaluationFlags::Gradient) || - // hasFlag(flags, ShapeEvaluationFlags::Hessian); - // } - - public: - /** - * @brief Constructs a FunctionIntegrator for a specific mesh. - * @param mesh Shared pointer to the mesh to integrate over. - * @param params Optional parameters to define quadrature orders per element type. - */ - explicit FunctionIntegrator(std::shared_ptr mesh, - const IntegrationSchemeParams& params = {}); - - /** - * @brief Updates the integration parameters (quadrature orders). - * @details Changing params automatically invalidates existing caches to ensure - * that subsequent integrations use the correct number of quadrature points. - */ - void setIntegrationSchemeParams(const IntegrationSchemeParams& params){ - if (m_schemes_params != params) { - m_schemes_params = params; - m_cacheInitialized = false; - } - } - - /** @name L2 and H1 Norm Calculations */ - ///@{ - /** - * @brief Computes L2 and H1-semi norms of an expression in one pass. - * @param expr The symbolic expression to evaluate (e.g., a FEField). - * @param l2Out Pointer to store the L2 norm result: @f[ \sqrt{\int_{E} |u|^2 d\mathbf{x}} @f]. - * @param h1SemiOut Pointer to store the H1-semi norm result: @f[ \sqrt{\int_{E} |\nabla u|^2 d\mathbf{x}} @f]. - * @param entity (Optional) The specific mesh entity/domain to integrate over. - * @note If entity is not provided, integration is performed over the whole mesh volume. - */ - void L2Norm(const std::shared_ptr &expr, CfemReal* l2Out, CfemReal* h1SemiOut); - void L2Norm(const std::shared_ptr &expr, CfemReal* l2Out, CfemReal* h1SemiOut, const std::string& entity_name); - void L2Norm(const std::shared_ptr &expr, CfemReal* l2Out, CfemReal* h1SemiOut, CfemInt entity_id); - void L2Norm(const std::shared_ptr &expr, CfemReal* l2Out, CfemReal* h1SemiOut, const MeshEntity& entity); - ///@} - - /** @name General Integral Computations */ - ///@{ - /** - * @brief Computes the integral of a symbolic expression over a domain. - * @param expr The expression to integrate. - * @param entity (Optional) The domain (volume or boundary) defined by name, ID, or object. - * @return The scalar result of the integration. - */ - CfemReal computeIntegral(const std::shared_ptr &expr); - CfemReal computeIntegral(const std::shared_ptr &expr, const std::string& entity_name); - CfemReal computeIntegral(const std::shared_ptr &expr, CfemInt entity_id); - CfemReal computeIntegral(const std::shared_ptr &expr, const MeshEntity& entity); - ///@} - - CfemReal computeFlux(const std::shared_ptr &expr, const MeshEntity& boundary_entity, const MeshEntity& volume_entity); - CfemReal computeFlux(const std::shared_ptr &expr, const MeshEntity& boundary_entity); - CfemReal computeFlux(const std::shared_ptr &expr, const std::string& boundaryName); - CfemReal computeFlux(const std::shared_ptr &expr, const std::string& boundaryName, const std::string& volumeName); - CfemReal computeFlux(const std::shared_ptr &expr, CfemInt boundaryId); - CfemReal computeFlux(const std::shared_ptr &expr, CfemInt boundaryId, CfemInt volumeId); - }; -} -#endif \ No newline at end of file diff --git a/libs/prepostprocessing/numerical_integration/include/ScalarFunctionalEvaluator.h b/libs/prepostprocessing/numerical_integration/include/ScalarFunctionalEvaluator.h new file mode 100644 index 0000000..85accf5 --- /dev/null +++ b/libs/prepostprocessing/numerical_integration/include/ScalarFunctionalEvaluator.h @@ -0,0 +1,300 @@ +//************************************************************************ +// --- CFEM++ Library +// --- +// --- Copyright 2024-2026 Ismaël Tchinda Ngueyong et al. +// --- ALL RIGHTS RESERVED +// --- +// --- This software is protected by international copyright laws. +// --- Use, distribution, or modification of this software in any form, +// --- source or binary, for personal, academic, or non-commercial +// --- purposes is permitted free of charge, provided that this +// --- copyright notice and this permission notice appear in all +// --- copies and supporting documentation. +// --- +// --- The authors and contributors provide this code "as is" without +// --- any express or implied warranty. In no event shall they be +// --- held liable for any damages arising from the use of this software. +//************************************************************************ + +#pragma once + +#include "CfemExpression.h" +#include "Quadrature.h" +#include "cfem_topology.h" +#include "FESpace.h" +#include +#include +#include +#include + +namespace cfem +{ + class Mesh; + class MeshEntity; + + /** + * @class ScalarFunctionalEvaluator + * @brief Engine for high-performance numerical integration of FE expressions. + * + * This class handles the reduction of symbolic expressions into scalar values (integrals). + * It supports multi-target integration, allowing the calculation of several values + * (like L2 and H1 norms) in a single mesh sweep to maximize cache efficiency. + * + * Features: + * - Hybrid OpenMP parallelism for large-scale meshes. + * - Multi-level caching of shape functions and geometric mappings. + * - Integration over volumes (Cells) and boundaries (Facets/Physical Groups). + */ + class ScalarFunctionalEvaluator + { + private: + std::shared_ptr m_mesh; + IntegrationSchemeParams m_schemes_params; + bool m_cacheInitialized = false; + + using EvaluationTarget = std::pair, CfemReal*>; + + /** + * @brief Numerically integrates a set of scalar expressions over a mesh entity. + * + * @details + * This function evaluates one or more scalar expressions at each quadrature point + * of every cell belonging to the given mesh entity. It performs a highly optimized, + * SIMD-friendly numerical quadrature and stores the final weighted sums into the + * corresponding output pointers. + * + * The integration is performed in three phases: + * -# **Analysis & Binding** (O(1)): Inspects all targets to determine required + * shape evaluation flags, geometric dependencies, and FE spaces. Binds each + * expression to a compact space-index mapping. + * -# **Reference Cache Build**: For each cell type present in the entity, precomputes + * and caches reference shape functions, padded strides, and quadrature points. + * This cache is strictly read-only during the parallel phase. + * -# **Parallel Integration**: Iterates over all cells using OpenMP. For each cell, + * it computes the geometric mapping (Jacobian, determinant, physical derivatives), + * evaluates all expressions in batch (SoA format), and computes the reduction. + * + * @par Affine Cell Optimization + * For affine cells (linear geometry or explicitly flagged), the Jacobian and its + * derived quantities (determinant, inverse, physical derivatives) are computed only + * once per cell and hoisted out of the quadrature loop. + * + * @par Thread Safety & Parallelism + * Each OpenMP thread maintains its own local evaluation context and accumulator array. + * Results are safely reduced into the final output pointers inside a critical section + * at the end of the entity loop. + * + * @par Arena Memory Management + * Physical derivatives and expression intermediates are allocated via a zero-copy, + * O(1) per-thread Arena allocator (@ref EvalContext). A strict bookmark/release + * mechanism guarantees that temporary AST buffers are reclaimed after each target, + * while SIMD-aligned physical derivatives are preserved for the cell's lifetime. + * + * @param[in] targets + * A list of (expression, output pointer) pairs. Each expression must be scalar + * (i.e., @ref CfemExpression::getNumComponents() == 1). If the output pointer + * is non-null, its underlying value is **overwritten** (reset to zero before + * accumulation). Null output pointers are silently skipped. + * + * @param[in] entity + * The mesh entity over which to integrate (defines the subset of cells to visit + * via @ref MeshEntity::getCellIds()). + * + * @pre All expressions in @p targets must be compatible with the mesh's spatial + * dimension and geometric order. + * @pre Output pointers, if non-null, must remain valid for the duration of the call. + * + * @note If @p targets is empty or all expressions are null, the function returns + * immediately with near-zero overhead. + * + * @warning This function is **not reentrant**: it must not be called concurrently + * on the same @ref ScalarFunctionalEvaluator instance, as internal state + * (e.g., scheme parameters) is shared across threads. + * + * @see EvalContext + * @see MappingInfoBatchView + * @see ReferenceElement::computeMappingBatch + */ + void integrate(const std::vector &targets, const MeshEntity &entity); + + /** + * @brief Numerically integrates a set of scalar expressions over a boundary mesh entity. + * + * @details + * This function evaluates scalar expressions at the quadrature points of boundary facets. + * Boundary integration in Finite Elements requires a highly optimized **Hybrid Geometry Architecture**: + * the surface measure (dS) and physical normals are derived from the facet's geometry, while + * the inverse Jacobian required for physical derivatives (gradients) must be extracted from the + * parent volume cell. + * + * The integration is performed in three highly optimized phases: + * -# **Analysis & Binding** (O(1)): Inspects all targets to determine required shape flags + * (including geometric normal dependencies) and binds expressions to FE spaces. + * -# **Hybrid Cache Build**: For each facet type and its parent cell type, precomputes + * reference shape functions on the facet, and simultaneously projects these quadrature + * points into the parent reference space to cache volume shape functions. + * -# **Parallel Integration**: Iterates over all boundary facets using OpenMP. It dynamically + * constructs a zero-copy **Hybrid View** (@ref MappingInfoBatchView) combining the facet's + * determinant/normals with the parent's inverse Jacobian, evaluates the expressions in + * SIMD-friendly batches, and reduces the results. + * + * @par Affine Cell Optimization + * If the parent cell is affine, both the facet mapping and parent volume mapping (Jacobians) + * are hoisted out of the quadrature loop and computed only once per facet. + * + * @par Arena Memory Management & Hybrid Views + * Memory allocations for physical derivatives are handled by a zero-allocation, O(1) + * thread-local Arena (@ref EvalContext). The engine creates a Zero-Copy Hybrid View that + * perfectly isolates the math kernels from the geometric complexity of embedded manifolds. + * Temporary AST evaluation buffers are efficiently reclaimed via a bookmark/release mechanism. + * + * @par Thread Safety & Parallelism + * Each OpenMP thread utilizes its own local context and accumulator array. The final + * boundary integrals are safely reduced into the output pointers within a critical section. + * + * @param[in] targets + * A list of (expression, output pointer) pairs. Each expression must be scalar + * (i.e., @ref CfemExpression::getNumComponents() == 1). If the output pointer + * is non-null, its underlying value is **overwritten** (reset to zero before + * accumulation). Null output pointers are silently skipped. + * + * @param[in] boundary + * The mesh entity representing the surface/boundary over which to integrate. + * Must contain valid facets (@ref MeshEntity::getFacetIds()). + * + * @param[in] volume + * The mesh entity representing the ambient volume. This is required to identify + * the valid parent cell ("left" or "right" cell) connected to the boundary facet + * in order to compute spatial gradients. + * + * @pre All boundary facets must be strictly attached to at least one valid parent cell + * present in the @p volume entity. Unattached facets are silently ignored. + * @pre Output pointers, if non-null, must remain valid for the duration of the call. + * + * @warning This function is **not reentrant**: it must not be called concurrently + * on the same @ref ScalarFunctionalEvaluator instance. + * + * @see EvalContext + * @see MappingInfoBatchView + * @see ScalarFunctionalEvaluator::integrate + */ + void integrateBoundary(const std::vector &targets, + const MeshEntity &boundary, + const MeshEntity &volume); + + /** + * @brief Internal dispatcher that selects the appropriate integration strategy. + * @details Decides whether to use volume integration or boundary integration + * based on the topological dimension of the provided entity. + */ + void dispatchIntegration(const std::vector &targets, + const MeshEntity &entity, + const MeshEntity &); + + public: + /** + * @brief Constructs a ScalarFunctionalEvaluator for a specific mesh. + * @param mesh Shared pointer to the mesh to integrate over. + * @param params Optional parameters to define quadrature orders per element type. + */ + explicit ScalarFunctionalEvaluator(std::shared_ptr mesh, + const IntegrationSchemeParams& params = {}); + + /** + * @brief Updates the integration parameters (quadrature orders). + * @details Changing params automatically invalidates existing caches to ensure + * that subsequent integrations use the correct number of quadrature points. + */ + void setIntegrationSchemeParams(const IntegrationSchemeParams& params){ + if (m_schemes_params != params) { + m_schemes_params = params; + m_cacheInitialized = false; + } + } + + /** @name L2 and H1 Norm Calculations */ + ///@{ + /** + * @brief Computes L2 and H1-semi norms of an expression in one pass. + * @param expr The symbolic expression to evaluate (e.g., a FEField). + * @param l2Out Pointer to store the L2 norm result: @f[ \sqrt{\int_{E} |u|^2 d\mathbf{x}} @f]. + * @param h1SemiOut Pointer to store the H1-semi norm result: @f[ \sqrt{\int_{E} |\nabla u|^2 d\mathbf{x}} @f]. + * @param entity (Optional) The specific mesh entity/domain to integrate over. + * @note If entity is not provided, integration is performed over the whole mesh volume. + */ + void L2Norm(const std::shared_ptr &expr, CfemReal* l2Out, CfemReal* h1SemiOut); + void L2Norm(const std::shared_ptr &expr, CfemReal* l2Out, CfemReal* h1SemiOut, const std::string& entity_name); + void L2Norm(const std::shared_ptr &expr, CfemReal* l2Out, CfemReal* h1SemiOut, CfemInt entity_id); + void L2Norm(const std::shared_ptr &expr, CfemReal* l2Out, CfemReal* h1SemiOut, const MeshEntity& entity); + ///@} + + /** @name General Integral Computations */ + ///@{ + /** + * @brief Computes the integral of a symbolic expression over a domain. + * @param expr The expression to integrate. + * @param entity (Optional) The domain (volume or boundary) defined by name, ID, or object. + * @return The scalar result of the integration. + */ + CfemReal computeIntegral(const std::shared_ptr &expr); + CfemReal computeIntegral(const std::shared_ptr &expr, const std::string& entity_name); + CfemReal computeIntegral(const std::shared_ptr &expr, CfemInt entity_id); + CfemReal computeIntegral(const std::shared_ptr &expr, const MeshEntity& entity); + ///@} + + /** @name Flux and Boundary Integral Computations */ + ///@{ + /** + * @brief Computes the surface integral (flux) of an expression over a boundary. + * @details This function evaluates surface integrals, typically used for Neumann + * boundary conditions, traction forces, or flow rates. It internally utilizes the + * highly optimized @ref integrateBoundary engine. + * + * If the symbolic expression requires spatial gradients (e.g., calculating viscous + * stresses or heat flux \f$ \nabla u \cdot \mathbf{n} \f$), the parent @p volume_entity + * is strictly required to provide the correct inverse Jacobian for the chain rule. + * + * @param expr The symbolic expression to integrate over the surface. + * @param boundary_entity The boundary domain (facets) over which to integrate. + * @param volume_entity The ambient volume domain containing the parent cells of the boundary facets. + * @return The scalar result of the surface integral. + */ + CfemReal computeFlux(const std::shared_ptr &expr, const MeshEntity& boundary_entity, const MeshEntity& volume_entity); + + /** + * @brief Computes the surface integral over a boundary. + * @note This overload implicitly uses the entire mesh ("Universe" entity) as the parent volume. + * @param expr The symbolic expression to integrate. + * @param boundary_entity The boundary domain. + * @return The scalar result of the surface integral. + */ + CfemReal computeFlux(const std::shared_ptr &expr, const MeshEntity& boundary_entity); + + /** + * @brief Computes the surface integral using boundary and volume physical names. + * @overload + */ + CfemReal computeFlux(const std::shared_ptr &expr, const std::string& boundaryName, const std::string& volumeName); + + /** + * @brief Computes the surface integral using a boundary physical name. + * @note Implicitly uses the entire mesh ("Universe") as the parent volume. + * @overload + */ + CfemReal computeFlux(const std::shared_ptr &expr, const std::string& boundaryName); + + /** + * @brief Computes the surface integral using boundary and volume physical IDs. + * @overload + */ + CfemReal computeFlux(const std::shared_ptr &expr, CfemInt boundaryId, CfemInt volumeId); + + /** + * @brief Computes the surface integral using a boundary physical ID. + * @note Implicitly uses the entire mesh ("Universe") as the parent volume. + * @overload + */ + CfemReal computeFlux(const std::shared_ptr &expr, CfemInt boundaryId); + ///@} + }; +} \ No newline at end of file diff --git a/libs/prepostprocessing/numerical_integration/src/FunctionIntegrator.cpp b/libs/prepostprocessing/numerical_integration/src/ScalarFunctionalEvaluator.cpp similarity index 67% rename from libs/prepostprocessing/numerical_integration/src/FunctionIntegrator.cpp rename to libs/prepostprocessing/numerical_integration/src/ScalarFunctionalEvaluator.cpp index 9f182a4..0e5df14 100644 --- a/libs/prepostprocessing/numerical_integration/src/FunctionIntegrator.cpp +++ b/libs/prepostprocessing/numerical_integration/src/ScalarFunctionalEvaluator.cpp @@ -16,7 +16,7 @@ // --- held liable for any damages arising from the use of this software. //************************************************************************ -#include "FunctionIntegrator.h" +#include "ScalarFunctionalEvaluator.h" #include "ReferenceElementFactory.h" #include "LagrangeSpace.h" #include "MathOperators.h" @@ -36,16 +36,16 @@ namespace cfem { - FunctionIntegrator::FunctionIntegrator(std::shared_ptr mesh, + ScalarFunctionalEvaluator::ScalarFunctionalEvaluator(std::shared_ptr mesh, const IntegrationSchemeParams &schemes_params) : m_mesh(std::move(mesh)), m_schemes_params(schemes_params) { if (!m_mesh) - CFEM_THROW("FunctionIntegrator requires a valid mesh."); + CFEM_THROW("ScalarFunctionalEvaluator requires a valid mesh."); } - void FunctionIntegrator::L2Norm(const std::shared_ptr &expr, + void ScalarFunctionalEvaluator::L2Norm(const std::shared_ptr &expr, CfemReal *l2Out, CfemReal *h1SemiOut, const MeshEntity &mesh_entity) @@ -60,82 +60,82 @@ namespace cfem if (h1SemiOut) *h1SemiOut = std::sqrt(std::max(0.0, h1semi_sqr)); } - void FunctionIntegrator::L2Norm(const std::shared_ptr &expr, + void ScalarFunctionalEvaluator::L2Norm(const std::shared_ptr &expr, CfemReal *l2Out, CfemReal *h1SemiOut, const std::string &entity_name) { L2Norm(expr, l2Out, h1SemiOut, m_mesh->getEntityConst(entity_name)); } - void FunctionIntegrator::L2Norm(const std::shared_ptr &expr, + void ScalarFunctionalEvaluator::L2Norm(const std::shared_ptr &expr, CfemReal *l2Out, CfemReal *h1SemiOut, CfemInt entity_id) { L2Norm(expr, l2Out, h1SemiOut, m_mesh->getEntityConst(entity_id)); } - void FunctionIntegrator::L2Norm(const std::shared_ptr &expr, + void ScalarFunctionalEvaluator::L2Norm(const std::shared_ptr &expr, CfemReal *l2Out, CfemReal *h1SemiOut) { L2Norm(expr, l2Out, h1SemiOut, m_mesh->getEntityConst(UNIVERSE_ID)); } - CfemReal FunctionIntegrator::computeIntegral(const std::shared_ptr &expr, + CfemReal ScalarFunctionalEvaluator::computeIntegral(const std::shared_ptr &expr, const MeshEntity &entity) { CfemReal result = 0.0; dispatchIntegration({{expr, &result}}, entity, m_mesh->getEntityConst(UNIVERSE_ID)); return result; } - CfemReal FunctionIntegrator::computeIntegral(const std::shared_ptr &expr) { + CfemReal ScalarFunctionalEvaluator::computeIntegral(const std::shared_ptr &expr) { return computeIntegral(expr, m_mesh->getEntityConst(UNIVERSE_ID)); } - CfemReal FunctionIntegrator::computeIntegral(const std::shared_ptr &expr, + CfemReal ScalarFunctionalEvaluator::computeIntegral(const std::shared_ptr &expr, const std::string &entity_name) { return computeIntegral(expr, m_mesh->getEntityConst(entity_name)); } - CfemReal FunctionIntegrator::computeIntegral(const std::shared_ptr &expr, + CfemReal ScalarFunctionalEvaluator::computeIntegral(const std::shared_ptr &expr, CfemInt entity_id) { return computeIntegral(expr, m_mesh->getEntityConst(entity_id)); } // ********************************************************************************************** - CfemReal FunctionIntegrator::computeFlux(const std::shared_ptr& expr, + CfemReal ScalarFunctionalEvaluator::computeFlux(const std::shared_ptr& expr, CfemInt boundaryId) { return computeFlux(expr, m_mesh->getEntityConst(boundaryId), m_mesh->getEntityConst(UNIVERSE_ID)); } - CfemReal FunctionIntegrator::computeFlux(const std::shared_ptr& expr, + CfemReal ScalarFunctionalEvaluator::computeFlux(const std::shared_ptr& expr, const std::string& boundary_name) { return computeFlux(expr, m_mesh->getEntityConst(boundary_name), m_mesh->getEntityConst(UNIVERSE_ID)); } - CfemReal FunctionIntegrator::computeFlux(const std::shared_ptr& expr, + CfemReal ScalarFunctionalEvaluator::computeFlux(const std::shared_ptr& expr, const std::string& boundary_name, const std::string& volume_name) { return computeFlux(expr, m_mesh->getEntityConst(boundary_name), m_mesh->getEntityConst(volume_name)); } - CfemReal FunctionIntegrator::computeFlux(const std::shared_ptr& expr, + CfemReal ScalarFunctionalEvaluator::computeFlux(const std::shared_ptr& expr, CfemInt boundary_id, CfemInt volume_id) { return computeFlux(expr, m_mesh->getEntityConst(boundary_id), m_mesh->getEntityConst(volume_id)); } - CfemReal FunctionIntegrator::computeFlux(const std::shared_ptr& expr, + CfemReal ScalarFunctionalEvaluator::computeFlux(const std::shared_ptr& expr, const MeshEntity& boundary_entity) { return computeFlux(expr, boundary_entity, m_mesh->getEntityConst(UNIVERSE_ID)); } - CfemReal FunctionIntegrator::computeFlux(const std::shared_ptr& expr, + CfemReal ScalarFunctionalEvaluator::computeFlux(const std::shared_ptr& expr, const MeshEntity& boundary_entity, const MeshEntity& volume_entity) { @@ -183,7 +183,7 @@ namespace cfem return totalFlux; } - void FunctionIntegrator::dispatchIntegration(const std::vector &targets, + void ScalarFunctionalEvaluator::dispatchIntegration(const std::vector &targets, const MeshEntity &entity, const MeshEntity &volume) { @@ -200,7 +200,7 @@ namespace cfem else for (const auto &target : targets) if (target.second) *(target.second) = 0.0; } -void FunctionIntegrator::integrate(const std::vector &targets, + void ScalarFunctionalEvaluator::integrate(const std::vector &targets, const MeshEntity &entity) { if (targets.empty()) return; @@ -220,7 +220,7 @@ void FunctionIntegrator::integrate(const std::vector &targets, if (!targets[i].first) continue; if (targets[i].first->getNumComponents() != 1) { - CFEM_THROW("FunctionIntegrator only accepts scalar expressions. " + CFEM_THROW("ScalarFunctionalEvaluator only accepts scalar expressions. " << "Please pass each component separately."); } @@ -357,7 +357,7 @@ void FunctionIntegrator::integrate(const std::vector &targets, CfemInt nQp = cache->xiPoints.size(); // Seul le Owner géométrique nécessite un redimensionnement (setup) - mapGeoBatch.setup(nQp, refElGeo->getNumNodes(), refElGeo->dimension(), false); + mapGeoBatch.setup(nQp, refElGeo->getNumNodes(), refElGeo->getDimension(), false); if (batchOutVals.size() < static_cast(nQp)) { batchOutVals.resize(nQp); @@ -458,22 +458,28 @@ void FunctionIntegrator::integrate(const std::vector &targets, } } - void FunctionIntegrator::integrateBoundary(const std::vector &targets, - const MeshEntity &boundary, - const MeshEntity &volume) + void ScalarFunctionalEvaluator::integrateBoundary(const std::vector &targets, + const MeshEntity &boundary, + const MeshEntity &volume) { if (targets.empty() || !boundary.containsFacets() || !volume.containsCells()) return; + // ============================================================ + // BINDING + // ============================================================ std::vector activeIdx; ShapeEvaluationFlags fieldShapesFlags = ShapeEvaluationFlags::None; std::set> requiredSpaces; bool fieldsDependsOnGeoNormal = false; + + CfemInt geoOrder = m_mesh->getOrderInt(); + CfemInt spatialDim = m_mesh->getSpatialDimension(); for (size_t i = 0; i < targets.size(); ++i) { if (!targets[i].first) continue; if (targets[i].first->getNumComponents() != 1) { - CFEM_THROW( "FunctionIntegrator only accepts scalar expressions. " + CFEM_THROW( "ScalarFunctionalEvaluator only accepts scalar expressions. " << "Please pass each component separately."); } @@ -500,38 +506,43 @@ void FunctionIntegrator::integrate(const std::vector &targets, targets[idx].first->bind(spaceToId); } - // const bool needsFieldFirstDerivatives = hasFlag(fieldShapesFlags, ShapeEvaluationFlags::Gradient); - // const ShapeEvaluationFlags geoFlags = ShapeEvaluationFlags::Value | ShapeEvaluationFlags::Jacobian; - // const ShapeEvaluationFlags normalEvalFlag = hasFlag(fieldShapesFlags, ShapeEvaluationFlags::Normal)? ShapeEvaluationFlags::Normal : ShapeEvaluationFlags::None; - const bool needsFieldFirstDerivatives = hasFlag(fieldShapesFlags, ShapeEvaluationFlags::FirstDerivatives); const bool needsFieldSecondDerivatives = hasFlag(fieldShapesFlags, ShapeEvaluationFlags::SecondDerivatives); + // Facet flags (for dS, Normal et Position) --- const MappingEvaluationFlags facetGeoMappingFlags = MappingEvaluationFlags::PhysicalPosition | MappingEvaluationFlags::Determinant | - (fieldsDependsOnGeoNormal? MappingEvaluationFlags::Normal : MappingEvaluationFlags::None); - const ShapeEvaluationFlags facetGeoShapeFlags = fieldShapesFlags | transformMappingToShapeFlags(facetGeoMappingFlags); + (fieldsDependsOnGeoNormal ? MappingEvaluationFlags::Normal : MappingEvaluationFlags::None); + const ShapeEvaluationFlags facetGeoShapeFlags = transformMappingToShapeFlags(facetGeoMappingFlags); + + // Parent flags (Parent geo mapping inverse jacobian for derivatives if required) const ShapeEvaluationFlags parentGeoShapeFlags = fieldShapesFlags; const MappingEvaluationFlags parentGeoMappingFlags = transformShapeToMappingFlags(fieldShapesFlags); IntegrationScheme scheme; - struct BoundarySpaceCache { + // ============================================================ + // HIBRID BATCH CACHING (Face + Volume) + // ============================================================ + struct BoundarySpaceCacheBatch { CfemInt contextIndex; - std::vector shapes; + ShapeInfoBatch shapesBatch; // Shapes evaluated at the facet nodes projected on the parent cell + CfemInt paddedNodeStride; // This will serve in the MappingInfoBatchView for HPC flat data storage }; - struct BoundaryCacheEntry { + struct BoundaryCacheEntryBatch { const QuadratureRule* quad = nullptr; - std::vector geoShapesParent; - std::vector geoShapesFacet; - std::vector spaceCaches; + DynamicVector xiFacetPoints; // Reference facet coords + DynamicVector xiParentPoints; // Projected corrds on the parent cell + + ShapeInfoBatch geoShapesParentBatch; + ShapeInfoBatch geoShapesFacetBatch; + std::vector spaceCaches; }; - std::vector> parentFaceCache(static_cast(CellType::COUNT)); - - CfemInt geoOrder = m_mesh->getOrderInt(); + // Indexed by: [CellType][LocalFacetId] + std::vector> parentFaceCache(static_cast(CellType::COUNT)); for (CellType cType : m_mesh->getCellTypesPresent()) { @@ -550,51 +561,71 @@ void FunctionIntegrator::integrate(const std::vector &targets, auto& entry = parentFaceCache[cIdx][f_local]; entry.quad = &quad; - entry.geoShapesParent.resize(lQuadPts.size()); - entry.geoShapesFacet.resize(lQuadPts.size()); - for (const auto& space : idToSpace) { - entry.spaceCaches.push_back({spaceToId[space.get()], std::vector(lQuadPts.size())}); + CfemInt nQp = static_cast(lQuadPts.size()); + entry.xiFacetPoints.resize(nQp); + entry.xiParentPoints.resize(nQp); + + // Quadrature points batch pre-projection + for (CfemInt q = 0; q < nQp; ++q) { + entry.xiFacetPoints[q] = lQuadPts[q].xi; + entry.xiParentPoints[q] = refElParentGeo.projectFacetRefCoordToCellRefCoord(f_local, lQuadPts[q].xi); } - for (size_t q = 0; q < lQuadPts.size(); ++q) - { - Vector3D xi_parent = refElParentGeo.projectFacetRefCoordToCellRefCoord(f_local, lQuadPts[q].xi); + // Batch shapes evaluations + refElFacetGeo.evaluateShapesBatch(entry.xiFacetPoints, facetGeoShapeFlags, entry.geoShapesFacetBatch); + entry.geoShapesFacetBatch.shapeMustBeUpdated = false; + + // Batch shape evaluation on the parent cell using the projected reference facet coords + refElParentGeo.evaluateShapesBatch(entry.xiParentPoints, parentGeoShapeFlags, entry.geoShapesParentBatch); + entry.geoShapesParentBatch.shapeMustBeUpdated = false; - //refElFacetGeo.evaluate(lQuadPts[q].xi, geoFlags, entry.geoShapesFacet[q]); - refElFacetGeo.evaluateShapes(lQuadPts[q].xi, facetGeoShapeFlags, entry.geoShapesFacet[q]); - entry.geoShapesFacet[q].shapeMustBeUpdated = false; + // Caching des espaces métiers + for (size_t sIdx = 0; sIdx < idToSpace.size(); ++sIdx) { + BoundarySpaceCacheBatch spCache; + spCache.contextIndex = spaceToId[idToSpace[sIdx].get()]; - refElParentGeo.evaluateShapes(xi_parent, parentGeoShapeFlags, entry.geoShapesParent[q]); - entry.geoShapesParent[q].shapeMustBeUpdated = false; - - for (size_t sIdx = 0; sIdx < idToSpace.size(); ++sIdx) { - CfemInt spaceOrder = idToSpace[sIdx]->getOrderInt(); - - const auto& refElSpace = ReferenceElementFactory::getReferenceElement(cType, spaceOrder); - refElSpace.evaluateShapes(xi_parent, fieldShapesFlags, entry.spaceCaches[sIdx].shapes[q]); - entry.spaceCaches[sIdx].shapes[q].shapeMustBeUpdated = false; - } + CfemInt spaceOrder = idToSpace[sIdx]->getOrderInt(); + const auto& refElSpace = ReferenceElementFactory::getReferenceElement(cType, spaceOrder); + + // Fields/expressions are evaluated at the PROJECTED coordinates (xiParentPoints) + refElSpace.evaluateShapesBatch(entry.xiParentPoints, fieldShapesFlags, spCache.shapesBatch); + spCache.shapesBatch.shapeMustBeUpdated = false; + + spCache.paddedNodeStride = kernel_utils::computePaddedStride(spCache.shapesBatch.numNodes); + + entry.spaceCaches.push_back(std::move(spCache)); } } } const std::vector &facet_ids = boundary.getFacetIds(); + // ============================================================ + // INTEGRATION LOOP USING THE BATCH ARCHITECTURE + // ============================================================ #pragma omp parallel { std::vector threadTotals(targets.size(), 0.0); EvalContext ctx; - CfemReal outVal[9] = {0.0}; - std::span spanOut(outVal, 1); + std::vector batchOutVals; - MappingInfo mapSurf; - MappingInfo mapParentGeo; - std::vector spaceMappings(idToSpace.size()); + // Two geometrics owners :one for the surface, another for the volume + MappingInfoBatch mapFacetGeoBatch; + MappingInfoBatch mapParentGeoBatch; + + MappingInfoBatchView geoFacetView; + std::vector spaceMappingsViews(idToSpace.size()); DynamicVector facetNodes; DynamicVector parentNodes; - ShapeInfo facetShape; + + CellType lastParentType = CellType::COUNT; + CfemInt lastLocalFacet = -1; + + const ReferenceElement* refElFacetGeo = nullptr; + const ReferenceElement* refElParentGeo = nullptr; + const BoundaryCacheEntryBatch* cache = nullptr; #pragma omp for schedule(static) for (CfemInt i = 0; i < boundary.getNumFacets(); ++i) @@ -615,105 +646,108 @@ void FunctionIntegrator::integrate(const std::vector &targets, if (parentId == -1) continue; + const auto &parentCell = m_mesh->getCell(parentId); const bool isAffine = (geoOrder == 1) || m_mesh->isCellAffine(parentId); - const auto &parentCell = m_mesh->getCell(parentId); - const auto& cache = parentFaceCache[static_cast(parentCell.type)][localFacetId]; - const auto &lQuadPts = cache.quad->getPoints(); + // Cache and allocationb management + if (parentCell.type != lastParentType || localFacetId != lastLocalFacet) { + lastParentType = parentCell.type; + lastLocalFacet = localFacetId; + + refElFacetGeo = &ReferenceElementFactory::getReferenceElement(facet.type, geoOrder); + refElParentGeo = &ReferenceElementFactory::getReferenceElement(parentCell.type, geoOrder); + cache = &parentFaceCache[static_cast(lastParentType)][lastLocalFacet]; + + CfemInt nQp = cache->xiFacetPoints.size(); + + mapFacetGeoBatch.setup(nQp, refElFacetGeo->getNumNodes(), refElFacetGeo->getDimension(), false); + mapParentGeoBatch.setup(nQp, refElParentGeo->getNumNodes(), refElParentGeo->getDimension(), false); + + if (batchOutVals.size() < static_cast(nQp)) { + batchOutVals.resize(nQp); + } + } m_mesh->getFacetVerticesCoords(fid, facetNodes); m_mesh->getCellVerticesCoords(parentId, parentNodes); - mapSurf.setup(static_cast(facetNodes.size())); - mapParentGeo.setup(static_cast(parentNodes.size())); - - for (size_t sIdx = 0; sIdx < idToSpace.size(); ++sIdx) { - spaceMappings[sIdx].setup(static_cast(cache.spaceCaches[sIdx].shapes[0].numNodes)); - } + // ------------------------------------------------------------- + // GEOMETRY BATCH EVALUATION (Surface et Volume) + // ------------------------------------------------------------- + mapFacetGeoBatch.isCellAffine = isAffine; + mapParentGeoBatch.isCellAffine = isAffine; - const auto &refElFacet = ReferenceElementFactory::getReferenceElement(facet.type, geoOrder); - const auto &refElParentGeo = ReferenceElementFactory::getReferenceElement(parentCell.type, geoOrder); + refElFacetGeo->computeMappingBatch(cache->xiFacetPoints, facetNodes, facetGeoMappingFlags, + cache->geoShapesFacetBatch, mapFacetGeoBatch); - if (isAffine) { - refElParentGeo.computeMapping( - lQuadPts[0].xi, - parentNodes, - parentGeoMappingFlags, - cache.geoShapesParent[0], - mapParentGeo - ); - refElFacet.computeMapping( - lQuadPts[0].xi, - facetNodes, - facetGeoMappingFlags, - cache.geoShapesFacet[0], - mapSurf - ); + if (needsFieldFirstDerivatives || needsFieldSecondDerivatives) { + refElParentGeo->computeMappingBatch(cache->xiParentPoints, parentNodes, parentGeoMappingFlags, + cache->geoShapesParentBatch, mapParentGeoBatch); } - for (size_t q = 0; q < lQuadPts.size(); ++q) - { - const auto &qp = lQuadPts[q]; - - Vector3D xi_parent = refElParentGeo.projectFacetRefCoordToCellRefCoord(localFacetId, qp.xi); + geoFacetView = mapFacetGeoBatch.createView(); - if (isAffine) { - // On ne calcule QUE la position physique, detJ et invJ sont déjà dans mapSurf - refElFacet.computeMapping( - qp.xi, - facetNodes, - MappingEvaluationFlags::PhysicalPosition, - cache.geoShapesFacet[q], - mapSurf - ); - } else { - if (needsFieldFirstDerivatives || needsFieldSecondDerivatives) { - refElParentGeo.computeMapping( - xi_parent, - parentNodes, - parentGeoMappingFlags, - cache.geoShapesParent[q], - mapParentGeo - ); - } - // pour une face courbee, on doit recalculer le jacobien de la transformation - refElFacet.computeMapping( - qp.xi, - facetNodes, - facetGeoMappingFlags, - cache.geoShapesFacet[q], - mapSurf - ); - } + // ------------------------------------------------------------- + // HYBRID VIEW ON THE MAPPING AND ARENA + // ------------------------------------------------------------- + ctx.resetBuffers(); + CfemInt nQp = cache->xiFacetPoints.size(); - const CfemReal dS = mapSurf.detJ * qp.weight; + for (size_t sIdx = 0; sIdx < cache->spaceCaches.size(); ++sIdx) { + const auto& spCache = cache->spaceCaches[sIdx]; + + // Lightweight view + spaceMappingsViews[sIdx] = geoFacetView; - ctx.resetBuffers(); - ctx.currentCellId = parentId; - ctx.singleWS.xi = xi_parent; - ctx.singleWS.physPos = mapSurf.physicalPosition; - ctx.geometricMapping = &mapSurf; - - for (size_t sIdx = 0; sIdx < cache.spaceCaches.size(); ++sIdx) { - const auto& spCache = cache.spaceCaches[sIdx]; - MappingInfo& mapSpace = spaceMappings[sIdx]; - - if (needsFieldFirstDerivatives) { - mapSpace.detJ = mapParentGeo.detJ; - mapSpace.invJacobian = mapParentGeo.invJacobian; - mapSpace.physicalPosition = mapSurf.physicalPosition; - mapSpace.validFlags |= MappingEvaluationFlags::PhysicalPosition | MappingEvaluationFlags::InverseJacobian; - - refElParentGeo.computePhysicalFirstDerivatives(spCache.shapes[q], mapSpace); - } + if (needsFieldFirstDerivatives) { + // Here we need the parent inverse mapping jacobian + spaceMappingsViews[sIdx].invJacobians = mapParentGeoBatch.invJacobians; + spaceMappingsViews[sIdx].validFlags |= MappingEvaluationFlags::InverseJacobian; + + CfemInt paddedNodes = spCache.paddedNodeStride; + std::span arenaStorage = ctx.getBuffer(spatialDim * nQp * paddedNodes); + + spaceMappingsViews[sIdx].setupPhysicalDerivativesMemory(arenaStorage, spCache.shapesBatch.numNodes, spatialDim, nQp); - ctx.setSpaceData(spCache.contextIndex, &spCache.shapes[q], &mapSpace); + // CRITICAL: Derivatives are computed using the parent geo mapping + refElParentGeo->computePhysicalFirstDerivativesBatch(spCache.shapesBatch, spaceMappingsViews[sIdx]); } + } - for (size_t idx : activeIdx) { - targets[idx].first->evaluate(parentId, xi_parent, spanOut, ctx); // Exactement comme l'archive (parentId) - threadTotals[idx] += spanOut[0] * dS; + // ------------------------------------------------------------- + // BINDING IN THE CONTEXT + // ------------------------------------------------------------- + ctx.currentCellId = parentId; + ctx.geometricMappingBatch = &geoFacetView; + ctx.batchWS.physPos = &mapFacetGeoBatch.physicalPositions; + + for (size_t sIdx = 0; sIdx < cache->spaceCaches.size(); ++sIdx) { + ctx.setSpaceDataBatch( + cache->spaceCaches[sIdx].contextIndex, + &cache->spaceCaches[sIdx].shapesBatch, + &spaceMappingsViews[sIdx] + ); + } + + // ------------------------------------------------------------- + // FIELD EVALUATION ON THE BOUNDARY + // ------------------------------------------------------------- + const auto &lQuadPts = cache->quad->getPoints(); + std::span spanOutBatch(batchOutVals.data(), nQp); + + for (size_t idx : activeIdx) { + auto bookmark = ctx.getBookmark(); + + targets[idx].first->evaluateBatch(parentId, nQp, spanOutBatch, ctx); + + CfemReal localSum = 0.0; + for (CfemInt q = 0; q < nQp; ++q) { + // Le mapFacetGeoBatch.detJs[q] contient bien le 'dS' surfacique. + localSum += spanOutBatch[q] * mapFacetGeoBatch.detJs[q] * lQuadPts[q].weight; } + threadTotals[idx] += localSum; + + ctx.releaseToBookmark(bookmark); } } diff --git a/tests/fem/test_domain_integrator.cpp b/tests/fem/test_domain_integrator.cpp index 3c9f01b..720e60e 100644 --- a/tests/fem/test_domain_integrator.cpp +++ b/tests/fem/test_domain_integrator.cpp @@ -19,7 +19,7 @@ #include #include "cfemutils.h" #include "Mesh.h" -#include "FunctionIntegrator.h" +#include "ScalarFunctionalEvaluator.h" #include "MathOperators.h" #include "FEField.h" #include "LagrangeSpace.h" @@ -29,7 +29,7 @@ using namespace cfem::sym; using namespace cfem::sym::symengine; // Fixture pour initialiser l'environnement et créer un maillage commun -class FunctionIntegratorTest : public ::testing::Test { +class ScalarFunctionalEvaluatorTest : public ::testing::Test { protected: void SetUp() override { // On crée un cube [-1, 1]^3 (Volume = 8.0, Surface d'une face = 4.0) @@ -41,16 +41,16 @@ class FunctionIntegratorTest : public ::testing::Test { ); IntegrationSchemeParams params{}; - integrator = std::make_unique(mesh, params); + integrator = std::make_unique(mesh, params); } std::shared_ptr mesh; - std::unique_ptr integrator; + std::unique_ptr integrator; const CfemReal TOL = 1e-12; }; // 1. Validation de l'intégration de volume (Fonction constante) -TEST_F(FunctionIntegratorTest, VolumeIntegralConstant) { +TEST_F(ScalarFunctionalEvaluatorTest, VolumeIntegralConstant) { auto one = scalar(1.0); CfemReal volume = integrator->computeIntegral(one); // Cube de côté 2 -> Volume = 2^3 = 8 @@ -58,7 +58,7 @@ TEST_F(FunctionIntegratorTest, VolumeIntegralConstant) { } // 2. Validation de l'intégration de surface (Fonction constante sur frontière) -TEST_F(FunctionIntegratorTest, SurfaceIntegralConstant) { +TEST_F(ScalarFunctionalEvaluatorTest, SurfaceIntegralConstant) { auto one = scalar(1.0); // Face "xmin" est à x = -1, de y=-1..1 et z=-1..1 -> Aire = 4.0 CfemReal area = integrator->computeIntegral(one, "xmin"); @@ -66,7 +66,7 @@ TEST_F(FunctionIntegratorTest, SurfaceIntegralConstant) { } // 3. Validation de l'interpolation et de la norme L2 (Cas Polynomial P2) -TEST_F(FunctionIntegratorTest, L2NormP2Projection) { +TEST_F(ScalarFunctionalEvaluatorTest, L2NormP2Projection) { auto Q = LagrangeSpace::create(mesh, Order::P2, FieldType::Scalar); auto p = Trial(Q, "p"); @@ -84,7 +84,7 @@ TEST_F(FunctionIntegratorTest, L2NormP2Projection) { } // // 4. Test d'un champ vectoriel (Divergence / Gradient) -// TEST_F(FunctionIntegratorTest, VectorFieldDivergence) { +// TEST_F(ScalarFunctionalEvaluatorTest, VectorFieldDivergence) { // auto V = LagrangeSpace::create(mesh, Order::P2, FieldType::Vector3D); // auto u = Trial(V, "u"); @@ -102,7 +102,7 @@ TEST_F(FunctionIntegratorTest, L2NormP2Projection) { // } // 5. Test de symétrie / Coordonnées -TEST_F(FunctionIntegratorTest, IntegralLinearFunction) { +TEST_F(ScalarFunctionalEvaluatorTest, IntegralLinearFunction) { // L'intégrale de 'x' sur [-1, 1] est 0 par symétrie auto expr_x = scalar(x); CfemReal val = integrator->computeIntegral(expr_x); @@ -114,7 +114,7 @@ TEST_F(FunctionIntegratorTest, IntegralLinearFunction) { } // 6. Test de l'erreur sur une face spécifique (Gradient normal) -TEST_F(FunctionIntegratorTest, SurfaceGradientError) { +TEST_F(ScalarFunctionalEvaluatorTest, SurfaceGradientError) { auto Q = LagrangeSpace::create(mesh, Order::P2, FieldType::Scalar); auto p = Trial(Q, "p2"); @@ -159,7 +159,7 @@ TEST_F(SurfaceManifoldTest, ComputeFluxOnSphere) { u->interpolate(analytical_u); IntegrationSchemeParams params{}; - FunctionIntegrator integrator(mesh, params); + ScalarFunctionalEvaluator integrator(mesh, params); CfemReal computedFlux = integrator.computeFlux(u, UNIVERSE_ID); @@ -183,7 +183,7 @@ TEST_F(SurfaceManifoldTest, SurfaceGradientError) { p->interpolate(analytical_p); IntegrationSchemeParams params{}; - FunctionIntegrator integrator(mesh, params); + ScalarFunctionalEvaluator integrator(mesh, params); CfemReal error_grad_s = sqrt(integrator.computeIntegral(mag_sqr(grad_s(p) - grad_s(analytical_p)))); EXPECT_LT(error_grad_s, 1e-4); diff --git a/tests/fem/test_fefield.cpp b/tests/fem/test_fefield.cpp index befc1f5..0c09d48 100644 --- a/tests/fem/test_fefield.cpp +++ b/tests/fem/test_fefield.cpp @@ -22,7 +22,7 @@ #include "Mesh.h" #include "FEField.h" #include "MathOperators.h" -#include "FunctionIntegrator.h" +#include "ScalarFunctionalEvaluator.h" #include "LagrangeSpace.h" #include "DGSpace.h" @@ -162,7 +162,7 @@ TEST_F(FEFieldTest, CG_Scalar_P1_Exactness) f->interpolate(analytical); CfemReal l2, h1; - FunctionIntegrator(m_mesh).L2Norm(f - analytical, &l2, &h1); + ScalarFunctionalEvaluator(m_mesh).L2Norm(f - analytical, &l2, &h1); EXPECT_NEAR(l2, 0.0, 1e-12); EXPECT_NEAR(h1, 0.0, 1e-11); } @@ -174,7 +174,7 @@ TEST_F(FEFieldTest, CG_Vector_P1_Exactness) velocity->interpolate(analytical); CfemReal l2Norm, h1SemiNorm; - FunctionIntegrator integrator(m_mesh, IntegrationSchemeParams{}); + ScalarFunctionalEvaluator integrator(m_mesh, IntegrationSchemeParams{}); integrator.L2Norm(velocity - analytical, &l2Norm, &h1SemiNorm); EXPECT_NEAR(l2Norm, 0.0, 1e-12) << "P1 GC Vector L2 interpolation failed."; @@ -188,7 +188,7 @@ TEST_F(FEFieldTest, CG_Scalar_P2_Exactness) f->interpolate(analytical); CfemReal l2, h1; - FunctionIntegrator(m_mesh).L2Norm(f - analytical, &l2, &h1); + ScalarFunctionalEvaluator(m_mesh).L2Norm(f - analytical, &l2, &h1); EXPECT_NEAR(l2, 0.0, 1e-12); EXPECT_NEAR(h1, 0.0, 1e-11); } @@ -203,7 +203,7 @@ TEST_F(FEFieldTest, CG_Vector_P2_Lambda_OpenMP) auto analytical = vector(x*x, y*x, x*z); CfemReal l2; - FunctionIntegrator(m_mesh).L2Norm(u - analytical, &l2, nullptr); + ScalarFunctionalEvaluator(m_mesh).L2Norm(u - analytical, &l2, nullptr); EXPECT_NEAR(l2, 0.0, 1e-11); EXPECT_NEAR(l2, 0.0, 1e-11); } @@ -215,7 +215,7 @@ TEST_F(FEFieldTest, DG_Scalar_P0_Constant) f->interpolate(analytical); CfemReal l2; - FunctionIntegrator(m_mesh).L2Norm(f - analytical, &l2, nullptr); + ScalarFunctionalEvaluator(m_mesh).L2Norm(f - analytical, &l2, nullptr); EXPECT_NEAR(l2, 0.0, 1e-12); } @@ -226,7 +226,7 @@ TEST_F(FEFieldTest, DG_Vector_P0_Constant) u->interpolate(analytical); CfemReal l2; - FunctionIntegrator(m_mesh).L2Norm(u - analytical, &l2, nullptr); + ScalarFunctionalEvaluator(m_mesh).L2Norm(u - analytical, &l2, nullptr); EXPECT_NEAR(l2, 0.0, 1e-12); } @@ -237,7 +237,7 @@ TEST_F(FEFieldTest, DG_Scalar_P1_Exactness) f->interpolate(analytical); CfemReal l2, h1; - FunctionIntegrator(m_mesh).L2Norm(f - analytical, &l2, &h1); + ScalarFunctionalEvaluator(m_mesh).L2Norm(f - analytical, &l2, &h1); EXPECT_NEAR(l2, 0.0, 1e-12); EXPECT_NEAR(h1, 0.0, 1e-11); } @@ -251,7 +251,7 @@ TEST_F(FEFieldTest, DG_Vector_P1_Lambda) auto analytical = vector(x + 1.0, y - 2.0, z); CfemReal l2; - FunctionIntegrator(m_mesh).L2Norm(u - analytical, &l2, nullptr); + ScalarFunctionalEvaluator(m_mesh).L2Norm(u - analytical, &l2, nullptr); EXPECT_NEAR(l2, 0.0, 1e-12); } @@ -262,7 +262,7 @@ TEST_F(FEFieldTest, DG_Scalar_P2_Exactness) f->interpolate(analytical); CfemReal l2, h1; - FunctionIntegrator(m_mesh).L2Norm(f - analytical, &l2, &h1); + ScalarFunctionalEvaluator(m_mesh).L2Norm(f - analytical, &l2, &h1); EXPECT_NEAR(l2, 0.0, 1e-12); } @@ -273,7 +273,7 @@ TEST_F(FEFieldTest, DG_Vector_P2_Exactness) u->interpolate(analytical); CfemReal l2, h1; - FunctionIntegrator(m_mesh).L2Norm(u - analytical, &l2, &h1); + ScalarFunctionalEvaluator(m_mesh).L2Norm(u - analytical, &l2, &h1); EXPECT_NEAR(l2, 0.0, 1e-12); } @@ -290,7 +290,7 @@ TEST_F(FEFieldTest, GradientEvaluation_P2_CG) f->interpolate(analytical); // L'intégrateur H1 semi-norm calcule ||grad(f) - grad(analytique)|| - CfemReal h1 = sqrt( FunctionIntegrator(m_mesh).computeIntegral(mag_sqr( grad(f - analytical) ) ) ); + CfemReal h1 = sqrt( ScalarFunctionalEvaluator(m_mesh).computeIntegral(mag_sqr( grad(f - analytical) ) ) ); EXPECT_NEAR(h1, 0.0, 1e-11) << "Gradient evaluation failed for P2 CG field."; } @@ -330,7 +330,7 @@ TEST_F(FEFieldTest, DGScalarP0Projection) density->interpolate(analytical); CfemReal l2Norm = 1.0; - FunctionIntegrator integrator(m_mesh, IntegrationSchemeParams{}); + ScalarFunctionalEvaluator integrator(m_mesh, IntegrationSchemeParams{}); integrator.L2Norm(density - analytical, &l2Norm, nullptr); EXPECT_NEAR(l2Norm, 0.0, 1e-12) << "P0 DG interpolation L2-error is too high. Check cell-center mapping."; diff --git a/tests/fem/test_quadrature.cpp b/tests/fem/test_quadrature.cpp index 4e8176a..973a3a1 100644 --- a/tests/fem/test_quadrature.cpp +++ b/tests/fem/test_quadrature.cpp @@ -282,7 +282,7 @@ TEST_F(MappingAccuracyTest, SlantedGradient3D) { DynamicVector nodalValues = {0.0, std::sqrt(2.0), 0.0}; ref.computeMapping( - ref.referenceNodes()[1], + ref.getReferenceNodes()[1], nodes, ShapeEvaluationFlags::FirstDerivatives, MappingEvaluationFlags::PhysicalFirstDerivatives, diff --git a/tests/fem/test_reference_element.cpp b/tests/fem/test_reference_element.cpp index ae0b9aa..207232b 100644 --- a/tests/fem/test_reference_element.cpp +++ b/tests/fem/test_reference_element.cpp @@ -91,10 +91,10 @@ class MappingTest : public ::testing::Test { }; -// --- 1. TEST DE KRONECKER : N_i(node_j) = delta_ij --- +// TEST DE KRONECKER : N_i(node_j) = delta_ij --- TEST_P(ReferenceElementTest, KroneckerDeltaProperty) { ReferenceElement* el = GetParam(); - const auto& nodes = el->referenceNodes(); + const auto& nodes = el->getReferenceNodes(); CfemInt n = el->getNumNodes(); for (CfemInt j = 0; j < n; ++j) { @@ -110,7 +110,7 @@ TEST_P(ReferenceElementTest, KroneckerDeltaProperty) { TEST_P(ReferenceElementTest, PartitionOfUnity) { ReferenceElement* el = GetParam(); - CfemInt dim = el->dimension(); + CfemInt dim = el->getDimension(); DynamicVector test_points = getTestPointsForDim(dim); @@ -129,10 +129,10 @@ TEST_P(ReferenceElementTest, PartitionOfUnity) { } } -// --- 3. TEST DE LA SOMME DES GRADIENTS : Sum(Grad N_i) = 0 --- +// TEST DE LA SOMME DES GRADIENTS : Sum(Grad N_i) = 0 --- TEST_P(ReferenceElementTest, GradientSumIsZero) { ReferenceElement* el = GetParam(); - CfemInt dim {el->dimension()}; + CfemInt dim {el->getDimension()}; DynamicVector test_points = getTestPointsForDim(dim); @@ -157,7 +157,7 @@ TEST_P(ReferenceElementTest, HessianFullConsistency) { ReferenceElement* el = GetParam(); const CfemInt n = el->getNumNodes(); - const CfemInt dim = el->dimension(); + const CfemInt dim = el->getDimension(); // Réutilisation de tes test_points robustes DynamicVector test_points = getTestPointsForDim(dim); @@ -198,7 +198,7 @@ TEST_P(ReferenceElementTest, HessianFullConsistency) { } -// --- 1. TEST : ÉLÉMENT 2D DANS L'ESPACE 3D --- +// TEST : ÉLÉMENT 2D DANS L'ESPACE 3D --- TEST_F(MappingTest, QuadPlanarMapping) { Q1ReferenceQuad quad; auto coords = createUnitQuad(); @@ -230,7 +230,7 @@ TEST_F(MappingTest, QuadPlanarMapping) { } } -// --- 2. TEST : DISTORSION AFFINE (Cisaillement) --- +// TEST : DISTORSION AFFINE (Cisaillement) --- // On teste si J^-T transforme correctement les angles TEST_F(MappingTest, Hex8ShearMapping) { Q1ReferenceHexahedron hex; @@ -320,41 +320,7 @@ TEST_F(MappingTest, P2TriangleCurvedHessians) { } } - - -// TEST_F(MappingTest, DegenerateAndInvertedTRIElement) { -// #ifndef NDEBUG -// P1ReferenceTriangle tri; -// DynamicVector coords(3); - -// // CCW Triangle (Valid Orientation) -// coords[0] = {0, 0, 0}; -// coords[1] = {1, 0, 0}; -// coords[2] = {1, 1, 0}; - -// MappingInfo map; -// ShapeInfo shape; -// Vector3D xi{1.0/3.0, 1.0/3.0, 0}; // Centroid - -// // IMPORTANT: Evaluate the shape functions first! -// tri.evaluate(xi, ShapeEvaluationFlags::Gradient, shape); - -// // 1. Check Initial State: Should be Valid (1) -// EXPECT_EQ(tri.checkInversion(shape, coords), 1); - -// // 2. Degenerate State: Collapse nodes 1 and 2 -// auto backup = coords[2]; -// coords[2] = coords[1]; -// EXPECT_EQ(tri.checkInversion(shape, coords), 0); -// coords[2] = backup; - -// // 3. Inverted State: Swap 0 and 1 to make it Clockwise -// std::swap(coords[0], coords[1]); -// EXPECT_EQ(tri.checkInversion(shape, coords), -1); -// #endif -// } - -// --- 5. TEST : PHYSICAL GRADIENT SUM = 0 (consistency check in physical space) --- +// TEST : PHYSICAL GRADIENT SUM = 0 (consistency check in physical space) --- TEST_F(MappingTest, PhysicalGradientSumIsZero) { Q1ReferenceQuad quad; auto coords = createUnitQuad(); @@ -382,7 +348,7 @@ TEST_F(MappingTest, PhysicalGradientSumIsZero) { EXPECT_NEAR(sumGrad.z, 0.0, tol); } -// --- 6. TEST : J * J_inv ≈ Identity (Jacobian inverse correctness) --- +// J * J_inv ~= Identity (Jacobian inverse correctness) --- TEST_F(MappingTest, JacobianInverseCheck) { Q1ReferenceHexahedron hex; auto coords = createUnitHex(); @@ -413,89 +379,65 @@ TEST_F(MappingTest, JacobianInverseCheck) { EXPECT_NEAR(check(i,j), identity(i,j), tol); } -// // --- 7. TEST : HIGH-ORDER 3D ELEMENT CURVATURE (P2 Tetrahedron) --- -// TEST_F(MappingTest, P2TetraCurvedHessians) { -// P2ReferenceTetrahedron tet; -// DynamicVector coords(10); - -// // Unit tetrahedron -// coords[0]={0,0,0}; coords[1]={1,0,0}; coords[2]={0,1,0}; coords[3]={0,0,1}; -// coords[4]={0.5,0,0}; coords[5]={0.5,0.5,0}; coords[6]={0,0.5,0}; -// coords[7]={0,0,0.5}; coords[8]={0.5,0,0.5}; coords[9]={0,0.5,0.5}; - -// // Apply small perturbation to create curvature -// coords[5].x += 0.1; coords[5].y += 0.05; coords[9].z += 0.1; - -// MappingInfo map; -// ShapeInfo shape; -// Vector3D xi{0.25, 0.25, 0.25}; - -// ShapeEvaluationFlags req = ShapeEvaluationFlags::Gradient | ShapeEvaluationFlags::Hessian; -// tet.computeMapping(xi, coords, req, shape, map); - -// EXPECT_GT(map.detJ, 0.0); - -// bool hasNonZeroHessian = false; -// for (CfemInt i=0; i<10; ++i) { -// const auto& H = map.physicalShapeHessians[i]; -// if (std::abs(H.xx())>1e-5 || std::abs(H.yy())>1e-5 || std::abs(H.zz())>1e-5 || -// std::abs(H.xy())>1e-5 || std::abs(H.xz())>1e-5 || std::abs(H.yz())>1e-5) { -// hasNonZeroHessian = true; -// break; -// } -// } -// EXPECT_TRUE(hasNonZeroHessian); -// } - /** * @test Verify that physical Hessians for a curved P2 Tetrahedron are correct. * For a P2 element, if we map the element to a specific quadratic transformation, * the physical Hessians should remain consistent with the chain rule. */ TEST_F(MappingTest, P2TetraCurvedHessians) { - P2ReferenceTetrahedron tet; // Assuming you have this implemented (10 nodes) + P2ReferenceTetrahedron tet; const CfemInt n = tet.getNumNodes(); DynamicVector coords(n); - // 1. Define a curved physical geometry - // We start with a standard P2 tet and "push" the mid-side nodes to create curvature - const auto& refNodes = tet.referenceNodes(); + // Define a curved physical geometry + const auto& refNodes = tet.getReferenceNodes(); for(CfemInt i = 0; i < n; ++i) { coords[i] = refNodes[i]; - - // Apply a quadratic distortion: x_phys = xi + a * xi^2 coords[i].x += 0.2 * refNodes[i].x * refNodes[i].x; coords[i].y += 0.1 * refNodes[i].x * refNodes[i].y; } MappingInfo map; ShapeInfo shape; - Vector3D xi{0.25, 0.25, 0.25}; // Evaluation point (inside the tet) + Vector3D xi{0.25, 0.25, 0.25}; ShapeEvaluationFlags shapeFlags = ShapeEvaluationFlags::FirstDerivatives | ShapeEvaluationFlags::SecondDerivatives; MappingEvaluationFlags mappingFlags = MappingEvaluationFlags::PhysicalFirstDerivatives | MappingEvaluationFlags::PhysicalSecondDerivatives; ASSERT_NO_THROW(tet.computeMapping(xi, coords, shapeFlags, mappingFlags, shape, map)); + + // SANITY CHECK + EXPECT_GT(map.detJ, 0.0) << "Curved element became inverted!"; + + bool hasNonZeroHessian = false; + for (CfemInt i = 0; i < n; ++i) { + const auto& H = map.hessian(i); + if (std::abs(H.xx())>1e-5 || std::abs(H.yy())>1e-5 || std::abs(H.zz())>1e-5 || + std::abs(H.xy())>1e-5 || std::abs(H.xz())>1e-5 || std::abs(H.yz())>1e-5) { + hasNonZeroHessian = true; + break; + } + } + ASSERT_TRUE(hasNonZeroHessian) << "All Hessians are zero! Mathematical checks below will falsely pass."; - // VALIDATION: The Partition of Unity for Hessians: The sum of physical Hessians must be exactly zero. + // MATHEMATICAL CORRECTNESS + // The Partition of Unity: Sum of Hessians = 0 SymTensor3D hessianSum; hessianSum.setZero(); for (CfemInt i = 0; i < n; ++i) { hessianSum += map.hessian(i); } - for (CfemInt i = 0; i < 6; ++i) { - EXPECT_NEAR(hessianSum[i], 0.0, 1e-12) + EXPECT_NEAR(hessianSum[i], 0.0, 1e-12) << "Physical Hessian sum (Partition of Unity) failed at component " << i; } - // Linear Field Reproduction: If we interpolate the field u = x_phys + y_phys + z_phys, the second derivative (Hessian) should be zero. + // Linear Field Reproduction: u = x + y + z => H(u) = 0 SymTensor3D fieldHessian; fieldHessian.setZero(); for (CfemInt i = 0; i < n; ++i) { fieldHessian += map.hessian(i) * (coords[i].x + coords[i].y + coords[i].z); } - for (CfemInt i = 0; i < 6; ++i) { EXPECT_NEAR(fieldHessian[i], 0.0, 1e-11) << "Curvature cancellation failed! Component " << i @@ -508,7 +450,7 @@ TEST_F(MappingTest, P2TetraQuadraticFieldReproduction) { const CfemInt n = tet.getNumNodes(); DynamicVector coords(n); - const auto& refNodes = tet.referenceNodes(); + const auto& refNodes = tet.getReferenceNodes(); // Create a distorted element using a linear (affine) transformation. // This ensures a constant Jacobian, allowing the P2 element to @@ -580,7 +522,7 @@ TEST_F(MappingTest, P2TetraInterpolationSuite) { // 1. Setup Physical Coordinates (Distorted but Affine for exactness) DynamicVector coords(n); - const auto& refNodes = tet.referenceNodes(); + const auto& refNodes = tet.getReferenceNodes(); for(CfemInt i = 0; i < n; ++i) { coords[i].x = 1.2 * refNodes[i].x + 0.1 * refNodes[i].y; coords[i].y = 0.9 * refNodes[i].y; diff --git a/tests/test_main.cpp b/tests/test_main.cpp index 180ced1..4119ae0 100644 --- a/tests/test_main.cpp +++ b/tests/test_main.cpp @@ -22,11 +22,11 @@ #include int main(int argc, char **argv) { - omp_set_num_threads(1); + // omp_set_num_threads(1); ::testing::InitGoogleTest(&argc, argv); cfem::Environment::initialize(argc, argv); - + int result = RUN_ALL_TESTS(); return result;