feat(nonlinear-static-newton-raphson): step 7 - newton-iteration-log-writer

This commit is contained in:
KOKO\Mimi
2026-08-24 14:34:19 +09:00
parent b0d53fb8f8
commit 902bc79e16
5 changed files with 746 additions and 0 deletions
+1
View File
@@ -4,6 +4,7 @@ add_library(
analysis/analysis_model.cpp
analysis/analysis_state.cpp
analysis/linear_static_analysis.cpp
analysis/newton_iteration_log.cpp
analysis/nonlinear_static_control.cpp
app/fesa_application.cpp
app/output_paths.cpp
+328
View File
@@ -0,0 +1,328 @@
#include "fesa/analysis/newton_iteration_log.h"
#include <array>
#include <charconv>
#include <cmath>
#include <limits>
#include <string>
#include <string_view>
#include <utility>
#include <vector>
namespace fesa {
namespace {
struct ChannelSpec {
std::string_view label_prefix;
std::string_view identity;
};
Status LogOutputFailure(const std::filesystem::path& log_path,
const std::string& message) {
return Status::Failure(
FailureCategory::kOutput,
{{Severity::kError,
"log-output-failure",
{{}, 0U},
"",
log_path.generic_u8string(),
message}});
}
std::string EscapeText(const std::string_view value) {
constexpr char kHexDigits[] = "0123456789ABCDEF";
std::string escaped;
escaped.reserve(value.size());
for (const unsigned char byte : value) {
switch (byte) {
case '\\':
escaped += "\\\\";
break;
case '\t':
escaped += "\\t";
break;
case '\n':
escaped += "\\n";
break;
case '\r':
escaped += "\\r";
break;
case '\0':
escaped += "\\0";
break;
default:
if (byte < 0x20U || byte == 0x7FU) {
escaped += "\\x";
escaped += kHexDigits[byte >> 4U];
escaped += kHexDigits[byte & 0x0FU];
} else {
escaped += static_cast<char>(byte);
}
break;
}
}
return escaped;
}
Result<std::string> FormatFiniteNumber(const double value,
const std::filesystem::path& log_path) {
if (!std::isfinite(value)) {
return Result<std::string>::Failure(
LogOutputFailure(log_path, "A Newton log numeric value is nonfinite."));
}
std::array<char, 64U> buffer{};
const auto conversion = std::to_chars(
buffer.data(), buffer.data() + buffer.size(), value,
std::chars_format::general, std::numeric_limits<double>::max_digits10);
if (conversion.ec != std::errc{}) {
return Result<std::string>::Failure(
LogOutputFailure(log_path, "A Newton log numeric value could not format."));
}
return Result<std::string>::Success(
std::string{buffer.data(), conversion.ptr});
}
Result<std::string_view> BranchLabel(
const NewtonConvergenceBranch branch,
const std::filesystem::path& log_path) {
switch (branch) {
case NewtonConvergenceBranch::kRelative:
return Result<std::string_view>::Success("relative");
case NewtonConvergenceBranch::kZeroScale:
return Result<std::string_view>::Success("zero-scale");
}
return Result<std::string_view>::Failure(LogOutputFailure(
log_path, "A Newton log convergence branch is not supported."));
}
Result<std::string_view> UnitLabel(const NewtonChannelUnit unit,
const std::filesystem::path& log_path) {
switch (unit) {
case NewtonChannelUnit::kForce:
return Result<std::string_view>::Success("force");
case NewtonChannelUnit::kForceLength:
return Result<std::string_view>::Success("force*length");
case NewtonChannelUnit::kLength:
return Result<std::string_view>::Success("length");
case NewtonChannelUnit::kDimensionless:
return Result<std::string_view>::Success("1");
}
return Result<std::string_view>::Failure(LogOutputFailure(
log_path, "A Newton log threshold unit is not supported."));
}
std::string StageLabel(const NewtonIterationStage stage) {
switch (stage) {
case NewtonIterationStage::kEntered:
return "iteration-entered";
case NewtonIterationStage::kKinematicsEvaluated:
return "trial-state";
case NewtonIterationStage::kInternalAssembled:
return "residual";
case NewtonIterationStage::kExternalAssembled:
return "load-boundary";
case NewtonIterationStage::kSolvedCorrection:
return "correction";
case NewtonIterationStage::kConvergenceEvaluated:
return "convergence-gates";
case NewtonIterationStage::kAccepted:
return "commit";
case NewtonIterationStage::kFailed:
return "failed";
}
return "failed";
}
bool AreChannelsEvaluated(const NewtonIterationLogRecord& record) {
if (!record.failure.present) {
return true;
}
return record.last_completed_stage ==
NewtonIterationStage::kConvergenceEvaluated ||
record.last_completed_stage == NewtonIterationStage::kAccepted;
}
void AppendField(std::vector<std::string>* fields, const std::string_view label,
const std::string& value) {
fields->push_back(std::string{label} + "=" + value);
}
Status AppendChannelFields(
std::vector<std::string>* fields, const ChannelSpec spec,
const NewtonChannelDecision& decision, const bool evaluated,
const std::string& not_evaluated, const std::filesystem::path& log_path) {
const std::string prefix{spec.label_prefix};
if (!evaluated) {
for (const std::string_view suffix :
{"channel", "numerator_norm", "denominator_scale", "branch",
"threshold", "threshold_unit", "pass"}) {
AppendField(fields, prefix + "_" + std::string{suffix}, not_evaluated);
}
return Status::Ok();
}
if (decision.identity != spec.identity) {
return LogOutputFailure(log_path,
"A Newton log channel identity is invalid.");
}
const auto numerator = FormatFiniteNumber(decision.numerator_norm, log_path);
if (!numerator.HasValue()) {
return numerator.GetStatus();
}
const auto denominator =
FormatFiniteNumber(decision.denominator_scale, log_path);
if (!denominator.HasValue()) {
return denominator.GetStatus();
}
const auto branch = BranchLabel(decision.branch, log_path);
if (!branch.HasValue()) {
return branch.GetStatus();
}
const auto threshold = FormatFiniteNumber(decision.threshold, log_path);
if (!threshold.HasValue()) {
return threshold.GetStatus();
}
const auto unit = UnitLabel(decision.threshold_unit, log_path);
if (!unit.HasValue()) {
return unit.GetStatus();
}
AppendField(fields, prefix + "_channel", decision.identity);
AppendField(fields, prefix + "_numerator_norm", numerator.Value());
AppendField(fields, prefix + "_denominator_scale", denominator.Value());
AppendField(fields, prefix + "_branch", std::string{branch.Value()});
AppendField(fields, prefix + "_threshold", threshold.Value());
AppendField(fields, prefix + "_threshold_unit", std::string{unit.Value()});
AppendField(fields, prefix + "_pass", decision.passed ? "1" : "0");
return Status::Ok();
}
Result<std::string> SerializeRecord(const NewtonIterationLogRecord& record,
const std::filesystem::path& log_path) {
const auto load_factor = FormatFiniteNumber(record.load_factor, log_path);
if (!load_factor.HasValue()) {
return Result<std::string>::Failure(load_factor.GetStatus());
}
const bool channels_evaluated = AreChannelsEvaluated(record);
const std::string not_evaluated =
"not-evaluated(" + EscapeText(record.failure.reason) + ")";
std::vector<std::string> fields;
fields.reserve(39U);
AppendField(&fields, "record", "newton-iteration");
AppendField(&fields, "schema", "nsnr-log-v1");
AppendField(&fields, "increment", std::to_string(record.increment_index));
AppendField(&fields, "target_factor", load_factor.Value());
AppendField(&fields, "iteration", std::to_string(record.iteration));
AppendField(&fields, "last_completed_stage",
StageLabel(record.last_completed_stage));
AppendField(&fields, "failing_stage",
record.failure.present ? StageLabel(record.failure.stage) : "none");
const std::array<ChannelSpec, 4U> specs{{
{"residual_force", "residual_force"},
{"residual_moment", "residual_moment"},
{"correction_translation", "correction_translation"},
{"correction_rotation", "correction_rotation"},
}};
const std::array<const NewtonChannelDecision*, 4U> decisions{{
&record.force_residual,
&record.moment_residual,
&record.translation_correction,
&record.rotation_correction,
}};
for (std::size_t index = 0U; index < specs.size(); ++index) {
const Status channel_status =
AppendChannelFields(&fields, specs[index], *decisions[index],
channels_evaluated, not_evaluated, log_path);
if (!channel_status.IsOk()) {
return Result<std::string>::Failure(channel_status);
}
}
AppendField(&fields, "combined_pass",
channels_evaluated ? (record.combined_passed ? "1" : "0")
: not_evaluated);
AppendField(&fields, "transition", EscapeText(record.transition));
AppendField(&fields, "outcome", EscapeText(record.outcome));
AppendField(&fields, "diagnostic_code", EscapeText(record.diagnostic_code));
std::string line;
for (std::size_t index = 0U; index < fields.size(); ++index) {
if (index != 0U) {
line += '\t';
}
line += fields[index];
}
line += '\n';
return Result<std::string>::Success(std::move(line));
}
} // namespace
Status NewtonIterationLogWriter::Open(const std::filesystem::path& log_path) {
if (state_ != State::kReady) {
state_ = State::kFailed;
return LogOutputFailure(log_path_, "The Newton log writer is not ready.");
}
log_path_ = log_path;
stream_.open(log_path_, std::ios::binary | std::ios::out | std::ios::trunc);
if (!stream_.is_open() || !stream_) {
state_ = State::kFailed;
return LogOutputFailure(log_path_, "The Newton log could not open.");
}
state_ = State::kOpen;
return Status::Ok();
}
Status NewtonIterationLogWriter::Write(const NewtonIterationLogRecord& record) {
if (state_ != State::kOpen) {
state_ = State::kFailed;
return LogOutputFailure(log_path_, "The Newton log writer is not open.");
}
const auto line = SerializeRecord(record, log_path_);
if (!line.HasValue()) {
state_ = State::kFailed;
return line.GetStatus();
}
stream_.write(line.Value().data(),
static_cast<std::streamsize>(line.Value().size()));
if (!stream_) {
state_ = State::kFailed;
return LogOutputFailure(log_path_, "The Newton log record could not write.");
}
return Status::Ok();
}
Status NewtonIterationLogWriter::Flush() {
if (state_ != State::kOpen) {
state_ = State::kFailed;
return LogOutputFailure(log_path_, "The Newton log writer is not open.");
}
stream_.flush();
if (!stream_) {
state_ = State::kFailed;
return LogOutputFailure(log_path_, "The Newton log could not flush.");
}
return Status::Ok();
}
Status NewtonIterationLogWriter::Close() {
if (state_ != State::kOpen) {
state_ = State::kFailed;
return LogOutputFailure(log_path_, "The Newton log writer is not open.");
}
stream_.close();
if (stream_.fail()) {
state_ = State::kFailed;
return LogOutputFailure(log_path_, "The Newton log could not close.");
}
state_ = State::kClosed;
return Status::Ok();
}
} // namespace fesa