eaa5cbeb21
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.
572 lines
26 KiB
C++
572 lines
26 KiB
C++
#include "fesa/analysis/hht_dynamic_procedure.h"
|
|
|
|
#include <gtest/gtest.h>
|
|
|
|
#include <algorithm>
|
|
#include <cmath>
|
|
#include <filesystem>
|
|
#include <fstream>
|
|
#include <iterator>
|
|
#include <optional>
|
|
#include <string>
|
|
|
|
#include "fesa/analysis/newton_iteration_log.h"
|
|
#include "fesa/assembly/parallel_for.h"
|
|
#include "fesa/solvers/linear/mkl_pardiso_solver.h"
|
|
#include "fesa/solvers/linear/mkl_pardiso_tangent_solver.h"
|
|
namespace {
|
|
fesa::Status InjectedTangentFailure() {
|
|
return fesa::Status::Failure(
|
|
fesa::FailureCategory::kSolver,
|
|
{{fesa::Severity::kError, "injected-tangent-backend", {}, "DYNAMIC",
|
|
{}, "Injected tangent backend failure."}});
|
|
}
|
|
|
|
class FailingTangent final : public fesa::TangentSolver {
|
|
public:
|
|
fesa::Status FactorizeFresh(const fesa::SparseMatrix&) override {
|
|
return InjectedTangentFailure();
|
|
}
|
|
fesa::Status SolveCorrection(const fesa::Vector&, fesa::Vector&) const override {
|
|
return InjectedTangentFailure();
|
|
}
|
|
};
|
|
|
|
std::string FailureText(const fesa::Status& status) {
|
|
std::string result;
|
|
for (const auto& diagnostic : status.Diagnostics()) {
|
|
if (!result.empty()) result += " | ";
|
|
result += diagnostic.code + ": " + diagnostic.message;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
fesa::ModelDefinition MakeDefinition(bool fixed = true);
|
|
|
|
fesa::ModelDefinition MakeRigidTranslationDefinition() {
|
|
auto definition = MakeDefinition(false);
|
|
const std::filesystem::path source{"dynamic-state.inp"};
|
|
definition.nodes.insert(
|
|
definition.nodes.end(),
|
|
{{{"Shell-B", 50, "0050"}, {2.0, 0.0, 0.0}, {source, 14U}},
|
|
{{"Shell-B", 60, "0060"}, {3.0, 0.0, 0.0}, {source, 15U}},
|
|
{{"Shell-B", 70, "0070"}, {3.0, 1.0, 0.0}, {source, 16U}},
|
|
{{"Shell-B", 80, "0080"}, {2.0, 1.0, 0.0}, {source, 17U}}});
|
|
definition.shell_elements.push_back(
|
|
{{"Shell-B", 19, "0019"}, fesa::ShellSourceElementType::kS4,
|
|
{4U, 5U, 6U, 7U}, 0U, 0U, {source, 18U}});
|
|
for (std::size_t node = 4U; node < 8U; ++node) {
|
|
definition.shell_node_initial_frames.push_back(
|
|
{static_cast<fesa::EntityIndex>(node), {0.0, 0.0, 1.0},
|
|
{1.0, 0.0, 0.0}, {0.0, 1.0, 0.0}});
|
|
}
|
|
definition.node_sets[0U] =
|
|
{"All-A", std::string{"Shell-A"}, {0U, 1U, 2U, 3U}, {source, 9U}};
|
|
definition.node_sets.push_back(
|
|
{"All-B", std::string{"Shell-B"}, {4U, 5U, 6U, 7U}, {source, 19U}});
|
|
definition.dynamic_step->loads = {{"All-A", 1, 1.0, {source, 20U}},
|
|
{"All-B", 1, 1.0, {source, 21U}}};
|
|
return definition;
|
|
}
|
|
|
|
std::array<double, 3> RotateGlobal(const std::array<double, 3>& value) {
|
|
constexpr double kCosZ = 0.7648421872844885;
|
|
constexpr double kSinZ = 0.6442176872376910;
|
|
constexpr double kCosX = 0.9210609940028851;
|
|
constexpr double kSinX = 0.3894183423086505;
|
|
return {kCosZ * value[0U] - kSinZ * kCosX * value[1U] +
|
|
kSinZ * kSinX * value[2U],
|
|
kSinZ * value[0U] + kCosZ * kCosX * value[1U] -
|
|
kCosZ * kSinX * value[2U],
|
|
kSinX * value[1U] + kCosX * value[2U]};
|
|
}
|
|
|
|
fesa::ModelDefinition MakeRotatedRigidTranslationDefinition() {
|
|
auto definition = MakeRigidTranslationDefinition();
|
|
for (auto& node : definition.nodes) node.coordinates = RotateGlobal(node.coordinates);
|
|
for (auto& frame : definition.shell_node_initial_frames) {
|
|
frame.director = RotateGlobal(frame.director);
|
|
frame.tangent_a = RotateGlobal(frame.tangent_a);
|
|
frame.tangent_b = RotateGlobal(frame.tangent_b);
|
|
}
|
|
const auto load = RotateGlobal({1.0, 0.0, 0.0});
|
|
const std::filesystem::path source{"dynamic-state.inp"};
|
|
definition.dynamic_step->loads = {{"All-A", 1, load[0U], {source, 22U}},
|
|
{"All-A", 2, load[1U], {source, 23U}},
|
|
{"All-A", 3, load[2U], {source, 24U}},
|
|
{"All-B", 1, load[0U], {source, 25U}},
|
|
{"All-B", 2, load[1U], {source, 26U}},
|
|
{"All-B", 3, load[2U], {source, 27U}}};
|
|
return definition;
|
|
}
|
|
|
|
fesa::ModelDefinition MakeFiniteRotationDefinition() {
|
|
auto definition = MakeDefinition(false);
|
|
definition.dynamic_step->initial_increment = 0.01;
|
|
definition.dynamic_step->time_period = 0.02;
|
|
definition.dynamic_step->increment_count = 2U;
|
|
definition.dynamic_step->loads = {
|
|
{"Loaded", 3, 1.0, {"dynamic-state.inp", 23U}}};
|
|
return definition;
|
|
}
|
|
|
|
fesa::ModelDefinition MakeRotatedFiniteRotationDefinition() {
|
|
auto definition = MakeFiniteRotationDefinition();
|
|
for (auto& node : definition.nodes) node.coordinates = RotateGlobal(node.coordinates);
|
|
for (auto& frame : definition.shell_node_initial_frames) {
|
|
frame.director = RotateGlobal(frame.director);
|
|
frame.tangent_a = RotateGlobal(frame.tangent_a);
|
|
frame.tangent_b = RotateGlobal(frame.tangent_b);
|
|
}
|
|
const auto load = RotateGlobal({0.0, 0.0, 1.0});
|
|
const std::filesystem::path source{"dynamic-state.inp"};
|
|
definition.dynamic_step->loads = {{"Loaded", 1, load[0U], {source, 24U}},
|
|
{"Loaded", 2, load[1U], {source, 25U}},
|
|
{"Loaded", 3, load[2U], {source, 26U}}};
|
|
return definition;
|
|
}
|
|
|
|
struct RigidTranslationRun {
|
|
fesa::DynamicExecutionResult execution;
|
|
std::string log;
|
|
};
|
|
|
|
void ExpectSameSourceIds(const std::vector<fesa::SourceEntityId>& left,
|
|
const std::vector<fesa::SourceEntityId>& right) {
|
|
ASSERT_EQ(left.size(), right.size());
|
|
for (std::size_t i = 0U; i < left.size(); ++i) {
|
|
EXPECT_EQ(left[i].instance_name, right[i].instance_name);
|
|
EXPECT_EQ(left[i].source_label, right[i].source_label);
|
|
EXPECT_EQ(left[i].source_label_text, right[i].source_label_text);
|
|
}
|
|
}
|
|
|
|
fesa::Result<RigidTranslationRun> RunRigidTranslation(
|
|
const fesa::ModelDefinition& definition, const fesa::ParallelFor& parallel,
|
|
const std::string& label) {
|
|
auto domain = fesa::Domain::Create(definition);
|
|
if (!domain.HasValue()) return fesa::Result<RigidTranslationRun>::Failure(
|
|
domain.GetStatus());
|
|
fesa::MklPardisoSolver mass;
|
|
fesa::MklPardisoTangentSolver tangent;
|
|
fesa::NewtonIterationLogWriter log;
|
|
const auto path = std::filesystem::temp_directory_path() /
|
|
("fesa-hht-rigid-" + label + ".log");
|
|
auto status = log.Open(path);
|
|
if (!status.IsOk()) return fesa::Result<RigidTranslationRun>::Failure(status);
|
|
auto result = fesa::HhtDynamicProcedureExecutor(parallel, mass, tangent)
|
|
.Execute(domain.Value(), log);
|
|
status = log.Close();
|
|
std::string log_text;
|
|
{
|
|
std::ifstream input{path, std::ios::binary};
|
|
log_text.assign(std::istreambuf_iterator<char>{input}, {});
|
|
}
|
|
std::filesystem::remove(path);
|
|
if (!status.IsOk()) return fesa::Result<RigidTranslationRun>::Failure(status);
|
|
if (!result.HasValue()) return fesa::Result<RigidTranslationRun>::Failure(
|
|
result.GetStatus());
|
|
return fesa::Result<RigidTranslationRun>::Success(
|
|
{std::move(result.Value()), log_text});
|
|
}
|
|
|
|
void ExpectSameDynamicHistory(const RigidTranslationRun& left,
|
|
const RigidTranslationRun& right) {
|
|
EXPECT_EQ(left.log, right.log);
|
|
const auto& left_frames = left.execution.state.AcceptedFrames();
|
|
const auto& right_frames = right.execution.state.AcceptedFrames();
|
|
ASSERT_EQ(left_frames.size(), right_frames.size());
|
|
for (std::size_t frame = 0U; frame < left_frames.size(); ++frame) {
|
|
const auto& lhs = left_frames[frame];
|
|
const auto& rhs = right_frames[frame];
|
|
EXPECT_EQ(lhs.time, rhs.time);
|
|
EXPECT_EQ(lhs.iteration_count, rhs.iteration_count);
|
|
EXPECT_EQ(lhs.nodal_internal_order, rhs.nodal_internal_order);
|
|
ExpectSameSourceIds(lhs.nodal_source_ids, rhs.nodal_source_ids);
|
|
EXPECT_EQ(lhs.nodal_triad, rhs.nodal_triad);
|
|
EXPECT_EQ(lhs.nodal_director, rhs.nodal_director);
|
|
for (const auto* values : {&lhs.generalized_coordinates, &lhs.velocity,
|
|
&lhs.acceleration, &lhs.algorithmic_acceleration,
|
|
&lhs.inertia_force, &lhs.internal_force,
|
|
&lhs.external_force, &lhs.residual, &lhs.reaction}) {
|
|
const auto* other = values == &lhs.generalized_coordinates
|
|
? &rhs.generalized_coordinates
|
|
: values == &lhs.velocity ? &rhs.velocity
|
|
: values == &lhs.acceleration ? &rhs.acceleration
|
|
: values == &lhs.algorithmic_acceleration
|
|
? &rhs.algorithmic_acceleration
|
|
: values == &lhs.inertia_force ? &rhs.inertia_force
|
|
: values == &lhs.internal_force ? &rhs.internal_force
|
|
: values == &lhs.external_force ? &rhs.external_force
|
|
: values == &lhs.residual ? &rhs.residual
|
|
: &rhs.reaction;
|
|
ASSERT_EQ(values->Size(), other->Size());
|
|
for (std::size_t i = 0U; i < values->Size(); ++i)
|
|
EXPECT_DOUBLE_EQ((*values)[i], (*other)[i]);
|
|
}
|
|
EXPECT_DOUBLE_EQ(lhs.kinetic_energy, rhs.kinetic_energy);
|
|
EXPECT_DOUBLE_EQ(lhs.physical_strain_energy, rhs.physical_strain_energy);
|
|
EXPECT_DOUBLE_EQ(lhs.drilling_energy, rhs.drilling_energy);
|
|
EXPECT_DOUBLE_EQ(lhs.external_work, rhs.external_work);
|
|
EXPECT_DOUBLE_EQ(lhs.energy_defect, rhs.energy_defect);
|
|
}
|
|
}
|
|
|
|
void ExpectRotatedDofVector(const fesa::Vector& source, const fesa::Vector& target,
|
|
const double scale) {
|
|
ASSERT_EQ(source.Size(), target.Size());
|
|
ASSERT_EQ(source.Size() % 6U, 0U);
|
|
for (std::size_t node = 0U; node < source.Size() / 6U; ++node)
|
|
for (const std::size_t offset : {0U, 3U}) {
|
|
const auto rotated = RotateGlobal(
|
|
{source[6U * node + offset], source[6U * node + offset + 1U],
|
|
source[6U * node + offset + 2U]});
|
|
for (std::size_t component = 0U; component < 3U; ++component)
|
|
EXPECT_LE(std::abs(rotated[component] -
|
|
target[6U * node + offset + component]) /
|
|
scale,
|
|
1e-10);
|
|
}
|
|
}
|
|
|
|
void ExpectCovariantDynamicHistory(const RigidTranslationRun& original,
|
|
const RigidTranslationRun& rotated,
|
|
const double time_scale,
|
|
const double reference_energy) {
|
|
constexpr double kCoordinateScale = 1.0;
|
|
const double velocity_scale = kCoordinateScale / time_scale;
|
|
const double acceleration_scale = kCoordinateScale / (time_scale * time_scale);
|
|
const auto& original_frames = original.execution.state.AcceptedFrames();
|
|
const auto& rotated_frames = rotated.execution.state.AcceptedFrames();
|
|
ASSERT_EQ(original_frames.size(), rotated_frames.size());
|
|
for (std::size_t frame = 0U; frame < original_frames.size(); ++frame) {
|
|
const auto& source = original_frames[frame];
|
|
const auto& target = rotated_frames[frame];
|
|
ExpectSameSourceIds(source.nodal_source_ids, target.nodal_source_ids);
|
|
EXPECT_LE(std::abs(source.kinetic_energy - target.kinetic_energy) /
|
|
reference_energy,
|
|
1e-10);
|
|
EXPECT_LE(std::abs(source.physical_strain_energy -
|
|
target.physical_strain_energy) /
|
|
reference_energy,
|
|
1e-10);
|
|
EXPECT_LE(std::abs(source.drilling_energy - target.drilling_energy) /
|
|
reference_energy,
|
|
1e-10);
|
|
ExpectRotatedDofVector(source.generalized_coordinates,
|
|
target.generalized_coordinates, kCoordinateScale);
|
|
ExpectRotatedDofVector(source.velocity, target.velocity, velocity_scale);
|
|
ExpectRotatedDofVector(source.acceleration, target.acceleration,
|
|
acceleration_scale);
|
|
ExpectRotatedDofVector(source.algorithmic_acceleration,
|
|
target.algorithmic_acceleration, acceleration_scale);
|
|
for (const auto* values : {&source.inertia_force, &source.internal_force,
|
|
&source.external_force, &source.residual,
|
|
&source.reaction}) {
|
|
const auto* other = values == &source.inertia_force ? &target.inertia_force
|
|
: values == &source.internal_force ? &target.internal_force
|
|
: values == &source.external_force ? &target.external_force
|
|
: values == &source.residual ? &target.residual
|
|
: &target.reaction;
|
|
ExpectRotatedDofVector(*values, *other, 1.0);
|
|
}
|
|
for (std::size_t node = 0U; node < source.nodal_triad.size(); ++node)
|
|
for (std::size_t row = 0U; row < 3U; ++row) {
|
|
const auto triad = RotateGlobal(source.nodal_triad[node][row]);
|
|
const auto director = RotateGlobal(source.nodal_director[node]);
|
|
for (std::size_t component = 0U; component < 3U; ++component) {
|
|
EXPECT_LE(std::abs(triad[component] -
|
|
target.nodal_triad[node][row][component]),
|
|
1e-10);
|
|
EXPECT_LE(std::abs(director[component] -
|
|
target.nodal_director[node][component]),
|
|
1e-10);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
fesa::ModelDefinition MakeDefinition(const bool fixed) {
|
|
const std::filesystem::path source{"dynamic-state.inp"};
|
|
fesa::ModelDefinition definition{};
|
|
definition.source_path = source;
|
|
definition.source_content_identity = "hht-output-unit-fixture";
|
|
definition.nodes = {{{"Shell-A", 10, "0010"}, {0.0, 0.0, 0.0}, {source, 1U}},
|
|
{{"Shell-A", 20, "20"}, {1.0, 0.0, 0.0}, {source, 2U}},
|
|
{{"Shell-A", 30, "30"}, {1.0, 1.0, 0.0}, {source, 3U}},
|
|
{{"Shell-A", 40, "40"}, {0.0, 1.0, 0.0}, {source, 4U}}};
|
|
definition.materials = {{"Material", 1000.0, 0.25, {source, 5U}}};
|
|
definition.densities = {{0U, 2.0, {source, 6U}}};
|
|
definition.shell_sections = {{"Shell", 0.1, 0U, {source, 7U}}};
|
|
definition.shell_elements = {{{"Shell-A", 9, "0009"},
|
|
fesa::ShellSourceElementType::kS4,
|
|
{0U, 1U, 2U, 3U},
|
|
0U,
|
|
0U,
|
|
{source, 8U}}};
|
|
for (std::size_t node = 0U; node < 4U; ++node) {
|
|
definition.shell_node_initial_frames.push_back(
|
|
{static_cast<fesa::EntityIndex>(node),
|
|
{0.0, 0.0, 1.0},
|
|
{1.0, 0.0, 0.0},
|
|
{0.0, 1.0, 0.0}});
|
|
}
|
|
definition.node_sets = {{"All", std::nullopt, {0U, 1U, 2U, 3U}, {source, 9U}},
|
|
{"Loaded", std::nullopt, {0U}, {source, 10U}}};
|
|
fesa::DynamicStepDefinition step{};
|
|
step.source_step_name_raw = "Transient Load";
|
|
step.source_step_name_canonical = "TRANSIENT LOAD";
|
|
step.initial_increment = 0.25;
|
|
step.time_period = 0.5;
|
|
step.increment_count = 2U;
|
|
step.maximum_increments = 3U;
|
|
if (fixed) {
|
|
step.boundaries = {{"All", 1, 6, 0.0, {source, 11U}}};
|
|
step.loads = {{"Loaded", 3, 4.0, {source, 12U}}};
|
|
}
|
|
definition.dynamic_step = step;
|
|
return definition;
|
|
}
|
|
|
|
TEST(HhtDynamicProcedure, ClampedStepLoadHasPhysicalReactionAndZeroMotion) {
|
|
auto domain = fesa::Domain::Create(MakeDefinition());
|
|
ASSERT_TRUE(domain.HasValue());
|
|
fesa::SerialParallelFor parallel;
|
|
fesa::MklPardisoSolver mass;
|
|
fesa::MklPardisoTangentSolver tangent;
|
|
fesa::NewtonIterationLogWriter log;
|
|
const auto path =
|
|
std::filesystem::temp_directory_path() / "fesa-hht-clamped.log";
|
|
ASSERT_TRUE(log.Open(path).IsOk());
|
|
const fesa::HhtDynamicProcedureExecutor executor(parallel, mass, tangent);
|
|
auto result = executor.Execute(domain.Value(), log);
|
|
ASSERT_TRUE(log.Close().IsOk());
|
|
std::filesystem::remove(path);
|
|
ASSERT_TRUE(result.HasValue())
|
|
<< (result.HasValue() ? ""
|
|
: result.GetStatus().Diagnostics().front().message);
|
|
ASSERT_EQ(result.Value().state.AcceptedFrames().size(), 3U);
|
|
for (const auto& frame : result.Value().state.AcceptedFrames()) {
|
|
EXPECT_EQ(frame.time_integration_formulation,
|
|
fesa::DynamicTimeIntegrationFormulation::kReducedCoordinateHht);
|
|
EXPECT_EQ(frame.iteration_count, 0U);
|
|
EXPECT_DOUBLE_EQ(frame.reaction[2U], -4.0);
|
|
EXPECT_DOUBLE_EQ(frame.external_force[2U], 4.0);
|
|
EXPECT_DOUBLE_EQ(frame.kinetic_energy, 0.0);
|
|
EXPECT_DOUBLE_EQ(frame.energy_defect, 0.0);
|
|
for (std::size_t i = 0; i < 24U; ++i) {
|
|
EXPECT_DOUBLE_EQ(frame.generalized_coordinates[i], 0.0);
|
|
EXPECT_DOUBLE_EQ(frame.acceleration[i], 0.0);
|
|
}
|
|
}
|
|
}
|
|
|
|
TEST(HhtDynamicProcedure, UnloadedFreeChannelsRemainExactlyAtRest) {
|
|
for (const int fixed_first_dof : {0, 1, 4}) {
|
|
SCOPED_TRACE(fixed_first_dof);
|
|
auto definition = MakeDefinition(false);
|
|
if (fixed_first_dof != 0) {
|
|
definition.dynamic_step->boundaries = {{"All",
|
|
fixed_first_dof,
|
|
fixed_first_dof + 2,
|
|
0.0,
|
|
{"dynamic-state.inp", 11U}}};
|
|
}
|
|
auto domain = fesa::Domain::Create(std::move(definition));
|
|
ASSERT_TRUE(domain.HasValue());
|
|
fesa::SerialParallelFor parallel;
|
|
fesa::MklPardisoSolver mass;
|
|
fesa::MklPardisoTangentSolver tangent;
|
|
fesa::NewtonIterationLogWriter log;
|
|
const auto path =
|
|
std::filesystem::temp_directory_path() / "fesa-hht-rest.log";
|
|
ASSERT_TRUE(log.Open(path).IsOk());
|
|
auto result = fesa::HhtDynamicProcedureExecutor(parallel, mass, tangent)
|
|
.Execute(domain.Value(), log);
|
|
ASSERT_TRUE(log.Close().IsOk());
|
|
std::filesystem::remove(path);
|
|
ASSERT_TRUE(result.HasValue())
|
|
<< (result.HasValue()
|
|
? ""
|
|
: result.GetStatus().Diagnostics().front().message);
|
|
ASSERT_EQ(result.Value().state.AcceptedFrames().size(), 3U);
|
|
for (const auto& frame : result.Value().state.AcceptedFrames()) {
|
|
EXPECT_EQ(frame.time_integration_formulation,
|
|
fesa::DynamicTimeIntegrationFormulation::kReducedCoordinateHht);
|
|
EXPECT_EQ(frame.iteration_count, 0U);
|
|
for (const auto* values :
|
|
{&frame.generalized_coordinates, &frame.velocity,
|
|
&frame.acceleration, &frame.algorithmic_acceleration,
|
|
&frame.reaction, &frame.residual}) {
|
|
for (std::size_t i = 0; i < values->Size(); ++i)
|
|
EXPECT_DOUBLE_EQ((*values)[i], 0.0);
|
|
}
|
|
EXPECT_DOUBLE_EQ(frame.kinetic_energy, 0.0);
|
|
EXPECT_DOUBLE_EQ(frame.physical_strain_energy, 0.0);
|
|
EXPECT_DOUBLE_EQ(frame.drilling_energy, 0.0);
|
|
EXPECT_DOUBLE_EQ(frame.energy_defect, 0.0);
|
|
for (const double metric : frame.free_residual_metrics)
|
|
EXPECT_DOUBLE_EQ(metric, 0.0);
|
|
}
|
|
}
|
|
}
|
|
|
|
TEST(HhtDynamicProcedure, LargeDirectStepStartsFromAcceptedGeometry) {
|
|
auto definition = MakeDefinition(false);
|
|
definition.materials[0U].youngs_modulus = 1e9;
|
|
definition.dynamic_step->increment_count = 1U;
|
|
definition.dynamic_step->time_period =
|
|
definition.dynamic_step->initial_increment;
|
|
definition.node_sets[0U].node_indices = {0U, 3U};
|
|
definition.node_sets[1U].node_indices = {2U};
|
|
definition.dynamic_step->boundaries = {
|
|
{"All", 1, 6, 0.0, {"dynamic-state.inp", 11U}}};
|
|
definition.dynamic_step->loads = {
|
|
{"Loaded", 3, -40000.0, {"dynamic-state.inp", 12U}}};
|
|
auto domain = fesa::Domain::Create(std::move(definition));
|
|
ASSERT_TRUE(domain.HasValue());
|
|
fesa::SerialParallelFor parallel;
|
|
fesa::MklPardisoSolver mass;
|
|
fesa::MklPardisoTangentSolver tangent;
|
|
fesa::NewtonIterationLogWriter log;
|
|
const auto path =
|
|
std::filesystem::temp_directory_path() / "fesa-hht-large-direct.log";
|
|
ASSERT_TRUE(log.Open(path).IsOk());
|
|
auto result = fesa::HhtDynamicProcedureExecutor(parallel, mass, tangent)
|
|
.Execute(domain.Value(), log);
|
|
ASSERT_TRUE(log.Close().IsOk());
|
|
ASSERT_TRUE(result.HasValue())
|
|
<< (result.HasValue() ? ""
|
|
: result.GetStatus().Diagnostics().front().message);
|
|
EXPECT_GT(
|
|
std::abs(result.Value().state.AcceptedFrames()[0U].acceleration[14U]),
|
|
1e5);
|
|
EXPECT_LT(result.Value().state.CommittedState().generalized_coordinates[14U],
|
|
0.0);
|
|
for (double metric :
|
|
result.Value().state.CommittedState().free_residual_metrics)
|
|
EXPECT_LE(metric, 1e-10);
|
|
EXPECT_GT(result.Value().state.CommittedState().iteration_count, 0U);
|
|
const auto& frames = result.Value().state.AcceptedFrames();
|
|
const double alpha = domain.Value().Dynamic()->alpha;
|
|
for (std::size_t frame_index = 1U; frame_index < frames.size(); ++frame_index) {
|
|
const auto& frame = frames[frame_index];
|
|
EXPECT_EQ(frame.time_integration_formulation,
|
|
fesa::DynamicTimeIntegrationFormulation::kReducedCoordinateHht);
|
|
for (std::size_t dof = 0U; dof < frame.acceleration.Size(); ++dof) {
|
|
EXPECT_DOUBLE_EQ(frame.algorithmic_acceleration[dof],
|
|
(1.0 + alpha) * frame.acceleration[dof] -
|
|
alpha * frames[frame_index - 1U].acceleration[dof]);
|
|
}
|
|
}
|
|
std::filesystem::remove(path);
|
|
}
|
|
|
|
TEST(HhtDynamicProcedure, PreservesBackendCauseAndAddsInitialFailureContext) {
|
|
auto definition = MakeDefinition(false);
|
|
definition.dynamic_step->dynamic_location = {"dynamic-state.inp", 13U};
|
|
auto domain = fesa::Domain::Create(std::move(definition));
|
|
ASSERT_TRUE(domain.HasValue());
|
|
fesa::SerialParallelFor parallel;
|
|
fesa::MklPardisoSolver mass;
|
|
FailingTangent tangent;
|
|
fesa::NewtonIterationLogWriter log;
|
|
const auto path =
|
|
std::filesystem::temp_directory_path() / "fesa-hht-context.log";
|
|
ASSERT_TRUE(log.Open(path).IsOk());
|
|
const auto result = fesa::HhtDynamicProcedureExecutor(parallel, mass, tangent)
|
|
.Execute(domain.Value(), log);
|
|
ASSERT_TRUE(log.Close().IsOk());
|
|
std::filesystem::remove(path);
|
|
ASSERT_FALSE(result.HasValue());
|
|
ASSERT_FALSE(result.GetStatus().Diagnostics().empty());
|
|
EXPECT_EQ(result.GetStatus().Diagnostics().front().code,
|
|
"injected-tangent-backend");
|
|
const auto found = std::find_if(
|
|
result.GetStatus().Diagnostics().begin(), result.GetStatus().Diagnostics().end(),
|
|
[](const fesa::Diagnostic& diagnostic) {
|
|
return diagnostic.code == "dynamic-procedure-context";
|
|
});
|
|
ASSERT_NE(found, result.GetStatus().Diagnostics().end());
|
|
EXPECT_EQ(found->location.file, std::filesystem::path{"dynamic-state.inp"});
|
|
EXPECT_EQ(found->location.line, 13U);
|
|
EXPECT_EQ(found->entity_identity, "Transient Load");
|
|
EXPECT_NE(found->message.find("stage=initial"), std::string::npos);
|
|
EXPECT_NE(found->message.find("increment=0"), std::string::npos);
|
|
EXPECT_NE(found->message.find("time=0"), std::string::npos);
|
|
}
|
|
|
|
TEST(HhtDynamicProcedure,
|
|
UniformTranslationHasNoShellStrainAndIsDeterministicAcrossPartitions) {
|
|
const auto definition = MakeRigidTranslationDefinition();
|
|
const auto rotated_definition = MakeRotatedRigidTranslationDefinition();
|
|
const auto finite_rotation_definition = MakeFiniteRotationDefinition();
|
|
const auto rotated_finite_rotation_definition =
|
|
MakeRotatedFiniteRotationDefinition();
|
|
const fesa::SerialParallelFor serial;
|
|
const fesa::TbbParallelFor tbb;
|
|
auto first = RunRigidTranslation(definition, serial, "serial-first");
|
|
auto partitioned = RunRigidTranslation(definition, tbb, "tbb");
|
|
auto second = RunRigidTranslation(definition, serial, "serial-second");
|
|
auto rotated = RunRigidTranslation(rotated_definition, serial, "rotated");
|
|
auto finite_rotation = RunRigidTranslation(finite_rotation_definition, serial,
|
|
"finite-rotation");
|
|
auto rotated_finite_rotation = RunRigidTranslation(
|
|
rotated_finite_rotation_definition, serial, "rotated-finite-rotation");
|
|
ASSERT_TRUE(first.HasValue())
|
|
<< (first.HasValue() ? "" : FailureText(first.GetStatus()));
|
|
ASSERT_TRUE(partitioned.HasValue())
|
|
<< (partitioned.HasValue() ? "" : FailureText(partitioned.GetStatus()));
|
|
ASSERT_TRUE(second.HasValue())
|
|
<< (second.HasValue() ? "" : FailureText(second.GetStatus()));
|
|
ASSERT_TRUE(rotated.HasValue())
|
|
<< (rotated.HasValue() ? "" : FailureText(rotated.GetStatus()));
|
|
ASSERT_TRUE(finite_rotation.HasValue())
|
|
<< (finite_rotation.HasValue() ? "" : FailureText(finite_rotation.GetStatus()));
|
|
ASSERT_TRUE(rotated_finite_rotation.HasValue())
|
|
<< (rotated_finite_rotation.HasValue()
|
|
? ""
|
|
: FailureText(rotated_finite_rotation.GetStatus()));
|
|
ExpectSameDynamicHistory(first.Value(), partitioned.Value());
|
|
ExpectSameDynamicHistory(first.Value(), second.Value());
|
|
ExpectCovariantDynamicHistory(first.Value(), rotated.Value(), 0.5,
|
|
2.0 * 1000.0 * 0.1);
|
|
ExpectCovariantDynamicHistory(finite_rotation.Value(),
|
|
rotated_finite_rotation.Value(), 0.02,
|
|
1000.0 * 0.1);
|
|
const auto& frames = first.Value().execution.state.AcceptedFrames();
|
|
ASSERT_EQ(frames.size(), 3U);
|
|
constexpr double kReferenceEnergy = 2.0 * 1000.0 * 0.1;
|
|
constexpr double kCoordinateScale = 1.0;
|
|
for (const auto& frame : frames) {
|
|
EXPECT_LE(std::abs(frame.physical_strain_energy) / kReferenceEnergy, 1e-10);
|
|
EXPECT_LE(std::abs(frame.drilling_energy) / kReferenceEnergy, 1e-10);
|
|
for (std::size_t node = 1U; node < 8U; ++node)
|
|
EXPECT_LE(std::abs(frame.generalized_coordinates[6U * node] -
|
|
frame.generalized_coordinates[0U]) /
|
|
kCoordinateScale,
|
|
1e-12);
|
|
for (std::size_t node = 0U; node < 8U; ++node) {
|
|
EXPECT_LE(std::abs(frame.generalized_coordinates[6U * node + 1U]) /
|
|
kCoordinateScale,
|
|
1e-12);
|
|
EXPECT_LE(std::abs(frame.generalized_coordinates[6U * node + 2U]) /
|
|
kCoordinateScale,
|
|
1e-12);
|
|
for (std::size_t rotation = 3U; rotation < 6U; ++rotation)
|
|
EXPECT_LE(std::abs(frame.generalized_coordinates[6U * node + rotation]),
|
|
1e-12);
|
|
}
|
|
}
|
|
EXPECT_GT(frames.back().generalized_coordinates[0U], 0.0);
|
|
double maximum_rotation = 0.0;
|
|
for (const auto& frame : finite_rotation.Value().execution.state.AcceptedFrames())
|
|
for (std::size_t node = 0U; node < 4U; ++node)
|
|
for (std::size_t component = 3U; component < 6U; ++component)
|
|
maximum_rotation = (std::max)(
|
|
maximum_rotation, std::abs(frame.generalized_coordinates[6U * node + component]));
|
|
EXPECT_GT(maximum_rotation, 0.0);
|
|
}
|
|
|
|
} // namespace
|