feat(beam-reference-qualification): correlate Abaqus beam results

This commit is contained in:
KOKO\Mimi
2026-08-03 01:45:30 +09:00
parent b68f6ee143
commit 675379779f
21 changed files with 962 additions and 119 deletions
+149
View File
@@ -6,6 +6,7 @@
#include <cstddef>
#include <iomanip>
#include <limits>
#include <map>
#include <optional>
#include <set>
#include <sstream>
@@ -439,4 +440,152 @@ ComparisonReport compare_samples(
return report;
}
CorrelationReport correlate_samples(
const std::span<const ComparisonSample> samples) {
struct Accumulator final {
double squared_error{};
double squared_reference{};
double squared_absolute_scale{};
std::size_t value_count{};
};
CorrelationReport report{true, {}, {}};
if (samples.empty()) {
add_failure(
report.failures,
"validation.empty_comparison",
"No comparison samples were provided for correlation.");
report.evaluable = false;
return report;
}
std::set<PositionKey> positions;
std::map<std::pair<ReferenceQuantity, std::size_t>, Accumulator>
accumulators;
for (const ComparisonSample& sample : samples) {
const PositionKey key{
sample.quantity,
sample.position.instance_name,
sample.position.entity_label,
sample.position.end_node_label,
};
if (!positions.insert(key).second) {
add_failure(
report.failures,
"validation.duplicate_result_position",
unevaluable_failure_text(
sample.quantity,
sample.position,
sample.tolerance,
"duplicate_result_position"));
continue;
}
const std::size_t expected =
expected_component_count(sample.quantity);
if (
sample.reference.size() != expected ||
sample.actual.size() != expected) {
add_failure(
report.failures,
"validation.component_count_mismatch",
unevaluable_failure_text(
sample.quantity,
sample.position,
sample.tolerance,
"component_count_mismatch") +
" expected=" + std::to_string(expected) +
" reference_count=" +
std::to_string(sample.reference.size()) +
" actual_count=" +
std::to_string(sample.actual.size()));
continue;
}
if (
!std::isfinite(sample.tolerance.relative) ||
!std::isfinite(sample.tolerance.absolute_scale) ||
sample.tolerance.relative < 0.0 ||
sample.tolerance.absolute_scale < 0.0) {
add_failure(
report.failures,
"validation.invalid_tolerance",
unevaluable_failure_text(
sample.quantity,
sample.position,
sample.tolerance,
"invalid_tolerance"));
continue;
}
for (std::size_t component = 0; component < expected; ++component) {
const double reference = sample.reference[component];
const double actual = sample.actual[component];
if (!std::isfinite(reference) || !std::isfinite(actual)) {
add_failure(
report.failures,
"validation.nonfinite_comparison_value",
scalar_failure_text(
sample,
component,
std::numeric_limits<double>::infinity()));
continue;
}
const double error = actual - reference;
Accumulator& accumulator =
accumulators[{sample.quantity, component}];
accumulator.squared_error += error * error;
accumulator.squared_reference += reference * reference;
accumulator.squared_absolute_scale +=
sample.tolerance.absolute_scale *
sample.tolerance.absolute_scale;
++accumulator.value_count;
}
}
for (const auto& [key, accumulator] : accumulators) {
const auto [quantity, component] = key;
const double error_l2 = std::sqrt(accumulator.squared_error);
const double root_mean_square_error =
error_l2 /
std::sqrt(static_cast<double>(accumulator.value_count));
const double reference_l2 =
std::sqrt(accumulator.squared_reference);
const double absolute_scale_l2 =
std::sqrt(accumulator.squared_absolute_scale);
const double denominator =
std::max(reference_l2, absolute_scale_l2);
const double relative_l2_error = denominator > 0.0
? error_l2 / denominator
: error_l2 == 0.0
? 0.0
: std::numeric_limits<
double>::infinity();
report.metrics.push_back({
quantity,
component,
accumulator.value_count,
root_mean_square_error,
relative_l2_error,
});
if (
!std::isfinite(root_mean_square_error) ||
!std::isfinite(relative_l2_error)) {
add_failure(
report.failures,
"validation.nonfinite_correlation_metric",
"quantity=" + std::string{quantity_name(quantity)} +
" component=" + component_name(quantity, component) +
" rmse=" + number_text(root_mean_square_error) +
" relative_l2=" + number_text(relative_l2_error));
}
}
report.evaluable = report.failures.empty();
return report;
}
} // namespace fesa