feat(cpp-object-oriented-modular-refactoring): step 19 - boundary-condition-policy
This commit is contained in:
@@ -7,7 +7,7 @@
|
||||
|
||||
#include "fesa/analysis/analysis_model.h"
|
||||
#include "fesa/analysis/analysis_state.h"
|
||||
#include "fesa/constraints/essential_constraints.h"
|
||||
#include "fesa/constraints/essential_constraint_policy.h"
|
||||
#include "fesa/core/status.h"
|
||||
#include "fesa/fem/dof_manager.h"
|
||||
#include "fesa/math/sparse_matrix.h"
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
#ifndef FESA_CONSTRAINTS_BOUNDARY_CONDITION_H_
|
||||
#define FESA_CONSTRAINTS_BOUNDARY_CONDITION_H_
|
||||
|
||||
#include <cstddef>
|
||||
#include <functional>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "fesa/core/diagnostic.h"
|
||||
#include "fesa/core/status.h"
|
||||
|
||||
namespace fesa {
|
||||
|
||||
class DofManager;
|
||||
class Domain;
|
||||
class SourceTargetResolver;
|
||||
|
||||
/// @brief Describes one prescribed value in stable full-DOF order.
|
||||
struct ConstraintDefinition {
|
||||
std::size_t source_order;
|
||||
std::size_t full_dof_index;
|
||||
double prescribed_value;
|
||||
};
|
||||
|
||||
/// @brief Provides immutable semantic and equation context to a boundary.
|
||||
/// @note Every referenced object must outlive a constraint request.
|
||||
struct BoundaryConditionContext {
|
||||
const Domain& domain;
|
||||
const DofManager& dof_manager;
|
||||
const SourceTargetResolver& target_resolver;
|
||||
};
|
||||
|
||||
/// @brief Produces ordered constraint definitions without equation mutation.
|
||||
class BoundaryCondition {
|
||||
public:
|
||||
virtual ~BoundaryCondition() = default;
|
||||
|
||||
/// @brief Resolves finite full-DOF definitions in stable target order.
|
||||
/// @param context Non-owning semantic and equation context for this call.
|
||||
/// @return Ordered definitions or a structured model failure.
|
||||
virtual Result<std::vector<ConstraintDefinition>> ResolveConstraints(
|
||||
const BoundaryConditionContext& context) const = 0;
|
||||
|
||||
/// @brief Returns the source location used by policy diagnostics.
|
||||
const SourceLocation& Location() const noexcept { return location_; }
|
||||
|
||||
/// @brief Returns the source target identity used by policy diagnostics.
|
||||
const std::string& TargetIdentity() const noexcept {
|
||||
return target_identity_;
|
||||
}
|
||||
|
||||
protected:
|
||||
/// @brief Creates a boundary with optional shared diagnostic provenance.
|
||||
BoundaryCondition(std::string target_identity = {},
|
||||
SourceLocation location = {})
|
||||
: target_identity_{std::move(target_identity)},
|
||||
location_{std::move(location)} {}
|
||||
|
||||
private:
|
||||
std::string target_identity_;
|
||||
SourceLocation location_;
|
||||
};
|
||||
|
||||
/// @brief Holds non-owning boundaries in an explicitly supplied source order.
|
||||
using BoundaryConditionView =
|
||||
std::vector<std::reference_wrapper<const BoundaryCondition>>;
|
||||
|
||||
} // namespace fesa
|
||||
|
||||
#endif // FESA_CONSTRAINTS_BOUNDARY_CONDITION_H_
|
||||
@@ -0,0 +1,43 @@
|
||||
#ifndef FESA_CONSTRAINTS_ESSENTIAL_CONSTRAINT_POLICY_H_
|
||||
#define FESA_CONSTRAINTS_ESSENTIAL_CONSTRAINT_POLICY_H_
|
||||
|
||||
#include "fesa/core/status.h"
|
||||
#include "fesa/math/sparse_matrix.h"
|
||||
#include "fesa/math/vector.h"
|
||||
|
||||
namespace fesa {
|
||||
|
||||
class DofManager;
|
||||
|
||||
/// @brief Stores full-stiffness blocks in stable free/constrained order.
|
||||
struct PartitionedStiffness {
|
||||
SparseMatrix k_ff;
|
||||
SparseMatrix k_fc;
|
||||
SparseMatrix k_cf;
|
||||
SparseMatrix k_cc;
|
||||
};
|
||||
|
||||
/// @brief Applies stable prescribed-displacement elimination.
|
||||
class EssentialConstraintPolicy {
|
||||
public:
|
||||
/// @brief Partitions full stiffness into Kff, Kfc, Kcf, and Kcc.
|
||||
Result<PartitionedStiffness> Partition(const SparseMatrix& full_stiffness,
|
||||
const DofManager& dof_manager) const;
|
||||
|
||||
/// @brief Gathers a full vector in stable free-equation order.
|
||||
Vector GatherFree(const Vector& full_values,
|
||||
const DofManager& dof_manager) const;
|
||||
|
||||
/// @brief Gathers a full vector in stable constrained-DOF order.
|
||||
Vector GatherConstrained(const Vector& full_values,
|
||||
const DofManager& dof_manager) const;
|
||||
|
||||
/// @brief Reconstructs full d from stable df and exact prescribed dc.
|
||||
Vector ReconstructFull(const Vector& free_values,
|
||||
const Vector& constrained_values,
|
||||
const DofManager& dof_manager) const;
|
||||
};
|
||||
|
||||
} // namespace fesa
|
||||
|
||||
#endif // FESA_CONSTRAINTS_ESSENTIAL_CONSTRAINT_POLICY_H_
|
||||
@@ -1,41 +0,0 @@
|
||||
#ifndef FESA_CONSTRAINTS_ESSENTIAL_CONSTRAINTS_H_
|
||||
#define FESA_CONSTRAINTS_ESSENTIAL_CONSTRAINTS_H_
|
||||
|
||||
#include "fesa/core/status.h"
|
||||
#include "fesa/math/sparse_matrix.h"
|
||||
#include "fesa/math/vector.h"
|
||||
|
||||
namespace fesa {
|
||||
|
||||
class DofManager;
|
||||
|
||||
/// @brief Stores full-stiffness blocks in stable free/constrained order.
|
||||
struct PartitionedStiffness {
|
||||
SparseMatrix kff;
|
||||
SparseMatrix kfc;
|
||||
SparseMatrix kcf;
|
||||
SparseMatrix kcc;
|
||||
};
|
||||
|
||||
/// @brief Applies stable prescribed-displacement elimination.
|
||||
class EssentialConstraints {
|
||||
public:
|
||||
/// @brief Partitions full stiffness into Kff, Kfc, Kcf, and Kcc.
|
||||
static Result<PartitionedStiffness> Partition(const SparseMatrix& full,
|
||||
const DofManager& dofs);
|
||||
|
||||
/// @brief Gathers a full vector in stable free-equation order.
|
||||
static Vector GatherFree(const Vector& full, const DofManager& dofs);
|
||||
|
||||
/// @brief Gathers a full vector in stable constrained-DOF order.
|
||||
static Vector GatherConstrained(const Vector& full, const DofManager& dofs);
|
||||
|
||||
/// @brief Reconstructs full d from stable df and exact prescribed dc.
|
||||
static Vector ReconstructFull(const Vector& free_values,
|
||||
const Vector& constrained_values,
|
||||
const DofManager& dofs);
|
||||
};
|
||||
|
||||
} // namespace fesa
|
||||
|
||||
#endif // FESA_CONSTRAINTS_ESSENTIAL_CONSTRAINTS_H_
|
||||
@@ -0,0 +1,48 @@
|
||||
#ifndef FESA_CONSTRAINTS_PRESCRIBED_DISPLACEMENT_H_
|
||||
#define FESA_CONSTRAINTS_PRESCRIBED_DISPLACEMENT_H_
|
||||
|
||||
#include <cstddef>
|
||||
|
||||
#include "fesa/constraints/boundary_condition.h"
|
||||
#include "fesa/core/diagnostic.h"
|
||||
#include "fesa/elements/element.h"
|
||||
#include "fesa/model/source_target_resolver.h"
|
||||
|
||||
namespace fesa {
|
||||
|
||||
/// @brief Emits one prescribed nodal displacement component for a target.
|
||||
class PrescribedDisplacementBoundaryCondition final : public BoundaryCondition {
|
||||
public:
|
||||
/// @brief Creates one prescribed component in stable source order.
|
||||
PrescribedDisplacementBoundaryCondition(SourceTargetQuery target,
|
||||
DofComponent component,
|
||||
double prescribed_value,
|
||||
std::size_t source_order,
|
||||
SourceLocation location = {});
|
||||
|
||||
/// @brief Resolves target-major full-DOF constraint definitions.
|
||||
Result<std::vector<ConstraintDefinition>> ResolveConstraints(
|
||||
const BoundaryConditionContext& context) const override;
|
||||
|
||||
/// @brief Returns the immutable source target query.
|
||||
const SourceTargetQuery& Target() const noexcept;
|
||||
|
||||
/// @brief Returns the prescribed global DOF component.
|
||||
DofComponent Component() const noexcept;
|
||||
|
||||
/// @brief Returns the exact prescribed displacement value.
|
||||
double PrescribedValue() const noexcept;
|
||||
|
||||
/// @brief Returns the stable boundary-definition order.
|
||||
std::size_t SourceOrder() const noexcept;
|
||||
|
||||
private:
|
||||
SourceTargetQuery target_;
|
||||
DofComponent component_;
|
||||
double prescribed_value_;
|
||||
std::size_t source_order_;
|
||||
};
|
||||
|
||||
} // namespace fesa
|
||||
|
||||
#endif // FESA_CONSTRAINTS_PRESCRIBED_DISPLACEMENT_H_
|
||||
@@ -7,6 +7,7 @@
|
||||
#include <vector>
|
||||
|
||||
#include "fesa/analysis/analysis_model.h"
|
||||
#include "fesa/constraints/boundary_condition.h"
|
||||
#include "fesa/elements/element.h"
|
||||
#include "fesa/math/vector.h"
|
||||
|
||||
@@ -39,6 +40,12 @@ class DofManager {
|
||||
Status Build(const AnalysisModel& analysis_model,
|
||||
const ElementView& elements);
|
||||
|
||||
/// @brief Builds mappings from explicit runtime elements and boundaries.
|
||||
/// @param boundaries Non-owning definitions in stable source order.
|
||||
/// @return Success after atomic replacement or a structured failure.
|
||||
Status Build(const AnalysisModel& analysis_model, const ElementView& elements,
|
||||
const BoundaryConditionView& boundaries);
|
||||
|
||||
/// @brief Returns the full node-by-component DOF count.
|
||||
std::size_t FullDofCount() const noexcept;
|
||||
/// @brief Returns the free-equation count.
|
||||
@@ -75,7 +82,8 @@ class DofManager {
|
||||
|
||||
/// @brief Builds from copied layouts after the caller fixes their order.
|
||||
Status BuildLayouts(const AnalysisModel& analysis_model,
|
||||
const std::vector<ElementDofLayout>& layouts);
|
||||
const std::vector<ElementDofLayout>& layouts,
|
||||
const BoundaryConditionView& boundaries);
|
||||
|
||||
/// @brief Takes ownership of fully validated stable equation mappings.
|
||||
DofManager(std::size_t full_dof_count,
|
||||
|
||||
@@ -7,6 +7,8 @@
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "fesa/constraints/boundary_condition.h"
|
||||
#include "fesa/constraints/prescribed_displacement.h"
|
||||
#include "fesa/core/status.h"
|
||||
#include "fesa/loads/concentrated_nodal_load.h"
|
||||
#include "fesa/loads/load.h"
|
||||
@@ -58,8 +60,12 @@ class StepDefinition {
|
||||
/// @brief Returns the source step name.
|
||||
const std::string& Name() const noexcept;
|
||||
|
||||
/// @brief Returns current boundary records in declaration order.
|
||||
const std::vector<BoundaryCondition>& Boundaries() const noexcept;
|
||||
/// @brief Returns polymorphic boundaries in stable source/component order.
|
||||
const BoundaryConditionView& BoundaryConditions() const noexcept;
|
||||
|
||||
/// @brief Returns prescribed displacements in stable source/component order.
|
||||
const DomainCollectionView<PrescribedDisplacementBoundaryCondition>&
|
||||
PrescribedDisplacements() const noexcept;
|
||||
|
||||
/// @brief Returns polymorphic loads in stable source order.
|
||||
const LoadView& Loads() const noexcept;
|
||||
@@ -90,7 +96,10 @@ class StepDefinition {
|
||||
explicit StepDefinition(StaticStepDefinition definition);
|
||||
|
||||
std::string name_;
|
||||
std::vector<BoundaryCondition> boundaries_;
|
||||
std::vector<std::unique_ptr<BoundaryCondition>> boundary_conditions_;
|
||||
BoundaryConditionView boundary_conditions_view_;
|
||||
DomainCollectionView<PrescribedDisplacementBoundaryCondition>
|
||||
prescribed_displacements_view_;
|
||||
std::vector<std::unique_ptr<Load>> loads_;
|
||||
LoadView loads_view_;
|
||||
DomainCollectionView<ConcentratedNodalLoad> concentrated_loads_view_;
|
||||
|
||||
@@ -36,7 +36,7 @@ struct ShellNodeInitialFrame {
|
||||
};
|
||||
|
||||
/// @brief Stores one prescribed nodal degree-of-freedom range.
|
||||
struct BoundaryCondition {
|
||||
struct PrescribedDisplacementDefinition {
|
||||
std::string target;
|
||||
int first_dof;
|
||||
int last_dof;
|
||||
@@ -55,7 +55,7 @@ struct NodalLoad {
|
||||
/// @brief Stores the approved single linear-static step definition.
|
||||
struct StaticStepDefinition {
|
||||
std::string name;
|
||||
std::vector<BoundaryCondition> boundaries;
|
||||
std::vector<PrescribedDisplacementDefinition> boundaries;
|
||||
std::vector<NodalLoad> loads;
|
||||
double initial_increment;
|
||||
double time_period;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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
|
||||
@@ -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;
|
||||
|
||||
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 (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) {
|
||||
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",
|
||||
boundary.location, "BOUNDARY", boundary.target,
|
||||
boundaries[source_order].get().Location(), "BOUNDARY",
|
||||
boundaries[source_order].get().TargetIdentity(),
|
||||
"Expanded boundary rows prescribe different values to one "
|
||||
"node/DOF."}});
|
||||
}
|
||||
prescribed = boundary.value;
|
||||
}
|
||||
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_),
|
||||
|
||||
@@ -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_;
|
||||
|
||||
@@ -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_; }
|
||||
|
||||
@@ -8,6 +8,7 @@ add_executable(
|
||||
unit/assembly/parallel_for_test.cpp
|
||||
unit/assembly/load_assembler_test.cpp
|
||||
unit/assembly/sparse_assembler_test.cpp
|
||||
unit/constraints/boundary_condition_test.cpp
|
||||
unit/constraints/essential_constraints_test.cpp
|
||||
unit/core/ascii_test.cpp
|
||||
unit/core/diagnostic_test.cpp
|
||||
|
||||
@@ -107,7 +107,7 @@ TEST(AnalysisModel, ClassifiesActiveEntitiesInStableOrder) {
|
||||
EXPECT_EQ(model.ActiveSections(),
|
||||
(std::vector<fesa::EntityIndex>{0U, 1U, 2U}));
|
||||
EXPECT_EQ(model.ActiveBoundaryConditions(),
|
||||
(std::vector<fesa::EntityIndex>{0U, 1U}));
|
||||
(std::vector<fesa::EntityIndex>{0U, 1U, 2U, 3U, 4U, 5U}));
|
||||
EXPECT_EQ(model.ActiveLoads(), (std::vector<fesa::EntityIndex>{0U, 1U, 2U}));
|
||||
ASSERT_EQ(model.Step().Loads().size(), 3U);
|
||||
EXPECT_EQ(&model.Step().Loads()[0U].get(),
|
||||
|
||||
@@ -27,9 +27,9 @@ struct LoadFixture {
|
||||
std::unique_ptr<fesa::DofManager> dofs;
|
||||
};
|
||||
|
||||
LoadFixture MakeFixture(const std::size_t node_count,
|
||||
std::vector<fesa::NodeSet> node_sets,
|
||||
std::vector<fesa::BoundaryCondition> boundaries,
|
||||
LoadFixture MakeFixture(
|
||||
const std::size_t node_count, std::vector<fesa::NodeSet> node_sets,
|
||||
std::vector<fesa::PrescribedDisplacementDefinition> boundaries,
|
||||
std::vector<fesa::NodalLoad> loads) {
|
||||
const std::filesystem::path source{"models/load-assembly.inp"};
|
||||
fesa::ModelDefinition definition{};
|
||||
@@ -73,7 +73,8 @@ LoadFixture MakeFixture(const std::size_t node_count,
|
||||
return {std::move(domain), std::move(model), std::move(dofs)};
|
||||
}
|
||||
|
||||
LoadFixture MakeShellFixture(std::vector<fesa::BoundaryCondition> boundaries,
|
||||
LoadFixture MakeShellFixture(
|
||||
std::vector<fesa::PrescribedDisplacementDefinition> boundaries,
|
||||
std::vector<fesa::NodalLoad> loads) {
|
||||
const std::filesystem::path source{"models/shell-load-assembly.inp"};
|
||||
fesa::ModelDefinition definition{};
|
||||
|
||||
@@ -0,0 +1,217 @@
|
||||
#include "fesa/constraints/boundary_condition.h"
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <cmath>
|
||||
#include <cstddef>
|
||||
#include <filesystem>
|
||||
#include <functional>
|
||||
#include <limits>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "fesa/analysis/analysis_model.h"
|
||||
#include "fesa/constraints/prescribed_displacement.h"
|
||||
#include "fesa/fem/dof_manager.h"
|
||||
#include "fesa/model/domain.h"
|
||||
#include "fesa/model/source_target_resolver.h"
|
||||
|
||||
namespace {
|
||||
|
||||
struct BoundaryFixture {
|
||||
std::unique_ptr<fesa::Domain> domain;
|
||||
std::unique_ptr<fesa::AnalysisModel> model;
|
||||
std::unique_ptr<fesa::DofManager> dofs;
|
||||
};
|
||||
|
||||
fesa::ModelDefinition MakeDefinition(
|
||||
std::vector<fesa::PrescribedDisplacementDefinition> boundaries = {}) {
|
||||
const std::filesystem::path source{"models/boundary-condition.inp"};
|
||||
fesa::ModelDefinition definition{};
|
||||
definition.source_path = source;
|
||||
definition.source_content_identity = "fnv1a64:boundary";
|
||||
definition.nodes = {{{"Beam-1", 10, "10"}, {0.0, 0.0, 0.0}, {source, 2U}},
|
||||
{{"Beam-1", 20, "20"}, {1.0, 0.0, 0.0}, {source, 3U}}};
|
||||
definition.node_sets = {
|
||||
{"Pair", std::string{"Beam-1"}, {1U, 0U}, {source, 4U}}};
|
||||
definition.steps = {{"Step-1",
|
||||
std::move(boundaries),
|
||||
{},
|
||||
0.1,
|
||||
1.0,
|
||||
0.01,
|
||||
1.0,
|
||||
{source, 10U}}};
|
||||
return definition;
|
||||
}
|
||||
|
||||
BoundaryFixture MakeFixture(
|
||||
std::vector<fesa::PrescribedDisplacementDefinition> boundaries = {}) {
|
||||
auto domain_result =
|
||||
fesa::Domain::Create(MakeDefinition(std::move(boundaries)));
|
||||
EXPECT_TRUE(domain_result.HasValue());
|
||||
auto domain =
|
||||
std::make_unique<fesa::Domain>(std::move(domain_result.Value()));
|
||||
|
||||
auto model_result = fesa::AnalysisModel::Create(*domain);
|
||||
EXPECT_TRUE(model_result.HasValue());
|
||||
auto model =
|
||||
std::make_unique<fesa::AnalysisModel>(std::move(model_result.Value()));
|
||||
|
||||
auto dof_result = fesa::DofManager::Create(*model);
|
||||
EXPECT_TRUE(dof_result.HasValue());
|
||||
auto dofs = std::make_unique<fesa::DofManager>(std::move(dof_result.Value()));
|
||||
return {std::move(domain), std::move(model), std::move(dofs)};
|
||||
}
|
||||
|
||||
class FakeBoundaryCondition final : public fesa::BoundaryCondition {
|
||||
public:
|
||||
explicit FakeBoundaryCondition(
|
||||
std::vector<fesa::ConstraintDefinition> definitions)
|
||||
: definitions_{std::move(definitions)} {}
|
||||
|
||||
fesa::Result<std::vector<fesa::ConstraintDefinition>> ResolveConstraints(
|
||||
const fesa::BoundaryConditionContext&) const override {
|
||||
return fesa::Result<std::vector<fesa::ConstraintDefinition>>::Success(
|
||||
definitions_);
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<fesa::ConstraintDefinition> definitions_;
|
||||
};
|
||||
|
||||
void ExpectFailureCode(const fesa::Status& status, const std::string& code) {
|
||||
ASSERT_FALSE(status.IsOk());
|
||||
ASSERT_EQ(status.Diagnostics().size(), 1U);
|
||||
EXPECT_EQ(status.Diagnostics()[0U].code, code);
|
||||
}
|
||||
|
||||
void ExpectCandidate(const fesa::DofManager& dofs,
|
||||
const std::vector<std::size_t>& constrained_dofs,
|
||||
const std::vector<double>& prescribed_values) {
|
||||
EXPECT_EQ(dofs.ConstrainedDofs(), constrained_dofs);
|
||||
ASSERT_EQ(dofs.PrescribedValues().Size(), prescribed_values.size());
|
||||
for (std::size_t index = 0U; index < prescribed_values.size(); ++index) {
|
||||
EXPECT_DOUBLE_EQ(dofs.PrescribedValues()[index], prescribed_values[index]);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
// C-BC-001
|
||||
TEST(BoundaryCondition, ResolvesStableNonzeroDefinitionsThroughBase) {
|
||||
static_assert(std::has_virtual_destructor_v<fesa::BoundaryCondition>);
|
||||
auto fixture = MakeFixture();
|
||||
const fesa::SourceTargetIndex target_index =
|
||||
fesa::SourceTargetIndex::FromDomain(*fixture.domain);
|
||||
const fesa::SourceTargetResolver resolver{target_index};
|
||||
const fesa::BoundaryConditionContext context{*fixture.domain, *fixture.dofs,
|
||||
resolver};
|
||||
const fesa::PrescribedDisplacementBoundaryCondition prescribed(
|
||||
{fesa::SourceEntityKind::kNode, "Beam-1", "pair"},
|
||||
fesa::DofComponent::kUz, 2.5, 4U);
|
||||
const fesa::BoundaryCondition& boundary = prescribed;
|
||||
|
||||
const auto definitions = boundary.ResolveConstraints(context);
|
||||
|
||||
ASSERT_TRUE(definitions.HasValue());
|
||||
ASSERT_EQ(definitions.Value().size(), 2U);
|
||||
EXPECT_EQ(definitions.Value()[0U].source_order, 4U);
|
||||
EXPECT_EQ(definitions.Value()[0U].full_dof_index, 8U);
|
||||
EXPECT_DOUBLE_EQ(definitions.Value()[0U].prescribed_value, 2.5);
|
||||
EXPECT_EQ(definitions.Value()[1U].source_order, 4U);
|
||||
EXPECT_EQ(definitions.Value()[1U].full_dof_index, 2U);
|
||||
EXPECT_DOUBLE_EQ(definitions.Value()[1U].prescribed_value, 2.5);
|
||||
}
|
||||
|
||||
TEST(BoundaryCondition, DomainOwnsExpandedPolymorphicDefinitionsStably) {
|
||||
const std::filesystem::path source{"models/boundary-condition.inp"};
|
||||
auto fixture = MakeFixture({{"Pair", 1, 2, 1.25, {source, 12U}}});
|
||||
|
||||
const auto& boundaries = fixture.model->Step().BoundaryConditions();
|
||||
ASSERT_EQ(boundaries.size(), 2U);
|
||||
EXPECT_EQ(fixture.model->ActiveBoundaryConditions(),
|
||||
(std::vector<fesa::EntityIndex>{0U, 1U}));
|
||||
|
||||
const fesa::SourceTargetIndex target_index =
|
||||
fesa::SourceTargetIndex::FromDomain(*fixture.domain);
|
||||
const fesa::SourceTargetResolver resolver{target_index};
|
||||
const fesa::BoundaryConditionContext context{*fixture.domain, *fixture.dofs,
|
||||
resolver};
|
||||
const auto first = boundaries[0U].get().ResolveConstraints(context);
|
||||
const auto second = boundaries[1U].get().ResolveConstraints(context);
|
||||
ASSERT_TRUE(first.HasValue());
|
||||
ASSERT_TRUE(second.HasValue());
|
||||
EXPECT_EQ(first.Value()[0U].source_order, 0U);
|
||||
EXPECT_EQ(first.Value()[0U].full_dof_index, 6U);
|
||||
EXPECT_EQ(second.Value()[0U].source_order, 1U);
|
||||
EXPECT_EQ(second.Value()[0U].full_dof_index, 7U);
|
||||
EXPECT_DOUBLE_EQ(first.Value()[0U].prescribed_value, 1.25);
|
||||
EXPECT_DOUBLE_EQ(second.Value()[0U].prescribed_value, 1.25);
|
||||
}
|
||||
|
||||
TEST(BoundaryCondition,
|
||||
RejectsDuplicateConflictAndNonfiniteBeforeCandidateCommit) {
|
||||
auto fixture = MakeFixture();
|
||||
const fesa::ElementView elements;
|
||||
FakeBoundaryCondition initial({{0U, 1U, 3.0}});
|
||||
ASSERT_TRUE(
|
||||
fixture.dofs->Build(*fixture.model, elements, {std::cref(initial)})
|
||||
.IsOk());
|
||||
ExpectCandidate(*fixture.dofs, {1U}, {3.0});
|
||||
|
||||
FakeBoundaryCondition duplicate({{0U, 2U, 4.0}, {0U, 2U, 4.0}});
|
||||
ExpectFailureCode(
|
||||
fixture.dofs->Build(*fixture.model, elements, {std::cref(duplicate)}),
|
||||
"duplicate-constraint-definition");
|
||||
ExpectCandidate(*fixture.dofs, {1U}, {3.0});
|
||||
|
||||
FakeBoundaryCondition first({{0U, 2U, 4.0}});
|
||||
FakeBoundaryCondition conflicting({{1U, 2U, -4.0}});
|
||||
ExpectFailureCode(
|
||||
fixture.dofs->Build(*fixture.model, elements,
|
||||
{std::cref(first), std::cref(conflicting)}),
|
||||
"conflicting-boundary-condition");
|
||||
ExpectCandidate(*fixture.dofs, {1U}, {3.0});
|
||||
|
||||
FakeBoundaryCondition nonfinite(
|
||||
{{0U, 2U, std::numeric_limits<double>::quiet_NaN()}});
|
||||
ExpectFailureCode(
|
||||
fixture.dofs->Build(*fixture.model, elements, {std::cref(nonfinite)}),
|
||||
"nonfinite-prescribed-value");
|
||||
ExpectCandidate(*fixture.dofs, {1U}, {3.0});
|
||||
}
|
||||
|
||||
TEST(BoundaryCondition, AcceptsIdenticalOverlapAcrossSourceDefinitions) {
|
||||
auto fixture = MakeFixture();
|
||||
FakeBoundaryCondition first({{0U, 4U, -2.0}});
|
||||
FakeBoundaryCondition second({{1U, 4U, -2.0}});
|
||||
|
||||
const fesa::Status status = fixture.dofs->Build(
|
||||
*fixture.model, {}, {std::cref(first), std::cref(second)});
|
||||
|
||||
ASSERT_TRUE(status.IsOk());
|
||||
ExpectCandidate(*fixture.dofs, {4U}, {-2.0});
|
||||
}
|
||||
|
||||
TEST(BoundaryCondition, ConcreteRejectsNonfinitePrescribedValue) {
|
||||
auto fixture = MakeFixture();
|
||||
const fesa::SourceTargetIndex target_index =
|
||||
fesa::SourceTargetIndex::FromDomain(*fixture.domain);
|
||||
const fesa::SourceTargetResolver resolver{target_index};
|
||||
const fesa::BoundaryConditionContext context{*fixture.domain, *fixture.dofs,
|
||||
resolver};
|
||||
const fesa::PrescribedDisplacementBoundaryCondition prescribed(
|
||||
{fesa::SourceEntityKind::kNode, "Beam-1", "10"}, fesa::DofComponent::kUx,
|
||||
std::numeric_limits<double>::infinity(), 0U);
|
||||
|
||||
const auto result = prescribed.ResolveConstraints(context);
|
||||
|
||||
ASSERT_FALSE(result.HasValue());
|
||||
ASSERT_EQ(result.GetStatus().Diagnostics().size(), 1U);
|
||||
EXPECT_EQ(result.GetStatus().Diagnostics()[0U].code,
|
||||
"nonfinite-prescribed-value");
|
||||
}
|
||||
@@ -1,5 +1,3 @@
|
||||
#include "fesa/constraints/essential_constraints.h"
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <cstddef>
|
||||
@@ -10,12 +8,14 @@
|
||||
#include <vector>
|
||||
|
||||
#include "fesa/analysis/analysis_model.h"
|
||||
#include "fesa/constraints/essential_constraint_policy.h"
|
||||
#include "fesa/fem/dof_manager.h"
|
||||
#include "fesa/model/domain.h"
|
||||
|
||||
namespace {
|
||||
|
||||
fesa::DofManager MakeDofs(std::vector<fesa::BoundaryCondition> boundaries) {
|
||||
fesa::DofManager MakeDofs(
|
||||
std::vector<fesa::PrescribedDisplacementDefinition> boundaries) {
|
||||
const std::filesystem::path source{"models/essential-constraints.inp"};
|
||||
fesa::ModelDefinition definition{};
|
||||
definition.source_path = source;
|
||||
@@ -40,7 +40,7 @@ fesa::DofManager MakeDofs(std::vector<fesa::BoundaryCondition> boundaries) {
|
||||
}
|
||||
|
||||
fesa::DofManager MakeShellSizedDofs(
|
||||
std::vector<fesa::BoundaryCondition> boundaries) {
|
||||
std::vector<fesa::PrescribedDisplacementDefinition> boundaries) {
|
||||
const std::filesystem::path source{"models/shell-essential-constraints.inp"};
|
||||
fesa::ModelDefinition definition{};
|
||||
definition.source_path = source;
|
||||
@@ -124,93 +124,98 @@ TEST(EssentialConstraints, ExtractsHandComputedBlocksInStableOrder) {
|
||||
full_values[2U * 6U + 4U] = 0.0;
|
||||
const auto full = MakeMatrix(6U, 6U, full_values);
|
||||
|
||||
auto result = fesa::EssentialConstraints::Partition(full, dofs);
|
||||
const fesa::EssentialConstraintPolicy policy;
|
||||
auto result = policy.Partition(full, dofs);
|
||||
ASSERT_TRUE(result.HasValue());
|
||||
const auto& blocks = result.Value();
|
||||
|
||||
EXPECT_EQ(blocks.kff.RowOffsets(),
|
||||
EXPECT_EQ(blocks.k_ff.RowOffsets(),
|
||||
(std::vector<std::size_t>{0U, 4U, 8U, 12U, 16U}));
|
||||
EXPECT_EQ(blocks.kff.ColumnIndices(),
|
||||
EXPECT_EQ(blocks.k_ff.ColumnIndices(),
|
||||
(std::vector<std::size_t>{0U, 1U, 2U, 3U, 0U, 1U, 2U, 3U, 0U, 1U,
|
||||
2U, 3U, 0U, 1U, 2U, 3U}));
|
||||
EXPECT_EQ(
|
||||
blocks.kff.Values(),
|
||||
blocks.k_ff.Values(),
|
||||
(std::vector<double>{1.0, 3.0, 4.0, 6.0, 21.0, 23.0, 24.0, 26.0, 31.0,
|
||||
33.0, 34.0, 36.0, 51.0, 53.0, 54.0, 56.0}));
|
||||
EXPECT_EQ(blocks.kfc.RowOffsets(),
|
||||
EXPECT_EQ(blocks.k_fc.RowOffsets(),
|
||||
(std::vector<std::size_t>{0U, 2U, 4U, 6U, 8U}));
|
||||
EXPECT_EQ(blocks.kfc.ColumnIndices(),
|
||||
EXPECT_EQ(blocks.k_fc.ColumnIndices(),
|
||||
(std::vector<std::size_t>{0U, 1U, 0U, 1U, 0U, 1U, 0U, 1U}));
|
||||
EXPECT_EQ(blocks.kfc.Values(),
|
||||
EXPECT_EQ(blocks.k_fc.Values(),
|
||||
(std::vector<double>{2.0, 5.0, 22.0, 0.0, 32.0, 35.0, 52.0, 55.0}));
|
||||
EXPECT_EQ(blocks.kcf.RowOffsets(), (std::vector<std::size_t>{0U, 4U, 8U}));
|
||||
EXPECT_EQ(blocks.kcf.ColumnIndices(),
|
||||
EXPECT_EQ(blocks.k_cf.RowOffsets(), (std::vector<std::size_t>{0U, 4U, 8U}));
|
||||
EXPECT_EQ(blocks.k_cf.ColumnIndices(),
|
||||
(std::vector<std::size_t>{0U, 1U, 2U, 3U, 0U, 1U, 2U, 3U}));
|
||||
EXPECT_EQ(blocks.kcf.Values(), (std::vector<double>{11.0, 13.0, 14.0, 16.0,
|
||||
41.0, 43.0, 44.0, 46.0}));
|
||||
EXPECT_EQ(blocks.kcc.RowOffsets(), (std::vector<std::size_t>{0U, 2U, 4U}));
|
||||
EXPECT_EQ(blocks.kcc.ColumnIndices(),
|
||||
EXPECT_EQ(
|
||||
blocks.k_cf.Values(),
|
||||
(std::vector<double>{11.0, 13.0, 14.0, 16.0, 41.0, 43.0, 44.0, 46.0}));
|
||||
EXPECT_EQ(blocks.k_cc.RowOffsets(), (std::vector<std::size_t>{0U, 2U, 4U}));
|
||||
EXPECT_EQ(blocks.k_cc.ColumnIndices(),
|
||||
(std::vector<std::size_t>{0U, 1U, 0U, 1U}));
|
||||
EXPECT_EQ(blocks.kcc.Values(), (std::vector<double>{12.0, 15.0, 42.0, 45.0}));
|
||||
EXPECT_EQ(blocks.k_cc.Values(),
|
||||
(std::vector<double>{12.0, 15.0, 42.0, 45.0}));
|
||||
|
||||
EXPECT_EQ(blocks.kfc.Values()[3U], 0.0);
|
||||
EXPECT_TRUE(blocks.kff.Validate().IsOk());
|
||||
EXPECT_TRUE(blocks.kfc.Validate().IsOk());
|
||||
EXPECT_TRUE(blocks.kcf.Validate().IsOk());
|
||||
EXPECT_TRUE(blocks.kcc.Validate().IsOk());
|
||||
EXPECT_EQ(blocks.k_fc.Values()[3U], 0.0);
|
||||
EXPECT_TRUE(blocks.k_ff.Validate().IsOk());
|
||||
EXPECT_TRUE(blocks.k_fc.Validate().IsOk());
|
||||
EXPECT_TRUE(blocks.k_cf.Validate().IsOk());
|
||||
EXPECT_TRUE(blocks.k_cc.Validate().IsOk());
|
||||
}
|
||||
|
||||
TEST(EssentialConstraints, HandlesNoAllAndMixedConstraints) {
|
||||
const auto full = MakeMatrix(6U, 6U, SequentialDense(6U));
|
||||
const fesa::EssentialConstraintPolicy policy;
|
||||
|
||||
const auto no_constraints = MakeDofs({});
|
||||
auto none = fesa::EssentialConstraints::Partition(full, no_constraints);
|
||||
auto none = policy.Partition(full, no_constraints);
|
||||
ASSERT_TRUE(none.HasValue());
|
||||
ExpectShape(none.Value().kff, 6U, 6U);
|
||||
ExpectShape(none.Value().kfc, 6U, 0U);
|
||||
ExpectShape(none.Value().kcf, 0U, 6U);
|
||||
ExpectShape(none.Value().kcc, 0U, 0U);
|
||||
EXPECT_EQ(none.Value().kff.Values(), full.Values());
|
||||
ExpectShape(none.Value().k_ff, 6U, 6U);
|
||||
ExpectShape(none.Value().k_fc, 6U, 0U);
|
||||
ExpectShape(none.Value().k_cf, 0U, 6U);
|
||||
ExpectShape(none.Value().k_cc, 0U, 0U);
|
||||
EXPECT_EQ(none.Value().k_ff.Values(), full.Values());
|
||||
|
||||
const auto all_constraints = MakeDofs({{"1", 1, 6, 1.0, {{}, 12U}}});
|
||||
auto all = fesa::EssentialConstraints::Partition(full, all_constraints);
|
||||
auto all = policy.Partition(full, all_constraints);
|
||||
ASSERT_TRUE(all.HasValue());
|
||||
ExpectShape(all.Value().kff, 0U, 0U);
|
||||
ExpectShape(all.Value().kfc, 0U, 6U);
|
||||
ExpectShape(all.Value().kcf, 6U, 0U);
|
||||
ExpectShape(all.Value().kcc, 6U, 6U);
|
||||
EXPECT_EQ(all.Value().kcc.Values(), full.Values());
|
||||
ExpectShape(all.Value().k_ff, 0U, 0U);
|
||||
ExpectShape(all.Value().k_fc, 0U, 6U);
|
||||
ExpectShape(all.Value().k_cf, 6U, 0U);
|
||||
ExpectShape(all.Value().k_cc, 6U, 6U);
|
||||
EXPECT_EQ(all.Value().k_cc.Values(), full.Values());
|
||||
|
||||
const auto mixed_constraints = MakeDofs({{"1", 3, 4, 0.0, {{}, 12U}}});
|
||||
auto mixed = fesa::EssentialConstraints::Partition(full, mixed_constraints);
|
||||
auto mixed = policy.Partition(full, mixed_constraints);
|
||||
ASSERT_TRUE(mixed.HasValue());
|
||||
ExpectShape(mixed.Value().kff, 4U, 4U);
|
||||
ExpectShape(mixed.Value().kfc, 4U, 2U);
|
||||
ExpectShape(mixed.Value().kcf, 2U, 4U);
|
||||
ExpectShape(mixed.Value().kcc, 2U, 2U);
|
||||
ExpectShape(mixed.Value().k_ff, 4U, 4U);
|
||||
ExpectShape(mixed.Value().k_fc, 4U, 2U);
|
||||
ExpectShape(mixed.Value().k_cf, 2U, 4U);
|
||||
ExpectShape(mixed.Value().k_cc, 2U, 2U);
|
||||
}
|
||||
|
||||
// MITC4-DOF-003
|
||||
TEST(EssentialConstraints,
|
||||
PreservesShellSizedNoMixedAndAllConstraintRoundTrips) {
|
||||
const auto full = MakeMatrix(24U, 24U, SequentialDense(24U));
|
||||
const fesa::EssentialConstraintPolicy policy;
|
||||
|
||||
const auto no_constraints = MakeShellSizedDofs({});
|
||||
auto none = fesa::EssentialConstraints::Partition(full, no_constraints);
|
||||
auto none = policy.Partition(full, no_constraints);
|
||||
ASSERT_TRUE(none.HasValue());
|
||||
ExpectShape(none.Value().kff, 24U, 24U);
|
||||
ExpectShape(none.Value().kfc, 24U, 0U);
|
||||
ExpectShape(none.Value().kcf, 0U, 24U);
|
||||
ExpectShape(none.Value().kcc, 0U, 0U);
|
||||
ExpectShape(none.Value().k_ff, 24U, 24U);
|
||||
ExpectShape(none.Value().k_fc, 24U, 0U);
|
||||
ExpectShape(none.Value().k_cf, 0U, 24U);
|
||||
ExpectShape(none.Value().k_cc, 0U, 0U);
|
||||
|
||||
const auto mixed_constraints = MakeShellSizedDofs(
|
||||
{{"1", 1, 6, 0.0, {{}, 12U}}, {"4", 2, 2, 2.5, {{}, 13U}}});
|
||||
auto mixed = fesa::EssentialConstraints::Partition(full, mixed_constraints);
|
||||
auto mixed = policy.Partition(full, mixed_constraints);
|
||||
ASSERT_TRUE(mixed.HasValue());
|
||||
ExpectShape(mixed.Value().kff, 17U, 17U);
|
||||
ExpectShape(mixed.Value().kfc, 17U, 7U);
|
||||
ExpectShape(mixed.Value().kcf, 7U, 17U);
|
||||
ExpectShape(mixed.Value().kcc, 7U, 7U);
|
||||
ExpectShape(mixed.Value().k_ff, 17U, 17U);
|
||||
ExpectShape(mixed.Value().k_fc, 17U, 7U);
|
||||
ExpectShape(mixed.Value().k_cf, 7U, 17U);
|
||||
ExpectShape(mixed.Value().k_cc, 7U, 7U);
|
||||
|
||||
fesa::Vector mixed_full{24U};
|
||||
for (std::size_t index = 0U; index < mixed_full.Size(); ++index) {
|
||||
@@ -221,9 +226,8 @@ TEST(EssentialConstraints,
|
||||
mixed_full[mixed_constraints.ConstrainedDofs()[index]] =
|
||||
mixed_constraints.PrescribedValues()[index];
|
||||
}
|
||||
const auto mixed_free =
|
||||
fesa::EssentialConstraints::GatherFree(mixed_full, mixed_constraints);
|
||||
const auto mixed_reconstructed = fesa::EssentialConstraints::ReconstructFull(
|
||||
const auto mixed_free = policy.GatherFree(mixed_full, mixed_constraints);
|
||||
const auto mixed_reconstructed = policy.ReconstructFull(
|
||||
mixed_free, mixed_constraints.PrescribedValues(), mixed_constraints);
|
||||
ASSERT_EQ(mixed_reconstructed.Size(), mixed_full.Size());
|
||||
for (std::size_t index = 0U; index < mixed_full.Size(); ++index) {
|
||||
@@ -235,14 +239,14 @@ TEST(EssentialConstraints,
|
||||
{"2", 1, 6, 2.0, {{}, 15U}},
|
||||
{"3", 1, 6, 3.0, {{}, 16U}},
|
||||
{"4", 1, 6, 4.0, {{}, 17U}}});
|
||||
auto all = fesa::EssentialConstraints::Partition(full, all_constraints);
|
||||
auto all = policy.Partition(full, all_constraints);
|
||||
ASSERT_TRUE(all.HasValue());
|
||||
ExpectShape(all.Value().kff, 0U, 0U);
|
||||
ExpectShape(all.Value().kfc, 0U, 24U);
|
||||
ExpectShape(all.Value().kcf, 24U, 0U);
|
||||
ExpectShape(all.Value().kcc, 24U, 24U);
|
||||
ExpectShape(all.Value().k_ff, 0U, 0U);
|
||||
ExpectShape(all.Value().k_fc, 0U, 24U);
|
||||
ExpectShape(all.Value().k_cf, 24U, 0U);
|
||||
ExpectShape(all.Value().k_cc, 24U, 24U);
|
||||
|
||||
const auto all_reconstructed = fesa::EssentialConstraints::ReconstructFull(
|
||||
const auto all_reconstructed = policy.ReconstructFull(
|
||||
fesa::Vector{0U}, all_constraints.PrescribedValues(), all_constraints);
|
||||
ASSERT_EQ(all_reconstructed.Size(), 24U);
|
||||
for (std::size_t node = 0U; node < 4U; ++node) {
|
||||
@@ -253,6 +257,7 @@ TEST(EssentialConstraints,
|
||||
}
|
||||
|
||||
TEST(EssentialConstraints, ReconstructsNonzeroPrescribedValues) {
|
||||
const fesa::EssentialConstraintPolicy policy;
|
||||
const auto dofs =
|
||||
MakeDofs({{"1", 2, 2, 2.5, {{}, 12U}}, {"1", 5, 5, -3.25, {{}, 13U}}});
|
||||
fesa::Vector full{6U};
|
||||
@@ -263,9 +268,8 @@ TEST(EssentialConstraints, ReconstructsNonzeroPrescribedValues) {
|
||||
full[4U] = -3.25;
|
||||
full[5U] = 40.0;
|
||||
|
||||
const auto free = fesa::EssentialConstraints::GatherFree(full, dofs);
|
||||
const auto constrained =
|
||||
fesa::EssentialConstraints::GatherConstrained(full, dofs);
|
||||
const auto free = policy.GatherFree(full, dofs);
|
||||
const auto constrained = policy.GatherConstrained(full, dofs);
|
||||
EXPECT_EQ(free.Size(), 4U);
|
||||
EXPECT_DOUBLE_EQ(free[0U], 10.0);
|
||||
EXPECT_DOUBLE_EQ(free[1U], 20.0);
|
||||
@@ -277,8 +281,8 @@ TEST(EssentialConstraints, ReconstructsNonzeroPrescribedValues) {
|
||||
EXPECT_EQ(constrained[0U], dofs.PrescribedValues()[0U]);
|
||||
EXPECT_EQ(constrained[1U], dofs.PrescribedValues()[1U]);
|
||||
|
||||
const auto reconstructed = fesa::EssentialConstraints::ReconstructFull(
|
||||
free, dofs.PrescribedValues(), dofs);
|
||||
const auto reconstructed =
|
||||
policy.ReconstructFull(free, dofs.PrescribedValues(), dofs);
|
||||
ASSERT_EQ(reconstructed.Size(), full.Size());
|
||||
for (std::size_t index = 0U; index < full.Size(); ++index) {
|
||||
EXPECT_DOUBLE_EQ(reconstructed[index], full[index]);
|
||||
@@ -286,10 +290,10 @@ TEST(EssentialConstraints, ReconstructsNonzeroPrescribedValues) {
|
||||
}
|
||||
|
||||
TEST(EssentialConstraints, RejectsDimensionOrOrderMismatch) {
|
||||
const fesa::EssentialConstraintPolicy policy;
|
||||
const auto dofs = MakeDofs({{"1", 2, 2, 1.0, {{}, 12U}}});
|
||||
const auto wrong_square = MakeMatrix(5U, 5U, SequentialDense(5U));
|
||||
auto wrong_dimension =
|
||||
fesa::EssentialConstraints::Partition(wrong_square, dofs);
|
||||
auto wrong_dimension = policy.Partition(wrong_square, dofs);
|
||||
ASSERT_FALSE(wrong_dimension.HasValue());
|
||||
EXPECT_EQ(wrong_dimension.GetStatus().Category(),
|
||||
fesa::FailureCategory::kModel);
|
||||
@@ -298,18 +302,17 @@ TEST(EssentialConstraints, RejectsDimensionOrOrderMismatch) {
|
||||
"invalid-constraint-dimensions");
|
||||
|
||||
const auto rectangular = MakeMatrix(6U, 5U, std::vector<double>(30U, 0.0));
|
||||
auto wrong_order = fesa::EssentialConstraints::Partition(rectangular, dofs);
|
||||
auto wrong_order = policy.Partition(rectangular, dofs);
|
||||
ASSERT_FALSE(wrong_order.HasValue());
|
||||
EXPECT_EQ(wrong_order.GetStatus().Diagnostics()[0U].code,
|
||||
"invalid-constraint-dimensions");
|
||||
|
||||
EXPECT_THROW(static_cast<void>(fesa::EssentialConstraints::GatherFree(
|
||||
fesa::Vector{5U}, dofs)),
|
||||
EXPECT_THROW(static_cast<void>(policy.GatherFree(fesa::Vector{5U}, dofs)),
|
||||
std::invalid_argument);
|
||||
EXPECT_THROW(static_cast<void>(fesa::EssentialConstraints::GatherConstrained(
|
||||
fesa::Vector{7U}, dofs)),
|
||||
EXPECT_THROW(
|
||||
static_cast<void>(policy.GatherConstrained(fesa::Vector{7U}, dofs)),
|
||||
std::invalid_argument);
|
||||
EXPECT_THROW(static_cast<void>(fesa::EssentialConstraints::ReconstructFull(
|
||||
EXPECT_THROW(static_cast<void>(policy.ReconstructFull(
|
||||
fesa::Vector{4U}, fesa::Vector{2U}, dofs)),
|
||||
std::invalid_argument);
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "fesa/elements/element.h"
|
||||
#include "fesa/elements/element_definition.h"
|
||||
#include "fesa/io/abaqus/input_reader.h"
|
||||
#include "fesa/math/vector3.h"
|
||||
@@ -292,12 +293,15 @@ TEST(InpDomainMapping, MapsEverySupportedKeywordAndLegacyDeck) {
|
||||
EXPECT_DOUBLE_EQ(step.TimePeriod(), 1.5);
|
||||
EXPECT_DOUBLE_EQ(step.MinimumIncrement(), 0.01);
|
||||
EXPECT_DOUBLE_EQ(step.MaximumIncrement(), 1.5);
|
||||
ASSERT_EQ(step.Boundaries().size(), 2U);
|
||||
EXPECT_EQ(step.Boundaries()[0].target, "rootassembly");
|
||||
EXPECT_EQ(step.Boundaries()[0].first_dof, 1);
|
||||
EXPECT_DOUBLE_EQ(step.Boundaries()[0].value, 0.125);
|
||||
EXPECT_EQ(step.Boundaries()[1].last_dof, 2);
|
||||
EXPECT_DOUBLE_EQ(step.Boundaries()[1].value, 0.0);
|
||||
ASSERT_EQ(step.PrescribedDisplacements().Size(), 2U);
|
||||
EXPECT_EQ(step.PrescribedDisplacements()[0U].Target().target_name_or_label,
|
||||
"rootassembly");
|
||||
EXPECT_EQ(step.PrescribedDisplacements()[0U].Component(),
|
||||
fesa::DofComponent::kUx);
|
||||
EXPECT_DOUBLE_EQ(step.PrescribedDisplacements()[0U].PrescribedValue(), 0.125);
|
||||
EXPECT_EQ(step.PrescribedDisplacements()[1U].Component(),
|
||||
fesa::DofComponent::kUy);
|
||||
EXPECT_DOUBLE_EQ(step.PrescribedDisplacements()[1U].PrescribedValue(), 0.0);
|
||||
ASSERT_EQ(step.ConcentratedLoads().Size(), 1U);
|
||||
EXPECT_EQ(step.ConcentratedLoads()[0U].Target().target_name_or_label,
|
||||
"RootAssembly");
|
||||
@@ -411,7 +415,11 @@ OnlySecond, 2, 5.
|
||||
(std::vector<fesa::EntityIndex>{1U}));
|
||||
|
||||
ASSERT_EQ(domain.Steps().Size(), 1U);
|
||||
EXPECT_EQ(domain.Steps()[0].Boundaries()[0].target, "OnlySecond");
|
||||
EXPECT_EQ(domain.Steps()[0]
|
||||
.PrescribedDisplacements()[0U]
|
||||
.Target()
|
||||
.target_name_or_label,
|
||||
"OnlySecond");
|
||||
EXPECT_EQ(
|
||||
domain.Steps()[0].ConcentratedLoads()[0U].Target().target_name_or_label,
|
||||
"OnlySecond");
|
||||
@@ -421,7 +429,12 @@ OnlySecond, 2, 5.
|
||||
ReplaceOnce(ReplaceOnce(MinimalDeck(), "Root, 1, 6", "1, 1, 6"),
|
||||
"Tip, 2, -1.", "2, 2, -1."));
|
||||
ASSERT_TRUE(direct.HasValue());
|
||||
EXPECT_EQ(direct.Value().Steps()[0].Boundaries()[0].target, "1");
|
||||
EXPECT_EQ(direct.Value()
|
||||
.Steps()[0]
|
||||
.PrescribedDisplacements()[0U]
|
||||
.Target()
|
||||
.target_name_or_label,
|
||||
"1");
|
||||
EXPECT_EQ(direct.Value()
|
||||
.Steps()[0]
|
||||
.ConcentratedLoads()[0U]
|
||||
@@ -551,8 +564,8 @@ TEST(InpDomainMapping, NoOpAllowlistWarnsWithoutSemanticEffect) {
|
||||
EXPECT_EQ(with_no_ops.Value().ElementSets().size(),
|
||||
plain.Value().ElementSets().size());
|
||||
EXPECT_EQ(with_no_ops.Value().Steps().Size(), plain.Value().Steps().Size());
|
||||
EXPECT_EQ(with_no_ops.Value().Steps()[0].Boundaries().size(),
|
||||
plain.Value().Steps()[0].Boundaries().size());
|
||||
EXPECT_EQ(with_no_ops.Value().Steps()[0].BoundaryConditions().size(),
|
||||
plain.Value().Steps()[0].BoundaryConditions().size());
|
||||
EXPECT_EQ(with_no_ops.Value().Steps()[0].Loads().size(),
|
||||
plain.Value().Steps()[0].Loads().size());
|
||||
}
|
||||
@@ -613,8 +626,9 @@ TEST(InpDomainMapping, MapsS4AndS4rThroughOneMitc4Identity) {
|
||||
(std::array<double, 3>{0.0, 1.0, 0.0}));
|
||||
|
||||
ASSERT_EQ(domain.Steps().Size(), 1U);
|
||||
ASSERT_EQ(domain.Steps()[0].Boundaries().size(), 1U);
|
||||
EXPECT_EQ(domain.Steps()[0].Boundaries()[0].last_dof, 6);
|
||||
ASSERT_EQ(domain.Steps()[0].PrescribedDisplacements().Size(), 6U);
|
||||
EXPECT_EQ(domain.Steps()[0].PrescribedDisplacements()[5U].Component(),
|
||||
fesa::DofComponent::kUrz);
|
||||
ASSERT_EQ(domain.Steps()[0].ConcentratedLoads().Size(), 1U);
|
||||
EXPECT_DOUBLE_EQ(
|
||||
domain.Steps()[0].ConcentratedLoads()[0U].GlobalComponents()[5U], 1.0);
|
||||
|
||||
@@ -197,7 +197,7 @@ TEST(DomainModel, ImmutableOwnershipPreservesStableOrder) {
|
||||
EXPECT_DOUBLE_EQ(domain.Steps()[0].TimePeriod(), 1.0);
|
||||
EXPECT_DOUBLE_EQ(domain.Steps()[0].MinimumIncrement(), 1.0e-5);
|
||||
EXPECT_DOUBLE_EQ(domain.Steps()[0].MaximumIncrement(), 1.0);
|
||||
ASSERT_EQ(domain.Steps()[0].Boundaries().size(), 1U);
|
||||
ASSERT_EQ(domain.Steps()[0].BoundaryConditions().size(), 6U);
|
||||
ASSERT_EQ(domain.Steps()[0].Loads().size(), 1U);
|
||||
ASSERT_EQ(domain.Steps()[0].ConcentratedLoads().Size(), 1U);
|
||||
EXPECT_DOUBLE_EQ(
|
||||
|
||||
@@ -66,7 +66,7 @@ TEST(DomainModel, SourceAndInternalIdentityRemainDistinct) {
|
||||
fesa::EntityIndex{2},
|
||||
fesa::EntityIndex{4},
|
||||
{"models/beam.inp", 20U}};
|
||||
const fesa::BoundaryCondition boundary{
|
||||
const fesa::PrescribedDisplacementDefinition boundary{
|
||||
"Root", 1, 6, 0.0, {"models/beam.inp", 60U}};
|
||||
const fesa::NodalLoad load{"Tip", 3, -1000.0, {"models/beam.inp", 70U}};
|
||||
const fesa::StaticStepDefinition step{
|
||||
|
||||
@@ -332,12 +332,14 @@ fesa::ModelDefinition MakeShellDefinition(
|
||||
}
|
||||
definition.steps = {
|
||||
{"Step-1",
|
||||
constrain_all ? std::vector<fesa::BoundaryCondition>{{"All",
|
||||
constrain_all
|
||||
? std::vector<fesa::PrescribedDisplacementDefinition>{{"All",
|
||||
1,
|
||||
6,
|
||||
0.0,
|
||||
{source, 60U}}}
|
||||
: std::vector<fesa::BoundaryCondition>{},
|
||||
{source,
|
||||
60U}}}
|
||||
: std::vector<fesa::PrescribedDisplacementDefinition>{},
|
||||
std::move(loads),
|
||||
0.1,
|
||||
1.0,
|
||||
|
||||
Reference in New Issue
Block a user