fix: strengthen validation diagnostics
This commit is contained in:
+114
-3
@@ -776,6 +776,18 @@ inline const ShellSection* shellSectionForElement(const Domain& domain, GlobalId
|
||||
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()) {
|
||||
@@ -785,6 +797,26 @@ inline std::vector<Diagnostic> validateDomain(const Domain& domain) {
|
||||
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) {
|
||||
@@ -800,6 +832,11 @@ inline std::vector<Diagnostic> validateDomain(const Domain& domain) {
|
||||
}
|
||||
}
|
||||
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"));
|
||||
@@ -814,9 +851,19 @@ inline std::vector<Diagnostic> validateDomain(const Domain& domain) {
|
||||
}
|
||||
}
|
||||
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) {
|
||||
@@ -825,6 +872,68 @@ inline std::vector<Diagnostic> validateDomain(const Domain& domain) {
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -968,7 +1077,7 @@ class GaussianEliminationSolver final : public LinearSolver {
|
||||
const LocalIndex n = a.rows();
|
||||
SolveResult result;
|
||||
if (a.rows() != a.cols() || static_cast<LocalIndex>(b.size()) != n) {
|
||||
result.diagnostics.push_back({Severity::Error, "FESA-SOLVER-SIZE", "Linear system size mismatch", {}});
|
||||
result.diagnostics.push_back(makeDiagnostic(Severity::Error, "FESA-SOLVER-SIZE", "Linear system size mismatch", "solver"));
|
||||
return result;
|
||||
}
|
||||
for (LocalIndex col = 0; col < n; ++col) {
|
||||
@@ -982,7 +1091,8 @@ class GaussianEliminationSolver final : public LinearSolver {
|
||||
}
|
||||
}
|
||||
if (pivot_abs < 1.0e-12) {
|
||||
result.diagnostics.push_back({Severity::Error, "FESA-SINGULAR-SOLVER", "Reduced system is singular or ill-conditioned", {}});
|
||||
result.diagnostics.push_back(makeDiagnostic(Severity::Error, "FESA-SINGULAR-SOLVER",
|
||||
"Reduced system is singular or ill-conditioned", "solver"));
|
||||
return result;
|
||||
}
|
||||
if (pivot != col) {
|
||||
@@ -1409,7 +1519,8 @@ class LinearStaticAnalysis final : public Analysis {
|
||||
void solve(const Domain& domain, AnalysisResult& result) const override {
|
||||
DofManager dofs(domain);
|
||||
if (dofs.freeDofCount() == 0) {
|
||||
result.diagnostics.push_back({Severity::Error, "FESA-SINGULAR-NO-FREE-DOFS", "No free DOFs exist after applying constraints", {}});
|
||||
result.diagnostics.push_back(makeDiagnostic(Severity::Error, "FESA-SINGULAR-NO-FREE-DOFS",
|
||||
"No free DOFs exist after applying constraints", "dof"));
|
||||
return;
|
||||
}
|
||||
AssemblyResult assembly = assembleSystem(domain, dofs);
|
||||
|
||||
Reference in New Issue
Block a user