#pragma once #include "fesa/Core/Dof.hpp" #include "fesa/Core/Domain.hpp" #include "fesa/Util/Diagnostics.hpp" #include "fesa/Util/String.hpp" #include #include #include #include #include #include #include namespace fesa { inline std::optional numericTarget(const std::string& target) { return parseInt64(target); } inline std::vector resolveNodeTarget(const Domain& domain, const std::string& target, std::vector* 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 §ion; } } 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 validateDomain(const Domain& domain) { std::vector 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> 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 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