feat(beam-reference-qualification): correlate Abaqus beam results
This commit is contained in:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user