feat(nonlinear-static-newton-raphson): step 7 - newton-iteration-log-writer
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user