feat: implement consistent-mass HHT shell dynamics
This commit is contained in:
@@ -6,6 +6,8 @@
|
||||
#include <cmath>
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <functional>
|
||||
#include <limits>
|
||||
#include <iterator>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
@@ -412,4 +414,152 @@ TEST(NewtonIterationLog,
|
||||
"residual_moment_threshold_unit=1");
|
||||
}
|
||||
|
||||
fesa::DynamicNewtonIterationLogRecord CompleteDynamicRecord() {
|
||||
return {2U,
|
||||
3U,
|
||||
0.25,
|
||||
"convergence",
|
||||
"accepted",
|
||||
"",
|
||||
{2.0e-12, 0.0},
|
||||
{2.0, 0.0},
|
||||
{1.0e-11, 2.0e-11},
|
||||
{3.0e-11, 4.0e-11},
|
||||
{5.0e-11, 6.0e-11},
|
||||
{7.0e-11, 8.0e-11},
|
||||
true,
|
||||
true};
|
||||
}
|
||||
|
||||
std::string ReadLogBytes(const std::filesystem::path& path) {
|
||||
std::ifstream input{path, std::ios::binary};
|
||||
return std::string{std::istreambuf_iterator<char>{input},
|
||||
std::istreambuf_iterator<char>{}};
|
||||
}
|
||||
|
||||
TEST(DynamicNewtonIterationLog, WritesTimeAndAllTwoChannelMetrics) {
|
||||
TempDirectory directory{"dynamic-complete"};
|
||||
const auto path = directory.Path() / "dynamic_log.txt";
|
||||
fesa::NewtonIterationLogWriter writer;
|
||||
const auto record = CompleteDynamicRecord();
|
||||
ASSERT_TRUE(writer.Open(path).IsOk());
|
||||
ASSERT_TRUE(writer.Write(record).IsOk());
|
||||
ASSERT_TRUE(writer.Flush().IsOk());
|
||||
ASSERT_TRUE(writer.Close().IsOk());
|
||||
const std::string bytes = ReadLogBytes(path);
|
||||
ASSERT_FALSE(bytes.empty());
|
||||
EXPECT_EQ(bytes.back(), '\n');
|
||||
EXPECT_EQ(std::count(bytes.begin(), bytes.end(), '\n'), 1);
|
||||
EXPECT_EQ(bytes.find('\r'), std::string::npos);
|
||||
EXPECT_EQ(bytes.find("target_factor"), std::string::npos);
|
||||
EXPECT_EQ(bytes.find("nsnr-log-v1"), std::string::npos);
|
||||
const auto fields = SplitTabs(bytes.substr(0U, bytes.size() - 1U));
|
||||
EXPECT_EQ(fields.size(), 24U);
|
||||
EXPECT_EQ(FieldValue(fields, "record"), "record=dynamic-newton-iteration");
|
||||
EXPECT_EQ(FieldValue(fields, "schema"), "schema=hht-log-v1");
|
||||
EXPECT_EQ(FieldValue(fields, "increment"), "increment=2");
|
||||
EXPECT_EQ(FieldValue(fields, "iteration"), "iteration=3");
|
||||
EXPECT_EQ(FieldValue(fields, "time"), "time=0.25");
|
||||
EXPECT_EQ(FieldValue(fields, "stage"), "stage=convergence");
|
||||
EXPECT_EQ(FieldValue(fields, "residual_u_branch"),
|
||||
"residual_u_branch=relative");
|
||||
EXPECT_EQ(FieldValue(fields, "residual_phi_branch"),
|
||||
"residual_phi_branch=zero-scale");
|
||||
EXPECT_EQ(FieldValue(fields, "residual_pass"), "residual_pass=1");
|
||||
EXPECT_EQ(FieldValue(fields, "correction_pass"), "correction_pass=1");
|
||||
const std::vector<std::pair<std::string, double>> expected{
|
||||
{"residual_u_numerator", 2.0e-12},
|
||||
{"residual_u_scale", 2.0},
|
||||
{"residual_phi_numerator", 0.0},
|
||||
{"residual_phi_scale", 0.0},
|
||||
{"coordinate_correction_u", 1.0e-11},
|
||||
{"coordinate_correction_phi", 2.0e-11},
|
||||
{"velocity_correction_u", 3.0e-11},
|
||||
{"velocity_correction_phi", 4.0e-11},
|
||||
{"acceleration_correction_u", 5.0e-11},
|
||||
{"acceleration_correction_phi", 6.0e-11},
|
||||
{"algorithmic_correction_u", 7.0e-11},
|
||||
{"algorithmic_correction_phi", 8.0e-11}};
|
||||
for (const auto& metric : expected) {
|
||||
const auto text = ValueAfterEquals(FieldValue(fields, metric.first));
|
||||
double value = 0.0;
|
||||
const auto parsed =
|
||||
std::from_chars(text.data(), text.data() + text.size(), value);
|
||||
EXPECT_EQ(parsed.ec, std::errc{});
|
||||
EXPECT_EQ(parsed.ptr, text.data() + text.size());
|
||||
EXPECT_DOUBLE_EQ(value, metric.second);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(DynamicNewtonIterationLog, EscapesFailureContextAndKeepsZeroScaleFailure) {
|
||||
TempDirectory directory{"dynamic-failure"};
|
||||
const auto path = directory.Path() / "dynamic_log.txt";
|
||||
auto record = CompleteDynamicRecord();
|
||||
record.stage = "mass\tcheck";
|
||||
record.outcome = "failed\ntrial";
|
||||
record.failure_code = "dynamic\\mass\rregularity";
|
||||
record.residual_numerator = {1.0, 0.0};
|
||||
record.residual_scale = {0.0, 0.0};
|
||||
record.residual_pass = false;
|
||||
record.correction_pass = false;
|
||||
fesa::NewtonIterationLogWriter writer;
|
||||
ASSERT_TRUE(writer.Open(path).IsOk());
|
||||
ASSERT_TRUE(writer.Write(record).IsOk());
|
||||
ASSERT_TRUE(writer.Close().IsOk());
|
||||
const auto bytes = ReadLogBytes(path);
|
||||
EXPECT_EQ(std::count(bytes.begin(), bytes.end(), '\n'), 1);
|
||||
const auto fields = SplitTabs(bytes.substr(0U, bytes.size() - 1U));
|
||||
EXPECT_EQ(FieldValue(fields, "stage"), "stage=mass\\tcheck");
|
||||
EXPECT_EQ(FieldValue(fields, "outcome"), "outcome=failed\\ntrial");
|
||||
EXPECT_EQ(FieldValue(fields, "failure_code"),
|
||||
"failure_code=dynamic\\\\mass\\rregularity");
|
||||
EXPECT_EQ(FieldValue(fields, "residual_u_numerator"),
|
||||
"residual_u_numerator=1");
|
||||
EXPECT_EQ(FieldValue(fields, "residual_u_scale"), "residual_u_scale=0");
|
||||
EXPECT_EQ(FieldValue(fields, "residual_u_branch"),
|
||||
"residual_u_branch=zero-scale");
|
||||
EXPECT_EQ(FieldValue(fields, "residual_pass"), "residual_pass=0");
|
||||
}
|
||||
|
||||
TEST(DynamicNewtonIterationLog,
|
||||
InvalidEvidencePreservesFlushedPrefixAndFailsLifecycle) {
|
||||
using Record = fesa::DynamicNewtonIterationLogRecord;
|
||||
const double nan = std::numeric_limits<double>::quiet_NaN();
|
||||
const double infinity = std::numeric_limits<double>::infinity();
|
||||
const std::vector<std::function<void(Record&)>> mutations{
|
||||
[nan](Record& record) { record.time = nan; },
|
||||
[nan](Record& record) { record.residual_numerator[0U] = nan; },
|
||||
[infinity](Record& record) { record.residual_scale[1U] = infinity; },
|
||||
[nan](Record& record) { record.coordinate_correction[0U] = nan; },
|
||||
[infinity](Record& record) { record.velocity_correction[1U] = infinity; },
|
||||
[nan](Record& record) { record.acceleration_correction[0U] = nan; },
|
||||
[infinity](Record& record) {
|
||||
record.algorithmic_correction[1U] = infinity;
|
||||
},
|
||||
[](Record& record) { record.time = -0.1; },
|
||||
[](Record& record) { record.residual_scale[0U] = -1.0; },
|
||||
[](Record& record) { record.coordinate_correction[0U] = -1.0; },
|
||||
[](Record& record) { record.stage.clear(); },
|
||||
[](Record& record) { record.outcome.clear(); }};
|
||||
TempDirectory directory{"dynamic-invalid"};
|
||||
const auto path = directory.Path() / "dynamic_log.txt";
|
||||
for (std::size_t index = 0U; index < mutations.size(); ++index) {
|
||||
SCOPED_TRACE(index);
|
||||
fesa::NewtonIterationLogWriter writer;
|
||||
ASSERT_TRUE(writer.Open(path).IsOk());
|
||||
ASSERT_TRUE(writer.Write(CompleteDynamicRecord()).IsOk());
|
||||
ASSERT_TRUE(writer.Flush().IsOk());
|
||||
const std::string prefix = ReadLogBytes(path);
|
||||
auto invalid = CompleteDynamicRecord();
|
||||
mutations[index](invalid);
|
||||
ExpectLogOutputFailure(writer.Write(invalid));
|
||||
ExpectLogOutputFailure(writer.Write(CompleteDynamicRecord()));
|
||||
ExpectLogOutputFailure(writer.Flush());
|
||||
ExpectLogOutputFailure(writer.Close());
|
||||
EXPECT_EQ(ReadLogBytes(path), prefix);
|
||||
}
|
||||
fesa::NewtonIterationLogWriter unopened;
|
||||
ExpectLogOutputFailure(unopened.Write(CompleteDynamicRecord()));
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
Reference in New Issue
Block a user