Connect HHT field convergence to production acceptance and v2 logs
This commit is contained in:
@@ -191,8 +191,34 @@ Result<double> ReferenceLength(const Domain& domain) {
|
||||
return Result<double>::Success(length);
|
||||
}
|
||||
|
||||
Result<std::array<hht_field_convergence::FieldInput, 2>> MeasureEndpointFields(
|
||||
const DofManager& dofs, const DynamicAcceptedFrameState& trial,
|
||||
const DynamicAcceptedFrameState& accepted, const std::optional<Vector>& previous) {
|
||||
using Fields = std::array<hht_field_convergence::FieldInput, 2>;
|
||||
const auto count = dofs.FullDofCount();
|
||||
if (trial.generalized_coordinates.Size() != count || trial.residual.Size() != count ||
|
||||
accepted.generalized_coordinates.Size() != count || (previous && previous->Size() != count))
|
||||
return Result<Fields>::Failure(Failure("Invalid full endpoint field inventory."));
|
||||
Fields fields;
|
||||
for (auto& field : fields) if (previous) field.correction = 0.0;
|
||||
for (const auto full : dofs.FreeDofs()) {
|
||||
auto& field = fields[full % 6U < 3U ? 0U : 1U];
|
||||
const double residual = std::abs(trial.residual[full]);
|
||||
const double increment = std::abs(trial.generalized_coordinates[full] - accepted.generalized_coordinates[full]);
|
||||
const double correction = previous ? std::abs(trial.generalized_coordinates[full] - (*previous)[full]) : 0.0;
|
||||
if (!std::isfinite(residual) || !std::isfinite(increment) || !std::isfinite(correction))
|
||||
return Result<Fields>::Failure(Failure("Nonfinite full endpoint field arithmetic."));
|
||||
field.active = true;
|
||||
field.residual = (std::max)(field.residual, residual);
|
||||
field.increment = (std::max)(field.increment, increment);
|
||||
if (previous) field.correction = (std::max)(*field.correction, correction);
|
||||
}
|
||||
return Result<Fields>::Success(std::move(fields));
|
||||
}
|
||||
|
||||
Result<std::array<Vector, 2>> AssembleLoads(const Domain& domain,
|
||||
const DofManager& dofs) {
|
||||
const DofManager& dofs,
|
||||
std::array<std::vector<hht_field_convergence::FluxOccurrence>, 2>* field_flux) {
|
||||
const auto index = SourceTargetIndex::FromDomain(domain);
|
||||
const SourceTargetResolver resolver(index);
|
||||
const LoadContext context{domain, dofs, resolver};
|
||||
@@ -203,6 +229,9 @@ Result<std::array<Vector, 2>> AssembleLoads(const Domain& domain,
|
||||
if (!contributions.HasValue())
|
||||
return Result<std::array<Vector, 2>>::Failure(contributions.GetStatus());
|
||||
for (const auto& entry : contributions.Value()) {
|
||||
if (field_flux != nullptr)
|
||||
(*field_flux)[entry.full_dof_index % 6U < 3U ? 0U : 1U].push_back(
|
||||
{entry.value, 0.0});
|
||||
result[0U][entry.full_dof_index] += entry.value;
|
||||
result[1U][entry.full_dof_index] += std::abs(entry.value);
|
||||
if (!std::isfinite(result[0U][entry.full_dof_index]) ||
|
||||
@@ -489,6 +518,7 @@ Result<DynamicAssembly> EvaluateAssembly(
|
||||
Vector third(count);
|
||||
Vector third_scale(count);
|
||||
std::array<std::vector<CooContribution>, 6> entries;
|
||||
std::array<std::vector<hht_field_convergence::FluxOccurrence>, 2> field_flux;
|
||||
for (auto& values : entries) values.reserve(shells.size() * 24U * 24U);
|
||||
double momentum_scale = 0.0;
|
||||
double angular_scale = 0.0;
|
||||
@@ -546,6 +576,8 @@ Result<DynamicAssembly> EvaluateAssembly(
|
||||
const Vector mass_action = EntryProducts(mass_values, acceleration_values);
|
||||
for (std::size_t row = 0U; row < 24U; ++row) {
|
||||
const auto global = shells[e].scatter[row];
|
||||
field_flux[row % 6U < 3U ? 0U : 1U].push_back(
|
||||
{f.internal_force[row], m.force[row]});
|
||||
frame.internal_force[global] += f.internal_force[row];
|
||||
frame.inertia_force[global] += m.force[row];
|
||||
third[global] += f.force_directional_second[row];
|
||||
@@ -708,7 +740,7 @@ Result<DynamicAssembly> EvaluateAssembly(
|
||||
std::move(third), std::move(third_scale), std::move(rotations),
|
||||
momentum_scale, angular_scale, std::move(hidden_second),
|
||||
std::move(hidden_second_scale), std::move(hidden_gradient),
|
||||
std::move(trial_constraints)});
|
||||
std::move(trial_constraints), std::move(field_flux)});
|
||||
}
|
||||
|
||||
Status CompleteEquilibrium(const Domain& domain, const DofManager& dofs,
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
#include "fesa/math/sparse_matrix.h"
|
||||
#include "elements/nonlinear_mitc4_configuration_internal.h"
|
||||
#include "math/dense_blas_internal.h"
|
||||
#include "analysis/hht_field_convergence.h"
|
||||
|
||||
namespace fesa {
|
||||
class LinearSolver;
|
||||
@@ -95,6 +96,7 @@ struct DynamicAssembly {
|
||||
// Canonical scalar drilling-constraint derivative, indexed by global node.
|
||||
std::optional<SparseMatrix> hidden_gradient;
|
||||
std::optional<TrialConstraintData> trial_constraints;
|
||||
std::array<std::vector<hht_field_convergence::FluxOccurrence>, 2> field_flux;
|
||||
};
|
||||
|
||||
/// @brief Builds checked shell evaluators in stable source order.
|
||||
@@ -104,9 +106,17 @@ Result<std::vector<RuntimeShell>> BuildRuntimeShells(const Domain& domain,
|
||||
/// @brief Returns the maximum initial active-element edge length.
|
||||
Result<double> ReferenceLength(const Domain& domain);
|
||||
|
||||
/// @brief Measures free full endpoint residuals and actual u/total-phi changes.
|
||||
/// @note Previous is the selected trial in this increment, not a tangent lift.
|
||||
Result<std::array<hht_field_convergence::FieldInput, 2>> MeasureEndpointFields(
|
||||
const DofManager& dofs, const DynamicAcceptedFrameState& trial,
|
||||
const DynamicAcceptedFrameState& accepted,
|
||||
const std::optional<Vector>& previous);
|
||||
|
||||
/// @brief Assembles dead loads and their uncancelled absolute row scales.
|
||||
Result<std::array<Vector, 2>> AssembleLoads(const Domain& domain,
|
||||
const DofManager& dofs);
|
||||
const DofManager& dofs,
|
||||
std::array<std::vector<hht_field_convergence::FluxOccurrence>, 2>* field_flux = nullptr);
|
||||
|
||||
/// @brief Evaluates current kinetic and internal quantities without commit.
|
||||
Result<DynamicAssembly> EvaluateAssembly(
|
||||
|
||||
@@ -150,7 +150,7 @@ void ResidualRecord(const DofManager& dofs,
|
||||
frame.free_residual_metrics[1U] <= 1e-10;
|
||||
}
|
||||
|
||||
void CorrectionRecord(const Vector& coordinate, const Vector& velocity,
|
||||
bool CorrectionRecord(const Vector& coordinate, const Vector& velocity,
|
||||
const Vector& acceleration,
|
||||
const Vector& full_algorithmic,
|
||||
const Vector& reduced_algorithmic,
|
||||
@@ -189,12 +189,14 @@ void CorrectionRecord(const Vector& coordinate, const Vector& velocity,
|
||||
if (reduced_algorithmic.Size() != chart.p_columns.size() ||
|
||||
reduced_physical.Size() != chart.p_columns.size()) {
|
||||
record->correction_pass = false;
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
bool private_pass = true;
|
||||
for (std::size_t i = 0U; i < chart.p_dofs.size(); ++i)
|
||||
record->correction_pass = record->correction_pass &&
|
||||
PrivateCorrectionEqual(chart, i, full_algorithmic,
|
||||
reduced_algorithmic, reduced_physical, alpha);
|
||||
private_pass = private_pass && PrivateCorrectionEqual(
|
||||
chart, i, full_algorithmic, reduced_algorithmic, reduced_physical, alpha);
|
||||
record->correction_pass = record->correction_pass && private_pass;
|
||||
return private_pass;
|
||||
}
|
||||
|
||||
bool ExactlyEqual(const Vector& left, const Vector& right) {
|
||||
@@ -433,7 +435,8 @@ Result<DynamicExecutionResult> ExecuteReducedHht(
|
||||
auto length_result = ReferenceLength(domain);
|
||||
if (!length_result.HasValue()) return Result<DynamicExecutionResult>::Failure(length_result.GetStatus());
|
||||
const double length = length_result.Value();
|
||||
auto loads = AssembleLoads(domain, dofs);
|
||||
std::array<std::vector<hht_field_convergence::FluxOccurrence>, 2> load_flux;
|
||||
auto loads = AssembleLoads(domain, dofs, &load_flux);
|
||||
if (!loads.HasValue()) return Result<DynamicExecutionResult>::Failure(loads.GetStatus());
|
||||
auto parameters_result = MakeHhtParameters(domain.Dynamic()->alpha);
|
||||
if (!parameters_result.HasValue()) return Result<DynamicExecutionResult>::Failure(parameters_result.GetStatus());
|
||||
@@ -699,9 +702,11 @@ Result<DynamicExecutionResult> ExecuteReducedHht(
|
||||
status = log.Write(record); if (status.IsOk()) status = log.Flush();
|
||||
if (!status.IsOk()) return Result<DynamicExecutionResult>::Failure(status);
|
||||
|
||||
std::array<hht_field_convergence::History, 2> flux_history{};
|
||||
struct ReducedStepCandidate {
|
||||
DynamicAcceptedFrameState frame;
|
||||
ReducedHistory history;
|
||||
std::array<hht_field_convergence::History, 2> flux_history;
|
||||
};
|
||||
for (std::uint32_t increment = 1U;
|
||||
increment <= domain.Dynamic()->increment_count; ++increment) {
|
||||
@@ -711,9 +716,14 @@ Result<DynamicExecutionResult> ExecuteReducedHht(
|
||||
domain, failure, stage, increment, record.time);
|
||||
record.stage = stage;
|
||||
record.outcome = "failed";
|
||||
record.failure_code = contextual.Diagnostics().empty()
|
||||
? "dynamic-solver-failure"
|
||||
: contextual.Diagnostics().front().code;
|
||||
record.accepted = false;
|
||||
const auto cause = std::find_if(
|
||||
failure.Diagnostics().begin(), failure.Diagnostics().end(),
|
||||
[](const Diagnostic& diagnostic) {
|
||||
return diagnostic.code != "dynamic-procedure-context";
|
||||
});
|
||||
record.failure_code = cause == failure.Diagnostics().end()
|
||||
? "dynamic-solver-failure" : cause->code;
|
||||
const Status written = log.Write(record);
|
||||
return written.IsOk() ? contextual : written;
|
||||
};
|
||||
@@ -744,6 +754,10 @@ Result<DynamicExecutionResult> ExecuteReducedHht(
|
||||
record.increment_index = increment;
|
||||
record.time = trial_frame.time;
|
||||
record.correction_pass = true;
|
||||
record.policy_id = hht_field_convergence::kPolicyId;
|
||||
record.step_name = domain.Dynamic()->source_step_name_raw;
|
||||
std::optional<Vector> previous_selected_q;
|
||||
std::array<std::optional<double>, 2> previous_residual, older_residual;
|
||||
bool translation_private_algorithmic_pass = true;
|
||||
bool copy_translation_rates = false;
|
||||
Vector retained_velocity{history.p.Size()};
|
||||
@@ -755,6 +769,9 @@ Result<DynamicExecutionResult> ExecuteReducedHht(
|
||||
};
|
||||
ReducedShellHhtStep<ReducedStepCandidate> iteration_step(
|
||||
[&](std::uint32_t iteration) -> StepResult {
|
||||
record.fields = {};
|
||||
record.field_pass.reset();
|
||||
record.strict_residual_evaluated = false;
|
||||
record.iteration_index = iteration;
|
||||
record.stage = "evaluation";
|
||||
record.outcome = "candidate";
|
||||
@@ -896,7 +913,53 @@ Result<DynamicExecutionResult> ExecuteReducedHht(
|
||||
if (!operators.HasValue())
|
||||
return fail(operators.GetStatus());
|
||||
ResidualRecord(dofs, candidate, &record);
|
||||
if (record.residual_pass && record.correction_pass) {
|
||||
record.strict_residual_evaluated = true;
|
||||
auto measured_fields = MeasureEndpointFields(dofs, candidate, old, previous_selected_q);
|
||||
if (!measured_fields.HasValue()) return fail(measured_fields.GetStatus());
|
||||
auto field_inputs = std::move(measured_fields.Value());
|
||||
for (std::size_t c = 0; c < 2; ++c) {
|
||||
field_inputs[c].flux = trial.Value().assembly.field_flux[c];
|
||||
field_inputs[c].flux.insert(field_inputs[c].flux.end(),
|
||||
load_flux[c].begin(), load_flux[c].end());
|
||||
field_inputs[c].previous_residual = previous_residual[c];
|
||||
field_inputs[c].older_residual = older_residual[c];
|
||||
}
|
||||
const auto field_evaluation = hht_field_convergence::Evaluate(
|
||||
field_inputs, flux_history, iteration, length);
|
||||
for (std::size_t c = 0; c < 2; ++c) {
|
||||
auto& e = record.fields[c];
|
||||
const auto& in = field_inputs[c];
|
||||
const auto& out = field_evaluation.fields[c];
|
||||
if (in.active) {
|
||||
e.residual_numerator = in.residual;
|
||||
e.increment_norm = in.increment;
|
||||
e.correction_numerator = in.correction;
|
||||
}
|
||||
e.residual_scale = out.residual_scale;
|
||||
e.spatial_flux = out.spatial_flux;
|
||||
e.time_average_flux = out.time_average_flux;
|
||||
e.estimated_correction = out.estimated_correction;
|
||||
e.residual_threshold = out.residual_threshold;
|
||||
e.correction_threshold = out.correction_threshold;
|
||||
e.residual_pass = out.residual_pass;
|
||||
e.correction_pass = out.correction_pass;
|
||||
e.field_pass = out.field_pass;
|
||||
using Branch = hht_field_convergence::Branch;
|
||||
switch (out.branch) {
|
||||
case Branch::kNotEvaluated: e.branch = "not-evaluated"; break;
|
||||
case Branch::kLinear: e.branch = "linear"; break;
|
||||
case Branch::kZeroFlux: e.branch = "zero-flux"; break;
|
||||
case Branch::kNonquadratic: e.branch = "nonquadratic"; break;
|
||||
case Branch::kNormal: e.branch = "normal"; break;
|
||||
}
|
||||
}
|
||||
if (!field_evaluation.status.IsOk()) return fail(field_evaluation.status);
|
||||
record.field_pass = field_evaluation.field_pass;
|
||||
previous_selected_q = candidate.generalized_coordinates;
|
||||
older_residual = previous_residual;
|
||||
for (std::size_t c = 0; c < 2; ++c)
|
||||
previous_residual[c] = field_inputs[c].residual;
|
||||
if (field_evaluation.field_pass) {
|
||||
status = CompleteConstraintEvidence(
|
||||
domain, dofs, trial.Value().assembly, length, null_nodes,
|
||||
positive_solver, tangent_solver, &candidate);
|
||||
@@ -932,7 +995,7 @@ Result<DynamicExecutionResult> ExecuteReducedHht(
|
||||
return StepResult::Success(ReducedStepCandidate{
|
||||
std::move(candidate),
|
||||
{trial_p, trial_v, trial_a, trial_algorithmic,
|
||||
trial.Value().root.z}});
|
||||
trial.Value().root.z}, field_evaluation.pending});
|
||||
}
|
||||
if (!CanApplyOuterCorrection(iteration)) {
|
||||
double full_hidden = 0.0;
|
||||
@@ -1059,11 +1122,15 @@ Result<DynamicExecutionResult> ExecuteReducedHht(
|
||||
correction.Value().p[i] / (parameters.beta * h * h);
|
||||
reduced_physical[i] = acceleration_rate * correction.Value().p[i];
|
||||
}
|
||||
CorrectionRecord(correction.Value().coordinate,
|
||||
const bool private_correction_pass = CorrectionRecord(correction.Value().coordinate,
|
||||
correction.Value().velocity,
|
||||
correction.Value().acceleration, full_algorithmic,
|
||||
reduced_algorithmic, reduced_physical, chart.Value(),
|
||||
dofs, length, h, parameters.alpha, &record);
|
||||
record.strict_correction_evaluated = true;
|
||||
if (!private_correction_pass)
|
||||
return fail(Failure(domain, "reduced-dynamic-update-inconsistency",
|
||||
"The full/private correction acceleration arithmetic is inconsistent."));
|
||||
translation_private_algorithmic_pass = true;
|
||||
for (std::size_t i = 0U; i < chart.Value().p_dofs.size(); ++i)
|
||||
if (PhysicalCoordinateChannel(chart.Value(), i) == 0U)
|
||||
@@ -1115,16 +1182,21 @@ Result<DynamicExecutionResult> ExecuteReducedHht(
|
||||
iteration_step.TakeCandidate());
|
||||
},
|
||||
[&](ReducedStepCandidate&& accepted_candidate) -> Status {
|
||||
status = state.CommitAcceptedFrame(accepted_candidate.frame);
|
||||
if (!status.IsOk())
|
||||
return report_failure(status, "accepted");
|
||||
history = std::move(accepted_candidate.history);
|
||||
// Stage the complete accepted history. Output failure discards all
|
||||
// three snapshots; publication below consists only of noexcept moves.
|
||||
auto staged_state = state;
|
||||
status = staged_state.CommitAcceptedFrame(accepted_candidate.frame);
|
||||
if (!status.IsOk()) return report_failure(status, "accepted");
|
||||
record.stage = "accepted";
|
||||
record.outcome = "accepted";
|
||||
record.accepted = true;
|
||||
status = log.Write(record);
|
||||
if (status.IsOk())
|
||||
status = log.Flush();
|
||||
return status;
|
||||
if (status.IsOk()) status = log.Flush();
|
||||
if (!status.IsOk()) return status;
|
||||
state = std::move(staged_state);
|
||||
history = std::move(accepted_candidate.history);
|
||||
flux_history = accepted_candidate.flux_history;
|
||||
return Status::Ok();
|
||||
});
|
||||
if (!status.IsOk()) return Result<DynamicExecutionResult>::Failure(status);
|
||||
}
|
||||
|
||||
@@ -261,10 +261,100 @@ Result<std::string> SerializeRecord(const NewtonIterationLogRecord& record,
|
||||
return Result<std::string>::Success(std::move(line));
|
||||
}
|
||||
|
||||
// Candidate policy has separate names and units; v1 evidence is never redefined.
|
||||
Result<std::string> SerializeFieldRecord(
|
||||
const DynamicNewtonIterationLogRecord& record,
|
||||
const std::filesystem::path& path) {
|
||||
if (record.policy_id != "abaqus2025-field-on-fesa-v1" ||
|
||||
record.time < 0.0 || record.stage.empty() || record.outcome.empty())
|
||||
return Result<std::string>::Failure(
|
||||
LogOutputFailure(path, "Invalid HHT field-policy log identity."));
|
||||
std::vector<std::string> fields;
|
||||
const auto number = [&](const std::string& key,
|
||||
const std::optional<double>& value) -> Status {
|
||||
if (!value.has_value()) {
|
||||
AppendField(&fields, key, "not-evaluated");
|
||||
return Status::Ok();
|
||||
}
|
||||
auto formatted = FormatFiniteNumber(*value, path);
|
||||
if (!formatted.HasValue()) return formatted.GetStatus();
|
||||
if (*value < 0.0)
|
||||
return LogOutputFailure(path, "Negative HHT field norm or scale.");
|
||||
AppendField(&fields, key, formatted.Value());
|
||||
return Status::Ok();
|
||||
};
|
||||
const auto boolean = [&](const std::string& key, const std::optional<bool>& value) {
|
||||
AppendField(&fields, key, value.has_value() ? (*value ? "1" : "0") : "not-evaluated");
|
||||
};
|
||||
AppendField(&fields, "record", "dynamic-newton-iteration");
|
||||
AppendField(&fields, "schema", "hht-log-v2");
|
||||
AppendField(&fields, "policy_id", record.policy_id);
|
||||
AppendField(&fields, "step", EscapeText(record.step_name));
|
||||
AppendField(&fields, "increment", std::to_string(record.increment_index));
|
||||
auto status = number("time", record.time);
|
||||
if (!status.IsOk()) return Result<std::string>::Failure(status);
|
||||
AppendField(&fields, "iteration", std::to_string(record.iteration_index));
|
||||
AppendField(&fields, "stage", EscapeText(record.stage));
|
||||
AppendField(&fields, "outcome", EscapeText(record.outcome));
|
||||
AppendField(&fields, "failure_code", EscapeText(record.failure_code));
|
||||
boolean("field_pass", record.field_pass);
|
||||
boolean("accepted", record.accepted);
|
||||
for (std::size_t c = 0; c < 2; ++c) {
|
||||
const auto& e = record.fields[c];
|
||||
const std::string prefix = c == 0 ? "force_" : "moment_";
|
||||
const std::array<std::pair<const char*, std::optional<double>>, 9> numbers{{
|
||||
{"residual_numerator", e.residual_numerator}, {"residual_scale", e.residual_scale},
|
||||
{"spatial_flux", e.spatial_flux}, {"time_average_flux", e.time_average_flux},
|
||||
{"correction_numerator", e.correction_numerator}, {"increment_norm", e.increment_norm},
|
||||
{"estimated_correction", e.estimated_correction}, {"residual_threshold", e.residual_threshold},
|
||||
{"correction_threshold", e.correction_threshold}}};
|
||||
for (const auto& entry : numbers) {
|
||||
status = number(prefix + entry.first, entry.second);
|
||||
if (!status.IsOk()) return Result<std::string>::Failure(status);
|
||||
}
|
||||
AppendField(&fields, prefix + "residual_unit", c == 0 ? "force" : "moment");
|
||||
AppendField(&fields, prefix + "correction_unit", c == 0 ? "length" : "rad");
|
||||
if (e.branch != "normal" && e.branch != "zero-flux" && e.branch != "linear" &&
|
||||
e.branch != "nonquadratic" && e.branch != "not-evaluated")
|
||||
return Result<std::string>::Failure(LogOutputFailure(path, "Invalid HHT field branch."));
|
||||
AppendField(&fields, prefix + "branch", e.branch);
|
||||
boolean(prefix + "residual_pass", e.residual_pass);
|
||||
boolean(prefix + "correction_pass", e.correction_pass);
|
||||
boolean(prefix + "field_pass", e.field_pass);
|
||||
}
|
||||
for (std::size_t c = 0; c < 2; ++c) {
|
||||
const std::string suffix = c == 0 ? "u" : "phi";
|
||||
const auto strict = [&](double value, bool evaluated) {
|
||||
return evaluated ? std::optional<double>{value} : std::nullopt;
|
||||
};
|
||||
const std::array<std::pair<std::string, std::optional<double>>, 6> numbers{{
|
||||
{"residual_" + suffix + "_numerator", strict(record.residual_numerator[c], record.strict_residual_evaluated)},
|
||||
{"residual_" + suffix + "_scale", strict(record.residual_scale[c], record.strict_residual_evaluated)},
|
||||
{"coordinate_correction_" + suffix, strict(record.coordinate_correction[c], record.strict_correction_evaluated)},
|
||||
{"velocity_correction_" + suffix, strict(record.velocity_correction[c], record.strict_correction_evaluated)},
|
||||
{"acceleration_correction_" + suffix, strict(record.acceleration_correction[c], record.strict_correction_evaluated)},
|
||||
{"algorithmic_correction_" + suffix, strict(record.algorithmic_correction[c], record.strict_correction_evaluated)}}};
|
||||
for (const auto& entry : numbers) {
|
||||
status = number("strict_" + entry.first, entry.second);
|
||||
if (!status.IsOk()) return Result<std::string>::Failure(status);
|
||||
}
|
||||
}
|
||||
boolean("strict_residual_pass", record.strict_residual_evaluated ? std::optional<bool>{record.residual_pass} : std::nullopt);
|
||||
boolean("strict_correction_pass", record.strict_correction_evaluated ? std::optional<bool>{record.correction_pass} : std::nullopt);
|
||||
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));
|
||||
}
|
||||
|
||||
/// @brief Serializes dynamic evidence without static load-factor semantics.
|
||||
Result<std::string> SerializeRecord(
|
||||
const DynamicNewtonIterationLogRecord& record,
|
||||
const std::filesystem::path& log_path) {
|
||||
if (!record.policy_id.empty()) return SerializeFieldRecord(record, log_path);
|
||||
const auto time = FormatFiniteNumber(record.time, log_path);
|
||||
if (!time.HasValue()) return Result<std::string>::Failure(time.GetStatus());
|
||||
if (record.time < 0.0 || record.stage.empty() || record.outcome.empty()) {
|
||||
|
||||
Reference in New Issue
Block a user