feat(cpp-object-oriented-modular-refactoring): step 19 - boundary-condition-policy
This commit is contained in:
@@ -0,0 +1,169 @@
|
||||
#include "fesa/constraints/essential_constraint_policy.h"
|
||||
|
||||
#include <limits>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#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_CONSTRAINT_POLICY",
|
||||
identity,
|
||||
message}});
|
||||
}
|
||||
|
||||
/// @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.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});
|
||||
}
|
||||
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& dof_manager) {
|
||||
if (!dof_manager.ValidateInvariants().IsOk()) {
|
||||
throw std::invalid_argument{
|
||||
"DofManager constraint dimensions or order are invalid."};
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
Result<PartitionedStiffness> EssentialConstraintPolicy::Partition(
|
||||
const SparseMatrix& full_stiffness, const DofManager& dof_manager) const {
|
||||
const Status matrix_status = full_stiffness.Validate();
|
||||
if (!matrix_status.IsOk()) {
|
||||
return Result<PartitionedStiffness>::Failure(matrix_status);
|
||||
}
|
||||
if (full_stiffness.Rows() != full_stiffness.Columns() ||
|
||||
full_stiffness.Rows() != dof_manager.FullDofCount()) {
|
||||
return Result<PartitionedStiffness>::Failure(ConstraintFailure(
|
||||
"invalid-constraint-dimensions",
|
||||
std::to_string(full_stiffness.Rows()) + "x" +
|
||||
std::to_string(full_stiffness.Columns()),
|
||||
"Full stiffness must be square and match the DofManager full "
|
||||
"dimension."));
|
||||
}
|
||||
const Status dof_status = dof_manager.ValidateInvariants();
|
||||
if (!dof_status.IsOk()) {
|
||||
return Result<PartitionedStiffness>::Failure(dof_status);
|
||||
}
|
||||
|
||||
auto k_ff = ExtractBlock(full_stiffness, dof_manager.FreeDofs(),
|
||||
dof_manager.FreeDofs());
|
||||
if (!k_ff.HasValue()) {
|
||||
return Result<PartitionedStiffness>::Failure(k_ff.GetStatus());
|
||||
}
|
||||
auto k_fc = ExtractBlock(full_stiffness, dof_manager.FreeDofs(),
|
||||
dof_manager.ConstrainedDofs());
|
||||
if (!k_fc.HasValue()) {
|
||||
return Result<PartitionedStiffness>::Failure(k_fc.GetStatus());
|
||||
}
|
||||
auto k_cf = ExtractBlock(full_stiffness, dof_manager.ConstrainedDofs(),
|
||||
dof_manager.FreeDofs());
|
||||
if (!k_cf.HasValue()) {
|
||||
return Result<PartitionedStiffness>::Failure(k_cf.GetStatus());
|
||||
}
|
||||
auto k_cc = ExtractBlock(full_stiffness, dof_manager.ConstrainedDofs(),
|
||||
dof_manager.ConstrainedDofs());
|
||||
if (!k_cc.HasValue()) {
|
||||
return Result<PartitionedStiffness>::Failure(k_cc.GetStatus());
|
||||
}
|
||||
|
||||
return Result<PartitionedStiffness>::Success(
|
||||
{std::move(k_ff.Value()), std::move(k_fc.Value()),
|
||||
std::move(k_cf.Value()), std::move(k_cc.Value())});
|
||||
}
|
||||
|
||||
Vector EssentialConstraintPolicy::GatherFree(
|
||||
const Vector& full_values, const DofManager& dof_manager) const {
|
||||
RequireDofOrder(dof_manager);
|
||||
if (full_values.Size() != dof_manager.FullDofCount()) {
|
||||
throw std::invalid_argument{
|
||||
"Full vector size must match the DofManager full dimension."};
|
||||
}
|
||||
Vector reduced{dof_manager.FreeDofCount()};
|
||||
for (std::size_t equation = 0U; equation < dof_manager.FreeDofs().size();
|
||||
++equation) {
|
||||
reduced[equation] = full_values[dof_manager.FreeDofs()[equation]];
|
||||
}
|
||||
return reduced;
|
||||
}
|
||||
|
||||
Vector EssentialConstraintPolicy::GatherConstrained(
|
||||
const Vector& full_values, const DofManager& dof_manager) const {
|
||||
RequireDofOrder(dof_manager);
|
||||
if (full_values.Size() != dof_manager.FullDofCount()) {
|
||||
throw std::invalid_argument{
|
||||
"Full vector size must match the DofManager full dimension."};
|
||||
}
|
||||
Vector reduced{dof_manager.ConstrainedDofCount()};
|
||||
for (std::size_t index = 0U; index < dof_manager.ConstrainedDofs().size();
|
||||
++index) {
|
||||
reduced[index] = full_values[dof_manager.ConstrainedDofs()[index]];
|
||||
}
|
||||
return reduced;
|
||||
}
|
||||
|
||||
Vector EssentialConstraintPolicy::ReconstructFull(
|
||||
const Vector& free_values, const Vector& constrained_values,
|
||||
const DofManager& dof_manager) const {
|
||||
RequireDofOrder(dof_manager);
|
||||
if (free_values.Size() != dof_manager.FreeDofCount() ||
|
||||
constrained_values.Size() != dof_manager.ConstrainedDofCount()) {
|
||||
throw std::invalid_argument{
|
||||
"Reduced vector sizes must match the DofManager order."};
|
||||
}
|
||||
|
||||
Vector full{dof_manager.FullDofCount()};
|
||||
for (std::size_t equation = 0U; equation < dof_manager.FreeDofs().size();
|
||||
++equation) {
|
||||
full[dof_manager.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 < dof_manager.ConstrainedDofs().size();
|
||||
++index) {
|
||||
full[dof_manager.ConstrainedDofs()[index]] = constrained_values[index];
|
||||
}
|
||||
return full;
|
||||
}
|
||||
|
||||
} // namespace fesa
|
||||
@@ -1,161 +0,0 @@
|
||||
#include "fesa/constraints/essential_constraints.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <limits>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#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}});
|
||||
}
|
||||
|
||||
/// @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.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});
|
||||
}
|
||||
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 (!dofs.ValidateInvariants().IsOk()) {
|
||||
throw std::invalid_argument{
|
||||
"DofManager constraint dimensions or order are invalid."};
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
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 = dofs.ValidateInvariants();
|
||||
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());
|
||||
}
|
||||
|
||||
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::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& 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]] = 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
|
||||
@@ -0,0 +1,107 @@
|
||||
#include "fesa/constraints/prescribed_displacement.h"
|
||||
|
||||
#include <cmath>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "fesa/fem/dof_manager.h"
|
||||
#include "fesa/model/domain.h"
|
||||
|
||||
namespace fesa {
|
||||
namespace {
|
||||
|
||||
Status BoundaryFailure(const std::string& code, const SourceLocation& location,
|
||||
const std::string& identity,
|
||||
const std::string& message) {
|
||||
return Status::Failure(
|
||||
FailureCategory::kModel,
|
||||
{{Severity::kError, code, location, "BOUNDARY", identity, message}});
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
PrescribedDisplacementBoundaryCondition::
|
||||
PrescribedDisplacementBoundaryCondition(SourceTargetQuery target,
|
||||
const DofComponent component,
|
||||
const double prescribed_value,
|
||||
const std::size_t source_order,
|
||||
SourceLocation location)
|
||||
: BoundaryCondition{target.target_name_or_label, std::move(location)},
|
||||
target_{std::move(target)},
|
||||
component_{component},
|
||||
prescribed_value_{prescribed_value},
|
||||
source_order_{source_order} {}
|
||||
|
||||
Result<std::vector<ConstraintDefinition>>
|
||||
PrescribedDisplacementBoundaryCondition::ResolveConstraints(
|
||||
const BoundaryConditionContext& context) const {
|
||||
const std::string& identity = target_.target_name_or_label;
|
||||
if (!std::isfinite(prescribed_value_)) {
|
||||
return Result<std::vector<ConstraintDefinition>>::Failure(
|
||||
BoundaryFailure("nonfinite-prescribed-value", Location(), identity,
|
||||
"A prescribed displacement value must be finite."));
|
||||
}
|
||||
if (target_.entity_kind != SourceEntityKind::kNode) {
|
||||
return Result<std::vector<ConstraintDefinition>>::Failure(
|
||||
BoundaryFailure("invalid-boundary-target", Location(), identity,
|
||||
"A prescribed displacement requires a node target."));
|
||||
}
|
||||
|
||||
auto resolved = context.target_resolver.Resolve(target_);
|
||||
if (!resolved.HasValue()) {
|
||||
return Result<std::vector<ConstraintDefinition>>::Failure(BoundaryFailure(
|
||||
"invalid-boundary-target", Location(), identity,
|
||||
"The boundary target must resolve unambiguously to one node or one "
|
||||
"expanded node set."));
|
||||
}
|
||||
|
||||
std::vector<unsigned char> seen(context.domain.Nodes().size(), 0U);
|
||||
std::vector<ConstraintDefinition> definitions;
|
||||
definitions.reserve(resolved.Value().size());
|
||||
for (const auto& target : resolved.Value()) {
|
||||
const EntityIndex node = target.entity_index;
|
||||
if (node >= context.domain.Nodes().size() || seen[node] != 0U) {
|
||||
return Result<std::vector<ConstraintDefinition>>::Failure(BoundaryFailure(
|
||||
"invalid-boundary-target", Location(), identity,
|
||||
"The expanded node set must contain unique in-range stable "
|
||||
"node identities."));
|
||||
}
|
||||
seen[node] = 1U;
|
||||
try {
|
||||
definitions.push_back({source_order_,
|
||||
context.dof_manager.FullDof(node, component_),
|
||||
prescribed_value_});
|
||||
} catch (const std::out_of_range&) {
|
||||
return Result<std::vector<ConstraintDefinition>>::Failure(
|
||||
BoundaryFailure("invalid-boundary-target", Location(), identity,
|
||||
"The resolved boundary target must provide the "
|
||||
"prescribed full DOF."));
|
||||
}
|
||||
}
|
||||
return Result<std::vector<ConstraintDefinition>>::Success(
|
||||
std::move(definitions));
|
||||
}
|
||||
|
||||
const SourceTargetQuery& PrescribedDisplacementBoundaryCondition::Target()
|
||||
const noexcept {
|
||||
return target_;
|
||||
}
|
||||
|
||||
DofComponent PrescribedDisplacementBoundaryCondition::Component()
|
||||
const noexcept {
|
||||
return component_;
|
||||
}
|
||||
|
||||
double PrescribedDisplacementBoundaryCondition::PrescribedValue()
|
||||
const noexcept {
|
||||
return prescribed_value_;
|
||||
}
|
||||
|
||||
std::size_t PrescribedDisplacementBoundaryCondition::SourceOrder()
|
||||
const noexcept {
|
||||
return source_order_;
|
||||
}
|
||||
|
||||
} // namespace fesa
|
||||
Reference in New Issue
Block a user