feat(cpp-object-oriented-modular-refactoring): step 5 - solver-workflow-google-style

This commit is contained in:
KOKO\Mimi
2026-08-16 06:20:08 +09:00
parent e1c0e357dd
commit 24f006fe4a
52 changed files with 5817 additions and 6369 deletions
+188 -223
View File
@@ -1,6 +1,4 @@
#include "fesa/constraints/essential_constraints.hpp"
#include "fesa/fem/dof_manager.hpp"
#include "fesa/constraints/essential_constraints.h"
#include <algorithm>
#include <limits>
@@ -9,253 +7,220 @@
#include <utility>
#include <vector>
#include "fesa/fem/dof_manager.h"
namespace fesa {
namespace {
Status constraintFailure(
const std::string& code,
const std::string& identity,
const std::string& message) {
return Status::Failure(
FailureCategory::kModel,
{{Severity::kError,
code,
{{}, 0U},
"ESSENTIAL_CONSTRAINTS",
identity,
message}});
Status ConstraintFailure(const std::string& code, const std::string& identity,
const std::string& message) {
return Status::Failure(FailureCategory::kModel, {{Severity::kError,
code,
{{}, 0U},
"ESSENTIAL_CONSTRAINTS",
identity,
message}});
}
bool isStrictlyIncreasing(const std::vector<std::size_t>& values) {
return std::adjacent_find(
values.begin(),
values.end(),
[](const std::size_t left, const std::size_t right) {
return left >= right;
}) == values.end();
bool IsStrictlyIncreasing(const std::vector<std::size_t>& values) {
return std::adjacent_find(
values.begin(), values.end(),
[](const std::size_t left, const std::size_t right) {
return left >= right;
}) == values.end();
}
Status validateDofOrder(const DofManager& dofs) {
const std::size_t fullCount = dofs.fullDofCount();
const auto& freeDofs = dofs.freeDofs();
const auto& constrainedDofs = dofs.constrainedDofs();
if (freeDofs.size() != dofs.freeDofCount() ||
constrainedDofs.size() != dofs.constrainedDofCount() ||
dofs.prescribedValues().Size() != constrainedDofs.size() ||
constrainedDofs.size() > fullCount ||
freeDofs.size() != fullCount - constrainedDofs.size()) {
return constraintFailure(
"invalid-constraint-dimensions",
std::to_string(fullCount),
"DofManager full, free, constrained, and prescribed dimensions must agree.");
}
if (!isStrictlyIncreasing(freeDofs) ||
!isStrictlyIncreasing(constrainedDofs)) {
return constraintFailure(
"invalid-constraint-order",
std::to_string(fullCount),
"Free and constrained DOFs must use stable increasing full-DOF order.");
}
/// @brief Validates the stable full/free/constrained numbering invariant.
Status ValidateDofOrder(const DofManager& dofs) {
const std::size_t full_count = dofs.FullDofCount();
const auto& free_dofs = dofs.FreeDofs();
const auto& constrained_dofs = dofs.ConstrainedDofs();
if (free_dofs.size() != dofs.FreeDofCount() ||
constrained_dofs.size() != dofs.ConstrainedDofCount() ||
dofs.PrescribedValues().Size() != constrained_dofs.size() ||
constrained_dofs.size() > full_count ||
free_dofs.size() != full_count - constrained_dofs.size()) {
return ConstraintFailure("invalid-constraint-dimensions",
std::to_string(full_count),
"DofManager full, free, constrained, and "
"prescribed dimensions must agree.");
}
if (!IsStrictlyIncreasing(free_dofs) ||
!IsStrictlyIncreasing(constrained_dofs)) {
return ConstraintFailure(
"invalid-constraint-order", std::to_string(full_count),
"Free and constrained DOFs must use stable increasing full-DOF order.");
}
std::vector<unsigned char> ownership(fullCount, 0U);
try {
for (std::size_t equation = 0U;
equation < freeDofs.size();
++equation) {
const std::size_t fullDof = freeDofs[equation];
if (fullDof >= fullCount || ownership[fullDof] != 0U ||
dofs.freeEquation(fullDof) != equation) {
return constraintFailure(
"invalid-constraint-order",
std::to_string(fullDof),
"Free equation numbering must match the stable free-DOF order.");
}
ownership[fullDof] = 1U;
}
for (const std::size_t fullDof : constrainedDofs) {
if (fullDof >= fullCount || ownership[fullDof] != 0U ||
dofs.freeEquation(fullDof).has_value()) {
return constraintFailure(
"invalid-constraint-order",
std::to_string(fullDof),
"Constrained DOFs must be unique and absent from free equations.");
}
ownership[fullDof] = 2U;
}
} catch (const std::out_of_range&) {
return constraintFailure(
"invalid-constraint-dimensions",
std::to_string(fullCount),
"DofManager equation storage does not cover every full DOF.");
std::vector<unsigned char> ownership(full_count, 0U);
try {
for (std::size_t equation = 0U; equation < free_dofs.size(); ++equation) {
const std::size_t full_dof = free_dofs[equation];
if (full_dof >= full_count || ownership[full_dof] != 0U ||
dofs.FreeEquation(full_dof) != equation) {
return ConstraintFailure(
"invalid-constraint-order", std::to_string(full_dof),
"Free equation numbering must match the stable free-DOF order.");
}
ownership[full_dof] = 1U;
}
if (std::find(ownership.begin(), ownership.end(), 0U) != ownership.end()) {
return constraintFailure(
"invalid-constraint-order",
std::to_string(fullCount),
"Free and constrained DOFs must partition the complete full-DOF range.");
for (const std::size_t full_dof : constrained_dofs) {
if (full_dof >= full_count || ownership[full_dof] != 0U ||
dofs.FreeEquation(full_dof).has_value()) {
return ConstraintFailure(
"invalid-constraint-order", std::to_string(full_dof),
"Constrained DOFs must be unique and absent from free equations.");
}
ownership[full_dof] = 2U;
}
return Status::Ok();
} catch (const std::out_of_range&) {
return ConstraintFailure(
"invalid-constraint-dimensions", std::to_string(full_count),
"DofManager equation storage does not cover every full DOF.");
}
if (std::find(ownership.begin(), ownership.end(), 0U) != ownership.end()) {
return ConstraintFailure("invalid-constraint-order",
std::to_string(full_count),
"Free and constrained DOFs must partition the "
"complete full-DOF range.");
}
return Status::Ok();
}
Result<SparseMatrix> extractBlock(
const SparseMatrix& full,
const std::vector<std::size_t>& rowDofs,
const std::vector<std::size_t>& columnDofs) {
const std::size_t absent = (std::numeric_limits<std::size_t>::max)();
std::vector<std::size_t> localColumn(full.Columns(), absent);
for (std::size_t column = 0U; column < columnDofs.size(); ++column) {
localColumn[columnDofs[column]] = column;
}
/// @brief Extracts one partition without changing the supplied DOF order.
Result<SparseMatrix> ExtractBlock(const SparseMatrix& full,
const std::vector<std::size_t>& row_dofs,
const std::vector<std::size_t>& column_dofs) {
const std::size_t absent = (std::numeric_limits<std::size_t>::max)();
std::vector<std::size_t> local_column(full.Columns(), absent);
for (std::size_t column = 0U; column < column_dofs.size(); ++column) {
local_column[column_dofs[column]] = column;
}
SparsePattern pattern;
pattern.rowOffsets.reserve(rowDofs.size() + 1U);
pattern.rowOffsets.push_back(0U);
std::vector<CooContribution> contributions;
contributions.reserve(full.Values().size());
for (std::size_t localRow = 0U;
localRow < rowDofs.size();
++localRow) {
const std::size_t fullRow = rowDofs[localRow];
for (std::size_t position = full.RowOffsets()[fullRow];
position < full.RowOffsets()[fullRow + 1U];
++position) {
const std::size_t column =
localColumn[full.ColumnIndices()[position]];
if (column == absent) {
continue;
}
pattern.columnIndices.push_back(column);
// One source CSR entry maps to one block slot, so exact numeric
// values and structural zeros survive without a new reduction.
contributions.push_back({
localRow,
column,
full.Values()[position],
localRow,
position});
}
pattern.rowOffsets.push_back(pattern.columnIndices.size());
SparsePattern pattern;
pattern.row_offsets.reserve(row_dofs.size() + 1U);
pattern.row_offsets.push_back(0U);
std::vector<CooContribution> contributions;
contributions.reserve(full.Values().size());
for (std::size_t local_row = 0U; local_row < row_dofs.size(); ++local_row) {
const std::size_t full_row = row_dofs[local_row];
for (std::size_t position = full.RowOffsets()[full_row];
position < full.RowOffsets()[full_row + 1U]; ++position) {
const std::size_t column = local_column[full.ColumnIndices()[position]];
if (column == absent) {
continue;
}
pattern.column_indices.push_back(column);
// One source CSR entry maps to one block slot, so exact numeric
// values and structural zeros survive without a new reduction.
contributions.push_back(
{local_row, column, full.Values()[position], local_row, position});
}
return SparseMatrix::FromCoo(
rowDofs.size(),
columnDofs.size(),
std::move(contributions),
pattern);
pattern.row_offsets.push_back(pattern.column_indices.size());
}
return SparseMatrix::FromCoo(row_dofs.size(), column_dofs.size(),
std::move(contributions), pattern);
}
void requireDofOrder(const DofManager& dofs) {
if (!validateDofOrder(dofs).IsOk()) {
throw std::invalid_argument{
"DofManager constraint dimensions or order are invalid."};
}
void RequireDofOrder(const DofManager& dofs) {
if (!ValidateDofOrder(dofs).IsOk()) {
throw std::invalid_argument{
"DofManager constraint dimensions or order are invalid."};
}
}
} // namespace
} // namespace
Result<PartitionedStiffness> EssentialConstraints::partition(
const SparseMatrix& full,
const DofManager& dofs) {
const Status matrixStatus = full.Validate();
if (!matrixStatus.IsOk()) {
return Result<PartitionedStiffness>::Failure(matrixStatus);
}
if (full.Rows() != full.Columns() ||
full.Rows() != dofs.fullDofCount()) {
return Result<PartitionedStiffness>::Failure(constraintFailure(
"invalid-constraint-dimensions",
std::to_string(full.Rows()) + "x" +
std::to_string(full.Columns()),
"Full stiffness must be square and match the DofManager full dimension."));
}
const Status dofStatus = validateDofOrder(dofs);
if (!dofStatus.IsOk()) {
return Result<PartitionedStiffness>::Failure(dofStatus);
}
Result<PartitionedStiffness> EssentialConstraints::Partition(
const SparseMatrix& full, const DofManager& dofs) {
const Status matrix_status = full.Validate();
if (!matrix_status.IsOk()) {
return Result<PartitionedStiffness>::Failure(matrix_status);
}
if (full.Rows() != full.Columns() || full.Rows() != dofs.FullDofCount()) {
return Result<PartitionedStiffness>::Failure(ConstraintFailure(
"invalid-constraint-dimensions",
std::to_string(full.Rows()) + "x" + std::to_string(full.Columns()),
"Full stiffness must be square and match the DofManager full "
"dimension."));
}
const Status dof_status = ValidateDofOrder(dofs);
if (!dof_status.IsOk()) {
return Result<PartitionedStiffness>::Failure(dof_status);
}
auto kff = extractBlock(full, dofs.freeDofs(), dofs.freeDofs());
if (!kff.HasValue()) {
return Result<PartitionedStiffness>::Failure(kff.GetStatus());
}
auto kfc = extractBlock(full, dofs.freeDofs(), dofs.constrainedDofs());
if (!kfc.HasValue()) {
return Result<PartitionedStiffness>::Failure(kfc.GetStatus());
}
auto kcf = extractBlock(full, dofs.constrainedDofs(), dofs.freeDofs());
if (!kcf.HasValue()) {
return Result<PartitionedStiffness>::Failure(kcf.GetStatus());
}
auto kcc = extractBlock(
full, dofs.constrainedDofs(), dofs.constrainedDofs());
if (!kcc.HasValue()) {
return Result<PartitionedStiffness>::Failure(kcc.GetStatus());
}
auto kff = ExtractBlock(full, dofs.FreeDofs(), dofs.FreeDofs());
if (!kff.HasValue()) {
return Result<PartitionedStiffness>::Failure(kff.GetStatus());
}
auto kfc = ExtractBlock(full, dofs.FreeDofs(), dofs.ConstrainedDofs());
if (!kfc.HasValue()) {
return Result<PartitionedStiffness>::Failure(kfc.GetStatus());
}
auto kcf = ExtractBlock(full, dofs.ConstrainedDofs(), dofs.FreeDofs());
if (!kcf.HasValue()) {
return Result<PartitionedStiffness>::Failure(kcf.GetStatus());
}
auto kcc = ExtractBlock(full, dofs.ConstrainedDofs(), dofs.ConstrainedDofs());
if (!kcc.HasValue()) {
return Result<PartitionedStiffness>::Failure(kcc.GetStatus());
}
return Result<PartitionedStiffness>::Success({
std::move(kff.Value()),
std::move(kfc.Value()),
std::move(kcf.Value()),
std::move(kcc.Value())});
return Result<PartitionedStiffness>::Success(
{std::move(kff.Value()), std::move(kfc.Value()), std::move(kcf.Value()),
std::move(kcc.Value())});
}
Vector EssentialConstraints::gatherFree(
const Vector& full,
const DofManager& dofs) {
requireDofOrder(dofs);
if (full.Size() != dofs.fullDofCount()) {
throw std::invalid_argument{
"Full vector size must match the DofManager full dimension."};
}
Vector reduced{dofs.freeDofCount()};
for (std::size_t equation = 0U;
equation < dofs.freeDofs().size();
++equation) {
reduced[equation] = full[dofs.freeDofs()[equation]];
}
return reduced;
Vector EssentialConstraints::GatherFree(const Vector& full,
const DofManager& dofs) {
RequireDofOrder(dofs);
if (full.Size() != dofs.FullDofCount()) {
throw std::invalid_argument{
"Full vector size must match the DofManager full dimension."};
}
Vector reduced{dofs.FreeDofCount()};
for (std::size_t equation = 0U; equation < dofs.FreeDofs().size();
++equation) {
reduced[equation] = full[dofs.FreeDofs()[equation]];
}
return reduced;
}
Vector EssentialConstraints::gatherConstrained(
const Vector& full,
const DofManager& dofs) {
requireDofOrder(dofs);
if (full.Size() != dofs.fullDofCount()) {
throw std::invalid_argument{
"Full vector size must match the DofManager full dimension."};
}
Vector reduced{dofs.constrainedDofCount()};
for (std::size_t index = 0U;
index < dofs.constrainedDofs().size();
++index) {
reduced[index] = full[dofs.constrainedDofs()[index]];
}
return reduced;
Vector EssentialConstraints::GatherConstrained(const Vector& full,
const DofManager& dofs) {
RequireDofOrder(dofs);
if (full.Size() != dofs.FullDofCount()) {
throw std::invalid_argument{
"Full vector size must match the DofManager full dimension."};
}
Vector reduced{dofs.ConstrainedDofCount()};
for (std::size_t index = 0U; index < dofs.ConstrainedDofs().size(); ++index) {
reduced[index] = full[dofs.ConstrainedDofs()[index]];
}
return reduced;
}
Vector EssentialConstraints::reconstructFull(
const Vector& freeValues,
const Vector& constrainedValues,
const DofManager& dofs) {
requireDofOrder(dofs);
if (freeValues.Size() != dofs.freeDofCount() ||
constrainedValues.Size() != dofs.constrainedDofCount()) {
throw std::invalid_argument{
"Reduced vector sizes must match the DofManager order."};
}
Vector EssentialConstraints::ReconstructFull(const Vector& free_values,
const Vector& constrained_values,
const DofManager& dofs) {
RequireDofOrder(dofs);
if (free_values.Size() != dofs.FreeDofCount() ||
constrained_values.Size() != dofs.ConstrainedDofCount()) {
throw std::invalid_argument{
"Reduced vector sizes must match the DofManager order."};
}
Vector full{dofs.fullDofCount()};
for (std::size_t equation = 0U;
equation < dofs.freeDofs().size();
++equation) {
full[dofs.freeDofs()[equation]] = freeValues[equation];
}
// Preserve caller-supplied dc exactly; nonzero prescribed displacement is
// never replaced with an implicit homogeneous constraint.
for (std::size_t index = 0U;
index < dofs.constrainedDofs().size();
++index) {
full[dofs.constrainedDofs()[index]] = constrainedValues[index];
}
return full;
Vector full{dofs.FullDofCount()};
for (std::size_t equation = 0U; equation < dofs.FreeDofs().size();
++equation) {
full[dofs.FreeDofs()[equation]] = free_values[equation];
}
// Preserve caller-supplied dc exactly; nonzero prescribed displacement is
// never replaced with an implicit homogeneous constraint.
for (std::size_t index = 0U; index < dofs.ConstrainedDofs().size(); ++index) {
full[dofs.ConstrainedDofs()[index]] = constrained_values[index];
}
return full;
}
} // namespace fesa
} // namespace fesa