feat(cpp-object-oriented-modular-refactoring): step 19 - boundary-condition-policy

This commit is contained in:
KOKO\Mimi
2026-08-16 11:23:20 +09:00
parent f26e0a61a8
commit 64a43948ef
27 changed files with 919 additions and 365 deletions
+2 -1
View File
@@ -9,7 +9,8 @@ add_library(
assembly/parallel_for.cpp
assembly/sparse_assembler.cpp
build_info.cpp
constraints/essential_constraints.cpp
constraints/essential_constraint_policy.cpp
constraints/prescribed_displacement.cpp
core/ascii.cpp
core/diagnostic.cpp
core/status.cpp
+2 -1
View File
@@ -101,7 +101,8 @@ AnalysisModel::AnalysisModel(const Domain& domain) : domain_{&domain} {
}
}
for (std::size_t index = 0U; index < Step().Boundaries().size(); ++index) {
for (std::size_t index = 0U; index < Step().BoundaryConditions().size();
++index) {
active_boundary_conditions_.push_back(static_cast<EntityIndex>(index));
}
for (std::size_t index = 0U; index < Step().Loads().size(); ++index) {
+5 -4
View File
@@ -109,7 +109,8 @@ Status LinearStaticAnalysis::AssembleAndPartitionStiffness() {
full_stiffness_ =
std::make_unique<SparseMatrix>(std::move(stiffness.Value()));
auto partitioned = EssentialConstraints::Partition(*full_stiffness_, *dofs_);
auto partitioned =
EssentialConstraintPolicy{}.Partition(*full_stiffness_, *dofs_);
if (!partitioned.HasValue()) {
return partitioned.GetStatus();
}
@@ -120,7 +121,7 @@ Status LinearStaticAnalysis::AssembleAndPartitionStiffness() {
Status LinearStaticAnalysis::Factorize() {
// This call intentionally precedes all load assembly in Analysis::Run.
return linear_solver_.Factorize(partitioned_stiffness_->kff);
return linear_solver_.Factorize(partitioned_stiffness_->k_ff);
}
Status LinearStaticAnalysis::AssembleLoadsAndEffectiveRhs() {
@@ -131,7 +132,7 @@ Status LinearStaticAnalysis::AssembleLoadsAndEffectiveRhs() {
state_->ExternalForce() = std::move(full_load.Value());
auto rhs = LoadAssembler::EffectiveFreeRhs(state_->ExternalForce(),
partitioned_stiffness_->kfc,
partitioned_stiffness_->k_fc,
dofs_->PrescribedValues(), *dofs_);
if (!rhs.HasValue()) {
return rhs.GetStatus();
@@ -148,7 +149,7 @@ Status LinearStaticAnalysis::SubstituteAndReconstruct() {
return solve_status;
}
state_->Displacement() = EssentialConstraints::ReconstructFull(
state_->Displacement() = EssentialConstraintPolicy{}.ReconstructFull(
free_displacement, dofs_->PrescribedValues(), *dofs_);
return Status::Ok();
}
+2 -2
View File
@@ -8,7 +8,7 @@
#include <utility>
#include <vector>
#include "fesa/constraints/essential_constraints.h"
#include "fesa/constraints/essential_constraint_policy.h"
#include "fesa/loads/load.h"
#include "fesa/model/source_target_resolver.h"
@@ -292,7 +292,7 @@ Result<Vector> LoadAssembler::EffectiveFreeRhs(const Vector& full_load,
correction[row] = sum;
}
Vector rhs = EssentialConstraints::GatherFree(full_load, dofs);
Vector rhs = EssentialConstraintPolicy{}.GatherFree(full_load, dofs);
// The constrained vector is already in DofManager order, so this is the
// approved elimination equation rhs = Ff - Kfc*dc without reordering dc.
for (std::size_t row = 0U; row < rhs.Size(); ++row) {
@@ -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
+81 -40
View File
@@ -2,6 +2,7 @@
#include <algorithm>
#include <array>
#include <cmath>
#include <cstddef>
#include <limits>
#include <stdexcept>
@@ -47,21 +48,6 @@ std::vector<DofComponent> FullNodeComponents() {
DofComponent::kUrx, DofComponent::kUry, DofComponent::kUrz};
}
std::vector<EntityIndex> ExpandBoundaryTarget(
const SourceTargetResolver& resolver, const BoundaryCondition& boundary) {
auto resolved =
resolver.Resolve({SourceEntityKind::kNode, "", boundary.target});
if (!resolved.HasValue()) {
return {};
}
std::vector<EntityIndex> indices;
indices.reserve(resolved.Value().size());
for (const auto& target : resolved.Value()) {
indices.push_back(target.entity_index);
}
return indices;
}
void AppendScatter(std::vector<std::vector<std::size_t>>& columns_by_row,
const std::vector<std::size_t>& scatter) {
for (const std::size_t row : scatter) {
@@ -124,7 +110,8 @@ Result<DofManager> DofManager::Create(const AnalysisModel& model) {
}
DofManager dofs;
const Status status = dofs.BuildLayouts(model, layouts);
const Status status =
dofs.BuildLayouts(model, layouts, model.Step().BoundaryConditions());
if (!status.IsOk()) {
return Result<DofManager>::Failure(status);
}
@@ -133,16 +120,24 @@ Result<DofManager> DofManager::Create(const AnalysisModel& model) {
Status DofManager::Build(const AnalysisModel& analysis_model,
const ElementView& elements) {
return Build(analysis_model, elements,
analysis_model.Step().BoundaryConditions());
}
Status DofManager::Build(const AnalysisModel& analysis_model,
const ElementView& elements,
const BoundaryConditionView& boundaries) {
std::vector<ElementDofLayout> layouts;
layouts.reserve(elements.size());
for (const auto& element : elements) {
layouts.push_back(element.get().DofLayout());
}
return BuildLayouts(analysis_model, layouts);
return BuildLayouts(analysis_model, layouts, boundaries);
}
Status DofManager::BuildLayouts(const AnalysisModel& analysis_model,
const std::vector<ElementDofLayout>& layouts) {
const std::vector<ElementDofLayout>& layouts,
const BoundaryConditionView& boundaries) {
const Domain& domain = analysis_model.GetDomain();
if (domain.Nodes().size() >
(std::numeric_limits<std::size_t>::max)() / kDofsPerNode) {
@@ -177,29 +172,69 @@ Status DofManager::BuildLayouts(const AnalysisModel& analysis_model,
const SourceTargetResolver target_resolver{target_index};
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 :
analysis_model.ActiveBoundaryConditions()) {
const auto& boundary =
analysis_model.Step().Boundaries().at(boundary_index);
const auto target = ExpandBoundaryTarget(target_resolver, 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 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;
DofManager full_dof_map;
full_dof_map.full_dof_count_ = full_count;
const BoundaryConditionContext context{domain, full_dof_map, target_resolver};
std::vector<std::vector<ConstraintDefinition>> definitions_by_boundary;
definitions_by_boundary.reserve(boundaries.size());
for (std::size_t source_order = 0U; source_order < boundaries.size();
++source_order) {
auto definitions =
boundaries[source_order].get().ResolveConstraints(context);
if (!definitions.HasValue()) {
return definitions.GetStatus();
}
std::vector<std::size_t> seen_full_dofs;
seen_full_dofs.reserve(definitions.Value().size());
for (const auto& definition : definitions.Value()) {
if (definition.source_order != source_order) {
return DofFailure(
"invalid-constraint-order", analysis_model.Step().Location(),
std::to_string(definition.source_order),
"Every constraint definition must retain its supplying boundary "
"source order.");
}
if (definition.full_dof_index >= full_count) {
return DofFailure(
"invalid-constraint-index", analysis_model.Step().Location(),
std::to_string(definition.full_dof_index),
"A constraint definition full-DOF index is outside the active "
"model.");
}
if (!std::isfinite(definition.prescribed_value)) {
return DofFailure(
"nonfinite-prescribed-value", analysis_model.Step().Location(),
std::to_string(source_order),
"A constraint definition prescribed value must be finite.");
}
if (std::find(seen_full_dofs.begin(), seen_full_dofs.end(),
definition.full_dof_index) != seen_full_dofs.end()) {
return DofFailure(
"duplicate-constraint-definition", analysis_model.Step().Location(),
std::to_string(definition.full_dof_index),
"One boundary must not emit the same full DOF more than once.");
}
seen_full_dofs.push_back(definition.full_dof_index);
}
definitions_by_boundary.push_back(std::move(definitions.Value()));
}
std::vector<std::optional<double>> prescribed_by_full_dof(full_count);
for (std::size_t source_order = 0U;
source_order < definitions_by_boundary.size(); ++source_order) {
const auto& definitions = definitions_by_boundary[source_order];
for (const auto& definition : definitions) {
auto& prescribed = prescribed_by_full_dof[definition.full_dof_index];
if (prescribed && *prescribed != definition.prescribed_value) {
return Status::Failure(
FailureCategory::kInput,
{{Severity::kError, "conflicting-boundary-condition",
boundaries[source_order].get().Location(), "BOUNDARY",
boundaries[source_order].get().TargetIdentity(),
"Expanded boundary rows prescribe different values to one "
"node/DOF."}});
}
prescribed = definition.prescribed_value;
}
}
@@ -356,6 +391,12 @@ Status DofManager::ValidateInvariants() const {
"Full, free, constrained, prescribed, and equation dimensions must "
"agree.");
}
for (std::size_t index = 0U; index < prescribed_values_.Size(); ++index) {
if (!std::isfinite(prescribed_values_[index])) {
return DofFailure("nonfinite-prescribed-value", {}, std::to_string(index),
"Prescribed displacement values must remain finite.");
}
}
if (HasAdjacentDuplicate(free_dofs_)) {
return DofFailure("duplicate-dof-mapping", {},
std::to_string(full_dof_count_),
+7 -5
View File
@@ -108,7 +108,7 @@ struct RawStep {
SourceLocation location;
bool has_static{false};
std::array<double, 4> static_values{};
std::vector<BoundaryCondition> boundaries;
std::vector<PrescribedDisplacementDefinition> boundaries;
std::vector<NodalLoad> loads;
};
@@ -1202,8 +1202,9 @@ class MappingContext {
material.elastic_location = block.data[0].location;
}
void ParseBoundary(const KeywordBlock& block,
std::vector<BoundaryCondition>& destination) {
void ParseBoundary(
const KeywordBlock& block,
std::vector<PrescribedDisplacementDefinition>& destination) {
if (!ValidateParameters(block, {}) || block.data.empty()) {
if (!failure_) {
InputFailure("invalid-data-arity", block.location, block.canonical_name,
@@ -2086,7 +2087,8 @@ class MappingContext {
}
void FinalizeStep() {
std::vector<BoundaryCondition> boundaries = model_boundaries_;
std::vector<PrescribedDisplacementDefinition> boundaries =
model_boundaries_;
boundaries.insert(boundaries.end(), step_.boundaries.begin(),
step_.boundaries.end());
const SourceTargetIndex target_index = BuildSourceTargetIndex();
@@ -2138,7 +2140,7 @@ class MappingContext {
std::vector<RawInstance> instances_;
std::vector<RawAssemblySet> assembly_sets_;
std::vector<RawMaterial> materials_;
std::vector<BoundaryCondition> model_boundaries_;
std::vector<PrescribedDisplacementDefinition> model_boundaries_;
RawStep step_{};
std::vector<std::map<std::int64_t, std::pair<EntityIndex, EntityIndex>>>
part_section_assignments_;
+20 -3
View File
@@ -9,12 +9,24 @@ namespace fesa {
StepDefinition::StepDefinition(StaticStepDefinition definition)
: name_{std::move(definition.name)},
boundaries_{std::move(definition.boundaries)},
initial_increment_{definition.initial_increment},
time_period_{definition.time_period},
minimum_increment_{definition.minimum_increment},
maximum_increment_{definition.maximum_increment},
location_{std::move(definition.location)} {
for (auto& boundary : definition.boundaries) {
for (int source_dof = boundary.first_dof; source_dof <= boundary.last_dof;
++source_dof) {
const std::size_t source_order = boundary_conditions_.size();
auto owned = std::make_unique<PrescribedDisplacementBoundaryCondition>(
SourceTargetQuery{SourceEntityKind::kNode, "", boundary.target},
static_cast<DofComponent>(source_dof - 1), boundary.value,
source_order, boundary.location);
boundary_conditions_view_.push_back(std::cref(*owned));
prescribed_displacements_view_.Add(*owned);
boundary_conditions_.push_back(std::move(owned));
}
}
loads_.reserve(definition.loads.size());
loads_view_.reserve(definition.loads.size());
for (std::size_t source_order = 0U; source_order < definition.loads.size();
@@ -31,9 +43,14 @@ StepDefinition::StepDefinition(StaticStepDefinition definition)
const std::string& StepDefinition::Name() const noexcept { return name_; }
const std::vector<BoundaryCondition>& StepDefinition::Boundaries()
const BoundaryConditionView& StepDefinition::BoundaryConditions()
const noexcept {
return boundaries_;
return boundary_conditions_view_;
}
const DomainCollectionView<PrescribedDisplacementBoundaryCondition>&
StepDefinition::PrescribedDisplacements() const noexcept {
return prescribed_displacements_view_;
}
const LoadView& StepDefinition::Loads() const noexcept { return loads_view_; }