feat(hex8): connect Solid CLI and atomic HDF5 output

This commit is contained in:
KOKO\Mimi
2026-10-10 01:26:16 +09:00
parent 6b118aa456
commit e2c3c5bc55
15 changed files with 2484 additions and 8 deletions
+158
View File
@@ -9,6 +9,9 @@
#include <utility>
#include <vector>
#include "fesa/analysis/solid_dynamic_analysis_state.h"
#include "fesa/analysis/solid_static_state.h"
namespace fesa {
namespace {
@@ -320,6 +323,119 @@ Result<std::string> SerializeRecord(
return Result<std::string>::Success(std::move(line));
}
Result<std::string> JoinSolidFields(const std::vector<std::string>& fields) {
std::string line;
for (const auto& field : fields) {
if (!line.empty()) line += '\t';
line += field;
}
line += '\n';
return Result<std::string>::Success(std::move(line));
}
Result<std::string> SerializeRecord(const SolidStaticIterationRecord& record,
const std::string& step,
const std::filesystem::path& path) {
std::vector<std::string> 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<std::string>::Failure(factor.GetStatus());
AppendField(&fields, "load_factor", factor.Value());
AppendField(&fields, "stage", EscapeText(record.stage));
for (const auto& entry :
std::array<std::pair<ChannelSpec, const NewtonChannelDecision*>, 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<std::string>::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<std::string> SerializeRecord(const SolidDynamicIterationRecord& record,
const std::filesystem::path& path) {
std::vector<std::string> 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<std::string>::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<std::string>::Failure(
LogOutputFailure(path, "A Solid HHT correction metric is invalid."));
}
std::vector<std::pair<std::string, double>> 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<const char*, 4> 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<const char*, 5> 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<std::string>::Failure(formatted.GetStatus());
AppendField(&fields, number.first, formatted.Value());
}
const std::array<const char*, 4> 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<std::string>::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) {
@@ -379,6 +495,48 @@ Status NewtonIterationLogWriter::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<std::streamsize>(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<std::streamsize>(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;
+87 -3
View File
@@ -2,11 +2,13 @@
#include <utility>
#include "fesa/analysis/analysis_model.h"
#include "fesa/analysis/hht_dynamic_procedure.h"
#include "fesa/analysis/linear_frequency_procedure.h"
#include "fesa/analysis/linear_static_procedure.h"
#include "fesa/analysis/newton_iteration_log.h"
#include "fesa/analysis/nonlinear_static_procedure.h"
#include "fesa/analysis/solid_frequency_procedure.h"
#include "fesa/app/output_paths.h"
#include "fesa/io/abaqus/domain_mapper.h"
#include "fesa/io/abaqus/input_reader.h"
@@ -14,6 +16,7 @@
#include "fesa/results/eigen_results_writer.h"
#include "fesa/results/nonlinear_results_writer.h"
#include "fesa/results/results_writer.h"
#include "fesa/results/solid_results_writer.h"
#include "fesa/results/thermal_results_writer.h"
namespace fesa {
@@ -42,7 +45,10 @@ Result<PreparedAnalysisOutcome> ExecutePreparedAnalysis(
const LinearFrequencyProcedureExecutor& frequency_executor,
NewtonIterationLogWriter& log,
const HhtDynamicProcedureExecutor* dynamic_executor,
const TransientHeatProcedureExecutor* thermal_executor) {
const TransientHeatProcedureExecutor* thermal_executor,
const SolidStaticProcedureExecutor* solid_static_executor,
const SolidFrequencyProcedureExecutor* solid_frequency_executor,
const SolidHhtDynamicProcedureExecutor* solid_dynamic_executor) {
if (domain.ThermalStep() != nullptr) {
if (thermal_executor == nullptr) {
return Result<PreparedAnalysisOutcome>::Failure(
@@ -72,6 +78,63 @@ Result<PreparedAnalysisOutcome> ExecutePreparedAnalysis(
"Prepared analysis requires exactly one analysis step."}}));
}
if (domain.IsSolid()) {
const auto missing = []() {
return Result<PreparedAnalysisOutcome>::Failure(Status::Failure(
FailureCategory::kSolver,
{{Severity::kError,
"missing-solid-executor",
{},
"",
{},
"The selected Solid execution adapter is required."}}));
};
if (domain.Dynamic() != nullptr) {
if (solid_dynamic_executor == nullptr) return missing();
auto model = AnalysisModel::Create(domain);
if (!model.HasValue())
return Result<PreparedAnalysisOutcome>::Failure(model.GetStatus());
auto dofs = DofManager::Create(model.Value());
if (!dofs.HasValue())
return Result<PreparedAnalysisOutcome>::Failure(dofs.GetStatus());
auto state = SolidDynamicAnalysisState::Create(domain, dofs.Value());
if (!state.HasValue())
return Result<PreparedAnalysisOutcome>::Failure(state.GetStatus());
const Status execution =
solid_dynamic_executor->ExecuteInto(domain, state.Value());
for (const auto& record : state.Value().IterationRecords()) {
const Status written = log.Write(record);
if (!written.IsOk())
return Result<PreparedAnalysisOutcome>::Failure(written);
}
if (!execution.IsOk())
return Result<PreparedAnalysisOutcome>::Failure(execution);
return Result<PreparedAnalysisOutcome>::Success(
PreparedAnalysisOutcome{SolidDynamicExecutionResult{
std::move(dofs.Value()), std::move(state.Value())}});
}
if (domain.Steps()[0U].IsFrequency()) {
if (solid_frequency_executor == nullptr) return missing();
auto result = solid_frequency_executor->Execute(domain);
if (!result.HasValue())
return Result<PreparedAnalysisOutcome>::Failure(result.GetStatus());
return Result<PreparedAnalysisOutcome>::Success(
PreparedAnalysisOutcome{std::move(result.Value())});
}
if (solid_static_executor == nullptr) return missing();
SolidStaticState state;
const Status execution = solid_static_executor->ExecuteInto(domain, state);
for (const auto& record : state.IterationRecords()) {
const Status written = log.Write(record, domain.Steps()[0U].Name());
if (!written.IsOk())
return Result<PreparedAnalysisOutcome>::Failure(written);
}
if (!execution.IsOk())
return Result<PreparedAnalysisOutcome>::Failure(execution);
return Result<PreparedAnalysisOutcome>::Success(PreparedAnalysisOutcome{
SolidStaticExecutionResult{std::move(state), domain.Warnings()}});
}
if (domain.Dynamic() != nullptr) {
if (dynamic_executor == nullptr)
return Result<PreparedAnalysisOutcome>::Failure(
@@ -120,8 +183,29 @@ Status FinalizePreparedAnalysisResult(
const PreparedAnalysisOutcome& outcome,
const std::vector<Diagnostic>& diagnostics, ResultsWriter& linear_writer,
NonlinearResultsWriter& nonlinear_writer, EigenResultsWriter& eigen_writer,
DynamicResultsWriter* dynamic_writer,
ThermalResultsWriter* thermal_writer) {
DynamicResultsWriter* dynamic_writer, ThermalResultsWriter* thermal_writer,
SolidResultsWriter* solid_writer) {
if (std::holds_alternative<SolidStaticExecutionResult>(outcome) ||
std::holds_alternative<SolidFrequencyState>(outcome) ||
std::holds_alternative<SolidDynamicExecutionResult>(outcome)) {
if (solid_writer == nullptr)
return Status::Failure(FailureCategory::kOutput,
{{Severity::kError,
"missing-solid-writer",
{},
"",
{},
"Solid result writer is required."}});
if (const auto* result = std::get_if<SolidStaticExecutionResult>(&outcome))
return solid_writer->Write(paths.results_path, domain, result->state,
diagnostics);
if (const auto* result = std::get_if<SolidFrequencyState>(&outcome))
return solid_writer->Write(paths.results_path, domain, *result,
diagnostics);
return solid_writer->Write(
paths.results_path, domain,
std::get<SolidDynamicExecutionResult>(outcome).state, diagnostics);
}
if (std::holds_alternative<ThermalExecutionResult>(outcome)) {
if (thermal_writer == nullptr) {
return Status::Failure(FailureCategory::kOutput,