Files
FESADev/tests/unit/elements/element_capabilities_test.cpp
T

415 lines
19 KiB
C++

#include "fesa/elements/element_capabilities.h"
#include <gtest/gtest.h>
#include <array>
#include <cstring>
#include <limits>
#include <memory>
#include <type_traits>
#include <vector>
#include "fesa/elements/element_factory.h"
#include "fesa/elements/hex8_element.h"
#include "fesa/elements/nonlinear_mitc4_inertia.h"
#include "fesa/elements/thermal_shell.h"
namespace {
void Same(double a, double b) {
EXPECT_EQ(std::memcmp(&a, &b, sizeof(double)), 0);
}
template <std::size_t N>
void Same(const std::array<double, N>& a, const std::array<double, N>& b) {
for (std::size_t i = 0; i < N; ++i) Same(a[i], b[i]);
}
void Same(const fesa::Vector& a, const fesa::Vector& b) {
ASSERT_EQ(a.Size(), b.Size());
for (std::size_t i = 0; i < a.Size(); ++i) Same(a[i], b[i]);
}
void Same(const fesa::Matrix& a, const fesa::Matrix& b) {
ASSERT_EQ(a.Rows(), b.Rows());
ASSERT_EQ(a.Columns(), b.Columns());
for (std::size_t i = 0; i < a.Rows(); ++i)
for (std::size_t j = 0; j < a.Columns(); ++j) Same(a(i, j), b(i, j));
}
void Same(const fesa::Vector3& a, const fesa::Vector3& b) {
for (std::size_t i = 0; i < 3; ++i) Same(a[i], b[i]);
}
void Same(const fesa::Status& a, const fesa::Status& b) {
EXPECT_EQ(a.Category(), b.Category());
ASSERT_EQ(a.Diagnostics().size(), b.Diagnostics().size());
for (std::size_t i = 0; i < a.Diagnostics().size(); ++i) {
const auto& x = a.Diagnostics()[i];
const auto& y = b.Diagnostics()[i];
EXPECT_EQ(x.code, y.code);
EXPECT_EQ(x.location.file, y.location.file);
EXPECT_EQ(x.location.line, y.location.line);
EXPECT_EQ(x.keyword, y.keyword);
EXPECT_EQ(x.entity_identity, y.entity_identity);
EXPECT_EQ(x.message, y.message);
}
}
fesa::ElementDofLayout ShellLayout() {
return {{"shell", 9, "0009"}, {7, 2, 9, 4},
{fesa::DofComponent::kUx, fesa::DofComponent::kUy,
fesa::DofComponent::kUz, fesa::DofComponent::kUrx,
fesa::DofComponent::kUry, fesa::DofComponent::kUrz}};
}
struct ShellInput {
std::array<fesa::Node, 4> nodes{{
{{"shell", 1, "1"}, {-1., -1., 0.}, {"cap.inp", 1}},
{{"shell", 2, "2"}, {1., -1., 0.}, {"cap.inp", 2}},
{{"shell", 3, "3"}, {1., 1., 0.}, {"cap.inp", 3}},
{{"shell", 4, "4"}, {-1., 1., 0.}, {"cap.inp", 4}}}};
std::array<fesa::ShellNodeInitialFrame, 4> frames{{
{0, {0., 0., 1.}, {1., 0., 0.}, {0., 1., 0.}},
{1, {0., 0., 1.}, {1., 0., 0.}, {0., 1., 0.}},
{2, {0., 0., 1.}, {1., 0., 0.}, {0., 1., 0.}},
{3, {0., 0., 1.}, {1., 0., 0.}, {0., 1., 0.}}}};
fesa::ShellSection section{"section", .2, 0, {"cap.inp", 20}};
fesa::LinearElasticMaterial material{"elastic", 120., .25, {"cap.inp", 21}};
std::array<const fesa::Node*, 4> Pointers() const {
return {&nodes[0], &nodes[1], &nodes[2], &nodes[3]};
}
};
fesa::NonlinearMitc4TrialDofs Trial() {
fesa::NonlinearMitc4TrialDofs trial{};
for (std::size_t i = 0; i < 4; ++i) {
const double n = static_cast<double>(i + 1);
trial.displacement[i] = {.001 * n, -.002 * n, .0005 * n};
trial.rotation_vector[i] = {.01 * n, -.005 * n, .002 * n};
}
return trial;
}
void Same(const fesa::NonlinearMitc4Recovery& a,
const fesa::NonlinearMitc4Recovery& b) {
Same(a.physical_strain_energy, b.physical_strain_energy);
for (std::size_t g = 0; g < 4; ++g) {
const auto& x = a.gauss_points[g];
const auto& y = b.gauss_points[g];
EXPECT_EQ(x.location, y.location);
for (std::size_t i = 0; i < 2; ++i)
Same(x.natural_coordinates[i], y.natural_coordinates[i]);
for (std::size_t i = 0; i < 8; ++i) {
Same(x.generalized_strain[i], y.generalized_strain[i]);
Same(x.section_resultant[i], y.section_resultant[i]);
}
Same(x.local_frame.e1, y.local_frame.e1);
Same(x.local_frame.e2, y.local_frame.e2);
Same(x.local_frame.e3, y.local_frame.e3);
for (std::size_t s = 0; s < 3; ++s)
for (std::size_t i = 0; i < x.stress[s].components.size(); ++i)
Same(x.stress[s].components[i], y.stress[s].components[i]);
}
}
TEST(ElementCapabilities, ShellTrialPreservesCompleteDirectionalResponseAndFailure) {
ShellInput input;
auto made = fesa::NonlinearMitc4Shell::Create(
input.Pointers(), input.frames, input.section, input.material);
ASSERT_TRUE(made.HasValue());
auto kernel = std::make_unique<fesa::NonlinearMitc4Shell>(std::move(made.Value()));
std::unique_ptr<fesa::ShellTrialElement> capability =
std::make_unique<fesa::Mitc4TrialAdapter>(ShellLayout(), *kernel);
const auto trial = Trial();
fesa::Vector direction{24};
for (std::size_t i = 0; i < 24; ++i) direction[i] = .003 * (i + 1);
const auto saved_direction = direction;
const auto direct = fesa::EvaluateInternalResidualAndTangent(*kernel, trial, &direction);
const auto via = capability->EvaluateTrial(trial, &direction);
ASSERT_TRUE(direct.HasValue());
ASSERT_TRUE(via.HasValue());
const auto& a = direct.Value();
const auto& b = via.Value();
Same(a.internal_residual, b.internal_residual);
Same(a.internal_tangent, b.internal_tangent);
Same(a.internal_force, b.internal_force);
Same(a.recovery, b.recovery);
Same(a.physical_strain_energy, b.physical_strain_energy);
Same(a.physical_force, b.physical_force);
Same(a.drilling_force, b.drilling_force);
Same(a.drilling_tangent, b.drilling_tangent);
Same(a.drilling_metric, b.drilling_metric);
Same(a.drilling_energy, b.drilling_energy);
Same(a.force_directional_second, b.force_directional_second);
Same(a.drilling_force_directional_second, b.drilling_force_directional_second);
Same(a.drilling_constraint, b.drilling_constraint);
Same(a.drilling_constraint_gradient, b.drilling_constraint_gradient);
for (std::size_t n = 0; n < 4; ++n) {
Same(a.drilling_constraint_hessian[n], b.drilling_constraint_hessian[n]);
Same(a.drilling_constraint_third_directional_gradient[n],
b.drilling_constraint_third_directional_gradient[n]);
}
Same(direction, saved_direction);
EXPECT_EQ(capability->DofLayout().node_indices, ShellLayout().node_indices);
EXPECT_EQ(capability->DofLayout().components_per_node, ShellLayout().components_per_node);
auto invalid = trial;
invalid.rotation_vector[2] = {std::numeric_limits<double>::quiet_NaN(), 0., 0.};
const auto failed_direct = fesa::EvaluateInternalResidualAndTangent(*kernel, invalid);
const auto failed_via = capability->EvaluateTrial(invalid);
ASSERT_FALSE(failed_direct.HasValue());
ASSERT_FALSE(failed_via.HasValue());
Same(failed_direct.GetStatus(), failed_via.GetStatus());
const auto again = capability->EvaluateTrial(trial, &direction);
ASSERT_TRUE(again.HasValue());
Same(again.Value().internal_force, a.internal_force);
}
TEST(ElementCapabilities, ShellExternalConstraintRetainsEveryVariation) {
ShellInput input;
const auto kernel = fesa::NonlinearMitc4Shell::Create(
input.Pointers(), input.frames, input.section, input.material);
ASSERT_TRUE(kernel.HasValue());
fesa::Mitc4TrialAdapter adapter{ShellLayout(), kernel.Value()};
const fesa::ShellTrialElement& capability = adapter;
const fesa::Vector3 phi{.12, -.23, .07}, rate{.31, .02, -.17};
const fesa::Vector3 force{1., -2., 3.}, force_rate{.2, .7, -.4}, acceleration{.1, -.3, .9};
const auto direct = kernel.Value().EvaluateExternalDrillingConstraint(
2, phi, rate, force, force_rate, acceleration);
const auto via = capability.EvaluateExternalDrillingConstraint(
2, phi, rate, force, force_rate, acceleration);
ASSERT_TRUE(direct.HasValue());
ASSERT_TRUE(via.HasValue());
const auto& a = direct.Value();
const auto& b = via.Value();
Same(a.value, b.value);
Same(a.gradient, b.gradient);
Same(a.hessian, b.hessian);
Same(a.directional_first, b.directional_first);
Same(a.directional_second, b.directional_second);
Same(a.gradient_directional_first, b.gradient_directional_first);
Same(a.gradient_directional_second, b.gradient_directional_second);
const auto failed_direct = kernel.Value().EvaluateExternalDrillingConstraint(
4, phi, rate, force, force_rate, acceleration);
const auto failed_via = capability.EvaluateExternalDrillingConstraint(
4, phi, rate, force, force_rate, acceleration);
ASSERT_FALSE(failed_direct.HasValue());
ASSERT_FALSE(failed_via.HasValue());
Same(failed_direct.GetStatus(), failed_via.GetStatus());
}
TEST(ElementCapabilities, ShellInertiaRetainsCompletePhysicalResponse) {
ShellInput input;
const auto kernel = fesa::NonlinearMitc4Inertia::Create(
input.Pointers(), input.frames, input.section, input.material, 2.);
ASSERT_TRUE(kernel.HasValue());
std::unique_ptr<fesa::ShellInertiaElement> capability =
std::make_unique<fesa::Mitc4InertiaAdapter>(ShellLayout(), kernel.Value());
const auto trial = Trial();
fesa::Vector v{24}, acceleration{24};
for (std::size_t i = 0; i < 24; ++i) {
v[i] = .013 * (i + 1);
acceleration[i] = -.007 * (i + 1);
}
const auto saved_v = v, saved_acceleration = acceleration;
const auto direct = kernel.Value().Evaluate(trial, v, acceleration);
const auto via = capability->Evaluate(trial, v, acceleration);
ASSERT_TRUE(direct.HasValue());
ASSERT_TRUE(via.HasValue());
const auto& a = direct.Value();
const auto& b = via.Value();
Same(a.mass, b.mass);
Same(a.position_tangent, b.position_tangent);
Same(a.velocity_tangent, b.velocity_tangent);
Same(a.force, b.force);
Same(a.convective_force, b.convective_force);
Same(a.kinetic_energy, b.kinetic_energy);
Same(a.momentum, b.momentum);
Same(a.angular_momentum, b.angular_momentum);
Same(a.momentum_rate, b.momentum_rate);
Same(a.angular_momentum_rate, b.angular_momentum_rate);
Same(a.momentum_rate_scale, b.momentum_rate_scale);
Same(a.angular_momentum_rate_scale, b.angular_momentum_rate_scale);
Same(v, saved_v);
Same(acceleration, saved_acceleration);
const auto failed_direct = kernel.Value().Evaluate(trial, fesa::Vector{23}, acceleration);
const auto failed_via = capability->Evaluate(trial, fesa::Vector{23}, acceleration);
ASSERT_FALSE(failed_direct.HasValue());
ASSERT_FALSE(failed_via.HasValue());
Same(failed_direct.GetStatus(), failed_via.GetStatus());
}
TEST(ElementCapabilities, ThermalPreservesStationLayoutIntervalsAndAllTraces) {
const std::array<fesa::Vector3, 4> x{{{0., 0., 0.}, {1., 0., 0.},
{1., 1., 0.}, {0., 1., 0.}}};
const auto kernel = fesa::ThermalShell::Create(
{"thermal", 19, "0019"}, {7, 2, 9, 4}, x, 2., 3., 4., .5);
ASSERT_TRUE(kernel.HasValue());
std::unique_ptr<fesa::ThermalElement> capability =
std::make_unique<fesa::Ds4ThermalAdapter>(kernel.Value());
EXPECT_EQ(capability->DofLayout().source_id.source_label_text, "0019");
EXPECT_EQ(capability->DofLayout().node_indices, kernel.Value().NodeIndices());
for (std::size_t i = 0; i < 5; ++i)
EXPECT_EQ(static_cast<int>(capability->DofLayout().components[i]), 11 + i);
EXPECT_EQ(&capability->Conductivity(), &kernel.Value().Conductivity());
EXPECT_EQ(&capability->Capacity(), &kernel.Value().Capacity());
EXPECT_EQ(&capability->ConductivityError(), &kernel.Value().ConductivityError());
EXPECT_EQ(&capability->CapacityError(), &kernel.Value().CapacityError());
std::array<double, 20> temperature{};
for (std::size_t i = 0; i < 20; ++i) temperature[i] = static_cast<double>(i * i);
const auto saved = temperature;
const auto direct = kernel.Value().Recover(temperature);
const auto via = capability->Recover(temperature);
ASSERT_TRUE(direct.HasValue());
ASSERT_TRUE(via.HasValue());
for (std::size_t i = 0; i < 24; ++i)
Same(direct.Value().global_flux[i], via.Value().global_flux[i]);
for (std::size_t p = 0; p < 4; ++p)
for (std::size_t i = 0; i < 3; ++i)
Same(direct.Value().frames[p][i], via.Value().frames[p][i]);
EXPECT_EQ(temperature, saved);
temperature[9] = std::numeric_limits<double>::infinity();
const auto failed_direct = kernel.Value().Recover(temperature);
const auto failed_via = capability->Recover(temperature);
ASSERT_FALSE(failed_direct.HasValue());
ASSERT_FALSE(failed_via.HasValue());
Same(failed_direct.GetStatus(), failed_via.GetStatus());
}
fesa::Result<fesa::Hex8Element> Solid() {
const fesa::Hex8Element::Coordinates x{{
{-1., -1., -1.}, {1., -1., -1.}, {1., 1., -1.}, {-1., 1., -1.},
{-1., -1., 1.}, {1., -1., 1.}, {1., 1., 1.}, {-1., 1., 1.}}};
return fesa::Hex8Element::Create(
3, {{"solid", 42, "0042"}, {7, 2, 9, 4, 8, 3, 10, 5},
{fesa::DofComponent::kUx, fesa::DofComponent::kUy,
fesa::DofComponent::kUz}},
x, fesa::LinearElasticMaterial{"elastic", 120., .25, {}}, 2.);
}
void Same(const fesa::Hex8Response& a, const fesa::Hex8Response& b) {
Same(a.internal_force, b.internal_force);
Same(a.material_tangent, b.material_tangent);
Same(a.geometric_tangent, b.geometric_tangent);
Same(a.tangent, b.tangent);
Same(a.strain_energy, b.strain_energy);
for (std::size_t i = 0; i < 8; ++i) {
const auto& x = a.points[i];
const auto& y = b.points[i];
EXPECT_EQ(x.element, y.element);
EXPECT_EQ(x.integration_point, y.integration_point);
Same(x.natural_coordinates, y.natural_coordinates);
Same(x.reference_coordinates, y.reference_coordinates);
Same(x.strain, y.strain);
Same(x.cauchy_stress, y.cauchy_stress);
}
}
TEST(ElementCapabilities, SolidRetainsLinearNonlinearAndPairedResponses) {
auto made = Solid();
ASSERT_TRUE(made.HasValue());
const auto& kernel = made.Value();
const fesa::Hex8TrialAdapter adapter{kernel};
const fesa::SolidTrialElement& capability = adapter;
const fesa::Element& base = capability;
EXPECT_EQ(&base.DofLayout(), &kernel.DofLayout());
fesa::Vector u{24}, low{24};
for (std::size_t i = 0; i < 24; ++i) {
u[i] = .001 * static_cast<double>(i + 1);
low[i] = 1e-20 * static_cast<double>(i + 1);
}
const auto saved_u = u, saved_low = low;
const auto linear = kernel.EvaluateLinear(u);
const auto via_linear = capability.EvaluateLinear(u);
const auto nonlinear = kernel.EvaluateNonlinear(u);
const auto via_nonlinear = capability.EvaluateNonlinear(u);
ASSERT_TRUE(linear.HasValue());
ASSERT_TRUE(via_linear.HasValue());
ASSERT_TRUE(nonlinear.HasValue());
ASSERT_TRUE(via_nonlinear.HasValue());
Same(linear.Value(), via_linear.Value());
Same(nonlinear.Value(), via_nonlinear.Value());
for (const bool nonlinear_path : {false, true}) {
const auto direct = nonlinear_path ? kernel.EvaluateNonlinearHighLow(u, low)
: kernel.EvaluateLinearHighLow(u, low);
const auto via = nonlinear_path ? capability.EvaluateNonlinearHighLow(u, low)
: capability.EvaluateLinearHighLow(u, low);
ASSERT_TRUE(direct.HasValue());
ASSERT_TRUE(via.HasValue());
Same(direct.Value().high, via.Value().high);
Same(direct.Value().internal_force_low, via.Value().internal_force_low);
Same(direct.Value().tangent_low, via.Value().tangent_low);
Same(direct.Value().strain_energy_low, via.Value().strain_energy_low);
}
const auto k = kernel.ComputeStiffness(), via_k = base.ComputeStiffness();
const auto m = kernel.ComputeMass(), via_m = capability.ComputeMass();
ASSERT_TRUE(k.HasValue());
ASSERT_TRUE(via_k.HasValue());
ASSERT_TRUE(m.HasValue());
ASSERT_TRUE(via_m.HasValue());
Same(k.Value().values, via_k.Value().values);
Same(m.Value().values, via_m.Value().values);
Same(kernel.ReferenceWeights(), capability.ReferenceWeights());
const auto recovered = base.Recover(u), direct_recovered = kernel.Recover(u);
ASSERT_TRUE(recovered.HasValue());
ASSERT_TRUE(direct_recovered.HasValue());
EXPECT_EQ(recovered.Value().source_id.source_label_text, "0042");
const auto& rows = std::get<fesa::SolidElementResultRows>(recovered.Value().payload);
const auto& direct_rows = std::get<fesa::SolidElementResultRows>(direct_recovered.Value().payload);
ASSERT_EQ(rows.rows.size(), direct_rows.rows.size());
for (std::size_t i = 0; i < rows.rows.size(); ++i) {
EXPECT_EQ(rows.rows[i].element, direct_rows.rows[i].element);
EXPECT_EQ(rows.rows[i].integration_point, direct_rows.rows[i].integration_point);
Same(rows.rows[i].cauchy_stress, direct_rows.rows[i].cauchy_stress);
Same(rows.rows[i].strain, direct_rows.rows[i].strain);
}
Same(u, saved_u);
Same(low, saved_low);
const auto failed = capability.EvaluateNonlinear(fesa::Vector{23});
const auto direct_failed = kernel.EvaluateNonlinear(fesa::Vector{23});
ASSERT_FALSE(failed.HasValue());
ASSERT_FALSE(direct_failed.HasValue());
Same(failed.GetStatus(), direct_failed.GetStatus());
const auto paired_failed = capability.EvaluateNonlinearHighLow(u, fesa::Vector{23});
const auto direct_paired_failed = kernel.EvaluateNonlinearHighLow(u, fesa::Vector{23});
ASSERT_FALSE(paired_failed.HasValue());
ASSERT_FALSE(direct_paired_failed.HasValue());
Same(paired_failed.GetStatus(), direct_paired_failed.GetStatus());
const auto again = capability.EvaluateNonlinear(u);
ASSERT_TRUE(again.HasValue());
Same(again.Value(), nonlinear.Value());
}
TEST(ElementCapabilities, BoundViewsSurviveOwnerVectorMovesAndRejectOtherFamilies) {
static_assert(std::has_virtual_destructor_v<fesa::ShellTrialElement>);
static_assert(std::has_virtual_destructor_v<fesa::SolidTrialElement>);
static_assert(std::has_virtual_destructor_v<fesa::ShellInertiaElement>);
static_assert(std::has_virtual_destructor_v<fesa::ThermalElement>);
static_assert(std::has_virtual_destructor_v<fesa::ShellReferenceElement>);
auto made = Solid();
ASSERT_TRUE(made.HasValue());
std::vector<std::unique_ptr<fesa::Element>> owners;
owners.push_back(std::make_unique<fesa::Hex8Element>(std::move(made.Value())));
const auto* original = owners[0].get();
const fesa::ElementFactory factory;
auto capability = factory.BindSolidTrial(*original);
ASSERT_NE(capability, nullptr);
EXPECT_EQ(factory.BindShellReference(*original), nullptr);
// Reallocation moves unique_ptr owners, never the kernels they own.
owners.reserve(32);
auto transferred = std::move(owners);
EXPECT_EQ(transferred[0].get(), original);
EXPECT_EQ(&capability->DofLayout(), &original->DofLayout());
const auto evaluated = capability->EvaluateLinear(fesa::Vector{24});
ASSERT_TRUE(evaluated.HasValue());
capability.reset(); // Destruction through the role must not delete its kernel.
const auto recovered = original->Recover(fesa::Vector{24});
ASSERT_TRUE(recovered.HasValue());
ShellInput input;
auto shell = fesa::Mitc4Shell::Create(
input.Pointers(), {{{0., 0., 1.}, {0., 0., 1.}, {0., 0., 1.}, {0., 0., 1.}}},
input.section, input.material);
ASSERT_TRUE(shell.HasValue());
EXPECT_EQ(factory.BindSolidTrial(shell.Value()), nullptr);
auto reference = factory.BindShellReference(shell.Value());
ASSERT_NE(reference, nullptr);
EXPECT_EQ(&reference->DofLayout(), &shell.Value().DofLayout());
const auto direct_k = shell.Value().Stiffness();
const auto via_k = reference->Stiffness();
ASSERT_TRUE(direct_k.HasValue());
ASSERT_TRUE(via_k.HasValue());
Same(direct_k.Value().stabilized_global24, via_k.Value().stabilized_global24);
}
} // namespace