Files
FESADev/include/fesa/Core/Validation.hpp
T
2026-05-05 01:10:30 +09:00

220 lines
9.7 KiB
C++

#pragma once
#include "fesa/Core/Dof.hpp"
#include "fesa/Core/Domain.hpp"
#include "fesa/Util/Diagnostics.hpp"
#include "fesa/Util/String.hpp"
#include <algorithm>
#include <cmath>
#include <optional>
#include <set>
#include <string>
#include <utility>
#include <vector>
namespace fesa {
inline std::optional<GlobalId> numericTarget(const std::string& target) {
return parseInt64(target);
}
inline std::vector<GlobalId> resolveNodeTarget(const Domain& domain, const std::string& target, std::vector<Diagnostic>* diagnostics = nullptr,
const std::string& diagnostic_keyword = "node target") {
if (auto node_id = numericTarget(target)) {
if (domain.nodes.count(*node_id) == 0) {
if (diagnostics != nullptr) {
diagnostics->push_back(
makeDiagnostic(Severity::Error, "FESA-VALIDATION-MISSING-NODE", "Missing node target: " + target, diagnostic_keyword));
}
return {};
}
return {*node_id};
}
auto set_it = domain.node_sets.find(Domain::key(target));
if (set_it == domain.node_sets.end()) {
if (diagnostics != nullptr) {
diagnostics->push_back(
makeDiagnostic(Severity::Error, "FESA-VALIDATION-MISSING-NSET", "Missing node set: " + target, diagnostic_keyword));
}
return {};
}
return set_it->second.node_ids;
}
inline const ShellSection* shellSectionForElement(const Domain& domain, GlobalId element_id) {
for (const ShellSection& section : domain.shell_sections) {
auto set_it = domain.element_sets.find(Domain::key(section.element_set));
if (set_it == domain.element_sets.end()) {
continue;
}
if (std::find(set_it->second.element_ids.begin(), set_it->second.element_ids.end(), element_id) != set_it->second.element_ids.end()) {
return &section;
}
}
return nullptr;
}
inline std::string dofNameOrNumber(int abaqus_dof) {
auto dof = dofFromAbaqus(abaqus_dof);
if (dof) {
return dofLabel(*dof);
}
return "DOF " + std::to_string(abaqus_dof);
}
inline bool validAbaqusDofRange(int first, int last) {
return dofFromAbaqus(first).has_value() && dofFromAbaqus(last).has_value() && first <= last;
}
inline std::vector<Diagnostic> validateDomain(const Domain& domain) {
std::vector<Diagnostic> diagnostics;
if (domain.elements.empty()) {
diagnostics.push_back(makeDiagnostic(Severity::Error, "FESA-SINGULAR-NO-ACTIVE-ELEMENTS",
"No active elements exist in the current model", "analysis model"));
}
if (domain.boundary_conditions.empty()) {
diagnostics.push_back(makeDiagnostic(Severity::Warning, "FESA-SINGULAR-NO-BOUNDARY", "No boundary constraints are defined", "boundary"));
}
for (const auto& [set_key, set] : domain.node_sets) {
(void)set_key;
for (GlobalId node_id : set.node_ids) {
if (domain.nodes.count(node_id) == 0) {
diagnostics.push_back(makeDiagnostic(Severity::Error, "FESA-VALIDATION-NSET-MISSING-NODE",
"Node set " + set.name + " references missing node " + std::to_string(node_id),
"nset"));
}
}
}
for (const auto& [set_key, set] : domain.element_sets) {
(void)set_key;
for (GlobalId element_id : set.element_ids) {
if (domain.elements.count(element_id) == 0) {
diagnostics.push_back(makeDiagnostic(Severity::Error, "FESA-VALIDATION-ELSET-MISSING-ELEMENT",
"Element set " + set.name + " references missing element " + std::to_string(element_id),
"elset"));
}
}
}
for (const auto& [id, element] : domain.elements) {
for (GlobalId node_id : element.node_ids) {
if (domain.nodes.count(node_id) == 0) {
diagnostics.push_back(makeDiagnostic(Severity::Error, "FESA-VALIDATION-ELEMENT-MISSING-NODE",
"Element " + std::to_string(id) + " references missing node " + std::to_string(node_id),
"element"));
}
}
const ShellSection* section = shellSectionForElement(domain, id);
if (section == nullptr) {
diagnostics.push_back(makeDiagnostic(Severity::Error, "FESA-VALIDATION-MISSING-PROPERTY",
"Element " + std::to_string(id) + " has no assigned shell section", "element"));
}
}
for (const ShellSection& section : domain.shell_sections) {
if (section.thickness <= 0.0 || !std::isfinite(section.thickness)) {
diagnostics.push_back(makeDiagnostic(Severity::Error, "FESA-VALIDATION-NONPOSITIVE-THICKNESS",
"Shell section for element set " + section.element_set + " has non-positive thickness",
"shell section"));
}
if (domain.element_sets.count(Domain::key(section.element_set)) == 0) {
diagnostics.push_back(makeDiagnostic(Severity::Error, "FESA-VALIDATION-MISSING-ELSET",
"Shell section references missing element set: " + section.element_set, "shell section"));
}
auto material_it = domain.materials.find(Domain::key(section.material));
if (material_it == domain.materials.end()) {
diagnostics.push_back(makeDiagnostic(Severity::Error, "FESA-VALIDATION-MISSING-MATERIAL",
"Shell section references missing material: " + section.material, "shell section"));
} else if (material_it->second.elastic_modulus <= 0.0) {
diagnostics.push_back(makeDiagnostic(Severity::Error, "FESA-VALIDATION-INCOMPLETE-MATERIAL",
"Material has no valid elastic constants: " + section.material, "material"));
}
}
for (const BoundaryCondition& boundary : domain.boundary_conditions) {
if (!validAbaqusDofRange(boundary.first_dof, boundary.last_dof)) {
diagnostics.push_back(makeDiagnostic(Severity::Error, "FESA-VALIDATION-BOUNDARY-DOF",
"Boundary target " + boundary.target + " has invalid DOF range " +
dofNameOrNumber(boundary.first_dof) + " to " + dofNameOrNumber(boundary.last_dof),
"boundary"));
}
(void)resolveNodeTarget(domain, boundary.target, &diagnostics, "boundary");
}
for (const NodalLoad& load : domain.loads) {
if (!dofFromAbaqus(load.dof)) {
diagnostics.push_back(makeDiagnostic(Severity::Error, "FESA-VALIDATION-CLOAD-DOF",
"Load target " + load.target + " has invalid " + dofNameOrNumber(load.dof), "cload"));
}
(void)resolveNodeTarget(domain, load.target, &diagnostics, "cload");
}
const bool any_nonzero_load = std::any_of(domain.loads.begin(), domain.loads.end(), [](const NodalLoad& load) {
return std::fabs(load.magnitude) > 0.0;
});
if (!any_nonzero_load) {
diagnostics.push_back(makeDiagnostic(Severity::Warning, "FESA-SINGULAR-NO-NONZERO-LOAD", "No nonzero load is defined", "cload"));
}
std::set<std::pair<GlobalId, int>> constrained_dofs;
for (const BoundaryCondition& boundary : domain.boundary_conditions) {
if (!validAbaqusDofRange(boundary.first_dof, boundary.last_dof)) {
continue;
}
for (GlobalId node_id : resolveNodeTarget(domain, boundary.target)) {
if (domain.nodes.count(node_id) == 0) {
continue;
}
for (int dof = boundary.first_dof; dof <= boundary.last_dof; ++dof) {
constrained_dofs.insert(std::make_pair(node_id, dof - 1));
}
}
}
std::set<GlobalId> active_connectivity_nodes;
for (const auto& [element_id, element] : domain.elements) {
(void)element_id;
for (GlobalId node_id : element.node_ids) {
if (domain.nodes.count(node_id) != 0) {
active_connectivity_nodes.insert(node_id);
}
}
}
LocalIndex free_dof_count = 0;
LocalIndex weak_drilling_count = 0;
GlobalId weak_drilling_example = 0;
for (const auto& [node_id, node] : domain.nodes) {
(void)node;
for (Dof dof : allDofs()) {
const auto key = std::make_pair(node_id, dofIndex(dof));
if (constrained_dofs.count(key) != 0) {
continue;
}
++free_dof_count;
if (!domain.elements.empty() && active_connectivity_nodes.count(node_id) == 0) {
diagnostics.push_back(makeDiagnostic(Severity::Error, "FESA-SINGULAR-DOF-UNTOUCHED",
"Node " + std::to_string(node_id) + " DOF " + dofLabel(dof) +
" is free but is not touched by active element connectivity",
"dof"));
}
if (!domain.elements.empty() && active_connectivity_nodes.count(node_id) != 0 && dof == Dof::RZ) {
if (weak_drilling_count == 0) {
weak_drilling_example = node_id;
}
++weak_drilling_count;
}
}
}
if (!domain.nodes.empty() && free_dof_count == 0) {
diagnostics.push_back(makeDiagnostic(Severity::Error, "FESA-SINGULAR-NO-FREE-DOFS",
"No free DOFs exist after applying boundary constraints", "dof"));
}
if (weak_drilling_count > 0) {
diagnostics.push_back(makeDiagnostic(Severity::Warning, "FESA-SINGULAR-WEAK-DRILLING-DOF",
"Node " + std::to_string(weak_drilling_example) +
" DOF RZ is free; drilling rotation is weakly stabilized in Phase 1 (" +
std::to_string(weak_drilling_count) + " free drilling DOF(s))",
"dof"));
}
return diagnostics;
}
} // namespace fesa