#pragma once #include "fesa/Core/Core.hpp" #include "fesa/Element/Element.hpp" #include "fesa/Load/Load.hpp" #include "fesa/Math/Math.hpp" #include "fesa/Property/Property.hpp" #include "fesa/Util/Diagnostics.hpp" #include #include #include #include #include namespace fesa { inline SparsePattern buildReducedSparsePattern(const Domain& domain, const DofManager& dofs) { SparsePattern pattern; pattern.equation_count = dofs.freeDofCount(); std::set> ordered_entries; for (const auto& [element_id, element] : domain.elements) { (void)element_id; const auto equations = dofs.elementEquationIds(element); for (EquationId row : equations) { if (row < 0) { continue; } for (EquationId col : equations) { if (col < 0) { continue; } ordered_entries.insert({row, col}); } } } pattern.entries.reserve(ordered_entries.size()); for (const auto& entry : ordered_entries) { pattern.entries.push_back({entry.first, entry.second}); } return pattern; } inline std::vector recoverFullReaction(const DenseMatrix& k_full, const std::vector& u_full, const std::vector& f_full) { if (k_full.rows() != k_full.cols() || static_cast(u_full.size()) != k_full.cols() || static_cast(f_full.size()) != k_full.rows()) { throw std::runtime_error("full reaction size mismatch"); } std::vector reaction = k_full.multiply(u_full); for (std::size_t i = 0; i < reaction.size(); ++i) { reaction[i] -= f_full[i]; } return reaction; } struct AssemblyResult { DenseMatrix k_full; std::vector f_full; SparsePattern reduced_pattern; std::vector diagnostics; bool ok() const { return !hasError(diagnostics); } }; struct ReducedSystem { DenseMatrix k; std::vector f; std::vector free_full_indices; std::vector diagnostics; bool ok() const { return !hasError(diagnostics); } }; inline AssemblyResult assembleSystem(const Domain& domain, const DofManager& dofs, ElementStiffnessOptions options = {}) { AssemblyResult result; result.k_full = DenseMatrix(dofs.fullDofCount(), dofs.fullDofCount()); result.f_full = std::vector(static_cast(dofs.fullDofCount()), 0.0); result.reduced_pattern = buildReducedSparsePattern(domain, dofs); if (dofs.freeDofCount() > 0 && result.reduced_pattern.nonzeroCount() == 0) { result.diagnostics.push_back(makeDiagnostic(Severity::Error, "FESA-SINGULAR-SPARSE-PATTERN", "Reduced sparse pattern has no stiffness entries", "assembly")); } for (const auto& [element_id, element] : domain.elements) { const ShellSection* section = shellSectionForElement(domain, element_id); if (section == nullptr) { result.diagnostics.push_back( {Severity::Error, "FESA-ASSEMBLY-MISSING-PROPERTY", "Element has no shell section", {}}); continue; } const auto material_it = domain.materials.find(Domain::key(section->material)); if (material_it == domain.materials.end()) { result.diagnostics.push_back( {Severity::Error, "FESA-ASSEMBLY-MISSING-MATERIAL", "Element material is missing", {}}); continue; } std::array coordinates{}; for (std::size_t i = 0; i < 4; ++i) { coordinates[i] = domain.nodes.at(element.node_ids[i]).coordinates; } const auto stiffness = mitc4ElementStiffness(coordinates, material_it->second.elastic_modulus, material_it->second.poisson_ratio, section->thickness, options); result.diagnostics.insert(result.diagnostics.end(), stiffness.diagnostics.begin(), stiffness.diagnostics.end()); if (!stiffness.ok()) { continue; } const auto element_full_indices = dofs.elementFullDofIndices(element); for (LocalIndex a = 0; a < 24; ++a) { const LocalIndex ia = element_full_indices[static_cast(a)]; for (LocalIndex b = 0; b < 24; ++b) { const LocalIndex ib = element_full_indices[static_cast(b)]; result.k_full.add(ia, ib, stiffness.global(a, b)); } } } for (const NodalLoad& load : domain.loads) { const auto dof = dofFromAbaqus(load.dof); if (!dof) { result.diagnostics.push_back(makeDiagnostic(Severity::Error, "FESA-ASSEMBLY-LOAD-DOF", "Nodal load references an invalid DOF", "cload")); continue; } for (GlobalId node_id : resolveNodeTarget(domain, load.target, &result.diagnostics)) { result.f_full[static_cast(dofs.fullIndex(node_id, *dof))] += load.magnitude; } } return result; } inline ReducedSystem projectToReducedSystem(const AssemblyResult& assembly, const DofManager& dofs) { ReducedSystem result; result.k = DenseMatrix(dofs.freeDofCount(), dofs.freeDofCount()); result.f = std::vector(static_cast(dofs.freeDofCount()), 0.0); result.free_full_indices = dofs.freeFullIndices(); if (assembly.k_full.rows() != assembly.k_full.cols() || assembly.k_full.rows() != dofs.fullDofCount() || static_cast(assembly.f_full.size()) != dofs.fullDofCount()) { result.diagnostics.push_back(makeDiagnostic(Severity::Error, "FESA-ASSEMBLY-SIZE", "Full-space stiffness/load sizes do not match DofManager", "assembly")); return result; } if (dofs.freeDofCount() == 0) { result.diagnostics.push_back(makeDiagnostic(Severity::Error, "FESA-SINGULAR-NO-FREE-DOFS", "No free DOFs exist after applying constraints", "dof")); return result; } if (assembly.reduced_pattern.equation_count != dofs.freeDofCount() || assembly.reduced_pattern.nonzeroCount() == 0) { result.diagnostics.push_back(makeDiagnostic(Severity::Error, "FESA-SINGULAR-SPARSE-PATTERN", "Reduced sparse pattern is empty or inconsistent with free DOFs", "assembly")); return result; } for (LocalIndex i = 0; i < dofs.freeDofCount(); ++i) { const LocalIndex full_i = dofs.freeFullIndices()[static_cast(i)]; result.f[static_cast(i)] = assembly.f_full[static_cast(full_i)]; for (LocalIndex j = 0; j < dofs.freeDofCount(); ++j) { const LocalIndex full_j = dofs.freeFullIndices()[static_cast(j)]; result.k(i, j) = assembly.k_full(full_i, full_j); } } return result; } } // namespace fesa