feat(nonlinear-static-newton-raphson): step 11 - equilibrium-projection-and-reference-gate
This commit is contained in:
@@ -80,6 +80,14 @@ std::string ValueAfterEquals(const std::string& field) {
|
||||
return field.substr(field.find('=') + 1U);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
fesa::NewtonChannelDecision MakeDecision(const std::string& identity) {
|
||||
return {identity,
|
||||
0.0012345678901234567,
|
||||
@@ -324,4 +332,84 @@ TEST(NewtonIterationLog, LeavesZeroByteLogForNonNewtonControlledFailurePath) {
|
||||
EXPECT_EQ(std::filesystem::file_size(path), 0U);
|
||||
}
|
||||
|
||||
// NSNR-LOG-012
|
||||
TEST(NewtonIterationLog,
|
||||
SerializesEvaluatorProducedExternalScaleResidualDecisions) {
|
||||
TempDirectory directory{"external-scale"};
|
||||
const fesa::NewtonConvergenceInput zero_external{
|
||||
{"residual_force", VectorFrom({1.0e-3}), VectorFrom({2.0}),
|
||||
VectorFrom({0.0})},
|
||||
{"residual_moment", VectorFrom({1.0e-3}), VectorFrom({3.0}),
|
||||
VectorFrom({0.0})},
|
||||
{"correction_translation", VectorFrom({1.0e-2}), VectorFrom({1.0})},
|
||||
{"correction_rotation", VectorFrom({1.0e-2}), VectorFrom({1.0})},
|
||||
};
|
||||
const auto zero_decisions = fesa::EvaluateNewtonConvergence(zero_external);
|
||||
auto zero_record = CompleteRecord();
|
||||
zero_record.force_residual = zero_decisions.force_residual;
|
||||
zero_record.moment_residual = zero_decisions.moment_residual;
|
||||
zero_record.translation_correction = zero_decisions.translation_correction;
|
||||
zero_record.rotation_correction = zero_decisions.rotation_correction;
|
||||
zero_record.combined_passed = zero_decisions.combined_passed;
|
||||
const auto zero_fields =
|
||||
WriteAndRead(directory.Path() / "zero_log.txt", zero_record);
|
||||
|
||||
ASSERT_EQ(zero_fields.size(), 39U);
|
||||
EXPECT_EQ(FieldValue(zero_fields, "schema"), "schema=nsnr-log-v1");
|
||||
EXPECT_EQ(FieldValue(zero_fields, "residual_force_denominator_scale"),
|
||||
"residual_force_denominator_scale=0");
|
||||
EXPECT_EQ(FieldValue(zero_fields, "residual_moment_denominator_scale"),
|
||||
"residual_moment_denominator_scale=0");
|
||||
EXPECT_EQ(FieldValue(zero_fields, "residual_force_branch"),
|
||||
"residual_force_branch=zero-scale");
|
||||
EXPECT_EQ(FieldValue(zero_fields, "residual_moment_branch"),
|
||||
"residual_moment_branch=zero-scale");
|
||||
EXPECT_EQ(FieldValue(zero_fields, "residual_force_threshold"),
|
||||
"residual_force_threshold=0.001");
|
||||
EXPECT_EQ(FieldValue(zero_fields, "residual_moment_threshold"),
|
||||
"residual_moment_threshold=0.001");
|
||||
EXPECT_EQ(FieldValue(zero_fields, "residual_force_threshold_unit"),
|
||||
"residual_force_threshold_unit=force");
|
||||
EXPECT_EQ(FieldValue(zero_fields, "residual_moment_threshold_unit"),
|
||||
"residual_moment_threshold_unit=force*length");
|
||||
|
||||
const fesa::NewtonConvergenceInput nonzero_external{
|
||||
{"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 relative_decisions =
|
||||
fesa::EvaluateNewtonConvergence(nonzero_external);
|
||||
auto relative_record = CompleteRecord();
|
||||
relative_record.force_residual = relative_decisions.force_residual;
|
||||
relative_record.moment_residual = relative_decisions.moment_residual;
|
||||
relative_record.translation_correction =
|
||||
relative_decisions.translation_correction;
|
||||
relative_record.rotation_correction = relative_decisions.rotation_correction;
|
||||
relative_record.combined_passed = relative_decisions.combined_passed;
|
||||
const auto relative_fields =
|
||||
WriteAndRead(directory.Path() / "relative_log.txt", relative_record);
|
||||
|
||||
ASSERT_EQ(relative_fields.size(), 39U);
|
||||
EXPECT_EQ(FieldValue(relative_fields, "residual_force_denominator_scale"),
|
||||
"residual_force_denominator_scale=2");
|
||||
EXPECT_EQ(FieldValue(relative_fields, "residual_moment_denominator_scale"),
|
||||
"residual_moment_denominator_scale=2");
|
||||
EXPECT_EQ(FieldValue(relative_fields, "residual_force_branch"),
|
||||
"residual_force_branch=relative");
|
||||
EXPECT_EQ(FieldValue(relative_fields, "residual_moment_branch"),
|
||||
"residual_moment_branch=relative");
|
||||
EXPECT_EQ(FieldValue(relative_fields, "residual_force_threshold"),
|
||||
"residual_force_threshold=0.01");
|
||||
EXPECT_EQ(FieldValue(relative_fields, "residual_moment_threshold"),
|
||||
"residual_moment_threshold=0.01");
|
||||
EXPECT_EQ(FieldValue(relative_fields, "residual_force_threshold_unit"),
|
||||
"residual_force_threshold_unit=1");
|
||||
EXPECT_EQ(FieldValue(relative_fields, "residual_moment_threshold_unit"),
|
||||
"residual_moment_threshold_unit=1");
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
@@ -143,6 +143,18 @@ fesa::NewtonConvergenceInput RelativeConvergenceInput() {
|
||||
};
|
||||
}
|
||||
|
||||
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();
|
||||
@@ -362,4 +374,161 @@ TEST(NonlinearConvergence, AppliesTypedZeroScaleGatesToEmptyChannels) {
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
|
||||
@@ -534,6 +534,31 @@ TEST(NonlinearHdf5ResultsWriter, ProjectsRotationalReactionToSpatialMoment) {
|
||||
EXPECT_GT(quadratic, 0.0);
|
||||
}
|
||||
|
||||
// NSNR-HDF5-014.
|
||||
TEST(NonlinearHdf5ResultsWriter, WritesSchemaV1GlobalBalanceErrorLocation) {
|
||||
TempDirectory directory{"global-balance-location"};
|
||||
auto fixture = MakeFixture(directory.Path() / "global-balance.inp");
|
||||
const auto output = directory.Path() / "global-balance_results.h5";
|
||||
|
||||
fesa::NonlinearHdf5ResultsWriter writer;
|
||||
ASSERT_TRUE(writer.Write(output, *fixture.domain, fixture.state, {}).IsOk());
|
||||
|
||||
const auto file = OpenFile(output);
|
||||
const auto equilibrium = OpenDataset(
|
||||
file.Get(), "/steps/Step-1/frames/1/global/equilibrium");
|
||||
EXPECT_EQ(DatasetDimensions(file.Get(),
|
||||
"/steps/Step-1/frames/1/global/equilibrium"),
|
||||
std::vector<hsize_t>({6U}));
|
||||
EXPECT_EQ(ReadStringAttribute(equilibrium.Get(), "component_names"),
|
||||
"FORCE_1,FORCE_2,FORCE_3,MOMENT_1,MOMENT_2,MOMENT_3");
|
||||
EXPECT_EQ(ReadStringAttribute(equilibrium.Get(), "location"),
|
||||
"global-balance-error");
|
||||
Hdf5Handle metadata{H5Gopen2(file.Get(), "/metadata", H5P_DEFAULT),
|
||||
H5Gclose};
|
||||
ASSERT_GE(metadata.Get(), 0);
|
||||
EXPECT_EQ(ReadUint64Attribute(metadata.Get(), "schema_version"), 1U);
|
||||
}
|
||||
|
||||
// NSNR-HDF5-012 and NSNR-HDF5-013.
|
||||
TEST(NonlinearHdf5ResultsWriter, StoresSortedDiagnosticsThroughBackendNeutralBoundary) {
|
||||
static_assert(std::is_base_of_v<fesa::NonlinearResultsWriter,
|
||||
|
||||
Reference in New Issue
Block a user