#pragma once #include "fesa/Element/MITC4MaterialIntegration.hpp" #include "fesa/Math/Math.hpp" #include #include #include #include #include namespace fesa { struct ElementStiffnessOptions { Real drilling_stiffness_scale = 1.0e-3; }; struct MITC4DrillingStabilizationResult { DenseMatrix local_with_drilling; Real reference_diagonal = 0.0; Real drilling_stiffness = 0.0; Real drilling_stiffness_scale = 0.0; std::vector diagnostics; bool ok() const { return !hasError(diagnostics); } }; struct MITC4ElementStiffnessResult { DenseMatrix local_without_drilling; DenseMatrix local_with_drilling; DenseMatrix global; std::size_t integration_point_count = 0; Real drilling_reference_diagonal = 0.0; Real drilling_stiffness = 0.0; Real drilling_stiffness_scale = 0.0; std::vector diagnostics; bool ok() const { return !hasError(diagnostics); } }; inline DenseMatrix mitc4LocalDofTransform(const MITC4Geometry& geometry) { DenseMatrix transform(24, 24); for (LocalIndex node = 0; node < 4; ++node) { const LocalIndex base = 6 * node; const auto& frame = geometry.nodal_frames[static_cast(node)]; const std::array axes = {frame.v1, frame.v2, frame.vn}; for (LocalIndex local_axis = 0; local_axis < 3; ++local_axis) { const Vec3 axis = axes[static_cast(local_axis)]; transform(base + local_axis, base + 0) = axis.x; transform(base + local_axis, base + 1) = axis.y; transform(base + local_axis, base + 2) = axis.z; transform(base + 3 + local_axis, base + 3) = axis.x; transform(base + 3 + local_axis, base + 4) = axis.y; transform(base + 3 + local_axis, base + 5) = axis.z; } } return transform; } inline MITC4StrainRows mitc4TransformStrainRowsToLocalDofs(const MITC4StrainRows& global_rows, const DenseMatrix& local_dof_transform) { MITC4StrainRows local_rows; local_rows.diagnostics = global_rows.diagnostics; if (hasError(local_rows.diagnostics)) { return local_rows; } for (std::size_t component = 0; component < 6; ++component) { for (LocalIndex local_dof = 0; local_dof < 24; ++local_dof) { Real value = 0.0; for (LocalIndex global_dof = 0; global_dof < 24; ++global_dof) { value += global_rows.rows[component][static_cast(global_dof)] * local_dof_transform(local_dof, global_dof); } local_rows.rows[component][static_cast(local_dof)] = value; } } return local_rows; } inline void accumulateMITC4BtDB(DenseMatrix& stiffness, const MITC4StrainRows& rows, const MITC4MaterialMatrix& material, Real factor) { for (LocalIndex i = 0; i < 24; ++i) { for (LocalIndex j = 0; j < 24; ++j) { Real value = 0.0; for (std::size_t a = 0; a < 6; ++a) { for (std::size_t b = 0; b < 6; ++b) { value += rows.rows[a][static_cast(i)] * material[a][b] * rows.rows[b][static_cast(j)]; } } stiffness.add(i, j, value * factor); } } } inline Real mitc4MinimumPositivePhysicalLocalDiagonal(const DenseMatrix& local_without_drilling, Real tolerance = 1.0e-12) { Real minimum = std::numeric_limits::infinity(); for (LocalIndex node = 0; node < 4; ++node) { for (LocalIndex local_dof = 0; local_dof < 5; ++local_dof) { const Real diagonal = local_without_drilling(6 * node + local_dof, 6 * node + local_dof); if (isFinite(diagonal) && diagonal > tolerance) { minimum = std::min(minimum, diagonal); } } } return minimum; } inline MITC4DrillingStabilizationResult mitc4ApplyDrillingStabilization(const DenseMatrix& local_without_drilling, Real drilling_stiffness_scale, Real tolerance = 1.0e-12) { MITC4DrillingStabilizationResult result; result.local_with_drilling = local_without_drilling; result.drilling_stiffness_scale = drilling_stiffness_scale; if (!isFinite(drilling_stiffness_scale) || drilling_stiffness_scale < 0.0) { result.diagnostics.push_back( mitc4Diagnostic("FESA-MITC4-DRILLING-SCALE", "MITC4 drilling stiffness scale must be non-negative and finite")); return result; } result.reference_diagonal = mitc4MinimumPositivePhysicalLocalDiagonal(local_without_drilling, tolerance); if (!isFinite(result.reference_diagonal)) { result.diagnostics.push_back(mitc4Diagnostic("FESA-MITC4-DRILLING-REFERENCE", "MITC4 drilling stiffness reference diagonal is not positive")); return result; } result.drilling_stiffness = drilling_stiffness_scale * result.reference_diagonal; for (LocalIndex node = 0; node < 4; ++node) { const LocalIndex gamma = 6 * node + 5; result.local_with_drilling.add(gamma, gamma, result.drilling_stiffness); } return result; } inline DenseMatrix mitc4TransformLocalStiffnessToGlobal(const DenseMatrix& local_stiffness, const DenseMatrix& local_dof_transform) { DenseMatrix global(24, 24); for (LocalIndex i = 0; i < 24; ++i) { for (LocalIndex j = 0; j < 24; ++j) { Real value = 0.0; for (LocalIndex a = 0; a < 24; ++a) { for (LocalIndex b = 0; b < 24; ++b) { value += local_dof_transform(a, i) * local_stiffness(a, b) * local_dof_transform(b, j); } } global(i, j) = value; } } return global; } inline MITC4ElementStiffnessResult mitc4ElementStiffness(const std::array& coordinates, Real elastic_modulus, Real poisson_ratio, Real thickness, ElementStiffnessOptions options = {}) { MITC4ElementStiffnessResult result; result.local_without_drilling = DenseMatrix(24, 24); result.local_with_drilling = DenseMatrix(24, 24); result.global = DenseMatrix(24, 24); result.drilling_stiffness_scale = options.drilling_stiffness_scale; const MITC4Geometry geometry = buildMITC4Geometry(coordinates, thickness); appendDiagnostics(result.diagnostics, geometry.diagnostics); if (hasError(result.diagnostics)) { return result; } const MITC4MaterialIntegrationData integration = mitc4BuildMaterialIntegrationData(geometry, elastic_modulus, poisson_ratio); appendDiagnostics(result.diagnostics, integration.diagnostics); if (hasError(result.diagnostics)) { return result; } result.integration_point_count = integration.samples.size(); const DenseMatrix local_dof_transform = mitc4LocalDofTransform(geometry); for (const MITC4MaterialIntegrationSample& sample : integration.samples) { const MITC4StrainRows local_rows = mitc4TransformStrainRowsToLocalDofs(sample.strain_rows, local_dof_transform); appendDiagnostics(result.diagnostics, local_rows.diagnostics); if (hasError(result.diagnostics)) { return result; } const Real factor = std::fabs(sample.basis.jacobian) * sample.point.weight; accumulateMITC4BtDB(result.local_without_drilling, local_rows, sample.convected_material, factor); } const auto drilling = mitc4ApplyDrillingStabilization(result.local_without_drilling, options.drilling_stiffness_scale); appendDiagnostics(result.diagnostics, drilling.diagnostics); result.local_with_drilling = drilling.local_with_drilling; result.drilling_reference_diagonal = drilling.reference_diagonal; result.drilling_stiffness = drilling.drilling_stiffness; result.drilling_stiffness_scale = drilling.drilling_stiffness_scale; if (hasError(result.diagnostics)) { return result; } result.global = mitc4TransformLocalStiffnessToGlobal(result.local_with_drilling, local_dof_transform); return result; } inline std::vector mitc4ElementInternalForce(const MITC4ElementStiffnessResult& stiffness, const std::vector& element_displacement) { if (stiffness.global.rows() != static_cast(element_displacement.size())) { throw std::runtime_error("MITC4 internal force size mismatch"); } return stiffness.global.multiply(element_displacement); } class MITC4ElementKernel { public: DenseMatrix stiffness(const std::array& coordinates, Real elastic_modulus, Real poisson_ratio, Real thickness, ElementStiffnessOptions options = {}) const { const auto result = mitc4ElementStiffness(coordinates, elastic_modulus, poisson_ratio, thickness, options); if (!result.ok()) { throw std::runtime_error(result.diagnostics.empty() ? "invalid MITC4 stiffness" : result.diagnostics.front().message); } return result.global; } }; } // namespace fesa