#include "fesa/fem/dof_manager.hpp" #include #include #include #include #include #include namespace fesa { namespace { constexpr std::size_t dofsPerNode = 6U; char asciiLower(char value) { if (value >= 'A' && value <= 'Z') { return static_cast(value + ('a' - 'A')); } return value; } bool equalName(const std::string& left, const std::string& right) { return left.size() == right.size() && std::equal( left.begin(), left.end(), right.begin(), [](char leftValue, char rightValue) { return asciiLower(leftValue) == asciiLower(rightValue); }); } bool tryPositiveInteger(const std::string& text, std::int64_t& value) { const char* const first = text.data(); const char* const last = first + text.size(); const auto parsed = std::from_chars(first, last, value); return parsed.ec == std::errc{} && parsed.ptr == last && value > 0; } std::vector expandBoundaryTarget( const Domain& domain, const BoundaryCondition& boundary) { for (const auto& set : domain.nodeSets()) { if (equalName(set.name, boundary.target)) { return set.nodeIndices; } } std::int64_t sourceLabel = 0; if (tryPositiveInteger(boundary.target, sourceLabel)) { for (std::size_t node = 0U; node < domain.nodes().size(); ++node) { if (domain.nodes()[node].sourceId.sourceLabel == sourceLabel) { return {static_cast(node)}; } } } return {}; } SparsePattern buildSparsePattern( std::size_t fullDofCount, const std::vector& activeElements, const std::vector>& elementScatters) { std::vector> columnsByRow(fullDofCount); for (const EntityIndex element : activeElements) { const auto& scatter = elementScatters.at(element); for (const std::size_t row : scatter) { auto& columns = columnsByRow[row]; columns.insert(columns.end(), scatter.begin(), scatter.end()); } } SparsePattern pattern; pattern.rowOffsets.reserve(fullDofCount + 1U); pattern.rowOffsets.push_back(0U); for (auto& columns : columnsByRow) { // Stable CSR structure is independent of element traversal duplicates. std::sort(columns.begin(), columns.end()); columns.erase(std::unique(columns.begin(), columns.end()), columns.end()); pattern.columnIndices.insert( pattern.columnIndices.end(), columns.begin(), columns.end()); pattern.rowOffsets.push_back(pattern.columnIndices.size()); } return pattern; } } // namespace Result DofManager::create(const AnalysisModel& model) { const Domain& domain = model.domain(); const std::size_t fullCount = domain.nodes().size() * dofsPerNode; std::vector> prescribedByFullDof(fullCount); for (const EntityIndex boundaryIndex : model.activeBoundaryConditions()) { const auto& boundary = model.step().boundaries.at(boundaryIndex); const auto target = expandBoundaryTarget(domain, boundary); for (const EntityIndex node : target) { for (int component = boundary.firstDof; component <= boundary.lastDof; ++component) { const std::size_t fullDof = static_cast(node) * dofsPerNode + static_cast(component - 1); auto& prescribed = prescribedByFullDof[fullDof]; if (prescribed && *prescribed != boundary.value) { return Result::failure(Status::failure( FailureCategory::input, {{Severity::error, "conflicting-boundary-condition", boundary.location, "BOUNDARY", boundary.target, "Expanded boundary rows prescribe different values to one node/DOF."}})); } prescribed = boundary.value; } } } std::vector freeDofs; std::vector constrainedDofs; std::vector constrainedValues; std::vector> freeEquations(fullCount); freeDofs.reserve(fullCount); constrainedDofs.reserve(fullCount); constrainedValues.reserve(fullCount); // A full-DOF scan fixes free equations, constrained DOFs, and dc in the // same stable order regardless of boundary declaration overlap. for (std::size_t fullDof = 0U; fullDof < fullCount; ++fullDof) { if (prescribedByFullDof[fullDof]) { constrainedDofs.push_back(fullDof); constrainedValues.push_back(*prescribedByFullDof[fullDof]); } else { freeEquations[fullDof] = freeDofs.size(); freeDofs.push_back(fullDof); } } Vector prescribedValues{constrainedValues.size()}; for (std::size_t index = 0U; index < constrainedValues.size(); ++index) { prescribedValues[index] = constrainedValues[index]; } std::vector> elementScatters( domain.elements().size()); for (const EntityIndex elementIndex : model.activeElements()) { const auto& element = domain.elements().at(elementIndex); auto& scatter = elementScatters.at(elementIndex); for (std::size_t endpoint = 0U; endpoint < element.nodeIndices.size(); ++endpoint) { const std::size_t node = element.nodeIndices[endpoint]; for (std::size_t component = 0U; component < dofsPerNode; ++component) { scatter[endpoint * dofsPerNode + component] = node * dofsPerNode + component; } } } auto pattern = buildSparsePattern( fullCount, model.activeElements(), elementScatters); return Result::success(DofManager{ fullCount, std::move(freeEquations), std::move(elementScatters), std::move(freeDofs), std::move(constrainedDofs), std::move(prescribedValues), std::move(pattern)}); } std::size_t DofManager::fullDofCount() const noexcept { return fullDofCount_; } std::size_t DofManager::freeDofCount() const noexcept { return freeDofs_.size(); } std::size_t DofManager::constrainedDofCount() const noexcept { return constrainedDofs_.size(); } std::size_t DofManager::fullDof( EntityIndex node, DofComponent component) const { const std::size_t componentIndex = static_cast(component); if (node >= fullDofCount_ / dofsPerNode || componentIndex >= dofsPerNode) { throw std::out_of_range{"Node or DOF component is out of range."}; } return static_cast(node) * dofsPerNode + componentIndex; } std::optional DofManager::freeEquation( std::size_t fullDof) const { return freeEquations_.at(fullDof); } const std::array& DofManager::elementScatter( EntityIndex element) const { return elementScatters_.at(element); } const std::vector& DofManager::freeDofs() const noexcept { return freeDofs_; } const std::vector& DofManager::constrainedDofs() const noexcept { return constrainedDofs_; } const Vector& DofManager::prescribedValues() const noexcept { return prescribedValues_; } const SparsePattern& DofManager::sparsePattern() const noexcept { return sparsePattern_; } DofManager::DofManager( std::size_t fullDofCount, std::vector> freeEquations, std::vector> elementScatters, std::vector freeDofs, std::vector constrainedDofs, Vector prescribedValues, SparsePattern sparsePattern) : fullDofCount_{fullDofCount}, freeEquations_{std::move(freeEquations)}, elementScatters_{std::move(elementScatters)}, freeDofs_{std::move(freeDofs)}, constrainedDofs_{std::move(constrainedDofs)}, prescribedValues_{std::move(prescribedValues)}, sparsePattern_{std::move(sparsePattern)} {} } // namespace fesa