// Copyright 2015. All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are met: // // * Redistributions of source code must retain the above copyright notice, // this list of conditions and the following disclaimer. // * Redistributions in binary form must reproduce the above copyright notice, // this list of conditions and the following disclaimer in the documentation // and/or other materials provided with the distribution. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE // POSSIBILITY OF SUCH DAMAGE. // // Author: ryan.latture@gmail.com (Ryan Latture) #include "threed_beam_fea.h" #include using namespace fea; class beamFEATest : public testing::Test { protected: beamFEATest() : JOB_L_BRACKET(), BCS_L_BRACKET(0), FORCES_L_BRACKET(0), JOB_CANTILEVER(), BCS_CANTILEVER(0), FORCES_CANTILEVER(0), assembleK3D() { }; virtual void SetUp() { using namespace fea; // L-bracket setup std::vector normal_vec = {0.0, 1.0, 0.0}; Props PROPS1(10.0, 10.0, 10.0, 10.0, normal_vec); Props PROPS2(10.0, 1.0, 1.0, 10.0, normal_vec); std::vector NODES = {Node(0.0, 0.0, 0.0), Node(1.0, 0.0, 0.0), Node(2.0, 0.0, 0.0), Node(2.0, 0.0, 1.0)}; std::vector ELEMS = {Elem(0, 1, PROPS1), Elem(1, 2, PROPS1), Elem(2, 3, PROPS2)}; JOB_L_BRACKET = Job(NODES, ELEMS); BC bc1(0, 0, 0.0); BC bc2(0, 1, 0.0); BC bc3(0, 2, 0.0); BC bc4(0, 3, 0.0); BC bc5(0, 4, 0.0); BC bc6(0, 5, 0.0); BC bc7(3, 1, 0.5); BCS_L_BRACKET = {bc1, bc2, bc3, bc4, bc5, bc6, bc7}; // cantilever setup std::vector normal_cantilever = {0.0, 0.0, 1.0}; Props props_cantilever(1.0, 1.0, 1.0, 1.0, normal_cantilever); std::vector nodes_cantilever = {Node(0.0, 0.0, 0.0), Node(1.0, 0.0, 0.0)}; std::vector elems_cantilever = {Elem(0, 1, props_cantilever)}; BCS_CANTILEVER = {BC(0, 0, 0.0), BC(0, 1, 0.0), BC(0, 2, 0.0), BC(0, 3, 0.0), BC(0, 4, 0.0), BC(0, 5, 0.0)}; FORCES_CANTILEVER = {Force(1, 1, 0.1)}; JOB_CANTILEVER = Job(nodes_cantilever, elems_cantilever); } Job JOB_L_BRACKET; std::vector BCS_L_BRACKET; std::vector FORCES_L_BRACKET; Job JOB_CANTILEVER; std::vector BCS_CANTILEVER; std::vector FORCES_CANTILEVER; GlobalStiffAssembler assembleK3D; public: EIGEN_MAKE_ALIGNED_OPERATOR_NEW; }; TEST_F(beamFEATest, TransformsLocalToGlobalCoords) { // test that identity matrix is recovered if local axes == global axes Eigen::Vector3d nx(1.0, 0.0, 0.0); Eigen::Vector3d nz(0.0, 0.0, 1.0); assembleK3D.calcAelem(nx, nz); LocalMatrix Aelem = assembleK3D.getAelem(); LocalMatrix expected; expected.setIdentity(); for (size_t i = 0; i < Aelem.rows(); ++i) { EXPECT_DOUBLE_EQ(expected(i), Aelem(i)); } } TEST_F(beamFEATest, AssemblesElementalStiffness) { assembleK3D.calcKelem(0, JOB_L_BRACKET); LocalMatrix Kelem = assembleK3D.getKelem(); std::vector> expected = {{10., 0., 0., 0., 0., 0., -10., 0., 0., 0., 0., 0.}, {0., 120., 0., 0., 0., 60., 0., -120., 0., 0., 0., 60.}, {0., 0., 120., 0., -60., 0., 0., 0., -120., 0., -60., 0.}, {0., 0., 0., 10., 0., 0., 0., 0., 0., -10., 0., 0.}, {0., 0., -60., 0., 40., 0., 0., 0., 60., 0., 20., 0.}, {0., 60., 0., 0., 0., 40., 0., -60., 0., 0., 0., 20.}, {-10., 0., 0., 0., 0., 0., 10., 0., 0., 0., 0., 0.}, {0., -120., 0., 0., 0., -60., 0., 120., 0., 0., 0., -60.}, {0., 0., -120., 0., 60., 0., 0., 0., 120., 0., 60., 0.}, {0., 0., 0., -10., 0., 0., 0., 0., 0., 10., 0., 0.}, {0., 0., -60., 0., 20., 0., 0., 0., 60., 0., 40., 0.}, {0., 60., 0., 0., 0., 20., 0., -60., 0., 0., 0., 40.}}; for (size_t i = 0; i < expected.size(); i++) { for (size_t j = 0; j < expected[i].size(); j++) { EXPECT_DOUBLE_EQ(expected[i][j], Kelem(i, j)); } } } TEST_F(beamFEATest, AssemblesGlobalStiffness) { unsigned int dofs_per_elem = 6; // calculate size of global stiffness matrix and force vector size_t size = dofs_per_elem * JOB_L_BRACKET.nodes.size() + FORCES_L_BRACKET.size(); // construct global stiffness matrix and force vector SparseMat Kg(size, size); std::vector ties; assembleK3D(Kg, JOB_L_BRACKET, ties); GlobalStiffMatrix KgDense(Kg); std::vector > expected = {{10., 0., 0., 0., 0., 0., -10., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.}, {0., 120., 0., 0., 0., 60., 0., -120., 0., 0., 0., 60., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.}, {0., 0., 120., 0., -60., 0., 0., 0., -120., 0., -60., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.}, {0., 0., 0., 10., 0., 0., 0., 0., 0., -10., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.}, {0., 0., -60., 0., 40., 0., 0., 0., 60., 0., 20., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.}, {0., 60., 0., 0., 0., 40., 0., -60., 0., 0., 0., 20., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.}, {-10., 0., 0., 0., 0., 0., 20., 0., 0., 0., 0., 0., -10., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.}, {0., -120., 0., 0., 0., -60., 0., 240., 0., 0., 0., 0., 0., -120., 0., 0., 0., 60., 0., 0., 0., 0., 0., 0.}, {0., 0., -120., 0., 60., 0., 0., 0., 240., 0., 0., 0., 0., 0., -120., 0., -60., 0., 0., 0., 0., 0., 0., 0.}, {0., 0., 0., -10., 0., 0., 0., 0., 0., 20., 0., 0., 0., 0., 0., -10., 0., 0., 0., 0., 0., 0., 0., 0.}, {0., 0., -60., 0., 20., 0., 0., 0., 0., 0., 80., 0., 0., 0., 60., 0., 20., 0., 0., 0., 0., 0., 0., 0.}, {0., 60., 0., 0., 0., 20., 0., 0., 0., 0., 0., 80., 0., -60., 0., 0., 0., 20., 0., 0., 0., 0., 0., 0.}, {0., 0., 0., 0., 0., 0., -10., 0., 0., 0., 0., 0., 22., 0., 0., 0., 6., 0., -12., 0., 0., 0., 6., 0.}, {0., 0., 0., 0., 0., 0., 0., -120., 0., 0., 0., -60., 0., 132., 0., -6., 0., -60., 0., -12., 0., -6., 0., 0.}, {0., 0., 0., 0., 0., 0., 0., 0., -120., 0., 60., 0., 0., 0., 130., 0., 60., 0., 0., 0., -10., 0., 0., 0.}, {0., 0., 0., 0., 0., 0., 0., 0., 0., -10., 0., 0., 0., -6., 0., 14., 0., 0., 0., 6., 0., 2., 0., 0.}, {0., 0., 0., 0., 0., 0., 0., 0., -60., 0., 20., 0., 6., 0., 60., 0., 44., 0., -6., 0., 0., 0., 2., 0.}, {0., 0., 0., 0., 0., 0., 0., 60., 0., 0., 0., 20., 0., -60., 0., 0., 0., 50., 0., 0., 0., 0., 0., -10.}, {0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., -12., 0., 0., 0., -6., 0., 12., 0., 0., 0., -6., 0.}, {0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., -12., 0., 6., 0., 0., 0., 12., 0., 6., 0., 0.}, {0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., -10., 0., 0., 0., 0., 0., 10., 0., 0., 0.}, {0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., -6., 0., 2., 0., 0., 0., 6., 0., 4., 0., 0.}, {0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 6., 0., 0., 0., 2., 0., -6., 0., 0., 0., 4., 0.}, {0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., -10., 0., 0., 0., 0., 0., 10.}}; for (size_t i = 0; i < expected.size(); i++) { for (size_t j = 0; j < expected[i].size(); j++) { EXPECT_DOUBLE_EQ(expected[i][j], KgDense(i, j)); } } } TEST_F(beamFEATest, CorrectNodalDisplacementsNoTies) { std::vector ties; Summary summary = solve(JOB_L_BRACKET, BCS_L_BRACKET, FORCES_L_BRACKET, ties, Options()); // the first 4 rows check nodal displacements // the last row is associated with the reaction // forces due to enforcing the BCs. std::vector > expected = {{0., 0., 0., 0., 0., 0.}, {0., 0.0520833333333333, 0., -0.0625, 0., 0.09375}, {0., 0.16666666666666666, 0., -0.125, 0., 0.125}, {0., 0.5, 0., -0.4375, 0., 0.125}, {0., 0.625, 0., -0.625, 0., 1.25, -0.625}}; for (size_t i = 0; i < summary.nodal_displacements.size(); ++i) { for (size_t j = 0; j < summary.nodal_displacements[i].size(); ++j) { EXPECT_NEAR(expected[i][j], summary.nodal_displacements[i][j], 1.e-10); } } } // In this test I create the same mesh as above with 1 exception // there are redundant nodes at (1,0,0) that I tie with extremely // stiff linear and rotational springs. If the tie constraints are // functioning properly, the ties should behave the same as having // rigid nodal joints, at least to several decimal places. TEST_F(beamFEATest, CorrectNodalDisplacementsWithStiffTies) { std::vector normal_vec = {0.0, 1.0, 0.0}; Props props1(10.0, 10.0, 10.0, 10.0, normal_vec); Props props2(10.0, 1.0, 1.0, 10.0, normal_vec); std::vector nodes = {Node(0.0, 0.0, 0.0), Node(1.0, 0.0, 0.0), Node(1.0, 0.0, 0.0), Node(2.0, 0.0, 0.0), Node(2.0, 0.0, 1.0)}; std::vector elems = {Elem(0, 1, props1), Elem(2, 3, props1), Elem(3, 4, props2)}; Job job_tie(nodes, elems); BC bc1(0, 0, 0.0); BC bc2(0, 1, 0.0); BC bc3(0, 2, 0.0); BC bc4(0, 3, 0.0); BC bc5(0, 4, 0.0); BC bc6(0, 5, 0.0); BC bc7(4, 1, 0.5); std::vector bcs = {bc1, bc2, bc3, bc4, bc5, bc6, bc7}; std::vector ties = {Tie(1, 2, 1.e8, 1.e8)}; std::vector forces; Summary summary = solve(job_tie, bcs, forces, ties, Options()); // The verification program used to generate expected values outputs data as floats std::vector > expected = {{0., 0., 0., 0., 0., 0.}, {0., 0.0520833333333333, 0., -0.0625, 0., 0.09375}, {0., 0.0520833333333333, 0., -0.0625, 0., 0.09375}, {0., 0.16666666666666666, 0., -0.125, 0., 0.125}, {0., 0.5, 0., -0.4375, 0., 0.125}, {0., 0.625, 0., -0.625, 0., 1.25, -0.625}}; for (size_t i = 0; i < summary.nodal_displacements.size(); ++i) { for (size_t j = 0; j < summary.nodal_displacements[i].size(); ++j) { EXPECT_NEAR(expected[i][j], summary.nodal_displacements[i][j], 1.e-7); } } } // This is simple cantilever beam with a point load at the // free end. This checks the end displacements match the analytical results. TEST_F(beamFEATest, CorrectTipDisplacementCantileverBeam) { std::vector ties; Summary summary = solve(JOB_CANTILEVER, BCS_CANTILEVER, FORCES_CANTILEVER, ties, Options()); // the first row checks nodal displacements of fixed node are zero // the second row checks the analytical result for tip displacement // for the given load in the y-direction of 0.01 yields the correct // displacement and rotation std::vector > expected = {{0., 0., 0., 0., 0., 0.}, {0., 0.033333333333333333, 0., 0.0, 0.0, 0.05}}; for (size_t i = 0; i < summary.nodal_displacements.size(); ++i) { for (size_t j = 0; j < summary.nodal_displacements[i].size(); ++j) EXPECT_DOUBLE_EQ(expected[i][j], summary.nodal_displacements[i][j]); } } // This test displaces a cantilever beam axially and // transverse to the beam axis. Nodal forces are // compared to the analytical result. TEST_F(beamFEATest, CorrectTipForcesCantileverBeam) { std::vector ties; std::vector forces; std::vector bcs = BCS_CANTILEVER; bcs.push_back(BC(1, 0, 0.1)); bcs.push_back(BC(1, 1, 0.1)); Options opts; opts.save_report = true; opts.save_nodal_forces = true; opts.save_nodal_displacements = true; Summary summary = solve(JOB_CANTILEVER, bcs, forces, ties, opts); std::vector > expected = {{-0.1, -0.3, 0., 0., 0., -0.3}, {0.1, 0.3, 0., 0.0, 0.0, 0.0}}; for (size_t i = 0; i < summary.nodal_forces.size(); ++i) { for (size_t j = 0; j < summary.nodal_forces[i].size(); ++j) EXPECT_DOUBLE_EQ(expected[i][j], summary.nodal_forces[i][j]); } } // This tests the tie constraints correctly deform. // I have extremely stiff elements and apply a // displacement to the end node. It is expected // that the tie will accommodate all the deformation. TEST_F(beamFEATest, CorrectDisplacementWeakTies) { std::vector normal_vec = {0.0, 1.0, 0.0}; Props props(1.e9, 1.e9, 1.e9, 1.e9, normal_vec); std::vector nodes = {Node(0.0, 0.0, 0.0), Node(1.0, 0.0, 0.0), Node(1.0, 0.0, 0.0), Node(2.0, 0.0, 0.0)}; std::vector elems = {Elem(0, 1, props), Elem(2, 3, props)}; Job job_tie(nodes, elems); BC bc1(0, 0, 0.0); BC bc2(0, 1, 0.0); BC bc3(0, 2, 0.0); BC bc4(0, 3, 0.0); BC bc5(0, 4, 0.0); BC bc6(0, 5, 0.0); BC bc7(3, 0, 0.5); std::vector bcs = {bc1, bc2, bc3, bc4, bc5, bc6, bc7}; std::vector ties = {Tie(1, 2, 0.01, 0.01)}; std::vector forces; Options opts; opts.epsilon = 1e-10; Summary summary = solve(job_tie, bcs, forces, ties, opts); std::vector > expected = {{0.0, 0.0, 0.0, 0.0, 0.0, 0.0}, {0.0, 0.0, 0.0, 0.0, 0.0, 0.0}, {0.5, 0.0, 0.0, 0.0, 0.0, 0.0}, {0.5, 0.0, 0.0, 0.0, 0.0, 0.0}}; for (size_t i = 0; i < summary.nodal_displacements.size(); ++i) { for (size_t j = 0; j < summary.nodal_displacements[i].size(); ++j) { EXPECT_NEAR(expected[i][j], summary.nodal_displacements[i][j], 1e-10); } } } TEST_F(beamFEATest, CorrectForcesWeakTies) { std::vector normal_vec = {0.0, 1.0, 0.0}; Props props(1.e9, 1.e9, 1.e9, 1.e9, normal_vec); std::vector nodes = {Node(0.0, 0.0, 0.0), Node(1.0, 0.0, 0.0), Node(1.0, 0.0, 0.0), Node(2.0, 0.0, 0.0)}; std::vector elems = {Elem(0, 1, props), Elem(2, 3, props)}; Job job_tie(nodes, elems); BC bc1(0, 0, 0.0); BC bc2(0, 1, 0.0); BC bc3(0, 2, 0.0); BC bc4(0, 3, 0.0); BC bc5(0, 4, 0.0); BC bc6(0, 5, 0.0); BC bc7(3, 0, 0.5); BC bc8(2, 3, 0.5); std::vector bcs = {bc1, bc2, bc3, bc4, bc5, bc6, bc7, bc8}; std::vector ties = {Tie(1, 2, 0.01, 0.01)}; std::vector forces; Options opts; opts.epsilon = 1e-10; Summary summary = solve(job_tie, bcs, forces, ties, opts); std::vector > expected = {{0.005, 0.0, 0.0, 0.005, 0.0, 0.0}}; for (size_t i = 0; i < summary.tie_forces.size(); ++i) { for (size_t j = 0; j < summary.tie_forces[i].size(); ++j) { EXPECT_NEAR(expected[i][j], summary.tie_forces[i][j], 1e-13); } } }