#include "fesa/model/shell_geometry.hpp" #include #include #include #include #include #include #include #include namespace { using Vector3 = std::array; fesa::Node node(fesa::EntityIndex label, Vector3 coordinates) { return { {"Shell-Instance", static_cast(label), std::to_string(label)}, coordinates, {"shell-geometry.inp", static_cast(label + 1U)}}; } fesa::Mitc4ShellDefinition element( fesa::EntityIndex label, std::array nodeIndices) { return { {"Shell-Instance", static_cast(label), std::to_string(label)}, fesa::ShellSourceElementType::s4, nodeIndices, 0U, 0U, {"shell-geometry.inp", static_cast(100U + label)}}; } std::vector sections(double thickness = 0.2) { return {{"Section", thickness, 0U, {"shell-geometry.inp", 90U}}}; } double dot(const Vector3& left, const Vector3& right) { return left[0] * right[0] + left[1] * right[1] + left[2] * right[2]; } Vector3 cross(const Vector3& left, const Vector3& right) { return { left[1] * right[2] - left[2] * right[1], left[2] * right[0] - left[0] * right[2], left[0] * right[1] - left[1] * right[0]}; } double norm(const Vector3& value) { return std::sqrt(dot(value, value)); } void expectVectorNear( const Vector3& actual, const Vector3& expected, double tolerance = 1.0e-12) { for (std::size_t component = 0U; component < actual.size(); ++component) { EXPECT_NEAR(actual[component], expected[component], tolerance); } } void expectRightHandedFrame(const fesa::ShellNodeInitialFrame& frame) { EXPECT_NEAR(norm(frame.director), 1.0, 1.0e-12); EXPECT_NEAR(norm(frame.tangentA), 1.0, 1.0e-12); EXPECT_NEAR(norm(frame.tangentB), 1.0, 1.0e-12); EXPECT_NEAR(dot(frame.director, frame.tangentA), 0.0, 1.0e-12); EXPECT_NEAR(dot(frame.director, frame.tangentB), 0.0, 1.0e-12); EXPECT_NEAR(dot(frame.tangentA, frame.tangentB), 0.0, 1.0e-12); expectVectorNear(cross(frame.tangentA, frame.tangentB), frame.director); } const fesa::ShellNodeInitialFrame& frameFor( const fesa::ShellGeometry& geometry, fesa::EntityIndex nodeIndex) { const auto found = std::find_if( geometry.nodalFrames.begin(), geometry.nodalFrames.end(), [nodeIndex](const fesa::ShellNodeInitialFrame& frame) { return frame.nodeIndex == nodeIndex; }); EXPECT_NE(found, geometry.nodalFrames.end()); return *found; } void expectFailureCode( const fesa::Result& result, const std::string& code) { ASSERT_FALSE(result.hasValue()); EXPECT_EQ(result.status().failureCategory(), fesa::FailureCategory::model); ASSERT_EQ(result.status().diagnostics().size(), 1U); EXPECT_EQ(result.status().diagnostics()[0].code, code); } } // namespace // MITC4-GEO-001 TEST(Mitc4Geometry, BuildsDeterministicFramesForPlanarRotatedAndWarpedElements) { const std::vector planarNodes{ node(0U, {0.0, 0.0, 0.0}), node(1U, {1.0, 0.0, 0.0}), node(2U, {1.0, 1.0, 0.0}), node(3U, {0.0, 1.0, 0.0})}; auto planar = fesa::preprocessShellGeometry( planarNodes, {element(10U, {0U, 1U, 2U, 3U})}, sections()); ASSERT_TRUE(planar.hasValue()); ASSERT_EQ(planar.value().elementData.size(), 1U); expectVectorNear(planar.value().elementData[0].normalCandidate, {0.0, 0.0, 1.0}); EXPECT_NEAR(planar.value().elementData[0].surfaceAreaWeight, 1.0, 1.0e-12); ASSERT_EQ(planar.value().nodalFrames.size(), 4U); for (const auto& frame : planar.value().nodalFrames) { expectVectorNear(frame.director, {0.0, 0.0, 1.0}); expectVectorNear(frame.tangentA, {1.0, 0.0, 0.0}); expectVectorNear(frame.tangentB, {0.0, 1.0, 0.0}); expectRightHandedFrame(frame); } const std::vector rotatedNodes{ node(0U, {0.0, 0.0, 0.0}), node(1U, {0.0, 1.0, 0.0}), node(2U, {0.0, 1.0, 1.0}), node(3U, {0.0, 0.0, 1.0})}; auto rotated = fesa::preprocessShellGeometry( rotatedNodes, {element(11U, {0U, 1U, 2U, 3U})}, sections()); ASSERT_TRUE(rotated.hasValue()); const auto& rotatedFrame = frameFor(rotated.value(), 0U); expectVectorNear(rotatedFrame.director, {1.0, 0.0, 0.0}); expectVectorNear(rotatedFrame.tangentA, {0.0, 1.0, 0.0}); expectVectorNear(rotatedFrame.tangentB, {0.0, 0.0, 1.0}); expectRightHandedFrame(rotatedFrame); const std::vector warpedNodes{ node(0U, {0.0, 0.0, 0.0}), node(1U, {2.0, 0.0, 0.0}), node(2U, {2.0, 1.0, 0.2}), node(3U, {0.0, 1.0, 0.0})}; auto warped = fesa::preprocessShellGeometry( warpedNodes, {element(12U, {0U, 1U, 2U, 3U})}, sections()); ASSERT_TRUE(warped.hasValue()); EXPECT_GT(warped.value().elementData[0].surfaceAreaWeight, 2.0); for (const auto& frame : warped.value().nodalFrames) { expectRightHandedFrame(frame); } } // MITC4-GEO-002 TEST(Mitc4Geometry, AreaWeightsSharedDirectorsInStableSourceIdentityOrder) { const std::vector nodes{ node(0U, {0.0, 0.0, 0.0}), node(1U, {1.0, 0.0, 0.0}), node(2U, {1.0, 1.0, 0.0}), node(3U, {0.0, 1.0, 0.0}), node(4U, {2.0, 0.0, 1.0}), node(5U, {2.0, 1.0, 1.0})}; const auto flat = element(10U, {0U, 1U, 2U, 3U}); const auto tilted = element(20U, {1U, 4U, 5U, 2U}); auto first = fesa::preprocessShellGeometry( nodes, {tilted, flat}, sections()); auto second = fesa::preprocessShellGeometry( nodes, {flat, tilted}, sections()); ASSERT_TRUE(first.hasValue()); ASSERT_TRUE(second.hasValue()); const Vector3 expectedSharedDirector{ -1.0 / std::sqrt(5.0), 0.0, 2.0 / std::sqrt(5.0)}; for (const auto sharedNode : {1U, 2U}) { const auto& firstFrame = frameFor(first.value(), sharedNode); const auto& secondFrame = frameFor(second.value(), sharedNode); expectVectorNear(firstFrame.director, expectedSharedDirector); expectVectorNear(firstFrame.director, secondFrame.director, 0.0); expectVectorNear(firstFrame.tangentA, {0.0, 1.0, 0.0}); expectRightHandedFrame(firstFrame); } } // MITC4-GEO-003 TEST(Mitc4Geometry, RejectsInvalidSurfaceJacobianAndIncidentOrientationCases) { const auto validElement = element(10U, {0U, 1U, 2U, 3U}); expectFailureCode( fesa::preprocessShellGeometry( {node(0U, {0.0, 0.0, 0.0}), node(1U, {0.0, 0.0, 0.0}), node(2U, {1.0, 1.0, 0.0}), node(3U, {0.0, 1.0, 0.0})}, {validElement}, sections()), "invalid-shell-geometry"); expectFailureCode( fesa::preprocessShellGeometry( {node(0U, {0.0, 0.0, 0.0}), node(1U, {1.0, 1.0, 0.0}), node(2U, {0.0, 1.0, 0.0}), node(3U, {1.0, 0.0, 0.0})}, {validElement}, sections()), "invalid-shell-geometry"); expectFailureCode( fesa::preprocessShellGeometry( {node(0U, {0.0, 0.0, 0.0}), node(1U, {1.0, 0.0, 0.0}), node(2U, {2.0, 0.0, 0.0}), node(3U, {3.0, 0.0, 0.0})}, {validElement}, sections()), "invalid-shell-geometry"); expectFailureCode( fesa::preprocessShellGeometry( {node(0U, {0.0, 0.0, 0.0}), node(1U, {1.0, 0.0, 0.0}), node(2U, {0.05, 0.05, 0.0}), node(3U, {0.0, 1.0, 0.0})}, {validElement}, sections()), "invalid-shell-geometry"); expectFailureCode( fesa::preprocessShellGeometry( {node(0U, {0.0, 0.0, 0.0}), node(1U, {1.0, 0.0, 0.0}), node(2U, {1.0, std::numeric_limits::quiet_NaN(), 0.0}), node(3U, {0.0, 1.0, 0.0})}, {validElement}, sections()), "invalid-shell-geometry"); expectFailureCode( fesa::preprocessShellGeometry( {node(0U, {0.0, 0.0, 0.0}), node(1U, {1.0, 0.0, 0.0}), node(2U, {1.0, 1.0, 0.0}), node(3U, {0.0, 1.0, 0.0})}, {validElement}, sections(0.0)), "invalid-shell-jacobian"); const std::vector opposedNodes{ node(0U, {0.0, 0.0, 0.0}), node(1U, {1.0, 0.0, 0.0}), node(2U, {1.0, 1.0, 0.0}), node(3U, {0.0, 1.0, 0.0})}; expectFailureCode( fesa::preprocessShellGeometry( opposedNodes, {element(10U, {0U, 1U, 2U, 3U}), element(20U, {0U, 3U, 2U, 1U})}, sections()), "opposed-incident-normal"); } // MITC4-GEO-004 TEST(Mitc4Geometry, ExposesTheCompleteRequiredValidationPointInventory) { const auto& points = fesa::shellGeometryValidationPoints(); ASSERT_EQ(points.size(), 17U); EXPECT_EQ( std::count_if(points.begin(), points.end(), [](const auto& point) { return point.kind == fesa::ShellGeometryPointKind::center; }), 1); EXPECT_EQ( std::count_if(points.begin(), points.end(), [](const auto& point) { return point.kind == fesa::ShellGeometryPointKind::stiffness; }), 8); EXPECT_EQ( std::count_if(points.begin(), points.end(), [](const auto& point) { return point.kind == fesa::ShellGeometryPointKind::tying; }), 4); EXPECT_EQ( std::count_if(points.begin(), points.end(), [](const auto& point) { return point.kind == fesa::ShellGeometryPointKind::recovery; }), 4); EXPECT_EQ(points.front().naturalCoordinates, (Vector3{0.0, 0.0, 0.0})); const double gauss = 1.0 / std::sqrt(3.0); EXPECT_EQ(points[1].naturalCoordinates, (Vector3{-gauss, -gauss, -gauss})); EXPECT_EQ(points[8].naturalCoordinates, (Vector3{-gauss, gauss, gauss})); EXPECT_EQ(points[9].naturalCoordinates, (Vector3{0.0, -1.0, 0.0})); EXPECT_EQ(points[12].naturalCoordinates, (Vector3{1.0, 0.0, 0.0})); EXPECT_EQ(points[13].naturalCoordinates, (Vector3{-gauss, -gauss, 0.0})); EXPECT_EQ(points[16].naturalCoordinates, (Vector3{-gauss, gauss, 0.0})); }