forked from pz4kybsvg/Conception
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
298 lines
10 KiB
298 lines
10 KiB
#include "drake/multibody/fem/isoparametric_element.h"
|
|
|
|
#include <limits>
|
|
|
|
#include <gtest/gtest.h>
|
|
|
|
#include "drake/common/test_utilities/eigen_matrix_compare.h"
|
|
#include "drake/common/test_utilities/expect_throws_message.h"
|
|
#include "drake/multibody/fem/linear_simplex_element.h"
|
|
|
|
namespace drake {
|
|
namespace multibody {
|
|
namespace fem {
|
|
namespace internal {
|
|
namespace {
|
|
|
|
constexpr int kNumSamplesTri = 4;
|
|
constexpr int kNumSamplesTet = 5;
|
|
|
|
// Test scalar value 2D function.
|
|
double LinearScalarFunction2D(const Vector2<double>& x) {
|
|
const Vector2<double> a{1.2, 3.4};
|
|
return 5.6 + a.dot(x);
|
|
}
|
|
|
|
// Test vector value 2D function.
|
|
Vector3<double> LinearVectorFunction2D(const Vector2<double>& x) {
|
|
Eigen::Matrix<double, 3, 2> a;
|
|
// clang-format off
|
|
a << 0.1, 1.2, 2.3,
|
|
3.4, 4.5, 5.6;
|
|
// clang-format on
|
|
return Vector3<double>{6.7, 7.8, 8.9} + a * x;
|
|
}
|
|
|
|
// Test scalar value 3D function.
|
|
double LinearScalarFunction3D(const Vector3<double>& x) {
|
|
const Vector3<double> a{1.2, 3.4, 7.8};
|
|
return 9.0 + a.dot(x);
|
|
}
|
|
|
|
// Test vector value 3D function.
|
|
Vector3<double> LinearVectorFunction3D(const Vector3<double>& x) {
|
|
Eigen::Matrix<double, 3, 3> a;
|
|
// clang-format off
|
|
a << 0.1, 1.2, 2.3,
|
|
3.4, 4.5, 5.6,
|
|
6.7, 7.8, 8.9;
|
|
// clang-format on
|
|
return Vector3<double>{6.7, 7.8, 8.9} + a * x;
|
|
}
|
|
|
|
class IsoparametricElementTest : public ::testing::Test {
|
|
protected:
|
|
void SetUp() override {}
|
|
|
|
// Sample locations for triangle elements.
|
|
std::array<Vector2<double>, kNumSamplesTri> tri_locations() const {
|
|
return {{{0.0, 0.0}, {0.1, 0.2}, {1, 0}, {0, 1}}};
|
|
}
|
|
|
|
// Sample locations for tetrahedral elements.
|
|
std::array<Vector3<double>, kNumSamplesTet> tet_locations() const {
|
|
return {
|
|
{{0.0, 0.0, 0.0}, {1, 0, 0}, {0, 1, 0}, {0, 0, 1}, {0.1, 0.2, 0.3}}};
|
|
}
|
|
|
|
// Triangle element in 2d.
|
|
LinearSimplexElement<double, 2, 2, kNumSamplesTri> tri_2d_{tri_locations()};
|
|
// Triangle element in 3d.
|
|
LinearSimplexElement<double, 2, 3, kNumSamplesTri> tri_3d_{tri_locations()};
|
|
// Tetrahedral element in 3d.
|
|
LinearSimplexElement<double, 3, 3, kNumSamplesTet> tet_{tet_locations()};
|
|
};
|
|
|
|
TEST_F(IsoparametricElementTest, NumNodes) {
|
|
EXPECT_EQ(tri_2d_.num_nodes, 3);
|
|
EXPECT_EQ(tri_3d_.num_nodes, 3);
|
|
EXPECT_EQ(tet_.num_nodes, 4);
|
|
}
|
|
|
|
TEST_F(IsoparametricElementTest, NumSampleLocations) {
|
|
EXPECT_EQ(tri_2d_.num_sample_locations, kNumSamplesTri);
|
|
EXPECT_EQ(tri_3d_.num_sample_locations, kNumSamplesTri);
|
|
EXPECT_EQ(tet_.num_sample_locations, kNumSamplesTet);
|
|
}
|
|
|
|
TEST_F(IsoparametricElementTest, GetLocations) {
|
|
const auto& tri_2d_samples = tri_2d_.locations();
|
|
const auto& tri_3d_samples = tri_3d_.locations();
|
|
const auto& tet_samples = tet_.locations();
|
|
EXPECT_EQ(tri_2d_samples, tri_locations());
|
|
EXPECT_EQ(tri_3d_samples, tri_locations());
|
|
EXPECT_EQ(tet_samples, tet_locations());
|
|
}
|
|
|
|
TEST_F(IsoparametricElementTest, Jacobian2DTriangle) {
|
|
/* Scale the unit triangle by a factor of two to get the reference triangle.
|
|
*/
|
|
Eigen::Matrix<double, 2, 3> xa;
|
|
// clang-format off
|
|
xa << 0, 2, 0,
|
|
0, 0, 2;
|
|
// clang-format on
|
|
const auto dxdxi = tri_2d_.CalcJacobian(xa);
|
|
EXPECT_EQ(dxdxi.size(), kNumSamplesTri);
|
|
for (int i = 0; i < kNumSamplesTri; ++i) {
|
|
EXPECT_TRUE(
|
|
CompareMatrices(dxdxi[i], 2.0 * MatrixX<double>::Identity(2, 2)));
|
|
}
|
|
}
|
|
|
|
TEST_F(IsoparametricElementTest, Jacobian3DTriangle) {
|
|
/* Put the vertices of the reference triangle at (0,0,0), (0,1,0) and
|
|
(0,0,2). */
|
|
Matrix3<double> xa;
|
|
// clang-format off
|
|
xa << 0, 0, 0,
|
|
0, 1, 0,
|
|
0, 0, 2;
|
|
// clang-format on
|
|
const auto dxdxi = tri_3d_.CalcJacobian(xa);
|
|
EXPECT_EQ(dxdxi.size(), kNumSamplesTri);
|
|
Eigen::Matrix<double, 3, 2> analytic_dxdxi;
|
|
// clang-format off
|
|
analytic_dxdxi << 0, 0, // The x-coordinate of reference is independent of
|
|
// the parent coordinate.
|
|
1, 0, // dy/dxi_0 = 1. dy/dxi_1 = 0.
|
|
0, 2; // dz/dxi_0 = 0. dz/dxi_1 = 2.
|
|
// clang-format on
|
|
for (int i = 0; i < kNumSamplesTri; ++i) {
|
|
EXPECT_TRUE(CompareMatrices(dxdxi[i], analytic_dxdxi));
|
|
}
|
|
}
|
|
|
|
TEST_F(IsoparametricElementTest, JacobianTetrahedron) {
|
|
/* Put the vertices of the reference triangle at (0,0,0), (0,1,0) and
|
|
(0, 0, 2), and (3,0,0). */
|
|
Eigen::Matrix<double, 3, 4> xa;
|
|
// clang-format off
|
|
xa << 0, 0, 0, 3,
|
|
0, 1, 0, 0,
|
|
0, 0, 2, 0;
|
|
// clang-format on
|
|
const auto dxdxi = tet_.CalcJacobian(xa);
|
|
EXPECT_EQ(dxdxi.size(), kNumSamplesTet);
|
|
Matrix3<double> analytic_dxdxi;
|
|
// clang-format off
|
|
analytic_dxdxi << 0, 0, 3, // dx/dxi_0 = 0. dx/dxi_1 = 0. dx/dxi_2 = 3.
|
|
1, 0, 0, // dy/dxi_0 = 1. dy/dxi_1 = 0. dy/dxi_2 = 0.
|
|
0, 2, 0; // dz/dxi_0 = 0. dz/dxi_1 = 2. dz/dxi_2 = 0.
|
|
// clang-format on
|
|
for (int i = 0; i < kNumSamplesTet; ++i) {
|
|
EXPECT_TRUE(CompareMatrices(dxdxi[i], analytic_dxdxi));
|
|
}
|
|
}
|
|
|
|
TEST_F(IsoparametricElementTest, JacobianInverse2DTriangle) {
|
|
/* Initialize a random triangle in 2D that is not degenerate (can be
|
|
inverted). */
|
|
Eigen::Matrix<double, 2, 3> xa;
|
|
// clang-format off
|
|
xa << 0.65, 0.18, 0.79,
|
|
0.52, 0.34, 0.34;
|
|
// clang-format on
|
|
const auto dxdxi = tri_2d_.CalcJacobian(xa);
|
|
const auto dxidx = tri_2d_.CalcJacobianPseudoinverse(dxdxi);
|
|
EXPECT_EQ(dxidx.size(), kNumSamplesTri);
|
|
for (int i = 0; i < kNumSamplesTri; ++i) {
|
|
EXPECT_TRUE(CompareMatrices(dxdxi[i] * dxidx[i],
|
|
MatrixX<double>::Identity(2, 2),
|
|
4.0 * std::numeric_limits<double>::epsilon()));
|
|
}
|
|
}
|
|
|
|
TEST_F(IsoparametricElementTest, JacobianInverse3DTriangle) {
|
|
/* Initialize a random triangle in 3D that is not degenerate (can be
|
|
inverted). */
|
|
Matrix3<double> xa;
|
|
// clang-format off
|
|
xa << 0.65, 0.18, 0.79,
|
|
0.52, 0.34, 0.34,
|
|
0.58, 0.43, 0.19;
|
|
// clang-format on
|
|
const auto dxdxi = tri_3d_.CalcJacobian(xa);
|
|
const auto dxidx = tri_3d_.CalcJacobianPseudoinverse(dxdxi);
|
|
EXPECT_EQ(dxidx.size(), kNumSamplesTri);
|
|
// The normal of the triangle should live in the null space of dξ/dx.
|
|
auto n = (xa.col(1) - xa.col(0)).cross(xa.col(2) - xa.col(0)).normalized();
|
|
for (int i = 0; i < kNumSamplesTri; ++i) {
|
|
/* The Jacobian dx/dξ is of dimension 3 x 2 and has full column rank (2).
|
|
dξ/dx should be the pseudoinverse of the Jacobian. */
|
|
EXPECT_TRUE(CompareMatrices(dxidx[i] * dxdxi[i],
|
|
MatrixX<double>::Identity(2, 2),
|
|
std::numeric_limits<double>::epsilon()));
|
|
// dx/dξ * dξ/dx should be symmetric.
|
|
auto A = dxdxi[i] * dxidx[i];
|
|
EXPECT_TRUE(CompareMatrices(A, A.transpose(),
|
|
std::numeric_limits<double>::epsilon()));
|
|
EXPECT_TRUE(CompareMatrices(VectorX<double>::Zero(2), dxidx[i] * n,
|
|
std::numeric_limits<double>::epsilon()));
|
|
}
|
|
}
|
|
|
|
TEST_F(IsoparametricElementTest, JacobianInverseTetrahedon) {
|
|
// Initialize a random tetrahedron in 3D that is not degenerate (can be
|
|
// inverted).
|
|
// clang-format off
|
|
Eigen::Matrix<double, 3, 4> xa;
|
|
xa << 0.65, 0.18, 0.79, -2.12,
|
|
0.52, 0.34, 0.34, 0.12,
|
|
0.58, 0.43, 0.19, -1.34;
|
|
// clang-format on
|
|
const auto dxdxi = tet_.CalcJacobian(xa);
|
|
const auto dxidx = tet_.CalcJacobianPseudoinverse(dxdxi);
|
|
EXPECT_EQ(dxidx.size(), kNumSamplesTet);
|
|
for (int i = 0; i < kNumSamplesTet; ++i) {
|
|
EXPECT_TRUE(CompareMatrices(dxidx[i] * dxdxi[i],
|
|
MatrixX<double>::Identity(3, 3),
|
|
16.0 * std::numeric_limits<double>::epsilon()));
|
|
}
|
|
}
|
|
|
|
TEST_F(IsoparametricElementTest, DegenerateElementPseudoinverseJacobian) {
|
|
/* Initialize a triangle in 3D that is degenerate. */
|
|
Matrix3<double> xa;
|
|
// clang-format off
|
|
xa << 1, 0, 0.5,
|
|
0, 1, 0.5,
|
|
0, 0, 0;
|
|
// clang-format on
|
|
const auto dxdxi = tri_3d_.CalcJacobian(xa);
|
|
DRAKE_EXPECT_THROWS_MESSAGE(
|
|
tri_3d_.CalcJacobianPseudoinverse(dxdxi),
|
|
"The element is degenerate and does not have a valid Jacobian "
|
|
"pseudoinverse \\(the pseudoinverse is not the left inverse\\).");
|
|
}
|
|
|
|
TEST_F(IsoparametricElementTest, InterpolateScalar2D) {
|
|
const double u0 = LinearScalarFunction2D({0, 0});
|
|
const double u1 = LinearScalarFunction2D({1, 0});
|
|
const double u2 = LinearScalarFunction2D({0, 1});
|
|
const Vector3<double> u(u0, u1, u2);
|
|
// Linear simplex element should interpolate linear functions perfectly.
|
|
for (int i = 0; i < kNumSamplesTri; ++i) {
|
|
EXPECT_DOUBLE_EQ(LinearScalarFunction2D(tri_2d_.locations()[i]),
|
|
tri_2d_.InterpolateNodalValues<1>(u.transpose())[i](0));
|
|
}
|
|
}
|
|
|
|
TEST_F(IsoparametricElementTest, InterpolateScalar3D) {
|
|
const double u0 = LinearScalarFunction3D({0, 0, 0});
|
|
const double u1 = LinearScalarFunction3D({1, 0, 0});
|
|
const double u2 = LinearScalarFunction3D({0, 1, 0});
|
|
const double u3 = LinearScalarFunction3D({0, 0, 1});
|
|
const Vector4<double> u(u0, u1, u2, u3);
|
|
// Linear simplex element should interpolate linear functions perfectly.
|
|
for (int i = 0; i < kNumSamplesTet; ++i) {
|
|
EXPECT_EQ(LinearScalarFunction3D(tet_.locations()[i]),
|
|
tet_.InterpolateNodalValues<1>(u.transpose())[i](0));
|
|
}
|
|
}
|
|
|
|
TEST_F(IsoparametricElementTest, InterpolateVector2D) {
|
|
const Vector3<double> u0(LinearVectorFunction2D({0, 0}));
|
|
const Vector3<double> u1(LinearVectorFunction2D({1, 0}));
|
|
const Vector3<double> u2(LinearVectorFunction2D({0, 1}));
|
|
Matrix3<double> u;
|
|
u << u0, u1, u2;
|
|
// Linear simplex element should interpolate linear functions perfectly.
|
|
for (int i = 0; i < kNumSamplesTri; ++i) {
|
|
EXPECT_TRUE(CompareMatrices(LinearVectorFunction2D(tri_2d_.locations()[i]),
|
|
tri_2d_.InterpolateNodalValues<3>(u)[i],
|
|
8.0 * std::numeric_limits<double>::epsilon()));
|
|
}
|
|
}
|
|
|
|
TEST_F(IsoparametricElementTest, InterpolateVector3D) {
|
|
const Vector3<double> u0(LinearVectorFunction3D({0, 0, 0}));
|
|
const Vector3<double> u1(LinearVectorFunction3D({1, 0, 0}));
|
|
const Vector3<double> u2(LinearVectorFunction3D({0, 1, 0}));
|
|
const Vector3<double> u3(LinearVectorFunction3D({0, 0, 1}));
|
|
Eigen::Matrix<double, 3, 4> u;
|
|
u << u0, u1, u2, u3;
|
|
// Linear simplex element should interpolate linear functions perfectly.
|
|
for (int i = 0; i < kNumSamplesTet; ++i) {
|
|
EXPECT_TRUE(CompareMatrices(LinearVectorFunction3D(tet_.locations()[i]),
|
|
tet_.InterpolateNodalValues<3>(u)[i],
|
|
8.0 * std::numeric_limits<double>::epsilon()));
|
|
}
|
|
}
|
|
|
|
} // namespace
|
|
} // namespace internal
|
|
} // namespace fem
|
|
} // namespace multibody
|
|
} // namespace drake
|