feat: implement consistent-mass HHT shell dynamics

This commit is contained in:
KOKO\Mimi
2026-09-22 21:38:42 +09:00
parent fec8cf09c2
commit f0dc42a280
41 changed files with 9294 additions and 125 deletions
@@ -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