feat(cpp-object-oriented-modular-refactoring): step 5 - solver-workflow-google-style
This commit is contained in:
+206
-221
@@ -1,4 +1,4 @@
|
||||
#include "fesa/fem/dof_manager.hpp"
|
||||
#include "fesa/fem/dof_manager.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <charconv>
|
||||
@@ -10,263 +10,248 @@
|
||||
namespace fesa {
|
||||
namespace {
|
||||
|
||||
constexpr std::size_t dofsPerNode = 6U;
|
||||
constexpr std::size_t kDofsPerNode = 6U;
|
||||
|
||||
char asciiLower(char value) {
|
||||
if (value >= 'A' && value <= 'Z') {
|
||||
return static_cast<char>(value + ('a' - 'A'));
|
||||
}
|
||||
return value;
|
||||
char AsciiLower(char value) {
|
||||
if (value >= 'A' && value <= 'Z') {
|
||||
return static_cast<char>(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 EqualName(const std::string& left, const std::string& right) {
|
||||
return left.size() == right.size() &&
|
||||
std::equal(left.begin(), left.end(), right.begin(),
|
||||
[](char left_value, char right_value) {
|
||||
return AsciiLower(left_value) == AsciiLower(right_value);
|
||||
});
|
||||
}
|
||||
|
||||
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;
|
||||
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<EntityIndex> expandBoundaryTarget(
|
||||
std::vector<EntityIndex> ExpandBoundaryTarget(
|
||||
const Domain& domain, const BoundaryCondition& boundary) {
|
||||
for (const auto& set : domain.NodeSets()) {
|
||||
if (equalName(set.name, boundary.target)) {
|
||||
return set.node_indices;
|
||||
for (const auto& set : domain.NodeSets()) {
|
||||
if (EqualName(set.name, boundary.target)) {
|
||||
return set.node_indices;
|
||||
}
|
||||
}
|
||||
|
||||
std::int64_t source_label = 0;
|
||||
if (TryPositiveInteger(boundary.target, source_label)) {
|
||||
for (std::size_t node = 0U; node < domain.Nodes().size(); ++node) {
|
||||
if (domain.Nodes()[node].source_id.source_label == source_label) {
|
||||
return {static_cast<EntityIndex>(node)};
|
||||
}
|
||||
}
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
template <std::size_t scatter_size>
|
||||
void AppendScatter(std::vector<std::vector<std::size_t>>& columns_by_row,
|
||||
const std::array<std::size_t, scatter_size>& scatter) {
|
||||
for (const std::size_t row : scatter) {
|
||||
auto& columns = columns_by_row[row];
|
||||
columns.insert(columns.end(), scatter.begin(), scatter.end());
|
||||
}
|
||||
}
|
||||
|
||||
/// @brief Builds sorted unique CSR columns by deterministic scatter traversal.
|
||||
SparsePattern BuildSparsePattern(
|
||||
std::size_t full_dof_count, const std::vector<EntityIndex>& active_elements,
|
||||
const std::vector<std::array<std::size_t, 12>>& element_scatters,
|
||||
const std::vector<std::array<std::size_t, 24>>& shell_element_scatters) {
|
||||
std::vector<std::vector<std::size_t>> columns_by_row(full_dof_count);
|
||||
for (const EntityIndex element : active_elements) {
|
||||
AppendScatter(columns_by_row, element_scatters.at(element));
|
||||
}
|
||||
// Every shell in the approved single-step shell subset is active.
|
||||
for (const auto& scatter : shell_element_scatters) {
|
||||
AppendScatter(columns_by_row, scatter);
|
||||
}
|
||||
|
||||
SparsePattern pattern;
|
||||
pattern.row_offsets.reserve(full_dof_count + 1U);
|
||||
pattern.row_offsets.push_back(0U);
|
||||
for (auto& columns : columns_by_row) {
|
||||
// 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.column_indices.insert(pattern.column_indices.end(), columns.begin(),
|
||||
columns.end());
|
||||
pattern.row_offsets.push_back(pattern.column_indices.size());
|
||||
}
|
||||
return pattern;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
Result<DofManager> DofManager::Create(const AnalysisModel& model) {
|
||||
const Domain& domain = model.GetDomain();
|
||||
const std::size_t full_count = domain.Nodes().size() * kDofsPerNode;
|
||||
|
||||
std::vector<std::optional<double>> prescribed_by_full_dof(full_count);
|
||||
for (const EntityIndex boundary_index : model.ActiveBoundaryConditions()) {
|
||||
const auto& boundary = model.Step().boundaries.at(boundary_index);
|
||||
const auto target = ExpandBoundaryTarget(domain, boundary);
|
||||
for (const EntityIndex node : target) {
|
||||
for (int component = boundary.first_dof; component <= boundary.last_dof;
|
||||
++component) {
|
||||
const std::size_t full_dof =
|
||||
static_cast<std::size_t>(node) * kDofsPerNode +
|
||||
static_cast<std::size_t>(component - 1);
|
||||
auto& prescribed = prescribed_by_full_dof[full_dof];
|
||||
if (prescribed && *prescribed != boundary.value) {
|
||||
return Result<DofManager>::Failure(Status::Failure(
|
||||
FailureCategory::kInput,
|
||||
{{Severity::kError, "conflicting-boundary-condition",
|
||||
boundary.location, "BOUNDARY", boundary.target,
|
||||
"Expanded boundary rows prescribe different values to one "
|
||||
"node/DOF."}}));
|
||||
}
|
||||
prescribed = boundary.value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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].source_id.source_label == sourceLabel) {
|
||||
return {static_cast<EntityIndex>(node)};
|
||||
}
|
||||
}
|
||||
std::vector<std::size_t> free_dofs;
|
||||
std::vector<std::size_t> constrained_dofs;
|
||||
std::vector<double> constrained_values;
|
||||
std::vector<std::optional<std::size_t>> free_equations(full_count);
|
||||
free_dofs.reserve(full_count);
|
||||
constrained_dofs.reserve(full_count);
|
||||
constrained_values.reserve(full_count);
|
||||
// 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 full_dof = 0U; full_dof < full_count; ++full_dof) {
|
||||
if (prescribed_by_full_dof[full_dof]) {
|
||||
constrained_dofs.push_back(full_dof);
|
||||
constrained_values.push_back(*prescribed_by_full_dof[full_dof]);
|
||||
} else {
|
||||
free_equations[full_dof] = free_dofs.size();
|
||||
free_dofs.push_back(full_dof);
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
Vector prescribed_values{constrained_values.size()};
|
||||
for (std::size_t index = 0U; index < constrained_values.size(); ++index) {
|
||||
prescribed_values[index] = constrained_values[index];
|
||||
}
|
||||
|
||||
std::vector<std::array<std::size_t, 12>> element_scatters(
|
||||
domain.Elements().size());
|
||||
for (const EntityIndex element_index : model.ActiveElements()) {
|
||||
const auto& element = domain.Elements().at(element_index);
|
||||
auto& scatter = element_scatters.at(element_index);
|
||||
for (std::size_t endpoint = 0U; endpoint < element.node_indices.size();
|
||||
++endpoint) {
|
||||
const std::size_t node = element.node_indices[endpoint];
|
||||
for (std::size_t component = 0U; component < kDofsPerNode; ++component) {
|
||||
scatter[endpoint * kDofsPerNode + component] =
|
||||
node * kDofsPerNode + component;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<std::array<std::size_t, 24>> shell_element_scatters(
|
||||
domain.ShellElements().size());
|
||||
for (std::size_t element_index = 0U;
|
||||
element_index < domain.ShellElements().size(); ++element_index) {
|
||||
const auto& element = domain.ShellElements()[element_index];
|
||||
auto& scatter = shell_element_scatters[element_index];
|
||||
for (std::size_t node_position = 0U;
|
||||
node_position < element.node_indices.size(); ++node_position) {
|
||||
const std::size_t node = element.node_indices[node_position];
|
||||
for (std::size_t component = 0U; component < kDofsPerNode; ++component) {
|
||||
scatter[node_position * kDofsPerNode + component] =
|
||||
node * kDofsPerNode + component;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto pattern = BuildSparsePattern(full_count, model.ActiveElements(),
|
||||
element_scatters, shell_element_scatters);
|
||||
return Result<DofManager>::Success(
|
||||
DofManager{full_count, std::move(free_equations),
|
||||
std::move(element_scatters), std::move(shell_element_scatters),
|
||||
std::move(free_dofs), std::move(constrained_dofs),
|
||||
std::move(prescribed_values), std::move(pattern)});
|
||||
}
|
||||
|
||||
template <std::size_t scatterSize>
|
||||
void appendScatter(
|
||||
std::vector<std::vector<std::size_t>>& columnsByRow,
|
||||
const std::array<std::size_t, scatterSize>& scatter) {
|
||||
for (const std::size_t row : scatter) {
|
||||
auto& columns = columnsByRow[row];
|
||||
columns.insert(columns.end(), scatter.begin(), scatter.end());
|
||||
}
|
||||
std::size_t DofManager::FullDofCount() const noexcept {
|
||||
return full_dof_count_;
|
||||
}
|
||||
|
||||
SparsePattern buildSparsePattern(
|
||||
std::size_t fullDofCount,
|
||||
const std::vector<EntityIndex>& activeElements,
|
||||
const std::vector<std::array<std::size_t, 12>>& elementScatters,
|
||||
const std::vector<std::array<std::size_t, 24>>& shellElementScatters) {
|
||||
std::vector<std::vector<std::size_t>> columnsByRow(fullDofCount);
|
||||
for (const EntityIndex element : activeElements) {
|
||||
appendScatter(columnsByRow, elementScatters.at(element));
|
||||
}
|
||||
// Every shell in the approved single-step shell subset is active.
|
||||
for (const auto& scatter : shellElementScatters) {
|
||||
appendScatter(columnsByRow, scatter);
|
||||
}
|
||||
|
||||
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;
|
||||
std::size_t DofManager::FreeDofCount() const noexcept {
|
||||
return free_dofs_.size();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
Result<DofManager> DofManager::create(const AnalysisModel& model) {
|
||||
const Domain& domain = model.domain();
|
||||
const std::size_t fullCount = domain.Nodes().size() * dofsPerNode;
|
||||
|
||||
std::vector<std::optional<double>> 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.first_dof;
|
||||
component <= boundary.last_dof;
|
||||
++component) {
|
||||
const std::size_t fullDof =
|
||||
static_cast<std::size_t>(node) * dofsPerNode +
|
||||
static_cast<std::size_t>(component - 1);
|
||||
auto& prescribed = prescribedByFullDof[fullDof];
|
||||
if (prescribed && *prescribed != boundary.value) {
|
||||
return Result<DofManager>::Failure(Status::Failure(
|
||||
FailureCategory::kInput,
|
||||
{{Severity::kError,
|
||||
"conflicting-boundary-condition",
|
||||
boundary.location,
|
||||
"BOUNDARY",
|
||||
boundary.target,
|
||||
"Expanded boundary rows prescribe different values to one node/DOF."}}));
|
||||
}
|
||||
prescribed = boundary.value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<std::size_t> freeDofs;
|
||||
std::vector<std::size_t> constrainedDofs;
|
||||
std::vector<double> constrainedValues;
|
||||
std::vector<std::optional<std::size_t>> 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<std::array<std::size_t, 12>> 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.node_indices.size(); ++endpoint) {
|
||||
const std::size_t node = element.node_indices[endpoint];
|
||||
for (std::size_t component = 0U; component < dofsPerNode; ++component) {
|
||||
scatter[endpoint * dofsPerNode + component] =
|
||||
node * dofsPerNode + component;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<std::array<std::size_t, 24>> shellElementScatters(
|
||||
domain.ShellElements().size());
|
||||
for (std::size_t elementIndex = 0U;
|
||||
elementIndex < domain.ShellElements().size();
|
||||
++elementIndex) {
|
||||
const auto& element = domain.ShellElements()[elementIndex];
|
||||
auto& scatter = shellElementScatters[elementIndex];
|
||||
for (std::size_t nodePosition = 0U;
|
||||
nodePosition < element.node_indices.size();
|
||||
++nodePosition) {
|
||||
const std::size_t node = element.node_indices[nodePosition];
|
||||
for (std::size_t component = 0U;
|
||||
component < dofsPerNode;
|
||||
++component) {
|
||||
scatter[nodePosition * dofsPerNode + component] =
|
||||
node * dofsPerNode + component;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto pattern = buildSparsePattern(
|
||||
fullCount,
|
||||
model.activeElements(),
|
||||
elementScatters,
|
||||
shellElementScatters);
|
||||
return Result<DofManager>::Success(DofManager{
|
||||
fullCount,
|
||||
std::move(freeEquations),
|
||||
std::move(elementScatters),
|
||||
std::move(shellElementScatters),
|
||||
std::move(freeDofs),
|
||||
std::move(constrainedDofs),
|
||||
std::move(prescribedValues),
|
||||
std::move(pattern)});
|
||||
std::size_t DofManager::ConstrainedDofCount() const noexcept {
|
||||
return constrained_dofs_.size();
|
||||
}
|
||||
|
||||
std::size_t DofManager::fullDofCount() const noexcept {
|
||||
return fullDofCount_;
|
||||
std::size_t DofManager::FullDof(EntityIndex node,
|
||||
DofComponent component) const {
|
||||
const std::size_t component_index = static_cast<std::size_t>(component);
|
||||
if (node >= full_dof_count_ / kDofsPerNode ||
|
||||
component_index >= kDofsPerNode) {
|
||||
throw std::out_of_range{"Node or DOF component is out of range."};
|
||||
}
|
||||
return static_cast<std::size_t>(node) * kDofsPerNode + component_index;
|
||||
}
|
||||
|
||||
std::size_t DofManager::freeDofCount() const noexcept {
|
||||
return freeDofs_.size();
|
||||
std::optional<std::size_t> DofManager::FreeEquation(
|
||||
std::size_t full_dof) const {
|
||||
return free_equations_.at(full_dof);
|
||||
}
|
||||
|
||||
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<std::size_t>(component);
|
||||
if (node >= fullDofCount_ / dofsPerNode || componentIndex >= dofsPerNode) {
|
||||
throw std::out_of_range{"Node or DOF component is out of range."};
|
||||
}
|
||||
return static_cast<std::size_t>(node) * dofsPerNode + componentIndex;
|
||||
}
|
||||
|
||||
std::optional<std::size_t> DofManager::freeEquation(
|
||||
std::size_t fullDof) const {
|
||||
return freeEquations_.at(fullDof);
|
||||
}
|
||||
|
||||
const std::array<std::size_t, 12>& DofManager::elementScatter(
|
||||
const std::array<std::size_t, 12>& DofManager::ElementScatter(
|
||||
EntityIndex element) const {
|
||||
return elementScatters_.at(element);
|
||||
return element_scatters_.at(element);
|
||||
}
|
||||
|
||||
const std::array<std::size_t, 24>& DofManager::shellElementScatter(
|
||||
const std::array<std::size_t, 24>& DofManager::ShellElementScatter(
|
||||
EntityIndex element) const {
|
||||
return shellElementScatters_.at(element);
|
||||
return shell_element_scatters_.at(element);
|
||||
}
|
||||
|
||||
const std::vector<std::size_t>& DofManager::freeDofs() const noexcept {
|
||||
return freeDofs_;
|
||||
const std::vector<std::size_t>& DofManager::FreeDofs() const noexcept {
|
||||
return free_dofs_;
|
||||
}
|
||||
|
||||
const std::vector<std::size_t>& DofManager::constrainedDofs() const noexcept {
|
||||
return constrainedDofs_;
|
||||
const std::vector<std::size_t>& DofManager::ConstrainedDofs() const noexcept {
|
||||
return constrained_dofs_;
|
||||
}
|
||||
|
||||
const Vector& DofManager::prescribedValues() const noexcept {
|
||||
return prescribedValues_;
|
||||
const Vector& DofManager::PrescribedValues() const noexcept {
|
||||
return prescribed_values_;
|
||||
}
|
||||
|
||||
const SparsePattern& DofManager::sparsePattern() const noexcept {
|
||||
return sparsePattern_;
|
||||
const SparsePattern& DofManager::GetSparsePattern() const noexcept {
|
||||
return sparse_pattern_;
|
||||
}
|
||||
|
||||
DofManager::DofManager(
|
||||
std::size_t fullDofCount,
|
||||
std::vector<std::optional<std::size_t>> freeEquations,
|
||||
std::vector<std::array<std::size_t, 12>> elementScatters,
|
||||
std::vector<std::array<std::size_t, 24>> shellElementScatters,
|
||||
std::vector<std::size_t> freeDofs,
|
||||
std::vector<std::size_t> constrainedDofs,
|
||||
Vector prescribedValues,
|
||||
SparsePattern sparsePattern)
|
||||
: fullDofCount_{fullDofCount},
|
||||
freeEquations_{std::move(freeEquations)},
|
||||
elementScatters_{std::move(elementScatters)},
|
||||
shellElementScatters_{std::move(shellElementScatters)},
|
||||
freeDofs_{std::move(freeDofs)},
|
||||
constrainedDofs_{std::move(constrainedDofs)},
|
||||
prescribedValues_{std::move(prescribedValues)},
|
||||
sparsePattern_{std::move(sparsePattern)} {}
|
||||
std::size_t full_dof_count,
|
||||
std::vector<std::optional<std::size_t>> free_equations,
|
||||
std::vector<std::array<std::size_t, 12>> element_scatters,
|
||||
std::vector<std::array<std::size_t, 24>> shell_element_scatters,
|
||||
std::vector<std::size_t> free_dofs,
|
||||
std::vector<std::size_t> constrained_dofs, Vector prescribed_values,
|
||||
SparsePattern sparse_pattern)
|
||||
: full_dof_count_{full_dof_count},
|
||||
free_equations_{std::move(free_equations)},
|
||||
element_scatters_{std::move(element_scatters)},
|
||||
shell_element_scatters_{std::move(shell_element_scatters)},
|
||||
free_dofs_{std::move(free_dofs)},
|
||||
constrained_dofs_{std::move(constrained_dofs)},
|
||||
prescribed_values_{std::move(prescribed_values)},
|
||||
sparse_pattern_{std::move(sparse_pattern)} {}
|
||||
|
||||
} // namespace fesa
|
||||
} // namespace fesa
|
||||
|
||||
Reference in New Issue
Block a user