202 lines
6.3 KiB
C++
202 lines
6.3 KiB
C++
#include <fesa/results/result_database.hpp>
|
|
|
|
#include <algorithm>
|
|
#include <cmath>
|
|
#include <cstdint>
|
|
#include <set>
|
|
#include <string>
|
|
#include <utility>
|
|
|
|
namespace fesa {
|
|
namespace {
|
|
|
|
void add_error(
|
|
std::vector<Diagnostic>& diagnostics,
|
|
std::string code,
|
|
std::string message) {
|
|
diagnostics.push_back({
|
|
DiagnosticStage::results,
|
|
Severity::error,
|
|
std::move(code),
|
|
std::move(message),
|
|
std::nullopt,
|
|
});
|
|
}
|
|
|
|
bool is_finite(const std::array<double, 6>& field) {
|
|
return std::ranges::all_of(
|
|
field,
|
|
[](const double component) {
|
|
return std::isfinite(component);
|
|
});
|
|
}
|
|
|
|
void validate_nodal_frame(
|
|
const NodalFrame& nodal,
|
|
std::vector<Diagnostic>& diagnostics) {
|
|
if (
|
|
nodal.displacement.size() != nodal.node_ids.size() ||
|
|
nodal.reaction.size() != nodal.node_ids.size()) {
|
|
add_error(
|
|
diagnostics,
|
|
"results.nodal_size_mismatch",
|
|
"Nodal IDs, displacement, and reaction fields must have "
|
|
"matching sizes.");
|
|
}
|
|
if (nodal.origins.size() != nodal.node_ids.size()) {
|
|
add_error(
|
|
diagnostics,
|
|
"results.nodal_origin_size_mismatch",
|
|
"Nodal IDs and origins must have matching sizes.");
|
|
}
|
|
|
|
std::set<std::int64_t> node_ids;
|
|
for (const NodeId node_id : nodal.node_ids) {
|
|
if (!node_ids.insert(node_id.value()).second) {
|
|
add_error(
|
|
diagnostics,
|
|
"results.duplicate_node_id",
|
|
"Nodal frame contains duplicate node ID " +
|
|
std::to_string(node_id.value()) + ".");
|
|
}
|
|
}
|
|
|
|
for (const auto& displacement : nodal.displacement) {
|
|
if (!is_finite(displacement)) {
|
|
add_error(
|
|
diagnostics,
|
|
"results.nonfinite_value",
|
|
"Nodal displacement contains a nonfinite component.");
|
|
}
|
|
}
|
|
|
|
for (const auto& reaction : nodal.reaction) {
|
|
if (!is_finite(reaction)) {
|
|
add_error(
|
|
diagnostics,
|
|
"results.nonfinite_value",
|
|
"Nodal reaction contains a nonfinite component.");
|
|
}
|
|
}
|
|
}
|
|
|
|
void validate_beam_section_result(
|
|
const BeamSectionResult& result,
|
|
std::vector<Diagnostic>& diagnostics) {
|
|
if (
|
|
!std::isfinite(result.xi) ||
|
|
!is_finite(result.section_strain) ||
|
|
!is_finite(result.section_force) ||
|
|
!std::isfinite(result.centroid_sigma_xx) ||
|
|
!std::ranges::all_of(
|
|
result.sigma_xx,
|
|
[](const double value) { return std::isfinite(value); })) {
|
|
add_error(
|
|
diagnostics,
|
|
"results.nonfinite_value",
|
|
"Beam section result contains a nonfinite value.");
|
|
}
|
|
}
|
|
|
|
void validate_element_frame(
|
|
const ElementFrame& element,
|
|
const NodalFrame& nodal,
|
|
std::vector<Diagnostic>& diagnostics) {
|
|
std::set<std::int64_t> nodal_ids;
|
|
for (const NodeId node_id : nodal.node_ids) {
|
|
nodal_ids.insert(node_id.value());
|
|
}
|
|
|
|
std::set<std::int64_t> element_ids;
|
|
for (const BeamElementFrame& beam : element.beams) {
|
|
if (!element_ids.insert(beam.element.value()).second) {
|
|
add_error(
|
|
diagnostics,
|
|
"results.duplicate_element_id",
|
|
"Element frame contains duplicate element ID " +
|
|
std::to_string(beam.element.value()) + ".");
|
|
}
|
|
|
|
if (
|
|
!is_finite(beam.local_frame.ex) ||
|
|
!is_finite(beam.local_frame.ey) ||
|
|
!is_finite(beam.local_frame.ez)) {
|
|
add_error(
|
|
diagnostics,
|
|
"results.nonfinite_value",
|
|
"Beam local frame contains a nonfinite component.");
|
|
}
|
|
|
|
const BeamSectionResult& first = beam.end_results[0];
|
|
const BeamSectionResult& second = beam.end_results[1];
|
|
if (first.end_node == second.end_node) {
|
|
add_error(
|
|
diagnostics,
|
|
"results.duplicate_beam_end_node",
|
|
"Beam element result contains the same node at both ends.");
|
|
}
|
|
if (
|
|
first.xi != -1.0 || second.xi != 1.0 ||
|
|
!nodal_ids.contains(first.end_node.value()) ||
|
|
!nodal_ids.contains(second.end_node.value())) {
|
|
add_error(
|
|
diagnostics,
|
|
"results.invalid_beam_connectivity",
|
|
"Beam end results must follow (-1, +1) connectivity and "
|
|
"reference nodes in the nodal frame.");
|
|
}
|
|
if (first.sigma_xx.size() != second.sigma_xx.size()) {
|
|
add_error(
|
|
diagnostics,
|
|
"results.recovery_point_count_mismatch",
|
|
"Beam end results must have matching recovery-point "
|
|
"counts.");
|
|
}
|
|
|
|
validate_beam_section_result(first, diagnostics);
|
|
validate_beam_section_result(second, diagnostics);
|
|
}
|
|
}
|
|
|
|
} // namespace
|
|
|
|
Status validate_result_database(const ResultDatabase& database) {
|
|
std::vector<Diagnostic> diagnostics;
|
|
std::set<std::string> step_names;
|
|
|
|
for (const ResultStep& step : database.steps) {
|
|
if (!step_names.insert(step.name).second) {
|
|
add_error(
|
|
diagnostics,
|
|
"results.duplicate_step_name",
|
|
"Result database contains duplicate step name '" +
|
|
step.name + "'.");
|
|
}
|
|
|
|
std::set<double> frame_times;
|
|
for (const ResultFrame& frame : step.frames) {
|
|
if (!std::isfinite(frame.step_time)) {
|
|
add_error(
|
|
diagnostics,
|
|
"results.nonfinite_value",
|
|
"Result frame has a nonfinite step time.");
|
|
} else if (!frame_times.insert(frame.step_time).second) {
|
|
add_error(
|
|
diagnostics,
|
|
"results.duplicate_frame_time",
|
|
"Result step '" + step.name +
|
|
"' contains duplicate frame time " +
|
|
std::to_string(frame.step_time) + ".");
|
|
}
|
|
|
|
validate_nodal_frame(frame.nodal, diagnostics);
|
|
validate_element_frame(
|
|
frame.element, frame.nodal, diagnostics);
|
|
}
|
|
}
|
|
|
|
return {diagnostics.empty(), std::move(diagnostics)};
|
|
}
|
|
|
|
} // namespace fesa
|