Files
FESADev/tests/unit/analysis/nonlinear_static_control_test.cpp
T

535 lines
22 KiB
C++

#include "fesa/analysis/nonlinear_static_control.h"
#include <gtest/gtest.h>
#include <algorithm>
#include <array>
#include <cmath>
#include <cstddef>
#include <cstdint>
#include <filesystem>
#include <memory>
#include <stdexcept>
#include <string>
#include <utility>
#include <vector>
#include "fesa/analysis/analysis_model.h"
#include "fesa/fem/dof_manager.h"
#include "fesa/model/domain.h"
#include "fesa/model/model_types.h"
namespace {
fesa::Vector VectorFrom(const std::vector<double>& values) {
fesa::Vector result{values.size()};
for (std::size_t index = 0U; index < values.size(); ++index) {
result[index] = values[index];
}
return result;
}
struct Fixture {
std::unique_ptr<fesa::Domain> domain;
std::unique_ptr<fesa::AnalysisModel> model;
std::unique_ptr<fesa::DofManager> dofs;
};
Fixture MakeFixture(
std::vector<fesa::PrescribedDisplacementDefinition> boundaries = {},
std::vector<fesa::NodalLoad> loads = {},
const double initial_increment = 0.1, const double time_period = 1.0,
const double minimum_increment = 1.0e-9,
const double maximum_increment = 5.0) {
const std::filesystem::path source{"nonlinear-static-control.inp"};
fesa::ModelDefinition definition{};
definition.source_path = source;
definition.source_content_identity = "control-fixture";
definition.nodes = {
{{"Shell-1", 10, "10"}, {0.0, 0.0, 0.0}, {source, 1U}},
{{"Shell-1", 20, "20"}, {1.0, 0.0, 0.0}, {source, 2U}},
{{"Shell-1", 30, "30"}, {1.0, 1.0, 0.0}, {source, 3U}},
{{"Shell-1", 40, "40"}, {0.0, 1.0, 0.0}, {source, 4U}},
{{"Shell-1", 50, "50"}, {-1.0, 1.0, 0.0}, {source, 5U}},
{{"Shell-1", 60, "60"}, {-1.0, 0.0, 0.0}, {source, 6U}},
};
definition.materials = {{"Material", 1000.0, 0.25, {source, 7U}}};
definition.shell_sections = {{"Shell", 0.1, 0U, {source, 8U}}};
definition.shell_elements = {
{{"Shell-1", 1, "1"},
fesa::ShellSourceElementType::kS4,
{0U, 1U, 2U, 3U},
0U,
0U,
{source, 9U}},
{{"Shell-1", 2, "2"},
fesa::ShellSourceElementType::kS4,
{0U, 3U, 4U, 5U},
0U,
0U,
{source, 10U}},
};
for (std::size_t node = 0U; node < definition.nodes.size(); ++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.steps = {{"Step-1",
std::move(boundaries),
std::move(loads),
fesa::StaticProcedureKind::kNonlinearFullNewton,
true,
initial_increment,
time_period,
minimum_increment,
maximum_increment,
{source, 11U}}};
auto domain_result = fesa::Domain::Create(std::move(definition));
if (!domain_result.HasValue()) {
throw std::runtime_error{"Control fixture Domain construction failed."};
}
auto domain =
std::make_unique<fesa::Domain>(std::move(domain_result.Value()));
auto model_result = fesa::AnalysisModel::Create(*domain);
if (!model_result.HasValue()) {
throw std::runtime_error{
"Control fixture AnalysisModel construction failed."};
}
auto model =
std::make_unique<fesa::AnalysisModel>(std::move(model_result.Value()));
auto dofs_result = fesa::DofManager::Create(*model);
if (!dofs_result.HasValue()) {
throw std::runtime_error{"Control fixture DofManager construction failed."};
}
auto dofs =
std::make_unique<fesa::DofManager>(std::move(dofs_result.Value()));
return {std::move(domain), std::move(model), std::move(dofs)};
}
fesa::Vector TrialCoordinates(const Fixture& fixture) {
return fesa::Vector{fixture.dofs->FullDofCount()};
}
fesa::Vector AddScaled(const fesa::Vector& values,
const fesa::Vector& direction, const double scale) {
fesa::Vector result = values;
result.Axpy(scale, direction);
return result;
}
double MaximumAbsoluteDifference(const fesa::Vector& left,
const fesa::Vector& right) {
if (left.Size() != right.Size()) {
throw std::invalid_argument{"Vector dimensions must match."};
}
double maximum = 0.0;
for (std::size_t index = 0U; index < left.Size(); ++index) {
maximum = (std::max)(maximum, std::abs(left[index] - right[index]));
}
return maximum;
}
fesa::NewtonConvergenceInput RelativeConvergenceInput() {
return {
{"residual_force", VectorFrom({1.0e-2}), VectorFrom({1.0}),
VectorFrom({0.5})},
{"residual_moment", VectorFrom({1.0e-2}), VectorFrom({1.0}),
VectorFrom({0.25})},
{"correction_translation", VectorFrom({1.0e-2}), VectorFrom({1.0})},
{"correction_rotation", VectorFrom({1.0e-2}), VectorFrom({1.0})},
};
}
fesa::NewtonConvergenceInput ExactZeroExternalResidualInput(
const double force_numerator, const double moment_numerator) {
return {
{"residual_force", VectorFrom({force_numerator}), VectorFrom({2.0}),
VectorFrom({0.0})},
{"residual_moment", VectorFrom({moment_numerator}), VectorFrom({3.0}),
VectorFrom({0.0})},
{"correction_translation", VectorFrom({1.0e-2}), VectorFrom({1.0})},
{"correction_rotation", VectorFrom({1.0e-2}), VectorFrom({1.0})},
};
}
// NSNR-CTRL-001, NSNR-CTRL-002, and NSNR-CTRL-017
TEST(NonlinearStaticControl, BuildsFixedFactorSequenceWithExactEndpoint) {
const auto fixture = MakeFixture();
const auto result =
fesa::BuildFixedLoadFactorSequence(fixture.domain->Steps()[0U]);
ASSERT_TRUE(result.HasValue());
ASSERT_EQ(result.Value().size(), 10U);
for (std::size_t index = 0U; index < result.Value().size(); ++index) {
const auto& increment = result.Value()[index];
EXPECT_EQ(increment.increment_index,
static_cast<std::uint32_t>(index + 1U));
EXPECT_DOUBLE_EQ(increment.load_factor,
static_cast<double>(index + 1U) * 0.1);
EXPECT_EQ(increment.is_factor_one, index + 1U == result.Value().size());
}
EXPECT_DOUBLE_EQ(result.Value().back().load_factor, 1.0);
const auto one_hundred = MakeFixture({}, {}, 0.01);
const auto one_hundred_result =
fesa::BuildFixedLoadFactorSequence(one_hundred.domain->Steps()[0U]);
ASSERT_TRUE(one_hundred_result.HasValue());
ASSERT_EQ(one_hundred_result.Value().size(), 100U);
EXPECT_EQ(one_hundred_result.Value().back().increment_index, 100U);
EXPECT_TRUE(one_hundred_result.Value().back().is_factor_one);
}
// NSNR-CTRL-003
TEST(NonlinearStaticControl, IgnoresPositiveMinimumAndMaximumDuringStepping) {
const auto first = MakeFixture({}, {}, 0.3, 1.0, 1.0e-12, 100.0);
const auto second = MakeFixture({}, {}, 0.3, 1.0, 0.29, 0.31);
const auto first_result =
fesa::BuildFixedLoadFactorSequence(first.domain->Steps()[0U]);
const auto second_result =
fesa::BuildFixedLoadFactorSequence(second.domain->Steps()[0U]);
ASSERT_TRUE(first_result.HasValue());
ASSERT_TRUE(second_result.HasValue());
ASSERT_EQ(first_result.Value().size(), second_result.Value().size());
for (std::size_t index = 0U; index < first_result.Value().size(); ++index) {
EXPECT_DOUBLE_EQ(first_result.Value()[index].load_factor,
second_result.Value()[index].load_factor);
EXPECT_EQ(first_result.Value()[index].increment_index,
second_result.Value()[index].increment_index);
}
}
// NSNR-CTRL-004 and NSNR-CTRL-005
TEST(NonlinearLoadBoundary, AppliesAbsoluteBoundaryTargetsWithoutCorrection) {
const std::filesystem::path source{"nonlinear-static-control.inp"};
const auto fixture = MakeFixture(
{{"10", 1, 1, 4.0, {source, 12U}}, {"20", 4, 4, -0.5, {source, 13U}}});
const auto result = fesa::ApplyAbsolutePrescribedDisplacementTarget(
fixture.domain->Steps()[0U], 0.25);
ASSERT_TRUE(result.HasValue());
ASSERT_EQ(result.Value().Size(), 2U);
EXPECT_DOUBLE_EQ(result.Value()[0U], 1.0);
EXPECT_DOUBLE_EQ(result.Value()[1U], -0.125);
for (const std::size_t constrained : fixture.dofs->ConstrainedDofs()) {
EXPECT_EQ(TrialCoordinates(fixture)[constrained], 0.0);
}
}
// NSNR-CTRL-006 and NSNR-CTRL-008
TEST(NonlinearLoadBoundary, AssemblesEverySharedNodeLoadOnceInFixedGlobalAxes) {
const std::filesystem::path source{"nonlinear-static-control.inp"};
const std::vector<fesa::NodalLoad> loads{
{"10", 1, 8.0, {source, 20U}},
{"10", 2, -4.0, {source, 21U}},
{"10", 4, 2.0, {source, 22U}},
};
const auto fixture = MakeFixture({}, loads);
const fesa::Vector trial = TrialCoordinates(fixture);
const auto result = fesa::AssembleGlobalFixedLoadContribution(
*fixture.domain, *fixture.dofs, trial, 0.25);
ASSERT_TRUE(result.HasValue());
ASSERT_EQ(result.Value().generalized_external_force.Size(),
fixture.dofs->FullDofCount());
EXPECT_DOUBLE_EQ(result.Value().generalized_external_force[0U], 2.0);
EXPECT_DOUBLE_EQ(result.Value().generalized_external_force[1U], -1.0);
EXPECT_DOUBLE_EQ(result.Value().generalized_external_force[3U], 0.5);
EXPECT_DOUBLE_EQ(fixture.domain->Steps()[0U]
.ConcentratedLoads()[0U]
.GlobalComponents()[0U],
8.0);
EXPECT_DOUBLE_EQ(fixture.domain->Steps()[0U]
.ConcentratedLoads()[2U]
.GlobalComponents()[3U],
2.0);
}
// NSNR-CTRL-007
TEST(NonlinearLoadBoundary, RejectsDirectorAlignedMomentAboveTheExactBand) {
const fesa::Vector3 director{0.0, 0.0, 1.0};
EXPECT_TRUE(
fesa::CheckMomentAdmissibility(director, {1.0, 0.0, 1.0e-12}).IsOk());
const auto rejected =
fesa::CheckMomentAdmissibility(director, {1.0, 0.0, 2.0e-12});
EXPECT_FALSE(rejected.IsOk());
ASSERT_EQ(rejected.Diagnostics().size(), 1U);
EXPECT_EQ(rejected.Diagnostics()[0U].code, "unsupported-drilling-load");
}
// NSNR-CTRL-009
TEST(NonlinearLoadBoundary, PreservesSourceOrderDuringGlobalAccumulation) {
const std::filesystem::path source{"nonlinear-static-control.inp"};
const auto fixture = MakeFixture({}, {{"10", 1, 1.0e16, {source, 30U}},
{"10", 1, -1.0e16, {source, 31U}},
{"10", 1, 1.0, {source, 32U}}});
const fesa::Vector trial = TrialCoordinates(fixture);
const auto first = fesa::AssembleGlobalFixedLoadContribution(
*fixture.domain, *fixture.dofs, trial, 1.0);
const auto second = fesa::AssembleGlobalFixedLoadContribution(
*fixture.domain, *fixture.dofs, trial, 1.0);
ASSERT_TRUE(first.HasValue());
ASSERT_TRUE(second.HasValue());
EXPECT_DOUBLE_EQ(first.Value().generalized_external_force[0U], 1.0);
EXPECT_EQ(first.Value().generalized_external_tangent.RowOffsets(),
second.Value().generalized_external_tangent.RowOffsets());
EXPECT_EQ(first.Value().generalized_external_tangent.ColumnIndices(),
second.Value().generalized_external_tangent.ColumnIndices());
EXPECT_EQ(first.Value().generalized_external_tangent.Values(),
second.Value().generalized_external_tangent.Values());
}
// NSNR-CTRL-010 and NSNR-CTRL-011
TEST(NonlinearLoadBoundary,
DifferentiatesOneFixedSpatialMomentAndUsesSubtraction) {
const std::filesystem::path source{"nonlinear-static-control.inp"};
const auto fixture = MakeFixture({}, {{"10", 4, 3.0, {source, 40U}}});
fesa::Vector trial = TrialCoordinates(fixture);
trial[5U] = 0.4;
const auto result = fesa::AssembleGlobalFixedLoadContribution(
*fixture.domain, *fixture.dofs, trial, 0.7);
ASSERT_TRUE(result.HasValue());
fesa::Vector direction{fixture.dofs->FullDofCount()};
direction[5U] = 1.0;
constexpr double kStep = 1.0e-6;
const auto plus = fesa::AssembleGlobalFixedLoadContribution(
*fixture.domain, *fixture.dofs, AddScaled(trial, direction, kStep), 0.7);
const auto minus = fesa::AssembleGlobalFixedLoadContribution(
*fixture.domain, *fixture.dofs, AddScaled(trial, direction, -kStep), 0.7);
ASSERT_TRUE(plus.HasValue());
ASSERT_TRUE(minus.HasValue());
fesa::Vector derivative = plus.Value().generalized_external_force;
derivative.Axpy(-1.0, minus.Value().generalized_external_force);
derivative.Scale(0.5 / kStep);
const fesa::Vector tangent_action =
result.Value().generalized_external_tangent.Multiply(direction);
EXPECT_LE(MaximumAbsoluteDifference(derivative, tangent_action), 1.0e-8);
fesa::Vector internal{fixture.dofs->FullDofCount()};
internal[3U] = 5.0;
fesa::Vector residual = internal;
residual.Axpy(-1.0, result.Value().generalized_external_force);
EXPECT_DOUBLE_EQ(residual[3U],
5.0 - result.Value().generalized_external_force[3U]);
EXPECT_DOUBLE_EQ(
-tangent_action[3U],
-result.Value().generalized_external_tangent.Multiply(direction)[3U]);
}
// NSNR-CTRL-012, NSNR-CTRL-013, and NSNR-CTRL-014
TEST(NonlinearConvergence, UsesOwnedRelativeThresholdsForEveryChannel) {
static_assert(fesa::kNewtonRelativeThreshold == 1.0e-2);
static_assert(fesa::kNewtonZeroScaleThreshold == 1.0e-3);
const auto result =
fesa::EvaluateNewtonConvergence(RelativeConvergenceInput());
EXPECT_TRUE(result.combined_passed);
const std::array<fesa::NewtonChannelDecision, 4> decisions{
result.force_residual, result.moment_residual,
result.translation_correction, result.rotation_correction};
for (const auto& decision : decisions) {
EXPECT_FALSE(decision.identity.empty());
EXPECT_DOUBLE_EQ(decision.numerator_norm, 1.0e-2);
EXPECT_DOUBLE_EQ(decision.denominator_scale, 1.0);
EXPECT_EQ(decision.branch, fesa::NewtonConvergenceBranch::kRelative);
EXPECT_DOUBLE_EQ(decision.threshold, fesa::kNewtonRelativeThreshold);
EXPECT_EQ(decision.threshold_unit, fesa::NewtonChannelUnit::kDimensionless);
EXPECT_TRUE(decision.passed);
}
}
// NSNR-CTRL-015 and NSNR-CTRL-016
TEST(NonlinearConvergence, AppliesTypedZeroScaleGatesToEmptyChannels) {
const fesa::NewtonConvergenceInput input{
{"residual_force", fesa::Vector{0U}, fesa::Vector{0U}, fesa::Vector{0U}},
{"residual_moment", fesa::Vector{0U}, fesa::Vector{0U}, fesa::Vector{0U}},
{"correction_translation", fesa::Vector{0U}, fesa::Vector{0U}},
{"correction_rotation", fesa::Vector{0U}, fesa::Vector{0U}},
};
const auto result = fesa::EvaluateNewtonConvergence(input);
const std::array<fesa::NewtonChannelDecision, 4> decisions{
result.force_residual, result.moment_residual,
result.translation_correction, result.rotation_correction};
const std::array<fesa::NewtonChannelUnit, 4> units{
fesa::NewtonChannelUnit::kForce, fesa::NewtonChannelUnit::kForceLength,
fesa::NewtonChannelUnit::kLength,
fesa::NewtonChannelUnit::kDimensionless};
EXPECT_TRUE(result.combined_passed);
for (std::size_t index = 0U; index < decisions.size(); ++index) {
EXPECT_DOUBLE_EQ(decisions[index].numerator_norm, 0.0);
EXPECT_DOUBLE_EQ(decisions[index].denominator_scale, 0.0);
EXPECT_EQ(decisions[index].branch,
fesa::NewtonConvergenceBranch::kZeroScale);
EXPECT_DOUBLE_EQ(decisions[index].threshold,
fesa::kNewtonZeroScaleThreshold);
EXPECT_EQ(decisions[index].threshold_unit, units[index]);
EXPECT_TRUE(decisions[index].passed);
}
}
// NSNR-CTRL-018
TEST(NonlinearConvergence,
SelectsTypedZeroScaleFromExactZeroExternalResidualChannels) {
const auto accepted = fesa::EvaluateNewtonConvergence(
ExactZeroExternalResidualInput(fesa::kNewtonZeroScaleThreshold,
fesa::kNewtonZeroScaleThreshold));
EXPECT_TRUE(accepted.combined_passed);
const std::array<fesa::NewtonChannelDecision, 2U> decisions{
accepted.force_residual, accepted.moment_residual};
const std::array<fesa::NewtonChannelUnit, 2U> units{
fesa::NewtonChannelUnit::kForce,
fesa::NewtonChannelUnit::kForceLength};
for (std::size_t index = 0U; index < decisions.size(); ++index) {
EXPECT_DOUBLE_EQ(decisions[index].denominator_scale, 0.0);
EXPECT_EQ(decisions[index].branch,
fesa::NewtonConvergenceBranch::kZeroScale);
EXPECT_DOUBLE_EQ(decisions[index].threshold,
fesa::kNewtonZeroScaleThreshold);
EXPECT_EQ(decisions[index].threshold_unit, units[index]);
EXPECT_TRUE(decisions[index].passed);
}
const double just_over = std::nextafter(
fesa::kNewtonZeroScaleThreshold,
std::numeric_limits<double>::infinity());
const auto force_rejected =
fesa::EvaluateNewtonConvergence(ExactZeroExternalResidualInput(
just_over, fesa::kNewtonZeroScaleThreshold));
const auto moment_rejected =
fesa::EvaluateNewtonConvergence(ExactZeroExternalResidualInput(
fesa::kNewtonZeroScaleThreshold, just_over));
EXPECT_FALSE(force_rejected.force_residual.passed);
EXPECT_FALSE(force_rejected.combined_passed);
EXPECT_FALSE(moment_rejected.moment_residual.passed);
EXPECT_FALSE(moment_rejected.combined_passed);
}
// NSNR-CTRL-019
TEST(NonlinearConvergence,
UsesRelativeResidualScaleOnlyWhenMatchingExternalScaleIsNonzero) {
fesa::NewtonConvergenceInput input{
{"residual_force", VectorFrom({2.0e-2}), VectorFrom({2.0}),
VectorFrom({1.0})},
{"residual_moment", VectorFrom({2.0e-2}), VectorFrom({1.0}),
VectorFrom({2.0})},
{"correction_translation", VectorFrom({1.0e-2}), VectorFrom({1.0})},
{"correction_rotation", VectorFrom({1.0e-2}), VectorFrom({1.0})},
};
const auto accepted = fesa::EvaluateNewtonConvergence(input);
EXPECT_TRUE(accepted.combined_passed);
for (const auto& decision : {accepted.force_residual,
accepted.moment_residual}) {
EXPECT_DOUBLE_EQ(decision.denominator_scale, 2.0);
EXPECT_EQ(decision.branch, fesa::NewtonConvergenceBranch::kRelative);
EXPECT_DOUBLE_EQ(decision.threshold, fesa::kNewtonRelativeThreshold);
EXPECT_EQ(decision.threshold_unit, fesa::NewtonChannelUnit::kDimensionless);
EXPECT_TRUE(decision.passed);
}
const double just_over = std::nextafter(2.0e-2,
std::numeric_limits<double>::infinity());
input.force_residual.free_residual = VectorFrom({just_over});
EXPECT_FALSE(fesa::EvaluateNewtonConvergence(input).force_residual.passed);
input.force_residual.free_residual = VectorFrom({2.0e-2});
input.moment_residual.free_residual = VectorFrom({just_over});
EXPECT_FALSE(fesa::EvaluateNewtonConvergence(input).moment_residual.passed);
input.moment_residual.free_residual = VectorFrom({2.0e-2});
input.force_residual.free_residual = VectorFrom({9.0e-4});
input.force_residual.free_internal = VectorFrom({5.0e-3});
input.force_residual.free_external = VectorFrom({1.0e-2});
const auto relative_rejected = fesa::EvaluateNewtonConvergence(input);
EXPECT_FALSE(relative_rejected.force_residual.passed);
EXPECT_FALSE(relative_rejected.combined_passed);
}
// NSNR-CTRL-020
TEST(NonlinearConvergence,
RejectsNonfiniteResidualInputsBeforeSelectingOrAcceptingAChannel) {
const std::array<double, 2U> invalid_values{
std::numeric_limits<double>::quiet_NaN(),
std::numeric_limits<double>::infinity()};
for (const double invalid : invalid_values) {
for (const bool is_moment : {false, true}) {
for (const std::size_t vector_index : {0U, 1U, 2U}) {
auto input = RelativeConvergenceInput();
fesa::NewtonResidualChannelInput* channel =
is_moment ? &input.moment_residual : &input.force_residual;
if (vector_index == 0U) {
channel->free_residual = VectorFrom({invalid});
} else if (vector_index == 1U) {
channel->free_internal = VectorFrom({invalid});
} else {
channel->free_external = VectorFrom({invalid});
}
const auto result = fesa::EvaluateNewtonConvergence(input);
const auto& decision =
is_moment ? result.moment_residual : result.force_residual;
EXPECT_FALSE(decision.passed);
EXPECT_FALSE(result.combined_passed);
}
}
}
auto input = RelativeConvergenceInput();
const double smallest_external = std::nextafter(
0.0, std::numeric_limits<double>::infinity());
input.force_residual.free_residual = VectorFrom({0.0});
input.force_residual.free_internal = VectorFrom({0.0});
input.force_residual.free_external = VectorFrom({smallest_external});
const auto relative = fesa::EvaluateNewtonConvergence(input);
EXPECT_TRUE(relative.force_residual.passed);
EXPECT_DOUBLE_EQ(relative.force_residual.denominator_scale,
smallest_external);
EXPECT_EQ(relative.force_residual.branch,
fesa::NewtonConvergenceBranch::kRelative);
EXPECT_DOUBLE_EQ(relative.force_residual.threshold,
fesa::kNewtonRelativeThreshold);
EXPECT_EQ(relative.force_residual.threshold_unit,
fesa::NewtonChannelUnit::kDimensionless);
}
// NSNR-CTRL-021
TEST(NonlinearConvergence, RequiresEveryResidualAndCorrectionChannelToPass) {
const auto accepted = fesa::EvaluateNewtonConvergence(RelativeConvergenceInput());
EXPECT_TRUE(accepted.combined_passed);
EXPECT_DOUBLE_EQ(accepted.translation_correction.denominator_scale, 1.0);
EXPECT_DOUBLE_EQ(accepted.rotation_correction.denominator_scale, 1.0);
EXPECT_DOUBLE_EQ(accepted.translation_correction.threshold,
fesa::kNewtonRelativeThreshold);
EXPECT_DOUBLE_EQ(accepted.rotation_correction.threshold,
fesa::kNewtonRelativeThreshold);
EXPECT_EQ(accepted.translation_correction.threshold_unit,
fesa::NewtonChannelUnit::kDimensionless);
EXPECT_EQ(accepted.rotation_correction.threshold_unit,
fesa::NewtonChannelUnit::kDimensionless);
for (const std::size_t failed_channel : {0U, 1U, 2U, 3U}) {
auto input = RelativeConvergenceInput();
const double just_over = std::nextafter(
1.0e-2, std::numeric_limits<double>::infinity());
if (failed_channel == 0U) {
input.force_residual.free_residual = VectorFrom({just_over});
} else if (failed_channel == 1U) {
input.moment_residual.free_residual = VectorFrom({just_over});
} else if (failed_channel == 2U) {
input.translation_correction.fresh_correction = VectorFrom({just_over});
} else {
input.rotation_correction.fresh_correction = VectorFrom({just_over});
}
const auto result = fesa::EvaluateNewtonConvergence(input);
EXPECT_FALSE(result.combined_passed);
}
}
} // namespace