#include "fesa/analysis/newton_iteration_log.h" #include #include #include #include #include #include #include #include #include "fesa/analysis/solid_dynamic_analysis_state.h" #include "fesa/analysis/solid_static_state.h" 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(byte); } break; } } return escaped; } Result FormatFiniteNumber(const double value, const std::filesystem::path& log_path) { if (!std::isfinite(value)) { return Result::Failure( LogOutputFailure(log_path, "A Newton log numeric value is nonfinite.")); } std::array buffer{}; const auto conversion = std::to_chars( buffer.data(), buffer.data() + buffer.size(), value, std::chars_format::general, std::numeric_limits::max_digits10); if (conversion.ec != std::errc{}) { return Result::Failure( LogOutputFailure(log_path, "A Newton log numeric value could not format.")); } return Result::Success( std::string{buffer.data(), conversion.ptr}); } Result BranchLabel( const NewtonConvergenceBranch branch, const std::filesystem::path& log_path) { switch (branch) { case NewtonConvergenceBranch::kRelative: return Result::Success("relative"); case NewtonConvergenceBranch::kZeroScale: return Result::Success("zero-scale"); } return Result::Failure(LogOutputFailure( log_path, "A Newton log convergence branch is not supported.")); } Result UnitLabel(const NewtonChannelUnit unit, const std::filesystem::path& log_path) { switch (unit) { case NewtonChannelUnit::kForce: return Result::Success("force"); case NewtonChannelUnit::kForceLength: return Result::Success("force*length"); case NewtonChannelUnit::kLength: return Result::Success("length"); case NewtonChannelUnit::kDimensionless: return Result::Success("1"); } return Result::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* fields, const std::string_view label, const std::string& value) { fields->push_back(std::string{label} + "=" + value); } Status AppendChannelFields( std::vector* 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 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::Failure(load_factor.GetStatus()); } const bool channels_evaluated = AreChannelsEvaluated(record); const std::string not_evaluated = "not-evaluated(" + EscapeText(record.failure.reason) + ")"; std::vector 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 specs{{ {"residual_force", "residual_force"}, {"residual_moment", "residual_moment"}, {"correction_translation", "correction_translation"}, {"correction_rotation", "correction_rotation"}, }}; const std::array 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::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::Success(std::move(line)); } /// @brief Serializes dynamic evidence without static load-factor semantics. Result SerializeRecord( const DynamicNewtonIterationLogRecord& record, const std::filesystem::path& log_path) { const auto time = FormatFiniteNumber(record.time, log_path); if (!time.HasValue()) return Result::Failure(time.GetStatus()); if (record.time < 0.0 || record.stage.empty() || record.outcome.empty()) { return Result::Failure(LogOutputFailure( log_path, "A dynamic Newton log requires nonnegative time, stage, and outcome.")); } std::vector fields; fields.reserve(24U); AppendField(&fields, "record", "dynamic-newton-iteration"); AppendField(&fields, "schema", "hht-log-v1"); AppendField(&fields, "increment", std::to_string(record.increment_index)); AppendField(&fields, "time", time.Value()); AppendField(&fields, "iteration", std::to_string(record.iteration_index)); AppendField(&fields, "stage", EscapeText(record.stage)); const std::array channels{"u", "phi"}; for (std::size_t channel = 0U; channel < channels.size(); ++channel) { const std::string suffix{channels[channel]}; const std::array, 6U> metrics{{ {"residual_" + suffix + "_numerator", record.residual_numerator[channel]}, {"residual_" + suffix + "_scale", record.residual_scale[channel]}, {"coordinate_correction_" + suffix, record.coordinate_correction[channel]}, {"velocity_correction_" + suffix, record.velocity_correction[channel]}, {"acceleration_correction_" + suffix, record.acceleration_correction[channel]}, {"algorithmic_correction_" + suffix, record.algorithmic_correction[channel]}, }}; for (const auto& metric : metrics) { const auto formatted = FormatFiniteNumber(metric.second, log_path); if (!formatted.HasValue()) { return Result::Failure(formatted.GetStatus()); } if (metric.second < 0.0) { return Result::Failure(LogOutputFailure( log_path, "A dynamic Newton log norm or scale is negative.")); } AppendField(&fields, metric.first, formatted.Value()); } AppendField( &fields, "residual_" + suffix + "_branch", record.residual_scale[channel] == 0.0 ? "zero-scale" : "relative"); } AppendField(&fields, "residual_pass", record.residual_pass ? "1" : "0"); AppendField(&fields, "correction_pass", record.correction_pass ? "1" : "0"); AppendField(&fields, "outcome", EscapeText(record.outcome)); AppendField(&fields, "failure_code", EscapeText(record.failure_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::Success(std::move(line)); } Result JoinSolidFields(const std::vector& fields) { std::string line; for (const auto& field : fields) { if (!line.empty()) line += '\t'; line += field; } line += '\n'; return Result::Success(std::move(line)); } Result SerializeRecord(const SolidStaticIterationRecord& record, const std::string& step, const std::filesystem::path& path) { std::vector fields; AppendField(&fields, "schema", "solid-static-log-v1"); AppendField(&fields, "step", EscapeText(step)); AppendField(&fields, "increment", std::to_string(record.increment_index)); AppendField(&fields, "iteration", std::to_string(record.iteration)); const auto factor = FormatFiniteNumber(record.load_factor, path); if (!factor.HasValue()) return Result::Failure(factor.GetStatus()); AppendField(&fields, "load_factor", factor.Value()); AppendField(&fields, "stage", EscapeText(record.stage)); for (const auto& entry : std::array, 2>{ {{{"residual_force", "residual_force"}, &record.force_residual}, {{"correction_translation", "correction_translation"}, &record.translation_correction}}}) { const Status status = AppendChannelFields( &fields, entry.first, *entry.second, record.convergence_evaluated, "not-evaluated(" + EscapeText(record.failure_reason) + ")", path); if (!status.IsOk()) return Result::Failure(status); } AppendField(&fields, "accepted", record.accepted ? "1" : "0"); AppendField(&fields, "diagnostic_code", EscapeText(record.diagnostic_code)); AppendField(&fields, "failure_reason", EscapeText(record.failure_reason)); return JoinSolidFields(fields); } Result SerializeRecord(const SolidDynamicIterationRecord& record, const std::filesystem::path& path) { std::vector fields; AppendField(&fields, "schema", "solid-hht-log-v1"); AppendField(&fields, "arithmetic", "normalized-high-low-physical-state;RN64-observables"); AppendField(&fields, "increment", std::to_string(record.increment_index)); AppendField(&fields, "iteration", std::to_string(record.evidence.iteration_index)); AppendField(&fields, "stage", EscapeText(record.stage)); AppendField(&fields, "outcome", EscapeText(record.outcome)); AppendField(&fields, "failure_code", EscapeText(record.failure_code)); const auto& e = record.evidence; for (const double metric : {e.residual_numerator, e.residual_scale, e.residual_metric, e.weighted_residual_numerator, e.recombined_residual_numerator, e.old_residual_numerator, e.weighted_equivalence_metric, e.update_metric}) { if (!std::isfinite(metric) || metric < 0) return Result::Failure(LogOutputFailure( path, "A Solid HHT norm, scale or metric is invalid.")); } for (const double metric : e.correction_metrics) { if (!std::isfinite(metric) || metric < 0) return Result::Failure( LogOutputFailure(path, "A Solid HHT correction metric is invalid.")); } std::vector> numbers{ {"time", record.time}, {"physical_residual_force_norm", e.residual_numerator}, {"uncancelled_force_scale", e.residual_scale}, {"physical_residual_max_ratio", e.residual_metric}, {"original_weighted_force_norm", e.weighted_residual_numerator}, {"solved_weighted_force_norm", e.recombined_residual_numerator}, {"old_physical_force_norm", e.old_residual_numerator}, {"weighted_equivalence_metric", e.weighted_equivalence_metric}, {"update_metric", e.update_metric}}; const std::array corrections{{"du_over_L", "h_dv_over_L", "h2_physical_da_over_L", "h2_algorithmic_dA_over_L"}}; for (std::size_t i = 0; i < 4; ++i) numbers.emplace_back(corrections[i], e.correction_metrics[i]); const std::array terms{ {"worst_r_force", "worst_Ma_force", "worst_internal_force", "worst_external_force", "worst_uncancelled_force_scale"}}; for (std::size_t i = 0; i < 5; ++i) numbers.emplace_back(terms[i], e.worst_force_terms[i]); for (const auto& number : numbers) { const auto formatted = FormatFiniteNumber(number.second, path); if (!formatted.HasValue()) return Result::Failure(formatted.GetStatus()); AppendField(&fields, number.first, formatted.Value()); } const std::array weighted{ {"worst_original_weighted_force", "worst_solved_weighted_force", "worst_old_physical_force", "worst_weighted_uncancelled_force_scale"}}; for (std::size_t i = 0; i < 4; ++i) { const auto formatted = FormatFiniteNumber(e.worst_weighted_terms[i], path); if (!formatted.HasValue()) return Result::Failure(formatted.GetStatus()); AppendField(&fields, weighted[i], formatted.Value()); } AppendField(&fields, "physical_residual_threshold", "1e-10"); AppendField(&fields, "correction_threshold", "1e-10"); AppendField(&fields, "update_threshold", "1e-12"); AppendField(&fields, "weighted_equivalence_threshold", "1e-12"); AppendField(&fields, "residual_unit", "force"); AppendField(&fields, "correction_unit", "dimensionless"); AppendField(&fields, "worst_full_dof", std::to_string(e.worst_full_dof)); AppendField(&fields, "zero_scale_exact", e.zero_scale_exact ? "1" : "0"); AppendField(&fields, "physical_residual_pass", e.residual_pass ? "1" : "0"); AppendField(&fields, "correction_pass", e.correction_pass ? "1" : "0"); AppendField(&fields, "update_pass", e.update_pass ? "1" : "0"); return JoinSolidFields(fields); } } // 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(line.Value().size())); if (!stream_) { state_ = State::kFailed; return LogOutputFailure(log_path_, "The Newton log record could not write."); } return Status::Ok(); } Status NewtonIterationLogWriter::Write( const DynamicNewtonIterationLogRecord& 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(line.Value().size())); if (!stream_) { state_ = State::kFailed; return LogOutputFailure(log_path_, "The dynamic Newton log record could not write."); } return Status::Ok(); } Status NewtonIterationLogWriter::Write(const SolidStaticIterationRecord& record, const std::string& step_name) { if (state_ != State::kOpen) { state_ = State::kFailed; return LogOutputFailure(log_path_, "The Newton log writer is not open."); } const auto line = SerializeRecord(record, step_name, log_path_); if (!line.HasValue()) { state_ = State::kFailed; return line.GetStatus(); } stream_.write(line.Value().data(), static_cast(line.Value().size())); if (!stream_) { state_ = State::kFailed; return LogOutputFailure(log_path_, "The Solid Newton log record could not write."); } return Status::Ok(); } Status NewtonIterationLogWriter::Write( const SolidDynamicIterationRecord& 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(line.Value().size())); if (!stream_) { state_ = State::kFailed; return LogOutputFailure(log_path_, "The Solid 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