feat: adopt lumped mass and reduced-coordinate shell HHT
Integrate Hex8 mass verification and fixed-null-basis shell dynamics with numerical and physics handoff evidence. Preserve interrupted reference and full-validation status; full integration validation is deferred until after performance optimization.
This commit is contained in:
@@ -79,18 +79,18 @@ fesa::Vector Difference(const fesa::Vector& left, const fesa::Vector& right,
|
||||
return result;
|
||||
}
|
||||
|
||||
TEST(NonlinearMitc4Inertia, IntegratesTheRectangleConsistentMassAndNull) {
|
||||
TEST(NonlinearMitc4Inertia, IntegratesTheRectangleNodalLumpedMassAndNull) {
|
||||
const auto nodes = Nodes();
|
||||
const auto evaluation =
|
||||
Inertia(nodes).Evaluate({}, fesa::Vector{24U}, fesa::Vector{24U});
|
||||
ASSERT_TRUE(evaluation.HasValue());
|
||||
const fesa::Matrix& mass = evaluation.Value().mass;
|
||||
constexpr std::array<double, 4> kWeights{4.0, 2.0, 1.0, 2.0};
|
||||
constexpr double kC0 = 0.8 / 36.0;
|
||||
for (std::size_t row = 0U; row < 4U; ++row) {
|
||||
for (std::size_t column = 0U; column < 4U; ++column) {
|
||||
const std::size_t offset = (column + 4U - row) % 4U;
|
||||
EXPECT_NEAR(mass(6U * row, 6U * column), kC0 * kWeights[offset], 1.0e-12);
|
||||
EXPECT_NEAR(mass(6U * row, 6U * column), row == column ? 0.2 : 0.0,
|
||||
1.0e-12);
|
||||
EXPECT_NEAR(mass(6U * row + 3U, 6U * column + 3U),
|
||||
row == column ? 0.2 * 0.2 * 0.2 / 12.0 : 0.0, 1.0e-12);
|
||||
}
|
||||
EXPECT_NEAR(mass(6U * row + 5U, 6U * row + 5U), 0.0, 1.0e-12);
|
||||
}
|
||||
@@ -122,6 +122,133 @@ TEST(NonlinearMitc4Inertia, UsesPhysicalAccelerationAndExactCurrentForce) {
|
||||
}
|
||||
}
|
||||
|
||||
TEST(NonlinearMitc4Inertia,
|
||||
PreservesTinyAngularAccelerationAlongsideRigidTranslationAcceleration) {
|
||||
for (const bool rotated : {false, true}) {
|
||||
SCOPED_TRACE(rotated);
|
||||
auto nodes = Nodes();
|
||||
auto frames = Frames();
|
||||
const auto rotation = fesa::ExpSkew(
|
||||
rotated ? fesa::Vector3{0.4, -0.2, 0.7} : fesa::Vector3{});
|
||||
const auto transform = [&](const std::array<double, 3>& value) {
|
||||
std::array<double, 3> result{};
|
||||
for (std::size_t i = 0U; i < 3U; ++i)
|
||||
for (std::size_t j = 0U; j < 3U; ++j)
|
||||
result[i] += rotation(i, j) * value[j];
|
||||
return result;
|
||||
};
|
||||
for (std::size_t node = 0U; node < 4U; ++node) {
|
||||
nodes[node].coordinates = transform(nodes[node].coordinates);
|
||||
frames[node].director = transform(frames[node].director);
|
||||
frames[node].tangent_a = transform(frames[node].tangent_a);
|
||||
frames[node].tangent_b = transform(frames[node].tangent_b);
|
||||
}
|
||||
const fesa::ShellSection section{"Section", 0.2, 0U, {"tiny.inp", 1U}};
|
||||
const fesa::LinearElasticMaterial material{
|
||||
"Material", 120.0, 0.25, {"tiny.inp", 2U}};
|
||||
const auto inertia = fesa::NonlinearMitc4Inertia::Create(
|
||||
Pointers(nodes), frames, section, material, 1.0);
|
||||
ASSERT_TRUE(inertia.HasValue());
|
||||
fesa::Vector acceleration{24U};
|
||||
for (std::size_t node = 0U; node < 4U; ++node)
|
||||
for (std::size_t c = 0U; c < 3U; ++c) {
|
||||
acceleration[6U * node + c] = c == 0U ? 15.0 : (c == 1U ? 12.0 : 0.0);
|
||||
acceleration[6U * node + 3U + c] = 1e-17 * frames[node].tangent_a[c];
|
||||
}
|
||||
const auto actual = inertia.Value().Evaluate({}, fesa::Vector{24U}, acceleration);
|
||||
ASSERT_TRUE(actual.HasValue());
|
||||
// Nodal m=rho*A*t/4=.2, rotary inertia=m*t^2/12; c=0 at v=0.
|
||||
constexpr double nodal_rotary_inertia = 0.2 * 0.2 * 0.2 / 12.0;
|
||||
for (std::size_t node = 0U; node < 4U; ++node) {
|
||||
double error = 0.0;
|
||||
double scale = 0.0;
|
||||
for (std::size_t c = 0U; c < 3U; ++c) {
|
||||
const auto full = 6U * node + 3U + c;
|
||||
const double expected = nodal_rotary_inertia * acceleration[full];
|
||||
error = std::max(error, std::abs(actual.Value().force[full] - expected));
|
||||
scale = std::max(scale, std::abs(expected));
|
||||
EXPECT_DOUBLE_EQ(actual.Value().convective_force[full], 0.0);
|
||||
}
|
||||
ASSERT_GT(scale, 0.0);
|
||||
EXPECT_LE(error / scale, 1e-12);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// At phi=0, Dq(M*a)[tangent_b]=-.5*j*epsilon*director for
|
||||
// a_phi=epsilon*tangent_a. An origin at the active midpoint also isolates
|
||||
// L=j*v_phi and Ldot=j*a_phi from orbital contributions.
|
||||
TEST(NonlinearMitc4Inertia, MixedScaleRotationDerivativeAndAngularMomentum) {
|
||||
for (const bool rotated : {false, true}) {
|
||||
auto nodes = Nodes();
|
||||
auto frames = Frames();
|
||||
const auto rotation = fesa::ExpSkew(
|
||||
rotated ? fesa::Vector3{0.4, -0.2, 0.7} : fesa::Vector3{});
|
||||
const auto transform = [&](const std::array<double, 3>& value) {
|
||||
std::array<double, 3> result{};
|
||||
for (std::size_t i = 0U; i < 3U; ++i)
|
||||
for (std::size_t j = 0U; j < 3U; ++j)
|
||||
result[i] += rotation(i, j) * value[j];
|
||||
return result;
|
||||
};
|
||||
for (std::size_t node = 0U; node < 4U; ++node) {
|
||||
nodes[node].coordinates = transform(nodes[node].coordinates);
|
||||
frames[node].director = transform(frames[node].director);
|
||||
frames[node].tangent_a = transform(frames[node].tangent_a);
|
||||
frames[node].tangent_b = transform(frames[node].tangent_b);
|
||||
}
|
||||
const auto origin = nodes[0].coordinates;
|
||||
for (auto& node : nodes)
|
||||
for (std::size_t c = 0U; c < 3U; ++c)
|
||||
node.coordinates[c] -= origin[c];
|
||||
const fesa::ShellSection section{"Section", 0.2, 0U, {"tiny.inp", 1U}};
|
||||
const fesa::LinearElasticMaterial material{
|
||||
"Material", 120.0, 0.25, {"tiny.inp", 2U}};
|
||||
const auto inertia = fesa::NonlinearMitc4Inertia::Create(
|
||||
Pointers(nodes), frames, section, material, 1.0);
|
||||
ASSERT_TRUE(inertia.HasValue());
|
||||
constexpr double nodal_rotary_inertia = 0.2 * 0.2 * 0.2 / 12.0;
|
||||
for (int power = 7; power <= 20; ++power) {
|
||||
SCOPED_TRACE(rotated);
|
||||
SCOPED_TRACE(power);
|
||||
const double epsilon = std::pow(10.0, -power);
|
||||
fesa::Vector acceleration{24U};
|
||||
acceleration[0] = 15.0;
|
||||
acceleration[1] = 12.0;
|
||||
for (std::size_t c = 0U; c < 3U; ++c)
|
||||
acceleration[3U + c] = epsilon * frames[0].tangent_a[c];
|
||||
const auto actual = inertia.Value().Evaluate(
|
||||
{}, fesa::Vector{24U}, acceleration);
|
||||
ASSERT_TRUE(actual.HasValue());
|
||||
double derivative_error = 0.0;
|
||||
double rate_error = 0.0;
|
||||
for (std::size_t c = 0U; c < 3U; ++c) {
|
||||
double derivative = 0.0;
|
||||
for (std::size_t k = 0U; k < 3U; ++k)
|
||||
derivative += actual.Value().position_tangent(3U + c, 3U + k) *
|
||||
frames[0].tangent_b[k];
|
||||
derivative_error = std::max(derivative_error,
|
||||
std::abs(derivative + 0.5 * nodal_rotary_inertia * epsilon *
|
||||
frames[0].director[c]));
|
||||
rate_error = std::max(rate_error,
|
||||
std::abs(actual.Value().angular_momentum_rate[c] -
|
||||
nodal_rotary_inertia * acceleration[3U + c]));
|
||||
}
|
||||
EXPECT_LE(derivative_error / (nodal_rotary_inertia * epsilon), 1e-12);
|
||||
EXPECT_LE(rate_error / (nodal_rotary_inertia * epsilon), 1e-12);
|
||||
const auto moving = inertia.Value().Evaluate(
|
||||
{}, acceleration, fesa::Vector{24U});
|
||||
ASSERT_TRUE(moving.HasValue());
|
||||
double momentum_error = 0.0;
|
||||
for (std::size_t c = 0U; c < 3U; ++c)
|
||||
momentum_error = std::max(momentum_error,
|
||||
std::abs(moving.Value().angular_momentum[c] -
|
||||
nodal_rotary_inertia * acceleration[3U + c]));
|
||||
EXPECT_LE(momentum_error / (nodal_rotary_inertia * epsilon), 1e-12);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
using V3 = fesa::Vector3;
|
||||
|
||||
V3 Multiply(const fesa::Matrix& matrix, const V3& value) {
|
||||
@@ -240,6 +367,48 @@ Fixture Warped() {
|
||||
return fixture;
|
||||
}
|
||||
|
||||
Fixture WarpedNonuniformDirectors() {
|
||||
Fixture fixture = Warped();
|
||||
for (const std::size_t node : {0U, 3U, 1U, 2U}) {
|
||||
const double angle = (node == 0U || node == 3U) ? -0.1 : 0.1;
|
||||
const double cosine = std::cos(angle);
|
||||
const double sine = std::sin(angle);
|
||||
fixture.frames[node].tangent_a = {cosine, 0.0, -sine};
|
||||
fixture.frames[node].tangent_b = {0.0, 1.0, 0.0};
|
||||
fixture.frames[node].director = {sine, 0.0, cosine};
|
||||
}
|
||||
return fixture;
|
||||
}
|
||||
|
||||
V3 RotateFrame(const V3& value) {
|
||||
constexpr double kCosZ = 0.7648421872844885;
|
||||
constexpr double kSinZ = 0.6442176872376910;
|
||||
constexpr double kCosX = 0.9210609940028851;
|
||||
constexpr double kSinX = 0.3894183423086505;
|
||||
return {kCosZ * value.X() - kSinZ * kCosX * value.Y() +
|
||||
kSinZ * kSinX * value.Z(),
|
||||
kSinZ * value.X() + kCosZ * kCosX * value.Y() -
|
||||
kCosZ * kSinX * value.Z(),
|
||||
kSinX * value.Y() + kCosX * value.Z()};
|
||||
}
|
||||
|
||||
Fixture FlatRotatedLargeOffset() {
|
||||
Fixture fixture;
|
||||
for (std::size_t node = 0U; node < 4U; ++node) {
|
||||
fixture.nodes[node].coordinates =
|
||||
(RotateFrame(V3{fixture.nodes[node].coordinates}) +
|
||||
V3{1.0e8, -2.0e8, 3.0e8})
|
||||
.Components();
|
||||
fixture.frames[node].director =
|
||||
RotateFrame(V3{fixture.frames[node].director}).Components();
|
||||
fixture.frames[node].tangent_a =
|
||||
RotateFrame(V3{fixture.frames[node].tangent_a}).Components();
|
||||
fixture.frames[node].tangent_b =
|
||||
RotateFrame(V3{fixture.frames[node].tangent_b}).Components();
|
||||
}
|
||||
return fixture;
|
||||
}
|
||||
|
||||
struct State {
|
||||
fesa::NonlinearMitc4TrialDofs q{};
|
||||
fesa::Vector v{24U};
|
||||
@@ -272,6 +441,44 @@ fesa::Vector ApplyD(fesa::Vector vector, const double length) {
|
||||
return vector;
|
||||
}
|
||||
|
||||
TEST(NonlinearMitc4Inertia,
|
||||
FlatRotatedLargeOffsetKeepsCenteredRotationalInertiaExactlyZero) {
|
||||
const Fixture fixture = FlatRotatedLargeOffset();
|
||||
const auto inertia = fixture.Create();
|
||||
ASSERT_TRUE(inertia.HasValue());
|
||||
fesa::Vector acceleration{24U};
|
||||
for (std::size_t node = 0U; node < 4U; ++node) {
|
||||
acceleration[6U * node] = 0.3;
|
||||
acceleration[6U * node + 1U] = -0.2;
|
||||
acceleration[6U * node + 2U] = 0.1;
|
||||
}
|
||||
const auto value = inertia.Value().Evaluate({}, fesa::Vector{24U}, acceleration);
|
||||
ASSERT_TRUE(value.HasValue());
|
||||
const auto mass_times_acceleration = value.Value().mass.Multiply(acceleration);
|
||||
for (std::size_t node = 0U; node < 4U; ++node) {
|
||||
for (std::size_t component = 0U; component < 3U; ++component) {
|
||||
EXPECT_DOUBLE_EQ(value.Value().force[6U * node + 3U + component], 0.0);
|
||||
EXPECT_DOUBLE_EQ(mass_times_acceleration[6U * node + 3U + component],
|
||||
0.0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST(NonlinearMitc4Inertia, WarpedGeometryRetainsNonzeroFirstThicknessMoment) {
|
||||
const auto inertia = WarpedNonuniformDirectors().Create();
|
||||
ASSERT_TRUE(inertia.HasValue());
|
||||
const auto value = inertia.Value().Evaluate({}, fesa::Vector{24U}, fesa::Vector{24U});
|
||||
ASSERT_TRUE(value.HasValue());
|
||||
double coupling = 0.0;
|
||||
for (std::size_t row = 0U; row < 24U; ++row) {
|
||||
for (std::size_t column = 0U; column < 24U; ++column) {
|
||||
if (row % 6U >= 3U && column % 6U < 3U)
|
||||
coupling = std::hypot(coupling, value.Value().mass(row, column));
|
||||
}
|
||||
}
|
||||
EXPECT_GT(coupling, 0.0);
|
||||
}
|
||||
|
||||
fesa::Matrix ScaledMatrix(const fesa::Matrix& matrix, const double length) {
|
||||
fesa::Matrix result{matrix.Rows(), matrix.Columns()};
|
||||
for (std::size_t i = 0; i < matrix.Rows(); ++i) {
|
||||
@@ -459,7 +666,8 @@ struct Integral {
|
||||
V3 angular_momentum_rate{};
|
||||
};
|
||||
|
||||
Integral IntegrateFivePoints(const Fixture& fixture, const State& state) {
|
||||
Integral IntegrateFivePoints(const Fixture& fixture, const State& state,
|
||||
const bool lumped = true) {
|
||||
const double r = std::sqrt(10.0 / 7.0);
|
||||
const double outer = std::sqrt(5.0 + 2.0 * r) / 3.0;
|
||||
const double inner = std::sqrt(5.0 - 2.0 * r) / 3.0;
|
||||
@@ -490,63 +698,76 @@ Integral IntegrateFivePoints(const Fixture& fixture, const State& state) {
|
||||
for (std::size_t xi = 0; xi < 5; ++xi) {
|
||||
for (std::size_t eta = 0; eta < 5; ++eta) {
|
||||
for (std::size_t zeta = 0; zeta < 5; ++zeta) {
|
||||
const double z = 0.5 * fixture.thickness * points[zeta];
|
||||
std::array<double, 4> shape{};
|
||||
std::array<V3, 24> h{};
|
||||
V3 a1{};
|
||||
V3 a2{};
|
||||
V3 a3{};
|
||||
V3 position{};
|
||||
V3 velocity{};
|
||||
V3 acceleration{};
|
||||
for (std::size_t node = 0; node < 4; ++node) {
|
||||
shape[node] = 0.25 * (1.0 + sx[node] * points[xi]) *
|
||||
(1.0 + sy[node] * points[eta]);
|
||||
const double dx = 0.25 * sx[node] * (1.0 + sy[node] * points[eta]);
|
||||
const double dy = 0.25 * sy[node] * (1.0 + sx[node] * points[xi]);
|
||||
const V3 d0{fixture.frames[node].director};
|
||||
const V3 x0{fixture.nodes[node].coordinates};
|
||||
a1 = a1 + dx * (x0 + z * d0);
|
||||
a2 = a2 + dy * (x0 + z * d0);
|
||||
a3 = a3 + 0.5 * fixture.thickness * shape[node] * d0;
|
||||
position = position + shape[node] * (x0 + state.q.displacement[node] +
|
||||
z * directors[node]);
|
||||
acceleration = acceleration + z * shape[node] * convective[node];
|
||||
for (std::size_t j = 0; j < 3; ++j) {
|
||||
std::array<double, 3> axis{};
|
||||
axis[j] = shape[node];
|
||||
h[6U * node + j] = V3{axis};
|
||||
h[6U * node + 3U + j] = z * shape[node] * first[node][j];
|
||||
for (std::size_t mass_node = 0; mass_node < (lumped ? 4U : 1U);
|
||||
++mass_node) {
|
||||
std::array<double, 4> kinetic_shape{};
|
||||
kinetic_shape[mass_node] = 1.0;
|
||||
const double z = 0.5 * fixture.thickness * points[zeta];
|
||||
std::array<double, 4> shape{};
|
||||
std::array<V3, 24> h{};
|
||||
V3 a1{};
|
||||
V3 a2{};
|
||||
V3 a3{};
|
||||
V3 position{};
|
||||
V3 velocity{};
|
||||
V3 acceleration{};
|
||||
for (std::size_t node = 0; node < 4; ++node) {
|
||||
shape[node] = 0.25 * (1.0 + sx[node] * points[xi]) *
|
||||
(1.0 + sy[node] * points[eta]);
|
||||
if (!lumped) kinetic_shape[node] = shape[node];
|
||||
const double dx = 0.25 * sx[node] * (1.0 + sy[node] * points[eta]);
|
||||
const double dy = 0.25 * sy[node] * (1.0 + sx[node] * points[xi]);
|
||||
const V3 d0{fixture.frames[node].director};
|
||||
const V3 x0{fixture.nodes[node].coordinates};
|
||||
a1 = a1 + dx * (x0 + z * d0);
|
||||
a2 = a2 + dy * (x0 + z * d0);
|
||||
a3 = a3 + 0.5 * fixture.thickness * shape[node] * d0;
|
||||
position = position +
|
||||
kinetic_shape[node] * (x0 + state.q.displacement[node] +
|
||||
z * directors[node]);
|
||||
acceleration =
|
||||
acceleration + z * kinetic_shape[node] * convective[node];
|
||||
for (std::size_t j = 0; j < 3; ++j) {
|
||||
std::array<double, 3> axis{};
|
||||
axis[j] = kinetic_shape[node];
|
||||
h[6U * node + j] = V3{axis};
|
||||
h[6U * node + 3U + j] = z * kinetic_shape[node] * first[node][j];
|
||||
}
|
||||
}
|
||||
}
|
||||
const double weight = fixture.density * weights[xi] * weights[eta] *
|
||||
weights[zeta] * a1.Dot(a2.Cross(a3));
|
||||
EXPECT_GT(weight, 0.0);
|
||||
integral.total_mass += weight;
|
||||
for (std::size_t i = 0; i < 24; ++i) {
|
||||
velocity = velocity + h[i] * state.v[i];
|
||||
acceleration = acceleration + h[i] * state.a[i];
|
||||
}
|
||||
integral.energy += 0.5 * weight * velocity.Dot(velocity);
|
||||
integral.power += weight * velocity.Dot(acceleration);
|
||||
integral.momentum = integral.momentum + weight * velocity;
|
||||
integral.angular_momentum =
|
||||
integral.angular_momentum + weight * position.Cross(velocity);
|
||||
integral.momentum_rate = integral.momentum_rate + weight * acceleration;
|
||||
integral.angular_momentum_rate = integral.angular_momentum_rate +
|
||||
weight * position.Cross(acceleration);
|
||||
for (std::size_t i = 0; i < 24; ++i) {
|
||||
integral.force[i] += weight * h[i].Dot(acceleration);
|
||||
for (std::size_t j = 0; j < 24; ++j) {
|
||||
integral.mass(i, j) += weight * h[i].Dot(h[j]);
|
||||
const double weight = fixture.density * weights[xi] * weights[eta] *
|
||||
weights[zeta] * a1.Dot(a2.Cross(a3)) *
|
||||
(lumped ? shape[mass_node] : 1.0);
|
||||
EXPECT_GT(weight, 0.0);
|
||||
integral.total_mass += weight;
|
||||
for (std::size_t i = 0; i < 24; ++i) {
|
||||
velocity = velocity + h[i] * state.v[i];
|
||||
acceleration = acceleration + h[i] * state.a[i];
|
||||
}
|
||||
}
|
||||
for (std::size_t i = 0; i < 4; ++i) {
|
||||
for (std::size_t j = 0; j < 4; ++j) {
|
||||
double moment = weight * shape[i] * shape[j];
|
||||
for (std::size_t k = 0; k < 3; ++k) {
|
||||
integral.moments[k][i][j] += moment;
|
||||
moment *= z;
|
||||
integral.energy += 0.5 * weight * velocity.Dot(velocity);
|
||||
integral.power += weight * velocity.Dot(acceleration);
|
||||
integral.momentum = integral.momentum + weight * velocity;
|
||||
integral.angular_momentum =
|
||||
integral.angular_momentum + weight * position.Cross(velocity);
|
||||
integral.momentum_rate =
|
||||
integral.momentum_rate + weight * acceleration;
|
||||
integral.angular_momentum_rate =
|
||||
integral.angular_momentum_rate +
|
||||
weight * position.Cross(acceleration);
|
||||
for (std::size_t i = 0; i < 24; ++i) {
|
||||
integral.force[i] += weight * h[i].Dot(acceleration);
|
||||
for (std::size_t j = 0; j < 24; ++j) {
|
||||
integral.mass(i, j) += weight * h[i].Dot(h[j]);
|
||||
}
|
||||
}
|
||||
for (std::size_t i = 0; i < 4; ++i) {
|
||||
for (std::size_t j = 0; j < 4; ++j) {
|
||||
double moment =
|
||||
lumped ? (i == mass_node && j == mass_node ? weight : 0.0)
|
||||
: weight * shape[i] * shape[j];
|
||||
for (std::size_t k = 0; k < 3; ++k) {
|
||||
integral.moments[k][i][j] += moment;
|
||||
moment *= z;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -557,8 +778,9 @@ Integral IntegrateFivePoints(const Fixture& fixture, const State& state) {
|
||||
}
|
||||
|
||||
TEST(NonlinearMitc4Inertia,
|
||||
MatchesIndependentWarpedVolumeMomentsEnergyAndPower) {
|
||||
for (const Fixture fixture : {Fixture{}, Warped()}) {
|
||||
MatchesIndependentWarpedNodalMomentsEnergyAndPower) {
|
||||
for (const Fixture fixture :
|
||||
{Fixture{}, Warped(), WarpedNonuniformDirectors()}) {
|
||||
const auto inertia = fixture.Create();
|
||||
ASSERT_TRUE(inertia.HasValue());
|
||||
const State state = FiniteState();
|
||||
@@ -658,6 +880,39 @@ TEST(NonlinearMitc4Inertia,
|
||||
}
|
||||
}
|
||||
|
||||
TEST(NonlinearMitc4Inertia,
|
||||
InitialMassMatchesEigenLumpingAndFiniteEnergyObeysJensen) {
|
||||
for (const Fixture fixture : {Fixture{}, Warped()}) {
|
||||
std::array<std::array<double, 3>, 4> directors{};
|
||||
for (std::size_t node = 0; node < 4; ++node)
|
||||
directors[node] = fixture.frames[node].director;
|
||||
const auto shell = fesa::Mitc4Shell::Create(
|
||||
Pointers(fixture.nodes), directors,
|
||||
{"Section", fixture.thickness, 0U, {"mass-test", 1U}},
|
||||
{"Material", fixture.modulus, 0.25, {"mass-test", 2U}});
|
||||
ASSERT_TRUE(shell.HasValue());
|
||||
const auto eigen = shell.Value().Mass(fixture.density);
|
||||
ASSERT_TRUE(eigen.HasValue());
|
||||
const auto inertia = fixture.Create();
|
||||
ASSERT_TRUE(inertia.HasValue());
|
||||
const auto initial =
|
||||
inertia.Value().Evaluate({}, fesa::Vector{24U}, fesa::Vector{24U});
|
||||
ASSERT_TRUE(initial.HasValue());
|
||||
for (std::size_t i = 0; i < 24; ++i) {
|
||||
for (std::size_t j = 0; j < 24; ++j) {
|
||||
EXPECT_NEAR(initial.Value().mass(i, j),
|
||||
eigen.Value().lumped_global24(i, j), 1.0e-12);
|
||||
if (i / 6U != j / 6U) EXPECT_EQ(initial.Value().mass(i, j), 0.0);
|
||||
}
|
||||
}
|
||||
const State state = FiniteState();
|
||||
const auto lumped = IntegrateFivePoints(fixture, state, true);
|
||||
const auto consistent = IntegrateFivePoints(fixture, state, false);
|
||||
EXPECT_GT(lumped.energy - consistent.energy, 0.0);
|
||||
EXPECT_NEAR(lumped.total_mass, consistent.total_mass, 1.0e-12);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(DynamicRotation, ThirdDerivativesMatchAllPermutationsAndInteriorSweep) {
|
||||
const std::array<V3, 5> states{
|
||||
V3{}, V3{1.0e-6, -2.0e-6, 3.0e-6}, V3{0.2, -0.3, 0.4},
|
||||
|
||||
Reference in New Issue
Block a user