feat(cpp-object-oriented-modular-refactoring): step 5 - solver-workflow-google-style
This commit is contained in:
@@ -0,0 +1,55 @@
|
|||||||
|
#ifndef FESA_ANALYSIS_ANALYSIS_MODEL_H_
|
||||||
|
#define FESA_ANALYSIS_ANALYSIS_MODEL_H_
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "fesa/model/domain.h"
|
||||||
|
|
||||||
|
namespace fesa {
|
||||||
|
|
||||||
|
/// @brief Provides the active-step view into a non-owned Domain.
|
||||||
|
/// @note The referenced Domain must outlive this object and retains all
|
||||||
|
/// semantic ownership.
|
||||||
|
class AnalysisModel {
|
||||||
|
public:
|
||||||
|
/// @brief Creates the sole active-step view for a valid Domain.
|
||||||
|
/// @param domain Domain that remains alive for the returned view's lifetime.
|
||||||
|
/// @return A stable view or an input-cardinality failure.
|
||||||
|
static Result<AnalysisModel> Create(const Domain& domain);
|
||||||
|
|
||||||
|
/// @brief Returns the non-owned Domain backing this view.
|
||||||
|
const Domain& GetDomain() const noexcept;
|
||||||
|
|
||||||
|
/// @brief Returns the sole active static step.
|
||||||
|
const StaticStepDefinition& Step() const noexcept;
|
||||||
|
|
||||||
|
/// @brief Returns active beam element indices in stable internal order.
|
||||||
|
const std::vector<EntityIndex>& ActiveElements() const noexcept;
|
||||||
|
|
||||||
|
/// @brief Returns reachable material indices in stable internal order.
|
||||||
|
const std::vector<EntityIndex>& ActiveMaterials() const noexcept;
|
||||||
|
|
||||||
|
/// @brief Returns reachable beam-section indices in stable internal order.
|
||||||
|
const std::vector<EntityIndex>& ActiveSections() const noexcept;
|
||||||
|
|
||||||
|
/// @brief Returns boundary-condition indices in source order.
|
||||||
|
const std::vector<EntityIndex>& ActiveBoundaryConditions() const noexcept;
|
||||||
|
|
||||||
|
/// @brief Returns concentrated-load indices in source order.
|
||||||
|
const std::vector<EntityIndex>& ActiveLoads() const noexcept;
|
||||||
|
|
||||||
|
private:
|
||||||
|
/// @brief Builds stable indices without copying the referenced Domain.
|
||||||
|
explicit AnalysisModel(const Domain& domain);
|
||||||
|
|
||||||
|
const Domain* domain_;
|
||||||
|
std::vector<EntityIndex> active_elements_;
|
||||||
|
std::vector<EntityIndex> active_materials_;
|
||||||
|
std::vector<EntityIndex> active_sections_;
|
||||||
|
std::vector<EntityIndex> active_boundary_conditions_;
|
||||||
|
std::vector<EntityIndex> active_loads_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace fesa
|
||||||
|
|
||||||
|
#endif // FESA_ANALYSIS_ANALYSIS_MODEL_H_
|
||||||
@@ -1,34 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include "fesa/model/domain.h"
|
|
||||||
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
namespace fesa {
|
|
||||||
|
|
||||||
// Provides the sole active-step view while the referenced Domain retains all
|
|
||||||
// semantic ownership and must outlive this object.
|
|
||||||
class AnalysisModel {
|
|
||||||
public:
|
|
||||||
static Result<AnalysisModel> create(const Domain& domain);
|
|
||||||
|
|
||||||
const Domain& domain() const noexcept;
|
|
||||||
const StaticStepDefinition& step() const noexcept;
|
|
||||||
const std::vector<EntityIndex>& activeElements() const noexcept;
|
|
||||||
const std::vector<EntityIndex>& activeMaterials() const noexcept;
|
|
||||||
const std::vector<EntityIndex>& activeSections() const noexcept;
|
|
||||||
const std::vector<EntityIndex>& activeBoundaryConditions() const noexcept;
|
|
||||||
const std::vector<EntityIndex>& activeLoads() const noexcept;
|
|
||||||
|
|
||||||
private:
|
|
||||||
explicit AnalysisModel(const Domain& domain);
|
|
||||||
|
|
||||||
const Domain* domain_;
|
|
||||||
std::vector<EntityIndex> activeElements_;
|
|
||||||
std::vector<EntityIndex> activeMaterials_;
|
|
||||||
std::vector<EntityIndex> activeSections_;
|
|
||||||
std::vector<EntityIndex> activeBoundaryConditions_;
|
|
||||||
std::vector<EntityIndex> activeLoads_;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace fesa
|
|
||||||
@@ -0,0 +1,102 @@
|
|||||||
|
#ifndef FESA_ANALYSIS_ANALYSIS_STATE_H_
|
||||||
|
#define FESA_ANALYSIS_ANALYSIS_STATE_H_
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
#include <cstddef>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "fesa/core/status.h"
|
||||||
|
#include "fesa/fem/dof_manager.h"
|
||||||
|
#include "fesa/math/vector.h"
|
||||||
|
#include "fesa/results/result_records.h"
|
||||||
|
|
||||||
|
namespace fesa {
|
||||||
|
|
||||||
|
/// @brief Owns mutable quantities required by the V0 linear-static frame.
|
||||||
|
class AnalysisState {
|
||||||
|
public:
|
||||||
|
/// @brief Allocates zeroed full-DOF vectors for a DOF manager.
|
||||||
|
/// @param dofs Owner of the full-DOF dimension used by every state vector.
|
||||||
|
/// @param identity Stable step and frame identity for this state.
|
||||||
|
static AnalysisState Create(const DofManager& dofs,
|
||||||
|
StepFrameIdentity identity);
|
||||||
|
|
||||||
|
/// @brief Returns mutable full-space displacement.
|
||||||
|
Vector& Displacement() noexcept;
|
||||||
|
/// @brief Returns full-space displacement.
|
||||||
|
const Vector& Displacement() const noexcept;
|
||||||
|
/// @brief Returns mutable full-space external force.
|
||||||
|
Vector& ExternalForce() noexcept;
|
||||||
|
/// @brief Returns full-space external force.
|
||||||
|
const Vector& ExternalForce() const noexcept;
|
||||||
|
/// @brief Returns mutable full-space internal force.
|
||||||
|
Vector& InternalForce() noexcept;
|
||||||
|
/// @brief Returns full-space internal force.
|
||||||
|
const Vector& InternalForce() const noexcept;
|
||||||
|
/// @brief Returns mutable full residual K*d-F.
|
||||||
|
Vector& Residual() noexcept;
|
||||||
|
/// @brief Returns full residual K*d-F.
|
||||||
|
const Vector& Residual() const noexcept;
|
||||||
|
/// @brief Returns mutable full-index reaction and free residual evidence.
|
||||||
|
Vector& Reaction() noexcept;
|
||||||
|
/// @brief Returns full-index reaction and free residual evidence.
|
||||||
|
const Vector& Reaction() const noexcept;
|
||||||
|
/// @brief Returns the stable step and frame identity.
|
||||||
|
const StepFrameIdentity& Identity() const noexcept;
|
||||||
|
/// @brief Returns mutable beam endpoint result rows.
|
||||||
|
std::vector<EndpointResultRow>& EndpointResults() noexcept;
|
||||||
|
/// @brief Returns beam endpoint result rows.
|
||||||
|
const std::vector<EndpointResultRow>& EndpointResults() const noexcept;
|
||||||
|
/// @brief Returns mutable beam Gauss result rows.
|
||||||
|
std::vector<GaussResultRow>& GaussResults() noexcept;
|
||||||
|
/// @brief Returns beam Gauss result rows.
|
||||||
|
const std::vector<GaussResultRow>& GaussResults() const noexcept;
|
||||||
|
/// @brief Returns mutable beam axial-stress rows.
|
||||||
|
std::vector<StressS11Row>& StressResults() noexcept;
|
||||||
|
/// @brief Returns beam axial-stress rows.
|
||||||
|
const std::vector<StressS11Row>& StressResults() const noexcept;
|
||||||
|
|
||||||
|
/// @brief Validates and atomically replaces all shell recovery evidence.
|
||||||
|
/// @param expected_element_order Unique shell indices in stable order.
|
||||||
|
/// @param candidate Complete shell rows, energy, and equilibrium evidence.
|
||||||
|
/// @return Success only after the complete candidate is validated and
|
||||||
|
/// committed; failure preserves the prior shell state.
|
||||||
|
Status CommitShellResults(
|
||||||
|
const std::vector<EntityIndex>& expected_element_order,
|
||||||
|
ShellStateCandidate candidate);
|
||||||
|
|
||||||
|
/// @brief Returns shell rows in stable element and location order.
|
||||||
|
const std::vector<ShellResultRow>& ShellResults() const noexcept;
|
||||||
|
/// @brief Returns physical shell strain energy without drilling energy.
|
||||||
|
double PhysicalStrainEnergy() const noexcept;
|
||||||
|
/// @brief Returns global force and moment equilibrium components.
|
||||||
|
const std::array<double, 6>& Equilibrium() const noexcept;
|
||||||
|
/// @brief Returns normalized shell verification metrics.
|
||||||
|
const std::array<double, 3>& VerificationMetrics() const noexcept;
|
||||||
|
|
||||||
|
private:
|
||||||
|
/// @brief Allocates state storage for one stable full-DOF dimension.
|
||||||
|
AnalysisState(std::size_t full_dof_count, StepFrameIdentity identity);
|
||||||
|
|
||||||
|
StepFrameIdentity identity_;
|
||||||
|
Vector displacement_;
|
||||||
|
Vector external_force_;
|
||||||
|
Vector internal_force_;
|
||||||
|
Vector residual_;
|
||||||
|
// Reactions retain full-index space so free residual components remain
|
||||||
|
// visible.
|
||||||
|
Vector reaction_;
|
||||||
|
// Recovery appends rows in stable element/location order.
|
||||||
|
std::vector<EndpointResultRow> endpoint_results_;
|
||||||
|
std::vector<GaussResultRow> gauss_results_;
|
||||||
|
std::vector<StressS11Row> stress_results_;
|
||||||
|
// Shell recovery is replaced only through validated candidate commit.
|
||||||
|
std::vector<ShellResultRow> shell_results_;
|
||||||
|
double physical_strain_energy_{0.0};
|
||||||
|
std::array<double, 6> equilibrium_{};
|
||||||
|
std::array<double, 3> verification_metrics_{};
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace fesa
|
||||||
|
|
||||||
|
#endif // FESA_ANALYSIS_ANALYSIS_STATE_H_
|
||||||
@@ -1,66 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include "fesa/core/status.h"
|
|
||||||
#include "fesa/fem/dof_manager.hpp"
|
|
||||||
#include "fesa/math/vector.h"
|
|
||||||
#include "fesa/results/result_records.hpp"
|
|
||||||
|
|
||||||
#include <array>
|
|
||||||
#include <cstddef>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
namespace fesa {
|
|
||||||
|
|
||||||
// Owns only the mutable quantities required by the V0 linear-static frame.
|
|
||||||
class AnalysisState {
|
|
||||||
public:
|
|
||||||
static AnalysisState create(
|
|
||||||
const DofManager& dofs, StepFrameIdentity identity);
|
|
||||||
|
|
||||||
Vector& displacement() noexcept;
|
|
||||||
const Vector& displacement() const noexcept;
|
|
||||||
Vector& externalForce() noexcept;
|
|
||||||
const Vector& externalForce() const noexcept;
|
|
||||||
Vector& internalForce() noexcept;
|
|
||||||
const Vector& internalForce() const noexcept;
|
|
||||||
Vector& residual() noexcept;
|
|
||||||
const Vector& residual() const noexcept;
|
|
||||||
Vector& reaction() noexcept;
|
|
||||||
const Vector& reaction() const noexcept;
|
|
||||||
const StepFrameIdentity& identity() const noexcept;
|
|
||||||
std::vector<EndpointResultRow>& endpointResults() noexcept;
|
|
||||||
const std::vector<EndpointResultRow>& endpointResults() const noexcept;
|
|
||||||
std::vector<GaussResultRow>& gaussResults() noexcept;
|
|
||||||
const std::vector<GaussResultRow>& gaussResults() const noexcept;
|
|
||||||
std::vector<StressS11Row>& stressResults() noexcept;
|
|
||||||
const std::vector<StressS11Row>& stressResults() const noexcept;
|
|
||||||
Status commitShellResults(
|
|
||||||
const std::vector<EntityIndex>& expectedElementOrder,
|
|
||||||
ShellStateCandidate candidate);
|
|
||||||
const std::vector<ShellResultRow>& shellResults() const noexcept;
|
|
||||||
double physicalStrainEnergy() const noexcept;
|
|
||||||
const std::array<double, 6>& equilibrium() const noexcept;
|
|
||||||
const std::array<double, 3>& verificationMetrics() const noexcept;
|
|
||||||
|
|
||||||
private:
|
|
||||||
AnalysisState(std::size_t fullDofCount, StepFrameIdentity identity);
|
|
||||||
|
|
||||||
StepFrameIdentity identity_;
|
|
||||||
Vector displacement_;
|
|
||||||
Vector externalForce_;
|
|
||||||
Vector internalForce_;
|
|
||||||
Vector residual_;
|
|
||||||
// Reactions retain full-index space so free residual components remain visible.
|
|
||||||
Vector reaction_;
|
|
||||||
// Recovery appends rows in stable element/location order.
|
|
||||||
std::vector<EndpointResultRow> endpointResults_;
|
|
||||||
std::vector<GaussResultRow> gaussResults_;
|
|
||||||
std::vector<StressS11Row> stressResults_;
|
|
||||||
// Shell recovery is replaced only through validated candidate commit.
|
|
||||||
std::vector<ShellResultRow> shellResults_;
|
|
||||||
double physicalStrainEnergy_{0.0};
|
|
||||||
std::array<double, 6> equilibrium_{};
|
|
||||||
std::array<double, 3> verificationMetrics_{};
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace fesa
|
|
||||||
@@ -0,0 +1,104 @@
|
|||||||
|
#ifndef FESA_ANALYSIS_LINEAR_STATIC_ANALYSIS_H_
|
||||||
|
#define FESA_ANALYSIS_LINEAR_STATIC_ANALYSIS_H_
|
||||||
|
|
||||||
|
#include <filesystem>
|
||||||
|
#include <memory>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "fesa/analysis/analysis_model.h"
|
||||||
|
#include "fesa/analysis/analysis_state.h"
|
||||||
|
#include "fesa/constraints/essential_constraints.h"
|
||||||
|
#include "fesa/core/status.h"
|
||||||
|
#include "fesa/fem/dof_manager.h"
|
||||||
|
#include "fesa/math/sparse_matrix.h"
|
||||||
|
#include "fesa/math/vector.h"
|
||||||
|
#include "fesa/model/domain.h"
|
||||||
|
|
||||||
|
namespace fesa {
|
||||||
|
|
||||||
|
class LinearSolver;
|
||||||
|
class ParallelFor;
|
||||||
|
class ResultsWriter;
|
||||||
|
|
||||||
|
/// @brief Carries input and authoritative output paths for one analysis run.
|
||||||
|
struct AnalysisRequest {
|
||||||
|
std::filesystem::path input_path;
|
||||||
|
std::filesystem::path output_path;
|
||||||
|
};
|
||||||
|
|
||||||
|
/// @brief Executes the current approved V0 analysis lifecycle.
|
||||||
|
class Analysis {
|
||||||
|
public:
|
||||||
|
virtual ~Analysis() = default;
|
||||||
|
|
||||||
|
/// @brief Runs the eight current lifecycle stages in their approved order.
|
||||||
|
/// @param request Input and output paths for this run.
|
||||||
|
/// @return The first stage failure or successful result finalization.
|
||||||
|
Status Run(const AnalysisRequest& request);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
/// @brief Initializes owned input and Domain state for a run.
|
||||||
|
virtual Status Initialize(const AnalysisRequest& request) = 0;
|
||||||
|
/// @brief Builds the non-owning active-model view.
|
||||||
|
virtual Status BuildAnalysisModel() = 0;
|
||||||
|
/// @brief Builds stable DOF numbering and the sparse structural pattern.
|
||||||
|
virtual Status BuildDofMapAndSparsePattern() = 0;
|
||||||
|
/// @brief Assembles full stiffness and stable constraint partitions.
|
||||||
|
virtual Status AssembleAndPartitionStiffness() = 0;
|
||||||
|
/// @brief Factorizes Kff before any load assembly.
|
||||||
|
virtual Status Factorize() = 0;
|
||||||
|
/// @brief Assembles loads and forms Ff-Kfc*dc without solving.
|
||||||
|
virtual Status AssembleLoadsAndEffectiveRhs() = 0;
|
||||||
|
/// @brief Substitutes the retained factorization and reconstructs full d.
|
||||||
|
virtual Status SubstituteAndReconstruct() = 0;
|
||||||
|
/// @brief Recovers a complete candidate and writes authoritative results.
|
||||||
|
virtual Status RecoverAndWriteResults() = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
/// @brief Orchestrates the single-step linear-static procedure.
|
||||||
|
/// @note Factorization, substitution, recovery, and writing remain separately
|
||||||
|
/// observable through injected backend boundaries.
|
||||||
|
class LinearStaticAnalysis final : public Analysis {
|
||||||
|
public:
|
||||||
|
/// @brief Creates a procedure using non-owned backend adapters.
|
||||||
|
/// @note All three adapters must outlive this analysis object.
|
||||||
|
LinearStaticAnalysis(const ParallelFor& parallel_for,
|
||||||
|
LinearSolver& linear_solver,
|
||||||
|
ResultsWriter& results_writer);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
/// @copydoc Analysis::Initialize
|
||||||
|
Status Initialize(const AnalysisRequest& request) override;
|
||||||
|
/// @copydoc Analysis::BuildAnalysisModel
|
||||||
|
Status BuildAnalysisModel() override;
|
||||||
|
/// @copydoc Analysis::BuildDofMapAndSparsePattern
|
||||||
|
Status BuildDofMapAndSparsePattern() override;
|
||||||
|
/// @copydoc Analysis::AssembleAndPartitionStiffness
|
||||||
|
Status AssembleAndPartitionStiffness() override;
|
||||||
|
/// @copydoc Analysis::Factorize
|
||||||
|
Status Factorize() override;
|
||||||
|
/// @copydoc Analysis::AssembleLoadsAndEffectiveRhs
|
||||||
|
Status AssembleLoadsAndEffectiveRhs() override;
|
||||||
|
/// @copydoc Analysis::SubstituteAndReconstruct
|
||||||
|
Status SubstituteAndReconstruct() override;
|
||||||
|
/// @copydoc Analysis::RecoverAndWriteResults
|
||||||
|
Status RecoverAndWriteResults() override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
const ParallelFor& parallel_for_;
|
||||||
|
LinearSolver& linear_solver_;
|
||||||
|
ResultsWriter& results_writer_;
|
||||||
|
AnalysisRequest request_;
|
||||||
|
std::unique_ptr<Domain> domain_;
|
||||||
|
std::unique_ptr<AnalysisModel> model_;
|
||||||
|
std::unique_ptr<DofManager> dofs_;
|
||||||
|
std::unique_ptr<AnalysisState> state_;
|
||||||
|
std::unique_ptr<SparseMatrix> full_stiffness_;
|
||||||
|
std::unique_ptr<PartitionedStiffness> partitioned_stiffness_;
|
||||||
|
std::unique_ptr<Vector> effective_rhs_;
|
||||||
|
std::vector<Diagnostic> diagnostics_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace fesa
|
||||||
|
|
||||||
|
#endif // FESA_ANALYSIS_LINEAR_STATIC_ANALYSIS_H_
|
||||||
@@ -1,78 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include "fesa/analysis/analysis_model.hpp"
|
|
||||||
#include "fesa/analysis/analysis_state.hpp"
|
|
||||||
#include "fesa/constraints/essential_constraints.hpp"
|
|
||||||
#include "fesa/core/status.h"
|
|
||||||
#include "fesa/fem/dof_manager.hpp"
|
|
||||||
#include "fesa/math/sparse_matrix.h"
|
|
||||||
#include "fesa/math/vector.h"
|
|
||||||
#include "fesa/model/domain.h"
|
|
||||||
|
|
||||||
#include <filesystem>
|
|
||||||
#include <memory>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
namespace fesa {
|
|
||||||
|
|
||||||
class LinearSolver;
|
|
||||||
class ParallelFor;
|
|
||||||
class ResultsWriter;
|
|
||||||
|
|
||||||
struct AnalysisRequest {
|
|
||||||
std::filesystem::path inputPath;
|
|
||||||
std::filesystem::path outputPath;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Fixes the public V0 lifecycle while leaving each analysis procedure to
|
|
||||||
// implement its approved stages.
|
|
||||||
class Analysis {
|
|
||||||
public:
|
|
||||||
virtual ~Analysis() = default;
|
|
||||||
Status run(const AnalysisRequest& request);
|
|
||||||
|
|
||||||
protected:
|
|
||||||
virtual Status initialize(const AnalysisRequest& request) = 0;
|
|
||||||
virtual Status buildAnalysisModel() = 0;
|
|
||||||
virtual Status buildDofMapAndSparsePattern() = 0;
|
|
||||||
virtual Status assembleAndPartitionStiffness() = 0;
|
|
||||||
virtual Status factorize() = 0;
|
|
||||||
virtual Status assembleLoadsAndEffectiveRhs() = 0;
|
|
||||||
virtual Status substituteAndReconstruct() = 0;
|
|
||||||
virtual Status recoverAndWriteResults() = 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Orchestrates the single-step B33 procedure through injected backend
|
|
||||||
// boundaries so factorization and substitution remain independently visible.
|
|
||||||
class LinearStaticAnalysis final : public Analysis {
|
|
||||||
public:
|
|
||||||
LinearStaticAnalysis(const ParallelFor& parallelFor,
|
|
||||||
LinearSolver& linearSolver,
|
|
||||||
ResultsWriter& resultsWriter);
|
|
||||||
|
|
||||||
protected:
|
|
||||||
Status initialize(const AnalysisRequest& request) override;
|
|
||||||
Status buildAnalysisModel() override;
|
|
||||||
Status buildDofMapAndSparsePattern() override;
|
|
||||||
Status assembleAndPartitionStiffness() override;
|
|
||||||
Status factorize() override;
|
|
||||||
Status assembleLoadsAndEffectiveRhs() override;
|
|
||||||
Status substituteAndReconstruct() override;
|
|
||||||
Status recoverAndWriteResults() override;
|
|
||||||
|
|
||||||
private:
|
|
||||||
const ParallelFor& parallelFor_;
|
|
||||||
LinearSolver& linearSolver_;
|
|
||||||
ResultsWriter& resultsWriter_;
|
|
||||||
AnalysisRequest request_;
|
|
||||||
std::unique_ptr<Domain> domain_;
|
|
||||||
std::unique_ptr<AnalysisModel> model_;
|
|
||||||
std::unique_ptr<DofManager> dofs_;
|
|
||||||
std::unique_ptr<AnalysisState> state_;
|
|
||||||
std::unique_ptr<SparseMatrix> fullStiffness_;
|
|
||||||
std::unique_ptr<PartitionedStiffness> partitionedStiffness_;
|
|
||||||
std::unique_ptr<Vector> effectiveRhs_;
|
|
||||||
std::vector<Diagnostic> diagnostics_;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace fesa
|
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
#ifndef FESA_ASSEMBLY_LOAD_ASSEMBLER_H_
|
||||||
|
#define FESA_ASSEMBLY_LOAD_ASSEMBLER_H_
|
||||||
|
|
||||||
|
#include "fesa/analysis/analysis_model.h"
|
||||||
|
#include "fesa/fem/dof_manager.h"
|
||||||
|
#include "fesa/math/sparse_matrix.h"
|
||||||
|
#include "fesa/math/vector.h"
|
||||||
|
|
||||||
|
namespace fesa {
|
||||||
|
|
||||||
|
/// @brief Assembles semantic nodal loads in stable source order.
|
||||||
|
class LoadAssembler {
|
||||||
|
public:
|
||||||
|
/// @brief Accumulates all active CLOAD rows in full-DOF space.
|
||||||
|
/// @return A finite full load vector or a structured model failure.
|
||||||
|
static Result<Vector> AssembleFullNodalLoad(const AnalysisModel& model,
|
||||||
|
const DofManager& dofs);
|
||||||
|
|
||||||
|
/// @brief Forms Ff-Kfc*dc in stable free/constrained order.
|
||||||
|
/// @note This operation neither factorizes nor invokes a solver.
|
||||||
|
static Result<Vector> EffectiveFreeRhs(const Vector& full_load,
|
||||||
|
const SparseMatrix& kfc,
|
||||||
|
const Vector& prescribed_values,
|
||||||
|
const DofManager& dofs);
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace fesa
|
||||||
|
|
||||||
|
#endif // FESA_ASSEMBLY_LOAD_ASSEMBLER_H_
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include "fesa/analysis/analysis_model.hpp"
|
|
||||||
#include "fesa/fem/dof_manager.hpp"
|
|
||||||
#include "fesa/math/sparse_matrix.h"
|
|
||||||
#include "fesa/math/vector.h"
|
|
||||||
|
|
||||||
namespace fesa {
|
|
||||||
|
|
||||||
// Assembles only semantic nodal CLOAD records and forms the eliminated free
|
|
||||||
// right-hand side; stiffness factorization remains an analysis responsibility.
|
|
||||||
class LoadAssembler {
|
|
||||||
public:
|
|
||||||
static Result<Vector> assembleFullNodalLoad(
|
|
||||||
const AnalysisModel& model,
|
|
||||||
const DofManager& dofs);
|
|
||||||
static Result<Vector> effectiveFreeRhs(
|
|
||||||
const Vector& fullLoad,
|
|
||||||
const SparseMatrix& kfc,
|
|
||||||
const Vector& prescribedValues,
|
|
||||||
const DofManager& dofs);
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace fesa
|
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
#ifndef FESA_ASSEMBLY_PARALLEL_FOR_H_
|
||||||
|
#define FESA_ASSEMBLY_PARALLEL_FOR_H_
|
||||||
|
|
||||||
|
#include <cstddef>
|
||||||
|
#include <functional>
|
||||||
|
|
||||||
|
namespace fesa {
|
||||||
|
|
||||||
|
/// @brief Executes independent index-addressed work behind a backend boundary.
|
||||||
|
/// @note Callers own output storage and each invocation may write only its
|
||||||
|
/// index-owned slot.
|
||||||
|
class ParallelFor {
|
||||||
|
public:
|
||||||
|
virtual ~ParallelFor() = default;
|
||||||
|
|
||||||
|
/// @brief Invokes body once for every index in [0, count).
|
||||||
|
virtual void Execute(std::size_t count,
|
||||||
|
const std::function<void(std::size_t)>& body) const = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
/// @brief Executes index-addressed work serially.
|
||||||
|
class SerialParallelFor final : public ParallelFor {
|
||||||
|
public:
|
||||||
|
/// @copydoc ParallelFor::Execute
|
||||||
|
void Execute(std::size_t count,
|
||||||
|
const std::function<void(std::size_t)>& body) const override;
|
||||||
|
};
|
||||||
|
|
||||||
|
/// @brief Executes index-addressed work through oneTBB.
|
||||||
|
class TbbParallelFor final : public ParallelFor {
|
||||||
|
public:
|
||||||
|
/// @copydoc ParallelFor::Execute
|
||||||
|
void Execute(std::size_t count,
|
||||||
|
const std::function<void(std::size_t)>& body) const override;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace fesa
|
||||||
|
|
||||||
|
#endif // FESA_ASSEMBLY_PARALLEL_FOR_H_
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include <cstddef>
|
|
||||||
#include <functional>
|
|
||||||
|
|
||||||
namespace fesa {
|
|
||||||
|
|
||||||
// Executes independent index-addressed work without exposing the backend.
|
|
||||||
// Callers own output storage and must confine each invocation to its index.
|
|
||||||
class ParallelFor {
|
|
||||||
public:
|
|
||||||
virtual ~ParallelFor() = default;
|
|
||||||
virtual void execute(
|
|
||||||
std::size_t count,
|
|
||||||
const std::function<void(std::size_t)>& body) const = 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
class SerialParallelFor final : public ParallelFor {
|
|
||||||
public:
|
|
||||||
void execute(
|
|
||||||
std::size_t count,
|
|
||||||
const std::function<void(std::size_t)>& body) const override;
|
|
||||||
};
|
|
||||||
|
|
||||||
class TbbParallelFor final : public ParallelFor {
|
|
||||||
public:
|
|
||||||
void execute(
|
|
||||||
std::size_t count,
|
|
||||||
const std::function<void(std::size_t)>& body) const override;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace fesa
|
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
#ifndef FESA_ASSEMBLY_SPARSE_ASSEMBLER_H_
|
||||||
|
#define FESA_ASSEMBLY_SPARSE_ASSEMBLER_H_
|
||||||
|
|
||||||
|
#include "fesa/core/status.h"
|
||||||
|
#include "fesa/math/sparse_matrix.h"
|
||||||
|
|
||||||
|
namespace fesa {
|
||||||
|
|
||||||
|
class AnalysisModel;
|
||||||
|
class DofManager;
|
||||||
|
class ParallelFor;
|
||||||
|
|
||||||
|
/// @brief Owns deterministic element-contribution reduction into global CSR.
|
||||||
|
class SparseAssembler {
|
||||||
|
public:
|
||||||
|
/// @brief Assembles stiffness in stable source-element and local-entry order.
|
||||||
|
/// @return A validated full-DOF CSR matrix or structured model failure.
|
||||||
|
/// @note Parallel workers produce index-owned local buffers; serial reduction
|
||||||
|
/// remains the sole global CSR writer.
|
||||||
|
static Result<SparseMatrix> AssembleStiffness(
|
||||||
|
const AnalysisModel& model, const DofManager& dofs,
|
||||||
|
const ParallelFor& parallel_for);
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace fesa
|
||||||
|
|
||||||
|
#endif // FESA_ASSEMBLY_SPARSE_ASSEMBLER_H_
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include "fesa/core/status.h"
|
|
||||||
#include "fesa/math/sparse_matrix.h"
|
|
||||||
|
|
||||||
namespace fesa {
|
|
||||||
|
|
||||||
class AnalysisModel;
|
|
||||||
class DofManager;
|
|
||||||
class ParallelFor;
|
|
||||||
|
|
||||||
class SparseAssembler {
|
|
||||||
public:
|
|
||||||
static Result<SparseMatrix> assembleStiffness(
|
|
||||||
const AnalysisModel& model,
|
|
||||||
const DofManager& dofs,
|
|
||||||
const ParallelFor& parallelFor);
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace fesa
|
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
#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_
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include "fesa/core/status.h"
|
|
||||||
#include "fesa/math/sparse_matrix.h"
|
|
||||||
#include "fesa/math/vector.h"
|
|
||||||
|
|
||||||
namespace fesa {
|
|
||||||
|
|
||||||
class DofManager;
|
|
||||||
|
|
||||||
struct PartitionedStiffness {
|
|
||||||
SparseMatrix kff;
|
|
||||||
SparseMatrix kfc;
|
|
||||||
SparseMatrix kcf;
|
|
||||||
SparseMatrix kcc;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Applies the DofManager's stable elimination order without owning equation
|
|
||||||
// numbering, load assembly, or a solver policy.
|
|
||||||
class EssentialConstraints {
|
|
||||||
public:
|
|
||||||
static Result<PartitionedStiffness> partition(
|
|
||||||
const SparseMatrix& full,
|
|
||||||
const DofManager& dofs);
|
|
||||||
static Vector gatherFree(const Vector& full, const DofManager& dofs);
|
|
||||||
static Vector gatherConstrained(
|
|
||||||
const Vector& full,
|
|
||||||
const DofManager& dofs);
|
|
||||||
static Vector reconstructFull(
|
|
||||||
const Vector& freeValues,
|
|
||||||
const Vector& constrainedValues,
|
|
||||||
const DofManager& dofs);
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace fesa
|
|
||||||
@@ -0,0 +1,83 @@
|
|||||||
|
#ifndef FESA_FEM_DOF_MANAGER_H_
|
||||||
|
#define FESA_FEM_DOF_MANAGER_H_
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
#include <cstddef>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <optional>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "fesa/analysis/analysis_model.h"
|
||||||
|
#include "fesa/math/vector.h"
|
||||||
|
|
||||||
|
namespace fesa {
|
||||||
|
|
||||||
|
/// @brief Identifies one component in the stable six-DOF node layout.
|
||||||
|
enum class DofComponent : std::uint8_t {
|
||||||
|
kUx,
|
||||||
|
kUy,
|
||||||
|
kUz,
|
||||||
|
kUrx,
|
||||||
|
kUry,
|
||||||
|
kUrz,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// @brief Stores the stable structural CSR pattern.
|
||||||
|
struct SparsePattern {
|
||||||
|
std::vector<std::size_t> row_offsets;
|
||||||
|
std::vector<std::size_t> column_indices;
|
||||||
|
};
|
||||||
|
|
||||||
|
/// @brief Owns full/free/constrained numbering, scatter maps, and CSR pattern.
|
||||||
|
class DofManager {
|
||||||
|
public:
|
||||||
|
/// @brief Creates every equation-space mapping for an active model.
|
||||||
|
static Result<DofManager> Create(const AnalysisModel& model);
|
||||||
|
|
||||||
|
/// @brief Returns the full node-by-component DOF count.
|
||||||
|
std::size_t FullDofCount() const noexcept;
|
||||||
|
/// @brief Returns the free-equation count.
|
||||||
|
std::size_t FreeDofCount() const noexcept;
|
||||||
|
/// @brief Returns the prescribed-DOF count.
|
||||||
|
std::size_t ConstrainedDofCount() const noexcept;
|
||||||
|
/// @brief Maps a stable node index and component to a full DOF.
|
||||||
|
std::size_t FullDof(EntityIndex node, DofComponent component) const;
|
||||||
|
/// @brief Returns the free equation for a full DOF when unconstrained.
|
||||||
|
std::optional<std::size_t> FreeEquation(std::size_t full_dof) const;
|
||||||
|
/// @brief Returns a beam scatter in endpoint/component order.
|
||||||
|
const std::array<std::size_t, 12>& ElementScatter(EntityIndex element) const;
|
||||||
|
/// @brief Returns a shell scatter in node/component order.
|
||||||
|
const std::array<std::size_t, 24>& ShellElementScatter(
|
||||||
|
EntityIndex element) const;
|
||||||
|
/// @brief Returns free full DOFs in stable increasing order.
|
||||||
|
const std::vector<std::size_t>& FreeDofs() const noexcept;
|
||||||
|
/// @brief Returns constrained full DOFs in stable increasing order.
|
||||||
|
const std::vector<std::size_t>& ConstrainedDofs() const noexcept;
|
||||||
|
/// @brief Returns dc in constrained-DOF order.
|
||||||
|
const Vector& PrescribedValues() const noexcept;
|
||||||
|
/// @brief Returns the full-space structural CSR pattern.
|
||||||
|
const SparsePattern& GetSparsePattern() const noexcept;
|
||||||
|
|
||||||
|
private:
|
||||||
|
/// @brief Takes ownership of fully validated stable equation mappings.
|
||||||
|
DofManager(std::size_t full_dof_count,
|
||||||
|
std::vector<std::optional<std::size_t>> free_equations,
|
||||||
|
std::vector<std::array<std::size_t, 12>> element_scatters,
|
||||||
|
std::vector<std::array<std::size_t, 24>> shell_element_scatters,
|
||||||
|
std::vector<std::size_t> free_dofs,
|
||||||
|
std::vector<std::size_t> constrained_dofs,
|
||||||
|
Vector prescribed_values, SparsePattern sparse_pattern);
|
||||||
|
|
||||||
|
std::size_t full_dof_count_;
|
||||||
|
std::vector<std::optional<std::size_t>> free_equations_;
|
||||||
|
std::vector<std::array<std::size_t, 12>> element_scatters_;
|
||||||
|
std::vector<std::array<std::size_t, 24>> shell_element_scatters_;
|
||||||
|
std::vector<std::size_t> free_dofs_;
|
||||||
|
std::vector<std::size_t> constrained_dofs_;
|
||||||
|
Vector prescribed_values_;
|
||||||
|
SparsePattern sparse_pattern_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace fesa
|
||||||
|
|
||||||
|
#endif // FESA_FEM_DOF_MANAGER_H_
|
||||||
@@ -1,69 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include "fesa/analysis/analysis_model.hpp"
|
|
||||||
#include "fesa/math/vector.h"
|
|
||||||
|
|
||||||
#include <array>
|
|
||||||
#include <cstddef>
|
|
||||||
#include <cstdint>
|
|
||||||
#include <optional>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
namespace fesa {
|
|
||||||
|
|
||||||
enum class DofComponent : std::uint8_t {
|
|
||||||
ux,
|
|
||||||
uy,
|
|
||||||
uz,
|
|
||||||
urx,
|
|
||||||
ury,
|
|
||||||
urz
|
|
||||||
};
|
|
||||||
|
|
||||||
struct SparsePattern {
|
|
||||||
std::vector<std::size_t> rowOffsets;
|
|
||||||
std::vector<std::size_t> columnIndices;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Owns every equation-space mapping so semantic model objects remain free of
|
|
||||||
// analysis-specific equation IDs.
|
|
||||||
class DofManager {
|
|
||||||
public:
|
|
||||||
static Result<DofManager> create(const AnalysisModel& model);
|
|
||||||
|
|
||||||
std::size_t fullDofCount() const noexcept;
|
|
||||||
std::size_t freeDofCount() const noexcept;
|
|
||||||
std::size_t constrainedDofCount() const noexcept;
|
|
||||||
std::size_t fullDof(EntityIndex node, DofComponent component) const;
|
|
||||||
std::optional<std::size_t> freeEquation(std::size_t fullDof) const;
|
|
||||||
const std::array<std::size_t, 12>& elementScatter(
|
|
||||||
EntityIndex element) const;
|
|
||||||
const std::array<std::size_t, 24>& shellElementScatter(
|
|
||||||
EntityIndex element) const;
|
|
||||||
const std::vector<std::size_t>& freeDofs() const noexcept;
|
|
||||||
const std::vector<std::size_t>& constrainedDofs() const noexcept;
|
|
||||||
const Vector& prescribedValues() const noexcept;
|
|
||||||
const SparsePattern& sparsePattern() const noexcept;
|
|
||||||
|
|
||||||
private:
|
|
||||||
DofManager(
|
|
||||||
std::size_t fullDofCount,
|
|
||||||
std::vector<std::optional<std::size_t>> freeEquations,
|
|
||||||
std::vector<std::array<std::size_t, 12>> elementScatters,
|
|
||||||
std::vector<std::array<std::size_t, 24>> shellElementScatters,
|
|
||||||
std::vector<std::size_t> freeDofs,
|
|
||||||
std::vector<std::size_t> constrainedDofs,
|
|
||||||
Vector prescribedValues,
|
|
||||||
SparsePattern sparsePattern);
|
|
||||||
|
|
||||||
std::size_t fullDofCount_;
|
|
||||||
std::vector<std::optional<std::size_t>> freeEquations_;
|
|
||||||
std::vector<std::array<std::size_t, 12>> elementScatters_;
|
|
||||||
std::vector<std::array<std::size_t, 24>> shellElementScatters_;
|
|
||||||
std::vector<std::size_t> freeDofs_;
|
|
||||||
std::vector<std::size_t> constrainedDofs_;
|
|
||||||
Vector prescribedValues_;
|
|
||||||
SparsePattern sparsePattern_;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace fesa
|
|
||||||
@@ -1,13 +1,13 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "fesa/results/results_writer.hpp"
|
#include "fesa/results/results_writer.h"
|
||||||
|
|
||||||
namespace fesa {
|
namespace fesa {
|
||||||
|
|
||||||
// Writes schema-v0 output while keeping backend and platform types private.
|
// Writes schema-v0 output while keeping backend and platform types private.
|
||||||
class Hdf5ResultsWriter final : public ResultsWriter {
|
class Hdf5ResultsWriter final : public ResultsWriter {
|
||||||
public:
|
public:
|
||||||
Status write(
|
Status Write(
|
||||||
const std::filesystem::path& outputPath,
|
const std::filesystem::path& outputPath,
|
||||||
const Domain& domain,
|
const Domain& domain,
|
||||||
const AnalysisState& state,
|
const AnalysisState& state,
|
||||||
|
|||||||
@@ -0,0 +1,86 @@
|
|||||||
|
#ifndef FESA_RESULTS_RESULT_RECORDS_H_
|
||||||
|
#define FESA_RESULTS_RESULT_RECORDS_H_
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
#include <cstddef>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "fesa/model/model_types.h"
|
||||||
|
|
||||||
|
namespace fesa {
|
||||||
|
|
||||||
|
/// @brief Identifies one deterministic result frame.
|
||||||
|
struct StepFrameIdentity {
|
||||||
|
std::string step_name;
|
||||||
|
std::size_t frame_index;
|
||||||
|
};
|
||||||
|
|
||||||
|
/// @brief Stores one beam endpoint action and section-resultant row.
|
||||||
|
struct EndpointResultRow {
|
||||||
|
EntityIndex element;
|
||||||
|
int endpoint;
|
||||||
|
SourceEntityId node;
|
||||||
|
std::array<double, 6> end_action;
|
||||||
|
std::array<double, 4> section_resultant;
|
||||||
|
};
|
||||||
|
|
||||||
|
/// @brief Stores one beam Gauss generalized result row.
|
||||||
|
struct GaussResultRow {
|
||||||
|
EntityIndex element;
|
||||||
|
int gauss_point;
|
||||||
|
std::array<double, 4> generalized_strain;
|
||||||
|
std::array<double, 4> generalized_resultant;
|
||||||
|
};
|
||||||
|
|
||||||
|
/// @brief Stores one beam axial-stress section-point row.
|
||||||
|
struct StressS11Row {
|
||||||
|
EntityIndex element;
|
||||||
|
int gauss_point;
|
||||||
|
std::size_t section_point;
|
||||||
|
double x1;
|
||||||
|
double x2;
|
||||||
|
double s11;
|
||||||
|
std::string source;
|
||||||
|
};
|
||||||
|
|
||||||
|
/// @brief Identifies one MITC4 midsurface integration location.
|
||||||
|
enum class ShellMidsurfaceLocation { kGp1, kGp2, kGp3, kGp4 };
|
||||||
|
|
||||||
|
/// @brief Identifies one through-thickness shell recovery position.
|
||||||
|
enum class ShellSectionPosition { kBottom, kMiddle, kTop };
|
||||||
|
|
||||||
|
/// @brief Stores one through-thickness shell stress row.
|
||||||
|
struct ShellSectionStressRow {
|
||||||
|
ShellSectionPosition position;
|
||||||
|
double zeta;
|
||||||
|
std::array<double, 3> components;
|
||||||
|
};
|
||||||
|
|
||||||
|
/// @brief Stores one MITC4 physical recovery row.
|
||||||
|
struct ShellResultRow {
|
||||||
|
EntityIndex element;
|
||||||
|
ShellMidsurfaceLocation location;
|
||||||
|
std::array<double, 2> natural_coordinates;
|
||||||
|
// Axis rows [e1,e2,e3], global-component columns.
|
||||||
|
std::array<std::array<double, 3>, 3> local_frame;
|
||||||
|
std::array<double, 8> generalized_strain;
|
||||||
|
std::array<double, 8> section_resultant;
|
||||||
|
// Fixed BOTTOM, MIDDLE, TOP order; components are [S11,S22,S12].
|
||||||
|
std::array<ShellSectionStressRow, 3> stress;
|
||||||
|
};
|
||||||
|
|
||||||
|
/// @brief Carries a complete shell result candidate for atomic validation.
|
||||||
|
struct ShellStateCandidate {
|
||||||
|
std::vector<ShellResultRow> rows;
|
||||||
|
double physical_strain_energy{0.0};
|
||||||
|
// [FORCE_1,FORCE_2,FORCE_3,MOMENT_1,MOMENT_2,MOMENT_3].
|
||||||
|
std::array<double, 6> equilibrium{};
|
||||||
|
// [FREE_RESIDUAL_NORMALIZED,FORCE_BALANCE_NORMALIZED,
|
||||||
|
// MOMENT_BALANCE_NORMALIZED].
|
||||||
|
std::array<double, 3> verification_metrics{};
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace fesa
|
||||||
|
|
||||||
|
#endif // FESA_RESULTS_RESULT_RECORDS_H_
|
||||||
@@ -1,83 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include "fesa/model/model_types.h"
|
|
||||||
|
|
||||||
#include <array>
|
|
||||||
#include <cstddef>
|
|
||||||
#include <string>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
namespace fesa {
|
|
||||||
|
|
||||||
struct StepFrameIdentity {
|
|
||||||
std::string stepName;
|
|
||||||
std::size_t frameIndex;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct EndpointResultRow {
|
|
||||||
EntityIndex element;
|
|
||||||
int endpoint;
|
|
||||||
SourceEntityId node;
|
|
||||||
std::array<double, 6> endAction;
|
|
||||||
std::array<double, 4> sectionResultant;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct GaussResultRow {
|
|
||||||
EntityIndex element;
|
|
||||||
int gaussPoint;
|
|
||||||
std::array<double, 4> generalizedStrain;
|
|
||||||
std::array<double, 4> generalizedResultant;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct StressS11Row {
|
|
||||||
EntityIndex element;
|
|
||||||
int gaussPoint;
|
|
||||||
std::size_t sectionPoint;
|
|
||||||
double x1;
|
|
||||||
double x2;
|
|
||||||
double s11;
|
|
||||||
std::string source;
|
|
||||||
};
|
|
||||||
|
|
||||||
enum class ShellMidsurfaceLocation {
|
|
||||||
gp1,
|
|
||||||
gp2,
|
|
||||||
gp3,
|
|
||||||
gp4
|
|
||||||
};
|
|
||||||
|
|
||||||
enum class ShellSectionPosition {
|
|
||||||
bottom,
|
|
||||||
middle,
|
|
||||||
top
|
|
||||||
};
|
|
||||||
|
|
||||||
struct ShellSectionStressRow {
|
|
||||||
ShellSectionPosition position;
|
|
||||||
double zeta;
|
|
||||||
std::array<double, 3> components;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct ShellResultRow {
|
|
||||||
EntityIndex element;
|
|
||||||
ShellMidsurfaceLocation location;
|
|
||||||
std::array<double, 2> naturalCoordinates;
|
|
||||||
// Axis rows [e1,e2,e3], global-component columns.
|
|
||||||
std::array<std::array<double, 3>, 3> localFrame;
|
|
||||||
std::array<double, 8> generalizedStrain;
|
|
||||||
std::array<double, 8> sectionResultant;
|
|
||||||
// Fixed BOTTOM, MIDDLE, TOP order; components are [S11,S22,S12].
|
|
||||||
std::array<ShellSectionStressRow, 3> stress;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct ShellStateCandidate {
|
|
||||||
std::vector<ShellResultRow> rows;
|
|
||||||
double physicalStrainEnergy{0.0};
|
|
||||||
// [FORCE_1,FORCE_2,FORCE_3,MOMENT_1,MOMENT_2,MOMENT_3].
|
|
||||||
std::array<double, 6> equilibrium{};
|
|
||||||
// [FREE_RESIDUAL_NORMALIZED,FORCE_BALANCE_NORMALIZED,
|
|
||||||
// MOMENT_BALANCE_NORMALIZED].
|
|
||||||
std::array<double, 3> verificationMetrics{};
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace fesa
|
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
#ifndef FESA_RESULTS_RESULT_RECOVERY_H_
|
||||||
|
#define FESA_RESULTS_RESULT_RECOVERY_H_
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "fesa/analysis/analysis_model.h"
|
||||||
|
#include "fesa/analysis/analysis_state.h"
|
||||||
|
#include "fesa/core/status.h"
|
||||||
|
#include "fesa/fem/dof_manager.h"
|
||||||
|
#include "fesa/math/sparse_matrix.h"
|
||||||
|
|
||||||
|
namespace fesa {
|
||||||
|
|
||||||
|
/// @brief Stores one normalized positive-local-x node-station row.
|
||||||
|
struct NodeStationResultRow {
|
||||||
|
SourceEntityId node;
|
||||||
|
EntityIndex representative_element;
|
||||||
|
std::array<double, 4> section_resultant;
|
||||||
|
};
|
||||||
|
|
||||||
|
/// @brief Recovers full equilibrium and active concrete element rows.
|
||||||
|
class ResultRecovery {
|
||||||
|
public:
|
||||||
|
/// @brief Builds and atomically commits a complete recovery candidate.
|
||||||
|
/// @return Success after full residual K*d-F and all result rows validate.
|
||||||
|
static Status Recover(const AnalysisModel& model, const DofManager& dofs,
|
||||||
|
const SparseMatrix& full_stiffness,
|
||||||
|
AnalysisState& state);
|
||||||
|
|
||||||
|
/// @brief Normalizes eligible endpoint rows to source node stations.
|
||||||
|
/// @param component_tolerances Per-component interior-station tolerances.
|
||||||
|
/// @return Stable source-node rows or a structured identity/value failure.
|
||||||
|
static Result<std::vector<NodeStationResultRow>>
|
||||||
|
NormalizeSectionResultantsToNodeStations(
|
||||||
|
const AnalysisModel& model,
|
||||||
|
const std::vector<EndpointResultRow>& endpoint_rows,
|
||||||
|
const std::array<double, 4>& component_tolerances);
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace fesa
|
||||||
|
|
||||||
|
#endif // FESA_RESULTS_RESULT_RECOVERY_H_
|
||||||
@@ -1,36 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include "fesa/analysis/analysis_model.hpp"
|
|
||||||
#include "fesa/analysis/analysis_state.hpp"
|
|
||||||
#include "fesa/core/status.h"
|
|
||||||
#include "fesa/fem/dof_manager.hpp"
|
|
||||||
#include "fesa/math/sparse_matrix.h"
|
|
||||||
|
|
||||||
#include <array>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
namespace fesa {
|
|
||||||
|
|
||||||
struct NodeStationResultRow {
|
|
||||||
SourceEntityId node;
|
|
||||||
EntityIndex representativeElement;
|
|
||||||
std::array<double, 4> sectionResultant;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Recovers full-space equilibrium and the active concrete element rows without
|
|
||||||
// exposing element or sparse-backend details to result consumers.
|
|
||||||
class ResultRecovery {
|
|
||||||
public:
|
|
||||||
static Status recover(const AnalysisModel& model,
|
|
||||||
const DofManager& dofs,
|
|
||||||
const SparseMatrix& fullStiffness,
|
|
||||||
AnalysisState& state);
|
|
||||||
|
|
||||||
static Result<std::vector<NodeStationResultRow>>
|
|
||||||
normalizeSectionResultantsToNodeStations(
|
|
||||||
const AnalysisModel& model,
|
|
||||||
const std::vector<EndpointResultRow>& endpointRows,
|
|
||||||
const std::array<double, 4>& componentTolerances);
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace fesa
|
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
#ifndef FESA_RESULTS_RESULTS_WRITER_H_
|
||||||
|
#define FESA_RESULTS_RESULTS_WRITER_H_
|
||||||
|
|
||||||
|
#include <filesystem>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "fesa/analysis/analysis_state.h"
|
||||||
|
#include "fesa/core/diagnostic.h"
|
||||||
|
#include "fesa/core/status.h"
|
||||||
|
#include "fesa/model/domain.h"
|
||||||
|
|
||||||
|
namespace fesa {
|
||||||
|
|
||||||
|
/// @brief Isolates authoritative result storage from the solver core.
|
||||||
|
class ResultsWriter {
|
||||||
|
public:
|
||||||
|
virtual ~ResultsWriter() = default;
|
||||||
|
|
||||||
|
/// @brief Writes one complete validated analysis state.
|
||||||
|
/// @return Success only after backend-specific finalization completes.
|
||||||
|
virtual Status Write(const std::filesystem::path& output_path,
|
||||||
|
const Domain& domain, const AnalysisState& state,
|
||||||
|
const std::vector<Diagnostic>& diagnostics) = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace fesa
|
||||||
|
|
||||||
|
#endif // FESA_RESULTS_RESULTS_WRITER_H_
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include "fesa/analysis/analysis_state.hpp"
|
|
||||||
#include "fesa/core/diagnostic.h"
|
|
||||||
#include "fesa/core/status.h"
|
|
||||||
#include "fesa/model/domain.h"
|
|
||||||
|
|
||||||
#include <filesystem>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
namespace fesa {
|
|
||||||
|
|
||||||
// Keeps the solver core independent of the authoritative result-storage backend.
|
|
||||||
class ResultsWriter {
|
|
||||||
public:
|
|
||||||
virtual ~ResultsWriter() = default;
|
|
||||||
|
|
||||||
virtual Status write(
|
|
||||||
const std::filesystem::path& outputPath,
|
|
||||||
const Domain& domain,
|
|
||||||
const AnalysisState& state,
|
|
||||||
const std::vector<Diagnostic>& diagnostics) = 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace fesa
|
|
||||||
@@ -1,14 +1,14 @@
|
|||||||
#include "fesa/analysis/analysis_model.hpp"
|
#include "fesa/analysis/analysis_model.h"
|
||||||
|
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
namespace fesa {
|
namespace fesa {
|
||||||
|
|
||||||
Result<AnalysisModel> AnalysisModel::create(const Domain& domain) {
|
Result<AnalysisModel> AnalysisModel::Create(const Domain& domain) {
|
||||||
if (domain.Steps().empty()) {
|
if (domain.Steps().empty()) {
|
||||||
return Result<AnalysisModel>::Failure(Status::Failure(
|
return Result<AnalysisModel>::Failure(
|
||||||
FailureCategory::kInput,
|
Status::Failure(FailureCategory::kInput,
|
||||||
{{Severity::kError,
|
{{Severity::kError,
|
||||||
"invalid-model-cardinality",
|
"invalid-model-cardinality",
|
||||||
{domain.SourcePath(), 0U},
|
{domain.SourcePath(), 0U},
|
||||||
@@ -17,79 +17,73 @@ Result<AnalysisModel> AnalysisModel::create(const Domain& domain) {
|
|||||||
"AnalysisModel requires exactly one static step."}}));
|
"AnalysisModel requires exactly one static step."}}));
|
||||||
}
|
}
|
||||||
if (domain.Steps().size() > 1U) {
|
if (domain.Steps().size() > 1U) {
|
||||||
const auto& secondStep = domain.Steps()[1];
|
const auto& second_step = domain.Steps()[1];
|
||||||
return Result<AnalysisModel>::Failure(Status::Failure(
|
return Result<AnalysisModel>::Failure(
|
||||||
FailureCategory::kInput,
|
Status::Failure(FailureCategory::kInput,
|
||||||
{{Severity::kError,
|
{{Severity::kError, "unsupported-multiple-step",
|
||||||
"unsupported-multiple-step",
|
second_step.location, "STEP", second_step.name,
|
||||||
secondStep.location,
|
|
||||||
"STEP",
|
|
||||||
secondStep.name,
|
|
||||||
"AnalysisModel does not support multiple steps."}}));
|
"AnalysisModel does not support multiple steps."}}));
|
||||||
}
|
}
|
||||||
return Result<AnalysisModel>::Success(AnalysisModel{domain});
|
return Result<AnalysisModel>::Success(AnalysisModel{domain});
|
||||||
}
|
}
|
||||||
|
|
||||||
const Domain& AnalysisModel::domain() const noexcept {
|
const Domain& AnalysisModel::GetDomain() const noexcept { return *domain_; }
|
||||||
return *domain_;
|
|
||||||
}
|
|
||||||
|
|
||||||
const StaticStepDefinition& AnalysisModel::step() const noexcept {
|
const StaticStepDefinition& AnalysisModel::Step() const noexcept {
|
||||||
return domain_->Steps().front();
|
return domain_->Steps().front();
|
||||||
}
|
}
|
||||||
|
|
||||||
const std::vector<EntityIndex>& AnalysisModel::activeElements() const noexcept {
|
const std::vector<EntityIndex>& AnalysisModel::ActiveElements() const noexcept {
|
||||||
return activeElements_;
|
return active_elements_;
|
||||||
}
|
}
|
||||||
|
|
||||||
const std::vector<EntityIndex>& AnalysisModel::activeMaterials() const noexcept {
|
const std::vector<EntityIndex>& AnalysisModel::ActiveMaterials()
|
||||||
return activeMaterials_;
|
const noexcept {
|
||||||
|
return active_materials_;
|
||||||
}
|
}
|
||||||
|
|
||||||
const std::vector<EntityIndex>& AnalysisModel::activeSections() const noexcept {
|
const std::vector<EntityIndex>& AnalysisModel::ActiveSections() const noexcept {
|
||||||
return activeSections_;
|
return active_sections_;
|
||||||
}
|
}
|
||||||
|
|
||||||
const std::vector<EntityIndex>&
|
const std::vector<EntityIndex>& AnalysisModel::ActiveBoundaryConditions()
|
||||||
AnalysisModel::activeBoundaryConditions() const noexcept {
|
const noexcept {
|
||||||
return activeBoundaryConditions_;
|
return active_boundary_conditions_;
|
||||||
}
|
}
|
||||||
|
|
||||||
const std::vector<EntityIndex>& AnalysisModel::activeLoads() const noexcept {
|
const std::vector<EntityIndex>& AnalysisModel::ActiveLoads() const noexcept {
|
||||||
return activeLoads_;
|
return active_loads_;
|
||||||
}
|
}
|
||||||
|
|
||||||
AnalysisModel::AnalysisModel(const Domain& domain) : domain_{&domain} {
|
AnalysisModel::AnalysisModel(const Domain& domain) : domain_{&domain} {
|
||||||
std::vector<bool> reachableMaterials(domain.Materials().size(), false);
|
std::vector<bool> reachable_materials(domain.Materials().size(), false);
|
||||||
std::vector<bool> reachableSections(domain.Sections().size(), false);
|
std::vector<bool> reachable_sections(domain.Sections().size(), false);
|
||||||
|
|
||||||
for (std::size_t index = 0U; index < domain.Elements().size(); ++index) {
|
for (std::size_t index = 0U; index < domain.Elements().size(); ++index) {
|
||||||
const auto& element = domain.Elements()[index];
|
const auto& element = domain.Elements()[index];
|
||||||
activeElements_.push_back(static_cast<EntityIndex>(index));
|
active_elements_.push_back(static_cast<EntityIndex>(index));
|
||||||
reachableMaterials[element.material_index] = true;
|
reachable_materials[element.material_index] = true;
|
||||||
reachableSections[element.section_index] = true;
|
reachable_sections[element.section_index] = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ascending vector positions are the stable internal order, independent
|
// Ascending vector positions are the stable internal order, independent
|
||||||
// of first reachability or duplicate element assignments.
|
// of first reachability or duplicate element assignments.
|
||||||
for (std::size_t index = 0U; index < reachableMaterials.size(); ++index) {
|
for (std::size_t index = 0U; index < reachable_materials.size(); ++index) {
|
||||||
if (reachableMaterials[index]) {
|
if (reachable_materials[index]) {
|
||||||
activeMaterials_.push_back(static_cast<EntityIndex>(index));
|
active_materials_.push_back(static_cast<EntityIndex>(index));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (std::size_t index = 0U; index < reachableSections.size(); ++index) {
|
for (std::size_t index = 0U; index < reachable_sections.size(); ++index) {
|
||||||
if (reachableSections[index]) {
|
if (reachable_sections[index]) {
|
||||||
activeSections_.push_back(static_cast<EntityIndex>(index));
|
active_sections_.push_back(static_cast<EntityIndex>(index));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (std::size_t index = 0U;
|
for (std::size_t index = 0U; index < Step().boundaries.size(); ++index) {
|
||||||
index < step().boundaries.size();
|
active_boundary_conditions_.push_back(static_cast<EntityIndex>(index));
|
||||||
++index) {
|
|
||||||
activeBoundaryConditions_.push_back(static_cast<EntityIndex>(index));
|
|
||||||
}
|
}
|
||||||
for (std::size_t index = 0U; index < step().loads.size(); ++index) {
|
for (std::size_t index = 0U; index < Step().loads.size(); ++index) {
|
||||||
activeLoads_.push_back(static_cast<EntityIndex>(index));
|
active_loads_.push_back(static_cast<EntityIndex>(index));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
#include "fesa/analysis/analysis_state.hpp"
|
#include "fesa/analysis/analysis_state.h"
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <array>
|
#include <array>
|
||||||
@@ -12,244 +12,212 @@ namespace {
|
|||||||
|
|
||||||
constexpr std::size_t kShellLocationsPerElement = 4U;
|
constexpr std::size_t kShellLocationsPerElement = 4U;
|
||||||
|
|
||||||
Status shellCandidateFailure(
|
/// @brief Creates a structured failure without mutating existing state.
|
||||||
const std::string& code,
|
Status ShellCandidateFailure(const std::string& code,
|
||||||
const std::string& identity,
|
const std::string& identity,
|
||||||
const std::string& message) {
|
const std::string& message) {
|
||||||
return Status::Failure(
|
return Status::Failure(
|
||||||
FailureCategory::kModel,
|
FailureCategory::kModel,
|
||||||
{{Severity::kError,
|
{{Severity::kError, code, {}, "ANALYSIS_STATE", identity, message}});
|
||||||
code,
|
|
||||||
{},
|
|
||||||
"ANALYSIS_STATE",
|
|
||||||
identity,
|
|
||||||
message}});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
template<std::size_t Size>
|
template <std::size_t Size>
|
||||||
bool finite(const std::array<double, Size>& values) {
|
/// @brief Tests one fixed-size candidate component array for finite values.
|
||||||
return std::all_of(
|
bool IsFinite(const std::array<double, Size>& values) {
|
||||||
values.begin(), values.end(),
|
return std::all_of(values.begin(), values.end(),
|
||||||
[](const double value) { return std::isfinite(value); });
|
[](const double value) { return std::isfinite(value); });
|
||||||
}
|
}
|
||||||
|
|
||||||
bool finite(const ShellResultRow& row) {
|
/// @brief Tests every physical shell row component for finite values.
|
||||||
if (!finite(row.naturalCoordinates) ||
|
bool IsFinite(const ShellResultRow& row) {
|
||||||
!finite(row.generalizedStrain) ||
|
if (!IsFinite(row.natural_coordinates) || !IsFinite(row.generalized_strain) ||
|
||||||
!finite(row.sectionResultant)) {
|
!IsFinite(row.section_resultant)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
for (const auto& axis : row.localFrame) {
|
for (const auto& axis : row.local_frame) {
|
||||||
if (!finite(axis)) {
|
if (!IsFinite(axis)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return std::all_of(
|
return std::all_of(row.stress.begin(), row.stress.end(),
|
||||||
row.stress.begin(), row.stress.end(),
|
|
||||||
[](const ShellSectionStressRow& stress) {
|
[](const ShellSectionStressRow& stress) {
|
||||||
return std::isfinite(stress.zeta) && finite(stress.components);
|
return std::isfinite(stress.zeta) &&
|
||||||
|
IsFinite(stress.components);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
AnalysisState AnalysisState::create(
|
AnalysisState AnalysisState::Create(const DofManager& dofs,
|
||||||
const DofManager& dofs, StepFrameIdentity identity) {
|
StepFrameIdentity identity) {
|
||||||
return AnalysisState{dofs.fullDofCount(), std::move(identity)};
|
return AnalysisState{dofs.FullDofCount(), std::move(identity)};
|
||||||
}
|
}
|
||||||
|
|
||||||
Vector& AnalysisState::displacement() noexcept {
|
Vector& AnalysisState::Displacement() noexcept { return displacement_; }
|
||||||
|
|
||||||
|
const Vector& AnalysisState::Displacement() const noexcept {
|
||||||
return displacement_;
|
return displacement_;
|
||||||
}
|
}
|
||||||
|
|
||||||
const Vector& AnalysisState::displacement() const noexcept {
|
Vector& AnalysisState::ExternalForce() noexcept { return external_force_; }
|
||||||
return displacement_;
|
|
||||||
|
const Vector& AnalysisState::ExternalForce() const noexcept {
|
||||||
|
return external_force_;
|
||||||
}
|
}
|
||||||
|
|
||||||
Vector& AnalysisState::externalForce() noexcept {
|
Vector& AnalysisState::InternalForce() noexcept { return internal_force_; }
|
||||||
return externalForce_;
|
|
||||||
|
const Vector& AnalysisState::InternalForce() const noexcept {
|
||||||
|
return internal_force_;
|
||||||
}
|
}
|
||||||
|
|
||||||
const Vector& AnalysisState::externalForce() const noexcept {
|
Vector& AnalysisState::Residual() noexcept { return residual_; }
|
||||||
return externalForce_;
|
|
||||||
}
|
|
||||||
|
|
||||||
Vector& AnalysisState::internalForce() noexcept {
|
const Vector& AnalysisState::Residual() const noexcept { return residual_; }
|
||||||
return internalForce_;
|
|
||||||
}
|
|
||||||
|
|
||||||
const Vector& AnalysisState::internalForce() const noexcept {
|
Vector& AnalysisState::Reaction() noexcept { return reaction_; }
|
||||||
return internalForce_;
|
|
||||||
}
|
|
||||||
|
|
||||||
Vector& AnalysisState::residual() noexcept {
|
const Vector& AnalysisState::Reaction() const noexcept { return reaction_; }
|
||||||
return residual_;
|
|
||||||
}
|
|
||||||
|
|
||||||
const Vector& AnalysisState::residual() const noexcept {
|
const StepFrameIdentity& AnalysisState::Identity() const noexcept {
|
||||||
return residual_;
|
|
||||||
}
|
|
||||||
|
|
||||||
Vector& AnalysisState::reaction() noexcept {
|
|
||||||
return reaction_;
|
|
||||||
}
|
|
||||||
|
|
||||||
const Vector& AnalysisState::reaction() const noexcept {
|
|
||||||
return reaction_;
|
|
||||||
}
|
|
||||||
|
|
||||||
const StepFrameIdentity& AnalysisState::identity() const noexcept {
|
|
||||||
return identity_;
|
return identity_;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<EndpointResultRow>& AnalysisState::endpointResults() noexcept {
|
std::vector<EndpointResultRow>& AnalysisState::EndpointResults() noexcept {
|
||||||
return endpointResults_;
|
return endpoint_results_;
|
||||||
}
|
}
|
||||||
|
|
||||||
const std::vector<EndpointResultRow>& AnalysisState::endpointResults() const noexcept {
|
const std::vector<EndpointResultRow>& AnalysisState::EndpointResults()
|
||||||
return endpointResults_;
|
const noexcept {
|
||||||
|
return endpoint_results_;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<GaussResultRow>& AnalysisState::gaussResults() noexcept {
|
std::vector<GaussResultRow>& AnalysisState::GaussResults() noexcept {
|
||||||
return gaussResults_;
|
return gauss_results_;
|
||||||
}
|
}
|
||||||
|
|
||||||
const std::vector<GaussResultRow>& AnalysisState::gaussResults() const noexcept {
|
const std::vector<GaussResultRow>& AnalysisState::GaussResults()
|
||||||
return gaussResults_;
|
const noexcept {
|
||||||
|
return gauss_results_;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<StressS11Row>& AnalysisState::stressResults() noexcept {
|
std::vector<StressS11Row>& AnalysisState::StressResults() noexcept {
|
||||||
return stressResults_;
|
return stress_results_;
|
||||||
}
|
}
|
||||||
|
|
||||||
const std::vector<StressS11Row>& AnalysisState::stressResults() const noexcept {
|
const std::vector<StressS11Row>& AnalysisState::StressResults() const noexcept {
|
||||||
return stressResults_;
|
return stress_results_;
|
||||||
}
|
}
|
||||||
|
|
||||||
Status AnalysisState::commitShellResults(
|
Status AnalysisState::CommitShellResults(
|
||||||
const std::vector<EntityIndex>& expectedElementOrder,
|
const std::vector<EntityIndex>& expected_element_order,
|
||||||
ShellStateCandidate candidate) {
|
ShellStateCandidate candidate) {
|
||||||
if (expectedElementOrder.size() >
|
if (expected_element_order.size() >
|
||||||
(std::numeric_limits<std::size_t>::max)() /
|
(std::numeric_limits<std::size_t>::max)() / kShellLocationsPerElement) {
|
||||||
kShellLocationsPerElement) {
|
return ShellCandidateFailure(
|
||||||
return shellCandidateFailure(
|
"invalid-shell-state-inventory", identity_.step_name,
|
||||||
"invalid-shell-state-inventory",
|
|
||||||
identity_.stepName,
|
|
||||||
"The expected shell result inventory is too large.");
|
"The expected shell result inventory is too large.");
|
||||||
}
|
}
|
||||||
const std::size_t expectedRowCount =
|
const std::size_t expected_row_count =
|
||||||
expectedElementOrder.size() * kShellLocationsPerElement;
|
expected_element_order.size() * kShellLocationsPerElement;
|
||||||
if (candidate.rows.size() != expectedRowCount) {
|
if (candidate.rows.size() != expected_row_count) {
|
||||||
return shellCandidateFailure(
|
return ShellCandidateFailure(
|
||||||
"invalid-shell-state-inventory",
|
"invalid-shell-state-inventory", identity_.step_name,
|
||||||
identity_.stepName,
|
|
||||||
"Shell results require exactly four rows per expected element.");
|
"Shell results require exactly four rows per expected element.");
|
||||||
}
|
}
|
||||||
if (std::adjacent_find(
|
if (std::adjacent_find(expected_element_order.begin(),
|
||||||
expectedElementOrder.begin(), expectedElementOrder.end(),
|
expected_element_order.end(),
|
||||||
[](const EntityIndex left, const EntityIndex right) {
|
[](const EntityIndex left, const EntityIndex right) {
|
||||||
return left >= right;
|
return left >= right;
|
||||||
}) != expectedElementOrder.end()) {
|
}) != expected_element_order.end()) {
|
||||||
return shellCandidateFailure(
|
return ShellCandidateFailure(
|
||||||
"invalid-shell-state-inventory",
|
"invalid-shell-state-inventory", identity_.step_name,
|
||||||
identity_.stepName,
|
|
||||||
"Expected shell elements must be unique and in stable index order.");
|
"Expected shell elements must be unique and in stable index order.");
|
||||||
}
|
}
|
||||||
|
|
||||||
const std::array<ShellMidsurfaceLocation, kShellLocationsPerElement>
|
const std::array<ShellMidsurfaceLocation, kShellLocationsPerElement>
|
||||||
expectedLocations{
|
expected_locations{
|
||||||
ShellMidsurfaceLocation::gp1,
|
ShellMidsurfaceLocation::kGp1, ShellMidsurfaceLocation::kGp2,
|
||||||
ShellMidsurfaceLocation::gp2,
|
ShellMidsurfaceLocation::kGp3, ShellMidsurfaceLocation::kGp4};
|
||||||
ShellMidsurfaceLocation::gp3,
|
|
||||||
ShellMidsurfaceLocation::gp4};
|
|
||||||
const double gauss = 1.0 / std::sqrt(3.0);
|
const double gauss = 1.0 / std::sqrt(3.0);
|
||||||
const std::array<std::array<double, 2>, kShellLocationsPerElement>
|
const std::array<std::array<double, 2>, kShellLocationsPerElement>
|
||||||
expectedCoordinates{
|
expected_coordinates{std::array<double, 2>{-gauss, -gauss},
|
||||||
std::array<double, 2>{-gauss, -gauss},
|
|
||||||
std::array<double, 2>{gauss, -gauss},
|
std::array<double, 2>{gauss, -gauss},
|
||||||
std::array<double, 2>{gauss, gauss},
|
std::array<double, 2>{gauss, gauss},
|
||||||
std::array<double, 2>{-gauss, gauss}};
|
std::array<double, 2>{-gauss, gauss}};
|
||||||
const std::array<ShellSectionPosition, 3> expectedPositions{
|
const std::array<ShellSectionPosition, 3> expected_positions{
|
||||||
ShellSectionPosition::bottom,
|
ShellSectionPosition::kBottom, ShellSectionPosition::kMiddle,
|
||||||
ShellSectionPosition::middle,
|
ShellSectionPosition::kTop};
|
||||||
ShellSectionPosition::top};
|
constexpr std::array<double, 3> expected_zeta{-1.0, 0.0, 1.0};
|
||||||
constexpr std::array<double, 3> expectedZeta{-1.0, 0.0, 1.0};
|
for (std::size_t element_order = 0U;
|
||||||
for (std::size_t elementOrder = 0U;
|
element_order < expected_element_order.size(); ++element_order) {
|
||||||
elementOrder < expectedElementOrder.size();
|
for (std::size_t point = 0U; point < kShellLocationsPerElement; ++point) {
|
||||||
++elementOrder) {
|
const auto& row =
|
||||||
for (std::size_t point = 0U;
|
candidate.rows[element_order * kShellLocationsPerElement + point];
|
||||||
point < kShellLocationsPerElement;
|
if (row.element != expected_element_order[element_order] ||
|
||||||
++point) {
|
row.location != expected_locations[point] ||
|
||||||
const auto& row = candidate.rows[
|
row.natural_coordinates != expected_coordinates[point]) {
|
||||||
elementOrder * kShellLocationsPerElement + point];
|
return ShellCandidateFailure(
|
||||||
if (row.element != expectedElementOrder[elementOrder] ||
|
"invalid-shell-state-inventory", std::to_string(row.element),
|
||||||
row.location != expectedLocations[point] ||
|
|
||||||
row.naturalCoordinates != expectedCoordinates[point]) {
|
|
||||||
return shellCandidateFailure(
|
|
||||||
"invalid-shell-state-inventory",
|
|
||||||
std::to_string(row.element),
|
|
||||||
"Shell rows must preserve element and GP1 through GP4 identity.");
|
"Shell rows must preserve element and GP1 through GP4 identity.");
|
||||||
}
|
}
|
||||||
for (std::size_t position = 0U;
|
for (std::size_t position = 0U; position < expected_positions.size();
|
||||||
position < expectedPositions.size();
|
|
||||||
++position) {
|
++position) {
|
||||||
if (row.stress[position].position !=
|
if (row.stress[position].position != expected_positions[position] ||
|
||||||
expectedPositions[position] ||
|
row.stress[position].zeta != expected_zeta[position]) {
|
||||||
row.stress[position].zeta != expectedZeta[position]) {
|
return ShellCandidateFailure(
|
||||||
return shellCandidateFailure(
|
"invalid-shell-state-inventory", std::to_string(row.element),
|
||||||
"invalid-shell-state-inventory",
|
|
||||||
std::to_string(row.element),
|
|
||||||
"Shell stress rows require BOTTOM, MIDDLE, TOP identity.");
|
"Shell stress rows require BOTTOM, MIDDLE, TOP identity.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!finite(row)) {
|
if (!IsFinite(row)) {
|
||||||
return shellCandidateFailure(
|
return ShellCandidateFailure(
|
||||||
"nonfinite-shell-state-value",
|
"nonfinite-shell-state-value", std::to_string(row.element),
|
||||||
std::to_string(row.element),
|
|
||||||
"Shell result rows must contain only finite values.");
|
"Shell result rows must contain only finite values.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!std::isfinite(candidate.physicalStrainEnergy) ||
|
if (!std::isfinite(candidate.physical_strain_energy) ||
|
||||||
!finite(candidate.equilibrium) ||
|
!IsFinite(candidate.equilibrium) ||
|
||||||
!finite(candidate.verificationMetrics)) {
|
!IsFinite(candidate.verification_metrics)) {
|
||||||
return shellCandidateFailure(
|
return ShellCandidateFailure(
|
||||||
"nonfinite-shell-state-value",
|
"nonfinite-shell-state-value", identity_.step_name,
|
||||||
identity_.stepName,
|
|
||||||
"Shell energy, equilibrium, and normalized metrics must be finite.");
|
"Shell energy, equilibrium, and normalized metrics must be finite.");
|
||||||
}
|
}
|
||||||
|
|
||||||
shellResults_ = std::move(candidate.rows);
|
shell_results_ = std::move(candidate.rows);
|
||||||
physicalStrainEnergy_ = candidate.physicalStrainEnergy;
|
physical_strain_energy_ = candidate.physical_strain_energy;
|
||||||
equilibrium_ = candidate.equilibrium;
|
equilibrium_ = candidate.equilibrium;
|
||||||
verificationMetrics_ = candidate.verificationMetrics;
|
verification_metrics_ = candidate.verification_metrics;
|
||||||
return Status::Ok();
|
return Status::Ok();
|
||||||
}
|
}
|
||||||
|
|
||||||
const std::vector<ShellResultRow>& AnalysisState::shellResults() const noexcept {
|
const std::vector<ShellResultRow>& AnalysisState::ShellResults()
|
||||||
return shellResults_;
|
const noexcept {
|
||||||
|
return shell_results_;
|
||||||
}
|
}
|
||||||
|
|
||||||
double AnalysisState::physicalStrainEnergy() const noexcept {
|
double AnalysisState::PhysicalStrainEnergy() const noexcept {
|
||||||
return physicalStrainEnergy_;
|
return physical_strain_energy_;
|
||||||
}
|
}
|
||||||
|
|
||||||
const std::array<double, 6>& AnalysisState::equilibrium() const noexcept {
|
const std::array<double, 6>& AnalysisState::Equilibrium() const noexcept {
|
||||||
return equilibrium_;
|
return equilibrium_;
|
||||||
}
|
}
|
||||||
|
|
||||||
const std::array<double, 3>& AnalysisState::verificationMetrics() const noexcept {
|
const std::array<double, 3>& AnalysisState::VerificationMetrics()
|
||||||
return verificationMetrics_;
|
const noexcept {
|
||||||
|
return verification_metrics_;
|
||||||
}
|
}
|
||||||
|
|
||||||
AnalysisState::AnalysisState(
|
AnalysisState::AnalysisState(std::size_t full_dof_count,
|
||||||
std::size_t fullDofCount, StepFrameIdentity identity)
|
StepFrameIdentity identity)
|
||||||
: identity_{std::move(identity)},
|
: identity_{std::move(identity)},
|
||||||
displacement_{fullDofCount},
|
displacement_{full_dof_count},
|
||||||
externalForce_{fullDofCount},
|
external_force_{full_dof_count},
|
||||||
internalForce_{fullDofCount},
|
internal_force_{full_dof_count},
|
||||||
residual_{fullDofCount},
|
residual_{full_dof_count},
|
||||||
reaction_{fullDofCount} {}
|
reaction_{full_dof_count} {}
|
||||||
|
|
||||||
} // namespace fesa
|
} // namespace fesa
|
||||||
|
|||||||
@@ -1,64 +1,63 @@
|
|||||||
#include "fesa/analysis/linear_static_analysis.hpp"
|
#include "fesa/analysis/linear_static_analysis.h"
|
||||||
|
|
||||||
#include "fesa/assembly/load_assembler.hpp"
|
|
||||||
#include "fesa/assembly/parallel_for.hpp"
|
|
||||||
#include "fesa/assembly/sparse_assembler.hpp"
|
|
||||||
#include "fesa/io/abaqus/domain_mapper.hpp"
|
|
||||||
#include "fesa/io/abaqus/input_reader.hpp"
|
|
||||||
#include "fesa/results/result_recovery.hpp"
|
|
||||||
#include "fesa/results/results_writer.hpp"
|
|
||||||
#include "fesa/solvers/linear/linear_solver.h"
|
|
||||||
|
|
||||||
#include <utility>
|
#include <utility>
|
||||||
|
|
||||||
|
#include "fesa/assembly/load_assembler.h"
|
||||||
|
#include "fesa/assembly/parallel_for.h"
|
||||||
|
#include "fesa/assembly/sparse_assembler.h"
|
||||||
|
#include "fesa/io/abaqus/domain_mapper.hpp"
|
||||||
|
#include "fesa/io/abaqus/input_reader.hpp"
|
||||||
|
#include "fesa/results/result_recovery.h"
|
||||||
|
#include "fesa/results/results_writer.h"
|
||||||
|
#include "fesa/solvers/linear/linear_solver.h"
|
||||||
|
|
||||||
namespace fesa {
|
namespace fesa {
|
||||||
|
|
||||||
Status Analysis::run(const AnalysisRequest& request) {
|
Status Analysis::Run(const AnalysisRequest& request) {
|
||||||
Status status = initialize(request);
|
Status status = Initialize(request);
|
||||||
if (!status.IsOk()) {
|
if (!status.IsOk()) {
|
||||||
return status;
|
return status;
|
||||||
}
|
}
|
||||||
status = buildAnalysisModel();
|
status = BuildAnalysisModel();
|
||||||
if (!status.IsOk()) {
|
if (!status.IsOk()) {
|
||||||
return status;
|
return status;
|
||||||
}
|
}
|
||||||
status = buildDofMapAndSparsePattern();
|
status = BuildDofMapAndSparsePattern();
|
||||||
if (!status.IsOk()) {
|
if (!status.IsOk()) {
|
||||||
return status;
|
return status;
|
||||||
}
|
}
|
||||||
status = assembleAndPartitionStiffness();
|
status = AssembleAndPartitionStiffness();
|
||||||
if (!status.IsOk()) {
|
if (!status.IsOk()) {
|
||||||
return status;
|
return status;
|
||||||
}
|
}
|
||||||
status = factorize();
|
status = Factorize();
|
||||||
if (!status.IsOk()) {
|
if (!status.IsOk()) {
|
||||||
return status;
|
return status;
|
||||||
}
|
}
|
||||||
status = assembleLoadsAndEffectiveRhs();
|
status = AssembleLoadsAndEffectiveRhs();
|
||||||
if (!status.IsOk()) {
|
if (!status.IsOk()) {
|
||||||
return status;
|
return status;
|
||||||
}
|
}
|
||||||
status = substituteAndReconstruct();
|
status = SubstituteAndReconstruct();
|
||||||
if (!status.IsOk()) {
|
if (!status.IsOk()) {
|
||||||
return status;
|
return status;
|
||||||
}
|
}
|
||||||
return recoverAndWriteResults();
|
return RecoverAndWriteResults();
|
||||||
}
|
}
|
||||||
|
|
||||||
LinearStaticAnalysis::LinearStaticAnalysis(
|
LinearStaticAnalysis::LinearStaticAnalysis(const ParallelFor& parallel_for,
|
||||||
const ParallelFor& parallelFor,
|
LinearSolver& linear_solver,
|
||||||
LinearSolver& linearSolver,
|
ResultsWriter& results_writer)
|
||||||
ResultsWriter& resultsWriter)
|
: parallel_for_{parallel_for},
|
||||||
: parallelFor_{parallelFor},
|
linear_solver_{linear_solver},
|
||||||
linearSolver_{linearSolver},
|
results_writer_{results_writer} {}
|
||||||
resultsWriter_{resultsWriter} {}
|
|
||||||
|
|
||||||
Status LinearStaticAnalysis::initialize(const AnalysisRequest& request) {
|
Status LinearStaticAnalysis::Initialize(const AnalysisRequest& request) {
|
||||||
// Clear dependent objects in reverse ownership order so a reused analysis
|
// Clear dependent objects in reverse ownership order so a reused analysis
|
||||||
// never exposes a view into a Domain from an earlier run.
|
// never exposes a view into a Domain from an earlier run.
|
||||||
effectiveRhs_.reset();
|
effective_rhs_.reset();
|
||||||
partitionedStiffness_.reset();
|
partitioned_stiffness_.reset();
|
||||||
fullStiffness_.reset();
|
full_stiffness_.reset();
|
||||||
state_.reset();
|
state_.reset();
|
||||||
dofs_.reset();
|
dofs_.reset();
|
||||||
model_.reset();
|
model_.reset();
|
||||||
@@ -66,7 +65,7 @@ Status LinearStaticAnalysis::initialize(const AnalysisRequest& request) {
|
|||||||
diagnostics_.clear();
|
diagnostics_.clear();
|
||||||
request_ = request;
|
request_ = request;
|
||||||
|
|
||||||
const auto parsed = AbaqusInputReader{}.read(request_.inputPath);
|
const auto parsed = AbaqusInputReader{}.read(request_.input_path);
|
||||||
if (!parsed.HasValue()) {
|
if (!parsed.HasValue()) {
|
||||||
return parsed.GetStatus();
|
return parsed.GetStatus();
|
||||||
}
|
}
|
||||||
@@ -81,8 +80,8 @@ Status LinearStaticAnalysis::initialize(const AnalysisRequest& request) {
|
|||||||
return Status::Ok();
|
return Status::Ok();
|
||||||
}
|
}
|
||||||
|
|
||||||
Status LinearStaticAnalysis::buildAnalysisModel() {
|
Status LinearStaticAnalysis::BuildAnalysisModel() {
|
||||||
auto model = AnalysisModel::create(*domain_);
|
auto model = AnalysisModel::Create(*domain_);
|
||||||
if (!model.HasValue()) {
|
if (!model.HasValue()) {
|
||||||
return model.GetStatus();
|
return model.GetStatus();
|
||||||
}
|
}
|
||||||
@@ -90,81 +89,78 @@ Status LinearStaticAnalysis::buildAnalysisModel() {
|
|||||||
return Status::Ok();
|
return Status::Ok();
|
||||||
}
|
}
|
||||||
|
|
||||||
Status LinearStaticAnalysis::buildDofMapAndSparsePattern() {
|
Status LinearStaticAnalysis::BuildDofMapAndSparsePattern() {
|
||||||
auto dofs = DofManager::create(*model_);
|
auto dofs = DofManager::Create(*model_);
|
||||||
if (!dofs.HasValue()) {
|
if (!dofs.HasValue()) {
|
||||||
return dofs.GetStatus();
|
return dofs.GetStatus();
|
||||||
}
|
}
|
||||||
dofs_ = std::make_unique<DofManager>(std::move(dofs.Value()));
|
dofs_ = std::make_unique<DofManager>(std::move(dofs.Value()));
|
||||||
state_ = std::make_unique<AnalysisState>(
|
state_ = std::make_unique<AnalysisState>(
|
||||||
AnalysisState::create(*dofs_, {"Step-1", 0U}));
|
AnalysisState::Create(*dofs_, {"Step-1", 0U}));
|
||||||
return Status::Ok();
|
return Status::Ok();
|
||||||
}
|
}
|
||||||
|
|
||||||
Status LinearStaticAnalysis::assembleAndPartitionStiffness() {
|
Status LinearStaticAnalysis::AssembleAndPartitionStiffness() {
|
||||||
auto stiffness = SparseAssembler::assembleStiffness(
|
auto stiffness =
|
||||||
*model_, *dofs_, parallelFor_);
|
SparseAssembler::AssembleStiffness(*model_, *dofs_, parallel_for_);
|
||||||
if (!stiffness.HasValue()) {
|
if (!stiffness.HasValue()) {
|
||||||
return stiffness.GetStatus();
|
return stiffness.GetStatus();
|
||||||
}
|
}
|
||||||
fullStiffness_ =
|
full_stiffness_ =
|
||||||
std::make_unique<SparseMatrix>(std::move(stiffness.Value()));
|
std::make_unique<SparseMatrix>(std::move(stiffness.Value()));
|
||||||
|
|
||||||
auto partitioned = EssentialConstraints::partition(
|
auto partitioned = EssentialConstraints::Partition(*full_stiffness_, *dofs_);
|
||||||
*fullStiffness_, *dofs_);
|
|
||||||
if (!partitioned.HasValue()) {
|
if (!partitioned.HasValue()) {
|
||||||
return partitioned.GetStatus();
|
return partitioned.GetStatus();
|
||||||
}
|
}
|
||||||
partitionedStiffness_ = std::make_unique<PartitionedStiffness>(
|
partitioned_stiffness_ =
|
||||||
std::move(partitioned.Value()));
|
std::make_unique<PartitionedStiffness>(std::move(partitioned.Value()));
|
||||||
return Status::Ok();
|
return Status::Ok();
|
||||||
}
|
}
|
||||||
|
|
||||||
Status LinearStaticAnalysis::factorize() {
|
Status LinearStaticAnalysis::Factorize() {
|
||||||
// This call intentionally precedes all load assembly in Analysis::run.
|
// This call intentionally precedes all load assembly in Analysis::Run.
|
||||||
return linearSolver_.Factorize(partitionedStiffness_->kff);
|
return linear_solver_.Factorize(partitioned_stiffness_->kff);
|
||||||
}
|
}
|
||||||
|
|
||||||
Status LinearStaticAnalysis::assembleLoadsAndEffectiveRhs() {
|
Status LinearStaticAnalysis::AssembleLoadsAndEffectiveRhs() {
|
||||||
auto fullLoad = LoadAssembler::assembleFullNodalLoad(*model_, *dofs_);
|
auto full_load = LoadAssembler::AssembleFullNodalLoad(*model_, *dofs_);
|
||||||
if (!fullLoad.HasValue()) {
|
if (!full_load.HasValue()) {
|
||||||
return fullLoad.GetStatus();
|
return full_load.GetStatus();
|
||||||
}
|
}
|
||||||
state_->externalForce() = std::move(fullLoad.Value());
|
state_->ExternalForce() = std::move(full_load.Value());
|
||||||
|
|
||||||
auto rhs = LoadAssembler::effectiveFreeRhs(
|
auto rhs = LoadAssembler::EffectiveFreeRhs(state_->ExternalForce(),
|
||||||
state_->externalForce(),
|
partitioned_stiffness_->kfc,
|
||||||
partitionedStiffness_->kfc,
|
dofs_->PrescribedValues(), *dofs_);
|
||||||
dofs_->prescribedValues(),
|
|
||||||
*dofs_);
|
|
||||||
if (!rhs.HasValue()) {
|
if (!rhs.HasValue()) {
|
||||||
return rhs.GetStatus();
|
return rhs.GetStatus();
|
||||||
}
|
}
|
||||||
effectiveRhs_ = std::make_unique<Vector>(std::move(rhs.Value()));
|
effective_rhs_ = std::make_unique<Vector>(std::move(rhs.Value()));
|
||||||
return Status::Ok();
|
return Status::Ok();
|
||||||
}
|
}
|
||||||
|
|
||||||
Status LinearStaticAnalysis::substituteAndReconstruct() {
|
Status LinearStaticAnalysis::SubstituteAndReconstruct() {
|
||||||
Vector freeDisplacement{dofs_->freeDofCount()};
|
Vector free_displacement{dofs_->FreeDofCount()};
|
||||||
const Status solveStatus =
|
const Status solve_status =
|
||||||
linearSolver_.Solve(*effectiveRhs_, freeDisplacement);
|
linear_solver_.Solve(*effective_rhs_, free_displacement);
|
||||||
if (!solveStatus.IsOk()) {
|
if (!solve_status.IsOk()) {
|
||||||
return solveStatus;
|
return solve_status;
|
||||||
}
|
}
|
||||||
|
|
||||||
state_->displacement() = EssentialConstraints::reconstructFull(
|
state_->Displacement() = EssentialConstraints::ReconstructFull(
|
||||||
freeDisplacement, dofs_->prescribedValues(), *dofs_);
|
free_displacement, dofs_->PrescribedValues(), *dofs_);
|
||||||
return Status::Ok();
|
return Status::Ok();
|
||||||
}
|
}
|
||||||
|
|
||||||
Status LinearStaticAnalysis::recoverAndWriteResults() {
|
Status LinearStaticAnalysis::RecoverAndWriteResults() {
|
||||||
const Status recoveryStatus = ResultRecovery::recover(
|
const Status recovery_status =
|
||||||
*model_, *dofs_, *fullStiffness_, *state_);
|
ResultRecovery::Recover(*model_, *dofs_, *full_stiffness_, *state_);
|
||||||
if (!recoveryStatus.IsOk()) {
|
if (!recovery_status.IsOk()) {
|
||||||
return recoveryStatus;
|
return recovery_status;
|
||||||
}
|
}
|
||||||
return resultsWriter_.write(
|
return results_writer_.Write(request_.output_path, *domain_, *state_,
|
||||||
request_.outputPath, *domain_, *state_, diagnostics_);
|
diagnostics_);
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace fesa
|
} // namespace fesa
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
#include "fesa/app/fesa_application.hpp"
|
#include "fesa/app/fesa_application.hpp"
|
||||||
|
|
||||||
#include "fesa/analysis/linear_static_analysis.hpp"
|
#include "fesa/analysis/linear_static_analysis.h"
|
||||||
#include "fesa/assembly/parallel_for.hpp"
|
#include "fesa/assembly/parallel_for.h"
|
||||||
#include "fesa/core/diagnostic.h"
|
#include "fesa/core/diagnostic.h"
|
||||||
#include "fesa/io/hdf5/hdf5_results_writer.hpp"
|
#include "fesa/io/hdf5/hdf5_results_writer.hpp"
|
||||||
#include "fesa/solvers/linear/mkl_pardiso_solver.h"
|
#include "fesa/solvers/linear/mkl_pardiso_solver.h"
|
||||||
@@ -91,8 +91,8 @@ int FesaApplication::run(const std::vector<std::string>& arguments) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
AnalysisRequest request;
|
AnalysisRequest request;
|
||||||
request.inputPath = arguments[0U];
|
request.input_path = arguments[0U];
|
||||||
request.outputPath = explicitOutputForm
|
request.output_path = explicitOutputForm
|
||||||
? std::filesystem::path{arguments[2U]}
|
? std::filesystem::path{arguments[2U]}
|
||||||
: std::filesystem::current_path() / "results.h5";
|
: std::filesystem::current_path() / "results.h5";
|
||||||
|
|
||||||
@@ -101,7 +101,7 @@ int FesaApplication::run(const std::vector<std::string>& arguments) {
|
|||||||
Hdf5ResultsWriter resultsWriter;
|
Hdf5ResultsWriter resultsWriter;
|
||||||
LinearStaticAnalysis analysis{
|
LinearStaticAnalysis analysis{
|
||||||
parallelFor, linearSolver, resultsWriter};
|
parallelFor, linearSolver, resultsWriter};
|
||||||
const Status status = analysis.run(request);
|
const Status status = analysis.Run(request);
|
||||||
if (status.IsOk()) {
|
if (status.IsOk()) {
|
||||||
return kSuccessExitCode;
|
return kSuccessExitCode;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,4 @@
|
|||||||
#include "fesa/assembly/load_assembler.hpp"
|
#include "fesa/assembly/load_assembler.h"
|
||||||
|
|
||||||
#include "fesa/constraints/essential_constraints.hpp"
|
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <charconv>
|
#include <charconv>
|
||||||
@@ -13,268 +11,229 @@
|
|||||||
#include <utility>
|
#include <utility>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
|
#include "fesa/constraints/essential_constraints.h"
|
||||||
|
|
||||||
namespace fesa {
|
namespace fesa {
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
constexpr std::size_t dofsPerNode = 6U;
|
constexpr std::size_t kDofsPerNode = 6U;
|
||||||
constexpr double shellMomentProjectionTolerance = 1.0e-12;
|
constexpr double kShellMomentProjectionTolerance = 1.0e-12;
|
||||||
|
|
||||||
Status loadFailure(
|
Status LoadFailure(const std::string& code, const SourceLocation& location,
|
||||||
const std::string& code,
|
const std::string& keyword, const std::string& identity,
|
||||||
const SourceLocation& location,
|
|
||||||
const std::string& keyword,
|
|
||||||
const std::string& identity,
|
|
||||||
const std::string& message) {
|
const std::string& message) {
|
||||||
return Status::Failure(
|
return Status::Failure(
|
||||||
FailureCategory::kModel,
|
FailureCategory::kModel,
|
||||||
{{Severity::kError, code, location, keyword, identity, message}});
|
{{Severity::kError, code, location, keyword, identity, message}});
|
||||||
}
|
}
|
||||||
|
|
||||||
char asciiLower(const char value) {
|
char AsciiLower(const char value) {
|
||||||
if (value >= 'A' && value <= 'Z') {
|
if (value >= 'A' && value <= 'Z') {
|
||||||
return static_cast<char>(value + ('a' - 'A'));
|
return static_cast<char>(value + ('a' - 'A'));
|
||||||
}
|
}
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool equalName(const std::string& left, const std::string& right) {
|
bool EqualName(const std::string& left, const std::string& right) {
|
||||||
return left.size() == right.size() &&
|
return left.size() == right.size() &&
|
||||||
std::equal(
|
std::equal(left.begin(), left.end(), right.begin(),
|
||||||
left.begin(),
|
[](const char left_value, const char right_value) {
|
||||||
left.end(),
|
return AsciiLower(left_value) == AsciiLower(right_value);
|
||||||
right.begin(),
|
|
||||||
[](const char leftValue, const char rightValue) {
|
|
||||||
return asciiLower(leftValue) == asciiLower(rightValue);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
bool tryPositiveInteger(const std::string& text, std::int64_t& value) {
|
bool TryPositiveInteger(const std::string& text, std::int64_t& value) {
|
||||||
const char* const first = text.data();
|
const char* const first = text.data();
|
||||||
const char* const last = first + text.size();
|
const char* const last = first + text.size();
|
||||||
const auto parsed = std::from_chars(first, last, value);
|
const auto parsed = std::from_chars(first, last, value);
|
||||||
return parsed.ec == std::errc{} && parsed.ptr == last && value > 0;
|
return parsed.ec == std::errc{} && parsed.ptr == last && value > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool isStrictlyIncreasing(const std::vector<std::size_t>& values) {
|
/// @brief Checks that equation-space indices preserve stable full-DOF order.
|
||||||
|
bool IsStrictlyIncreasing(const std::vector<std::size_t>& values) {
|
||||||
return std::adjacent_find(
|
return std::adjacent_find(
|
||||||
values.begin(),
|
values.begin(), values.end(),
|
||||||
values.end(),
|
|
||||||
[](const std::size_t left, const std::size_t right) {
|
[](const std::size_t left, const std::size_t right) {
|
||||||
return left >= right;
|
return left >= right;
|
||||||
}) == values.end();
|
}) == values.end();
|
||||||
}
|
}
|
||||||
|
|
||||||
Status validateDofOrder(
|
/// @brief Validates the full/free/constrained partition used by load assembly.
|
||||||
const DofManager& dofs,
|
Status ValidateDofOrder(const DofManager& dofs,
|
||||||
const std::size_t expectedFullCount,
|
const std::size_t expected_full_count,
|
||||||
const SourceLocation& location) {
|
const SourceLocation& location) {
|
||||||
const std::size_t fullCount = dofs.fullDofCount();
|
const std::size_t full_count = dofs.FullDofCount();
|
||||||
const auto& freeDofs = dofs.freeDofs();
|
const auto& free_dofs = dofs.FreeDofs();
|
||||||
const auto& constrainedDofs = dofs.constrainedDofs();
|
const auto& constrained_dofs = dofs.ConstrainedDofs();
|
||||||
if (fullCount != expectedFullCount ||
|
if (full_count != expected_full_count ||
|
||||||
freeDofs.size() != dofs.freeDofCount() ||
|
free_dofs.size() != dofs.FreeDofCount() ||
|
||||||
constrainedDofs.size() != dofs.constrainedDofCount() ||
|
constrained_dofs.size() != dofs.ConstrainedDofCount() ||
|
||||||
dofs.prescribedValues().Size() != constrainedDofs.size() ||
|
dofs.PrescribedValues().Size() != constrained_dofs.size() ||
|
||||||
constrainedDofs.size() > fullCount ||
|
constrained_dofs.size() > full_count ||
|
||||||
freeDofs.size() != fullCount - constrainedDofs.size()) {
|
free_dofs.size() != full_count - constrained_dofs.size()) {
|
||||||
return loadFailure(
|
return LoadFailure("invalid-load-dimensions", location, "LOAD_ASSEMBLER",
|
||||||
"invalid-load-dimensions",
|
std::to_string(full_count),
|
||||||
location,
|
"Full, free, constrained, prescribed, and model "
|
||||||
"LOAD_ASSEMBLER",
|
"dimensions must agree.");
|
||||||
std::to_string(fullCount),
|
|
||||||
"Full, free, constrained, prescribed, and model dimensions must agree.");
|
|
||||||
}
|
}
|
||||||
if (!isStrictlyIncreasing(freeDofs) ||
|
if (!IsStrictlyIncreasing(free_dofs) ||
|
||||||
!isStrictlyIncreasing(constrainedDofs)) {
|
!IsStrictlyIncreasing(constrained_dofs)) {
|
||||||
return loadFailure(
|
return LoadFailure(
|
||||||
"invalid-load-order",
|
"invalid-load-order", location, "LOAD_ASSEMBLER",
|
||||||
location,
|
std::to_string(full_count),
|
||||||
"LOAD_ASSEMBLER",
|
|
||||||
std::to_string(fullCount),
|
|
||||||
"Free and constrained DOFs must use stable increasing full-DOF order.");
|
"Free and constrained DOFs must use stable increasing full-DOF order.");
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<unsigned char> ownership(fullCount, 0U);
|
std::vector<unsigned char> ownership(full_count, 0U);
|
||||||
try {
|
try {
|
||||||
for (std::size_t equation = 0U;
|
for (std::size_t equation = 0U; equation < free_dofs.size(); ++equation) {
|
||||||
equation < freeDofs.size();
|
const std::size_t full_dof = free_dofs[equation];
|
||||||
++equation) {
|
if (full_dof >= full_count || ownership[full_dof] != 0U ||
|
||||||
const std::size_t fullDof = freeDofs[equation];
|
dofs.FreeEquation(full_dof) != equation) {
|
||||||
if (fullDof >= fullCount || ownership[fullDof] != 0U ||
|
return LoadFailure(
|
||||||
dofs.freeEquation(fullDof) != equation) {
|
"invalid-load-order", location, "LOAD_ASSEMBLER",
|
||||||
return loadFailure(
|
std::to_string(full_dof),
|
||||||
"invalid-load-order",
|
|
||||||
location,
|
|
||||||
"LOAD_ASSEMBLER",
|
|
||||||
std::to_string(fullDof),
|
|
||||||
"Free equation numbering must match stable full-DOF order.");
|
"Free equation numbering must match stable full-DOF order.");
|
||||||
}
|
}
|
||||||
ownership[fullDof] = 1U;
|
ownership[full_dof] = 1U;
|
||||||
}
|
}
|
||||||
for (const std::size_t fullDof : constrainedDofs) {
|
for (const std::size_t full_dof : constrained_dofs) {
|
||||||
if (fullDof >= fullCount || ownership[fullDof] != 0U ||
|
if (full_dof >= full_count || ownership[full_dof] != 0U ||
|
||||||
dofs.freeEquation(fullDof).has_value()) {
|
dofs.FreeEquation(full_dof).has_value()) {
|
||||||
return loadFailure(
|
return LoadFailure(
|
||||||
"invalid-load-order",
|
"invalid-load-order", location, "LOAD_ASSEMBLER",
|
||||||
location,
|
std::to_string(full_dof),
|
||||||
"LOAD_ASSEMBLER",
|
|
||||||
std::to_string(fullDof),
|
|
||||||
"Constrained DOFs must be unique and absent from free equations.");
|
"Constrained DOFs must be unique and absent from free equations.");
|
||||||
}
|
}
|
||||||
ownership[fullDof] = 2U;
|
ownership[full_dof] = 2U;
|
||||||
}
|
}
|
||||||
} catch (const std::out_of_range&) {
|
} catch (const std::out_of_range&) {
|
||||||
return loadFailure(
|
return LoadFailure(
|
||||||
"invalid-load-dimensions",
|
"invalid-load-dimensions", location, "LOAD_ASSEMBLER",
|
||||||
location,
|
std::to_string(full_count),
|
||||||
"LOAD_ASSEMBLER",
|
|
||||||
std::to_string(fullCount),
|
|
||||||
"DofManager equation storage must cover every full DOF.");
|
"DofManager equation storage must cover every full DOF.");
|
||||||
}
|
}
|
||||||
if (std::find(ownership.begin(), ownership.end(), 0U) != ownership.end()) {
|
if (std::find(ownership.begin(), ownership.end(), 0U) != ownership.end()) {
|
||||||
return loadFailure(
|
return LoadFailure(
|
||||||
"invalid-load-order",
|
"invalid-load-order", location, "LOAD_ASSEMBLER",
|
||||||
location,
|
std::to_string(full_count),
|
||||||
"LOAD_ASSEMBLER",
|
|
||||||
std::to_string(fullCount),
|
|
||||||
"Free and constrained DOFs must partition the full range.");
|
"Free and constrained DOFs must partition the full range.");
|
||||||
}
|
}
|
||||||
return Status::Ok();
|
return Status::Ok();
|
||||||
}
|
}
|
||||||
|
|
||||||
Result<std::vector<EntityIndex>> resolveTarget(
|
Result<std::vector<EntityIndex>> ResolveTarget(const Domain& domain,
|
||||||
const Domain& domain,
|
|
||||||
const NodalLoad& load) {
|
const NodalLoad& load) {
|
||||||
std::vector<const NodeSet*> matchingSets;
|
std::vector<const NodeSet*> matching_sets;
|
||||||
for (const auto& set : domain.NodeSets()) {
|
for (const auto& set : domain.NodeSets()) {
|
||||||
if (equalName(set.name, load.target)) {
|
if (EqualName(set.name, load.target)) {
|
||||||
matchingSets.push_back(&set);
|
matching_sets.push_back(&set);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<EntityIndex> matchingNodes;
|
std::vector<EntityIndex> matching_nodes;
|
||||||
std::int64_t label = 0;
|
std::int64_t label = 0;
|
||||||
if (tryPositiveInteger(load.target, label)) {
|
if (TryPositiveInteger(load.target, label)) {
|
||||||
for (std::size_t index = 0U; index < domain.Nodes().size(); ++index) {
|
for (std::size_t index = 0U; index < domain.Nodes().size(); ++index) {
|
||||||
if (domain.Nodes()[index].source_id.source_label == label) {
|
if (domain.Nodes()[index].source_id.source_label == label) {
|
||||||
matchingNodes.push_back(static_cast<EntityIndex>(index));
|
matching_nodes.push_back(static_cast<EntityIndex>(index));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (matchingSets.size() > 1U || matchingNodes.size() > 1U ||
|
if (matching_sets.size() > 1U || matching_nodes.size() > 1U ||
|
||||||
(!matchingSets.empty() && !matchingNodes.empty())) {
|
(!matching_sets.empty() && !matching_nodes.empty())) {
|
||||||
return Result<std::vector<EntityIndex>>::Failure(loadFailure(
|
return Result<std::vector<EntityIndex>>::Failure(
|
||||||
"invalid-load-target",
|
LoadFailure("invalid-load-target", load.location, "CLOAD", load.target,
|
||||||
load.location,
|
"The load target must resolve unambiguously to one node or "
|
||||||
"CLOAD",
|
"one expanded node set."));
|
||||||
load.target,
|
|
||||||
"The load target must resolve unambiguously to one node or one expanded node set."));
|
|
||||||
}
|
}
|
||||||
if (!matchingSets.empty()) {
|
if (!matching_sets.empty()) {
|
||||||
const auto& nodes = matchingSets.front()->node_indices;
|
const auto& nodes = matching_sets.front()->node_indices;
|
||||||
std::vector<unsigned char> seen(domain.Nodes().size(), 0U);
|
std::vector<unsigned char> seen(domain.Nodes().size(), 0U);
|
||||||
for (const EntityIndex node : nodes) {
|
for (const EntityIndex node : nodes) {
|
||||||
if (node >= domain.Nodes().size() || seen[node] != 0U) {
|
if (node >= domain.Nodes().size() || seen[node] != 0U) {
|
||||||
return Result<std::vector<EntityIndex>>::Failure(loadFailure(
|
return Result<std::vector<EntityIndex>>::Failure(LoadFailure(
|
||||||
"invalid-load-target",
|
"invalid-load-target", load.location, "CLOAD", load.target,
|
||||||
load.location,
|
"The expanded node set must contain unique in-range stable node "
|
||||||
"CLOAD",
|
"identities."));
|
||||||
load.target,
|
|
||||||
"The expanded node set must contain unique in-range stable node identities."));
|
|
||||||
}
|
}
|
||||||
seen[node] = 1U;
|
seen[node] = 1U;
|
||||||
}
|
}
|
||||||
return Result<std::vector<EntityIndex>>::Success(nodes);
|
return Result<std::vector<EntityIndex>>::Success(nodes);
|
||||||
}
|
}
|
||||||
if (!matchingNodes.empty()) {
|
if (!matching_nodes.empty()) {
|
||||||
return Result<std::vector<EntityIndex>>::Success(
|
return Result<std::vector<EntityIndex>>::Success(std::move(matching_nodes));
|
||||||
std::move(matchingNodes));
|
|
||||||
}
|
}
|
||||||
return Result<std::vector<EntityIndex>>::Failure(loadFailure(
|
return Result<std::vector<EntityIndex>>::Failure(LoadFailure(
|
||||||
"invalid-load-target",
|
"invalid-load-target", load.location, "CLOAD", load.target,
|
||||||
load.location,
|
|
||||||
"CLOAD",
|
|
||||||
load.target,
|
|
||||||
"The load target must resolve to one semantic node or node set."));
|
"The load target must resolve to one semantic node or node set."));
|
||||||
}
|
}
|
||||||
|
|
||||||
Status validateFiniteVector(
|
Status ValidateFiniteVector(const Vector& values,
|
||||||
const Vector& values,
|
|
||||||
const SourceLocation& location,
|
const SourceLocation& location,
|
||||||
const std::string& identity) {
|
const std::string& identity) {
|
||||||
for (std::size_t index = 0U; index < values.Size(); ++index) {
|
for (std::size_t index = 0U; index < values.Size(); ++index) {
|
||||||
if (!std::isfinite(values[index])) {
|
if (!std::isfinite(values[index])) {
|
||||||
return loadFailure(
|
return LoadFailure("nonfinite-load-value", location, "LOAD_ASSEMBLER",
|
||||||
"nonfinite-load-value",
|
|
||||||
location,
|
|
||||||
"LOAD_ASSEMBLER",
|
|
||||||
identity + ":" + std::to_string(index),
|
identity + ":" + std::to_string(index),
|
||||||
"Load and prescribed displacement vectors must contain finite values.");
|
"Load and prescribed displacement vectors must "
|
||||||
|
"contain finite values.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return Status::Ok();
|
return Status::Ok();
|
||||||
}
|
}
|
||||||
|
|
||||||
Status validateShellMoments(
|
Status ValidateShellMoments(const Domain& domain, const Vector& full_load) {
|
||||||
const Domain& domain,
|
|
||||||
const Vector& fullLoad) {
|
|
||||||
if (domain.ShellElements().empty()) {
|
if (domain.ShellElements().empty()) {
|
||||||
return Status::Ok();
|
return Status::Ok();
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<const ShellNodeInitialFrame*> frameByNode(
|
std::vector<const ShellNodeInitialFrame*> frame_by_node(domain.Nodes().size(),
|
||||||
domain.Nodes().size(), nullptr);
|
nullptr);
|
||||||
for (const auto& frame : domain.ShellNodeInitialFrames()) {
|
for (const auto& frame : domain.ShellNodeInitialFrames()) {
|
||||||
if (frame.node_index >= frameByNode.size() ||
|
if (frame.node_index >= frame_by_node.size() ||
|
||||||
frameByNode[frame.node_index] != nullptr) {
|
frame_by_node[frame.node_index] != nullptr) {
|
||||||
return loadFailure(
|
return LoadFailure(
|
||||||
"invalid-shell-director",
|
"invalid-shell-director", {domain.SourcePath(), 0U}, "NODE",
|
||||||
{domain.SourcePath(), 0U},
|
|
||||||
"NODE",
|
|
||||||
std::to_string(frame.node_index),
|
std::to_string(frame.node_index),
|
||||||
"Shell nodal directors must have unique in-range node identities.");
|
"Shell nodal directors must have unique in-range node identities.");
|
||||||
}
|
}
|
||||||
frameByNode[frame.node_index] = &frame;
|
frame_by_node[frame.node_index] = &frame;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (std::size_t node = 0U; node < domain.Nodes().size(); ++node) {
|
for (std::size_t node = 0U; node < domain.Nodes().size(); ++node) {
|
||||||
const double momentX = fullLoad[node * dofsPerNode + 3U];
|
const double moment_x = full_load[node * kDofsPerNode + 3U];
|
||||||
const double momentY = fullLoad[node * dofsPerNode + 4U];
|
const double moment_y = full_load[node * kDofsPerNode + 4U];
|
||||||
const double momentZ = fullLoad[node * dofsPerNode + 5U];
|
const double moment_z = full_load[node * kDofsPerNode + 5U];
|
||||||
if (momentX == 0.0 && momentY == 0.0 && momentZ == 0.0) {
|
if (moment_x == 0.0 && moment_y == 0.0 && moment_z == 0.0) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto* const frame = frameByNode[node];
|
const auto* const frame = frame_by_node[node];
|
||||||
if (frame == nullptr) {
|
if (frame == nullptr) {
|
||||||
return loadFailure(
|
return LoadFailure(
|
||||||
"invalid-shell-director",
|
"invalid-shell-director", domain.Nodes()[node].location, "NODE",
|
||||||
domain.Nodes()[node].location,
|
|
||||||
"NODE",
|
|
||||||
domain.Nodes()[node].source_id.source_label_text,
|
domain.Nodes()[node].source_id.source_label_text,
|
||||||
"A loaded shell node must have an approved initial director.");
|
"A loaded shell node must have an approved initial director.");
|
||||||
}
|
}
|
||||||
|
|
||||||
const double momentScale = std::max(
|
const double moment_scale = std::max(
|
||||||
std::abs(momentX),
|
std::abs(moment_x), std::max(std::abs(moment_y), std::abs(moment_z)));
|
||||||
std::max(std::abs(momentY), std::abs(momentZ)));
|
const double scaled_x = moment_x / moment_scale;
|
||||||
const double scaledX = momentX / momentScale;
|
const double scaled_y = moment_y / moment_scale;
|
||||||
const double scaledY = momentY / momentScale;
|
const double scaled_z = moment_z / moment_scale;
|
||||||
const double scaledZ = momentZ / momentScale;
|
const double scaled_norm = std::hypot(scaled_x, scaled_y, scaled_z);
|
||||||
const double scaledNorm = std::hypot(scaledX, scaledY, scaledZ);
|
const double scaled_dot = frame->director[0U] * scaled_x +
|
||||||
const double scaledDot =
|
frame->director[1U] * scaled_y +
|
||||||
frame->director[0U] * scaledX +
|
frame->director[2U] * scaled_z;
|
||||||
frame->director[1U] * scaledY +
|
const double projection_ratio = std::abs(scaled_dot) / scaled_norm;
|
||||||
frame->director[2U] * scaledZ;
|
if (!(projection_ratio <= kShellMomentProjectionTolerance)) {
|
||||||
const double projectionRatio = std::abs(scaledDot) / scaledNorm;
|
return LoadFailure("unsupported-drilling-load",
|
||||||
if (!(projectionRatio <= shellMomentProjectionTolerance)) {
|
domain.Nodes()[node].location, "CLOAD",
|
||||||
return loadFailure(
|
|
||||||
"unsupported-drilling-load",
|
|
||||||
domain.Nodes()[node].location,
|
|
||||||
"CLOAD",
|
|
||||||
domain.Nodes()[node].source_id.source_label_text,
|
domain.Nodes()[node].source_id.source_label_text,
|
||||||
"The aggregate nodal moment has an unsupported director-parallel component.");
|
"The aggregate nodal moment has an unsupported "
|
||||||
|
"director-parallel component.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return Status::Ok();
|
return Status::Ok();
|
||||||
@@ -282,183 +241,149 @@ Status validateShellMoments(
|
|||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
Result<Vector> LoadAssembler::assembleFullNodalLoad(
|
Result<Vector> LoadAssembler::AssembleFullNodalLoad(const AnalysisModel& model,
|
||||||
const AnalysisModel& model,
|
|
||||||
const DofManager& dofs) {
|
const DofManager& dofs) {
|
||||||
const Domain& domain = model.domain();
|
const Domain& domain = model.GetDomain();
|
||||||
if (domain.Nodes().size() >
|
if (domain.Nodes().size() >
|
||||||
(std::numeric_limits<std::size_t>::max)() / dofsPerNode) {
|
(std::numeric_limits<std::size_t>::max)() / kDofsPerNode) {
|
||||||
return Result<Vector>::Failure(loadFailure(
|
return Result<Vector>::Failure(LoadFailure(
|
||||||
"invalid-load-dimensions",
|
"invalid-load-dimensions", {domain.SourcePath(), 0U}, "LOAD_ASSEMBLER",
|
||||||
{domain.SourcePath(), 0U},
|
|
||||||
"LOAD_ASSEMBLER",
|
|
||||||
domain.SourceContentIdentity(),
|
domain.SourceContentIdentity(),
|
||||||
"The semantic node count cannot be represented in full-DOF order."));
|
"The semantic node count cannot be represented in full-DOF order."));
|
||||||
}
|
}
|
||||||
const std::size_t expectedFullCount =
|
const std::size_t expected_full_count = domain.Nodes().size() * kDofsPerNode;
|
||||||
domain.Nodes().size() * dofsPerNode;
|
const Status dof_status =
|
||||||
const Status dofStatus = validateDofOrder(
|
ValidateDofOrder(dofs, expected_full_count, {domain.SourcePath(), 0U});
|
||||||
dofs, expectedFullCount, {domain.SourcePath(), 0U});
|
if (!dof_status.IsOk()) {
|
||||||
if (!dofStatus.IsOk()) {
|
return Result<Vector>::Failure(dof_status);
|
||||||
return Result<Vector>::Failure(dofStatus);
|
|
||||||
}
|
}
|
||||||
for (std::size_t node = 0U; node < domain.Nodes().size(); ++node) {
|
for (std::size_t node = 0U; node < domain.Nodes().size(); ++node) {
|
||||||
for (std::size_t component = 0U;
|
for (std::size_t component = 0U; component < kDofsPerNode; ++component) {
|
||||||
component < dofsPerNode;
|
|
||||||
++component) {
|
|
||||||
try {
|
try {
|
||||||
if (dofs.fullDof(
|
if (dofs.FullDof(static_cast<EntityIndex>(node),
|
||||||
static_cast<EntityIndex>(node),
|
|
||||||
static_cast<DofComponent>(component)) !=
|
static_cast<DofComponent>(component)) !=
|
||||||
node * dofsPerNode + component) {
|
node * kDofsPerNode + component) {
|
||||||
return Result<Vector>::Failure(loadFailure(
|
return Result<Vector>::Failure(LoadFailure(
|
||||||
"invalid-load-order",
|
"invalid-load-order", domain.Nodes()[node].location,
|
||||||
domain.Nodes()[node].location,
|
|
||||||
"LOAD_ASSEMBLER",
|
"LOAD_ASSEMBLER",
|
||||||
domain.Nodes()[node].source_id.source_label_text,
|
domain.Nodes()[node].source_id.source_label_text,
|
||||||
"DofManager node/component identity must match full-DOF order."));
|
"DofManager node/component identity must match full-DOF order."));
|
||||||
}
|
}
|
||||||
} catch (const std::out_of_range&) {
|
} catch (const std::out_of_range&) {
|
||||||
return Result<Vector>::Failure(loadFailure(
|
return Result<Vector>::Failure(LoadFailure(
|
||||||
"invalid-load-dimensions",
|
"invalid-load-dimensions", domain.Nodes()[node].location,
|
||||||
domain.Nodes()[node].location,
|
"LOAD_ASSEMBLER", domain.Nodes()[node].source_id.source_label_text,
|
||||||
"LOAD_ASSEMBLER",
|
|
||||||
domain.Nodes()[node].source_id.source_label_text,
|
|
||||||
"DofManager must provide all six DOFs for every semantic node."));
|
"DofManager must provide all six DOFs for every semantic node."));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto& activeLoads = model.activeLoads();
|
const auto& active_loads = model.ActiveLoads();
|
||||||
const auto& loads = model.step().loads;
|
const auto& loads = model.Step().loads;
|
||||||
if (activeLoads.size() != loads.size()) {
|
if (active_loads.size() != loads.size()) {
|
||||||
return Result<Vector>::Failure(loadFailure(
|
return Result<Vector>::Failure(LoadFailure(
|
||||||
"invalid-load-order",
|
"invalid-load-order", model.Step().location, "CLOAD", model.Step().name,
|
||||||
model.step().location,
|
|
||||||
"CLOAD",
|
|
||||||
model.step().name,
|
|
||||||
"The active load view must include every sole-step load once."));
|
"The active load view must include every sole-step load once."));
|
||||||
}
|
}
|
||||||
|
|
||||||
Vector fullLoad{expectedFullCount};
|
Vector full_load{expected_full_count};
|
||||||
// Active load indices are required to be the original source order; this
|
// Active load indices are required to be the original source order; this
|
||||||
// loop is therefore also the fixed floating-point accumulation order.
|
// loop is therefore also the fixed floating-point accumulation order.
|
||||||
for (std::size_t sourceOrder = 0U;
|
for (std::size_t source_order = 0U; source_order < active_loads.size();
|
||||||
sourceOrder < activeLoads.size();
|
++source_order) {
|
||||||
++sourceOrder) {
|
const EntityIndex load_index = active_loads[source_order];
|
||||||
const EntityIndex loadIndex = activeLoads[sourceOrder];
|
if (static_cast<std::size_t>(load_index) != source_order ||
|
||||||
if (static_cast<std::size_t>(loadIndex) != sourceOrder ||
|
load_index >= loads.size()) {
|
||||||
loadIndex >= loads.size()) {
|
return Result<Vector>::Failure(LoadFailure(
|
||||||
return Result<Vector>::Failure(loadFailure(
|
"invalid-load-order", model.Step().location, "CLOAD",
|
||||||
"invalid-load-order",
|
std::to_string(source_order),
|
||||||
model.step().location,
|
|
||||||
"CLOAD",
|
|
||||||
std::to_string(sourceOrder),
|
|
||||||
"Active loads must retain complete stable source order."));
|
"Active loads must retain complete stable source order."));
|
||||||
}
|
}
|
||||||
const auto& load = loads[loadIndex];
|
const auto& load = loads[load_index];
|
||||||
if (load.dof < 1 || load.dof > static_cast<int>(dofsPerNode)) {
|
if (load.dof < 1 || load.dof > static_cast<int>(kDofsPerNode)) {
|
||||||
return Result<Vector>::Failure(loadFailure(
|
return Result<Vector>::Failure(LoadFailure(
|
||||||
"invalid-load-dof",
|
"invalid-load-dof", load.location, "CLOAD", load.target,
|
||||||
load.location,
|
|
||||||
"CLOAD",
|
|
||||||
load.target,
|
|
||||||
"A nodal load component must be in the range 1 through 6."));
|
"A nodal load component must be in the range 1 through 6."));
|
||||||
}
|
}
|
||||||
if (!std::isfinite(load.magnitude)) {
|
if (!std::isfinite(load.magnitude)) {
|
||||||
return Result<Vector>::Failure(loadFailure(
|
return Result<Vector>::Failure(
|
||||||
"nonfinite-load-value",
|
LoadFailure("nonfinite-load-value", load.location, "CLOAD",
|
||||||
load.location,
|
load.target, "A nodal load magnitude must be finite."));
|
||||||
"CLOAD",
|
|
||||||
load.target,
|
|
||||||
"A nodal load magnitude must be finite."));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
auto target = resolveTarget(domain, load);
|
auto target = ResolveTarget(domain, load);
|
||||||
if (!target.HasValue()) {
|
if (!target.HasValue()) {
|
||||||
return Result<Vector>::Failure(target.GetStatus());
|
return Result<Vector>::Failure(target.GetStatus());
|
||||||
}
|
}
|
||||||
const auto component = static_cast<DofComponent>(load.dof - 1);
|
const auto component = static_cast<DofComponent>(load.dof - 1);
|
||||||
for (const EntityIndex node : target.Value()) {
|
for (const EntityIndex node : target.Value()) {
|
||||||
const std::size_t fullDof = dofs.fullDof(node, component);
|
const std::size_t full_dof = dofs.FullDof(node, component);
|
||||||
const double accumulated = fullLoad[fullDof] + load.magnitude;
|
const double accumulated = full_load[full_dof] + load.magnitude;
|
||||||
if (!std::isfinite(accumulated)) {
|
if (!std::isfinite(accumulated)) {
|
||||||
return Result<Vector>::Failure(loadFailure(
|
return Result<Vector>::Failure(LoadFailure(
|
||||||
"nonfinite-load-accumulation",
|
"nonfinite-load-accumulation", load.location, "CLOAD", load.target,
|
||||||
load.location,
|
|
||||||
"CLOAD",
|
|
||||||
load.target,
|
|
||||||
"Source-order load accumulation produced a nonfinite value."));
|
"Source-order load accumulation produced a nonfinite value."));
|
||||||
}
|
}
|
||||||
fullLoad[fullDof] = accumulated;
|
full_load[full_dof] = accumulated;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const Status shellMomentStatus = validateShellMoments(domain, fullLoad);
|
const Status shell_moment_status = ValidateShellMoments(domain, full_load);
|
||||||
if (!shellMomentStatus.IsOk()) {
|
if (!shell_moment_status.IsOk()) {
|
||||||
return Result<Vector>::Failure(shellMomentStatus);
|
return Result<Vector>::Failure(shell_moment_status);
|
||||||
}
|
}
|
||||||
return Result<Vector>::Success(std::move(fullLoad));
|
return Result<Vector>::Success(std::move(full_load));
|
||||||
}
|
}
|
||||||
|
|
||||||
Result<Vector> LoadAssembler::effectiveFreeRhs(
|
Result<Vector> LoadAssembler::EffectiveFreeRhs(const Vector& full_load,
|
||||||
const Vector& fullLoad,
|
|
||||||
const SparseMatrix& kfc,
|
const SparseMatrix& kfc,
|
||||||
const Vector& prescribedValues,
|
const Vector& prescribed_values,
|
||||||
const DofManager& dofs) {
|
const DofManager& dofs) {
|
||||||
const SourceLocation location{{}, 0U};
|
const SourceLocation location{{}, 0U};
|
||||||
const Status dofStatus =
|
const Status dof_status = ValidateDofOrder(dofs, full_load.Size(), location);
|
||||||
validateDofOrder(dofs, fullLoad.Size(), location);
|
if (!dof_status.IsOk()) {
|
||||||
if (!dofStatus.IsOk()) {
|
return Result<Vector>::Failure(dof_status);
|
||||||
return Result<Vector>::Failure(dofStatus);
|
|
||||||
}
|
}
|
||||||
if (kfc.Rows() != dofs.freeDofCount() ||
|
if (kfc.Rows() != dofs.FreeDofCount() ||
|
||||||
kfc.Columns() != dofs.constrainedDofCount() ||
|
kfc.Columns() != dofs.ConstrainedDofCount() ||
|
||||||
prescribedValues.Size() != dofs.constrainedDofCount()) {
|
prescribed_values.Size() != dofs.ConstrainedDofCount()) {
|
||||||
return Result<Vector>::Failure(loadFailure(
|
return Result<Vector>::Failure(LoadFailure(
|
||||||
"invalid-load-dimensions",
|
"invalid-load-dimensions", location, "LOAD_ASSEMBLER",
|
||||||
location,
|
std::to_string(kfc.Rows()) + "x" + std::to_string(kfc.Columns()),
|
||||||
"LOAD_ASSEMBLER",
|
"Kfc rows/columns and prescribed values must match free/constrained "
|
||||||
std::to_string(kfc.Rows()) + "x" +
|
"order."));
|
||||||
std::to_string(kfc.Columns()),
|
|
||||||
"Kfc rows/columns and prescribed values must match free/constrained order."));
|
|
||||||
}
|
}
|
||||||
const Status matrixStatus = kfc.Validate();
|
const Status matrix_status = kfc.Validate();
|
||||||
if (!matrixStatus.IsOk()) {
|
if (!matrix_status.IsOk()) {
|
||||||
return Result<Vector>::Failure(matrixStatus);
|
return Result<Vector>::Failure(matrix_status);
|
||||||
}
|
}
|
||||||
const Status loadStatus =
|
const Status load_status =
|
||||||
validateFiniteVector(fullLoad, location, "full-load");
|
ValidateFiniteVector(full_load, location, "full-load");
|
||||||
if (!loadStatus.IsOk()) {
|
if (!load_status.IsOk()) {
|
||||||
return Result<Vector>::Failure(loadStatus);
|
return Result<Vector>::Failure(load_status);
|
||||||
}
|
}
|
||||||
const Status prescribedStatus = validateFiniteVector(
|
const Status prescribed_status =
|
||||||
prescribedValues, location, "prescribed-values");
|
ValidateFiniteVector(prescribed_values, location, "prescribed-values");
|
||||||
if (!prescribedStatus.IsOk()) {
|
if (!prescribed_status.IsOk()) {
|
||||||
return Result<Vector>::Failure(prescribedStatus);
|
return Result<Vector>::Failure(prescribed_status);
|
||||||
}
|
}
|
||||||
|
|
||||||
Vector correction{kfc.Rows()};
|
Vector correction{kfc.Rows()};
|
||||||
for (std::size_t row = 0U; row < kfc.Rows(); ++row) {
|
for (std::size_t row = 0U; row < kfc.Rows(); ++row) {
|
||||||
double sum = 0.0;
|
double sum = 0.0;
|
||||||
for (std::size_t position = kfc.RowOffsets()[row];
|
for (std::size_t position = kfc.RowOffsets()[row];
|
||||||
position < kfc.RowOffsets()[row + 1U];
|
position < kfc.RowOffsets()[row + 1U]; ++position) {
|
||||||
++position) {
|
|
||||||
const double product = kfc.Values()[position] *
|
const double product = kfc.Values()[position] *
|
||||||
prescribedValues[kfc.ColumnIndices()[position]];
|
prescribed_values[kfc.ColumnIndices()[position]];
|
||||||
if (!std::isfinite(product)) {
|
if (!std::isfinite(product)) {
|
||||||
return Result<Vector>::Failure(loadFailure(
|
return Result<Vector>::Failure(LoadFailure(
|
||||||
"nonfinite-load-accumulation",
|
"nonfinite-load-accumulation", location, "LOAD_ASSEMBLER",
|
||||||
location,
|
|
||||||
"LOAD_ASSEMBLER",
|
|
||||||
std::to_string(row),
|
std::to_string(row),
|
||||||
"Kfc times prescribed displacement produced a nonfinite product."));
|
"Kfc times prescribed displacement produced a nonfinite product."));
|
||||||
}
|
}
|
||||||
sum += product;
|
sum += product;
|
||||||
if (!std::isfinite(sum)) {
|
if (!std::isfinite(sum)) {
|
||||||
return Result<Vector>::Failure(loadFailure(
|
return Result<Vector>::Failure(LoadFailure(
|
||||||
"nonfinite-load-accumulation",
|
"nonfinite-load-accumulation", location, "LOAD_ASSEMBLER",
|
||||||
location,
|
|
||||||
"LOAD_ASSEMBLER",
|
|
||||||
std::to_string(row),
|
std::to_string(row),
|
||||||
"Kfc times prescribed displacement produced a nonfinite row sum."));
|
"Kfc times prescribed displacement produced a nonfinite row sum."));
|
||||||
}
|
}
|
||||||
@@ -466,16 +391,14 @@ Result<Vector> LoadAssembler::effectiveFreeRhs(
|
|||||||
correction[row] = sum;
|
correction[row] = sum;
|
||||||
}
|
}
|
||||||
|
|
||||||
Vector rhs = EssentialConstraints::gatherFree(fullLoad, dofs);
|
Vector rhs = EssentialConstraints::GatherFree(full_load, dofs);
|
||||||
// The constrained vector is already in DofManager order, so this is the
|
// The constrained vector is already in DofManager order, so this is the
|
||||||
// approved elimination equation rhs = Ff - Kfc*dc without reordering dc.
|
// approved elimination equation rhs = Ff - Kfc*dc without reordering dc.
|
||||||
for (std::size_t row = 0U; row < rhs.Size(); ++row) {
|
for (std::size_t row = 0U; row < rhs.Size(); ++row) {
|
||||||
const double value = rhs[row] - correction[row];
|
const double value = rhs[row] - correction[row];
|
||||||
if (!std::isfinite(value)) {
|
if (!std::isfinite(value)) {
|
||||||
return Result<Vector>::Failure(loadFailure(
|
return Result<Vector>::Failure(
|
||||||
"nonfinite-load-accumulation",
|
LoadFailure("nonfinite-load-accumulation", location, "LOAD_ASSEMBLER",
|
||||||
location,
|
|
||||||
"LOAD_ASSEMBLER",
|
|
||||||
std::to_string(row),
|
std::to_string(row),
|
||||||
"Effective RHS subtraction produced a nonfinite value."));
|
"Effective RHS subtraction produced a nonfinite value."));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,20 +1,18 @@
|
|||||||
#include "fesa/assembly/parallel_for.hpp"
|
#include "fesa/assembly/parallel_for.h"
|
||||||
|
|
||||||
#include <oneapi/tbb/parallel_for.h>
|
#include <oneapi/tbb/parallel_for.h>
|
||||||
|
|
||||||
namespace fesa {
|
namespace fesa {
|
||||||
|
|
||||||
void SerialParallelFor::execute(
|
void SerialParallelFor::Execute(
|
||||||
std::size_t count,
|
std::size_t count, const std::function<void(std::size_t)>& body) const {
|
||||||
const std::function<void(std::size_t)>& body) const {
|
|
||||||
for (std::size_t index = 0; index < count; ++index) {
|
for (std::size_t index = 0; index < count; ++index) {
|
||||||
body(index);
|
body(index);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void TbbParallelFor::execute(
|
void TbbParallelFor::Execute(
|
||||||
std::size_t count,
|
std::size_t count, const std::function<void(std::size_t)>& body) const {
|
||||||
const std::function<void(std::size_t)>& body) const {
|
|
||||||
if (count == 0U) {
|
if (count == 0U) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -23,8 +21,8 @@ void TbbParallelFor::execute(
|
|||||||
// process-wide concurrency or override the later MKL/TBB oversubscription
|
// process-wide concurrency or override the later MKL/TBB oversubscription
|
||||||
// policy. A body exception cancels sibling tasks and is rethrown; work
|
// policy. A body exception cancels sibling tasks and is rethrown; work
|
||||||
// already running during cancellation may still finish its indexed slot.
|
// already running during cancellation may still finish its indexed slot.
|
||||||
oneapi::tbb::parallel_for(
|
oneapi::tbb::parallel_for(std::size_t{0}, count,
|
||||||
std::size_t{0}, count, [&body](std::size_t index) { body(index); });
|
[&body](std::size_t index) { body(index); });
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace fesa
|
} // namespace fesa
|
||||||
|
|||||||
@@ -1,10 +1,4 @@
|
|||||||
#include "fesa/assembly/sparse_assembler.hpp"
|
#include "fesa/assembly/sparse_assembler.h"
|
||||||
|
|
||||||
#include "fesa/analysis/analysis_model.hpp"
|
|
||||||
#include "fesa/assembly/parallel_for.hpp"
|
|
||||||
#include "fesa/elements/euler_beam_3d.h"
|
|
||||||
#include "fesa/elements/mitc4_shell.h"
|
|
||||||
#include "fesa/fem/dof_manager.hpp"
|
|
||||||
|
|
||||||
#include <array>
|
#include <array>
|
||||||
#include <limits>
|
#include <limits>
|
||||||
@@ -14,6 +8,12 @@
|
|||||||
#include <utility>
|
#include <utility>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
|
#include "fesa/analysis/analysis_model.h"
|
||||||
|
#include "fesa/assembly/parallel_for.h"
|
||||||
|
#include "fesa/elements/euler_beam_3d.h"
|
||||||
|
#include "fesa/elements/mitc4_shell.h"
|
||||||
|
#include "fesa/fem/dof_manager.h"
|
||||||
|
|
||||||
namespace fesa {
|
namespace fesa {
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
@@ -25,73 +25,59 @@ constexpr std::size_t kShellElementDofCount = 24U;
|
|||||||
constexpr std::size_t kShellContributionCount =
|
constexpr std::size_t kShellContributionCount =
|
||||||
kShellElementDofCount * kShellElementDofCount;
|
kShellElementDofCount * kShellElementDofCount;
|
||||||
|
|
||||||
using BeamElementBuffer =
|
using BeamElementBuffer = std::array<CooContribution, kBeamContributionCount>;
|
||||||
std::array<CooContribution, kBeamContributionCount>;
|
using ShellElementBuffer = std::array<CooContribution, kShellContributionCount>;
|
||||||
using ShellElementBuffer =
|
|
||||||
std::array<CooContribution, kShellContributionCount>;
|
|
||||||
|
|
||||||
Result<SparseMatrix> assemblyFailure(
|
Result<SparseMatrix> AssemblyFailure(const std::string& code,
|
||||||
const std::string& code,
|
|
||||||
const SourceLocation& location,
|
const SourceLocation& location,
|
||||||
const std::string& identity,
|
const std::string& identity,
|
||||||
const std::string& message) {
|
const std::string& message) {
|
||||||
return Result<SparseMatrix>::Failure(Status::Failure(
|
return Result<SparseMatrix>::Failure(Status::Failure(
|
||||||
FailureCategory::kModel,
|
FailureCategory::kModel,
|
||||||
{{Severity::kError,
|
{{Severity::kError, code, location, "*ELEMENT", identity, message}}));
|
||||||
code,
|
|
||||||
location,
|
|
||||||
"*ELEMENT",
|
|
||||||
identity,
|
|
||||||
message}}));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
Result<SparseMatrix> SparseAssembler::assembleStiffness(
|
Result<SparseMatrix> SparseAssembler::AssembleStiffness(
|
||||||
const AnalysisModel& model,
|
const AnalysisModel& model, const DofManager& dofs,
|
||||||
const DofManager& dofs,
|
const ParallelFor& parallel_for) {
|
||||||
const ParallelFor& parallelFor) {
|
const Domain& domain = model.GetDomain();
|
||||||
const Domain& domain = model.domain();
|
|
||||||
if (domain.Nodes().size() >
|
if (domain.Nodes().size() >
|
||||||
(std::numeric_limits<std::size_t>::max)() / kDofsPerNode ||
|
(std::numeric_limits<std::size_t>::max)() / kDofsPerNode ||
|
||||||
dofs.fullDofCount() != domain.Nodes().size() * kDofsPerNode) {
|
dofs.FullDofCount() != domain.Nodes().size() * kDofsPerNode) {
|
||||||
return assemblyFailure(
|
return AssemblyFailure(
|
||||||
"invalid-assembly-dimensions",
|
"invalid-assembly-dimensions", {domain.SourcePath(), 0U},
|
||||||
{domain.SourcePath(), 0U},
|
std::to_string(dofs.FullDofCount()),
|
||||||
std::to_string(dofs.fullDofCount()),
|
|
||||||
"DofManager dimensions do not match the active model nodes.");
|
"DofManager dimensions do not match the active model nodes.");
|
||||||
}
|
}
|
||||||
if (!model.activeElements().empty() && !domain.ShellElements().empty()) {
|
if (!model.ActiveElements().empty() && !domain.ShellElements().empty()) {
|
||||||
return assemblyFailure(
|
return AssemblyFailure(
|
||||||
"unsupported-mixed-element-model",
|
"unsupported-mixed-element-model", {domain.SourcePath(), 0U},
|
||||||
{domain.SourcePath(), 0U},
|
|
||||||
"B33:FESA-MITC4",
|
"B33:FESA-MITC4",
|
||||||
"Sparse assembly does not support mixed beam and shell models.");
|
"Sparse assembly does not support mixed beam and shell models.");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!domain.ShellElements().empty()) {
|
if (!domain.ShellElements().empty()) {
|
||||||
if (domain.ShellElements().size() >
|
if (domain.ShellElements().size() >
|
||||||
(std::numeric_limits<std::size_t>::max)() /
|
(std::numeric_limits<std::size_t>::max)() / kShellContributionCount) {
|
||||||
kShellContributionCount) {
|
return AssemblyFailure(
|
||||||
return assemblyFailure(
|
"invalid-assembly-dimensions", {domain.SourcePath(), 0U},
|
||||||
"invalid-assembly-dimensions",
|
|
||||||
{domain.SourcePath(), 0U},
|
|
||||||
std::to_string(domain.ShellElements().size()),
|
std::to_string(domain.ShellElements().size()),
|
||||||
"Shell contribution storage exceeds the addressable range.");
|
"Shell contribution storage exceeds the addressable range.");
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<std::optional<std::array<double, 3>>> directorsByNode(
|
std::vector<std::optional<std::array<double, 3>>> directors_by_node(
|
||||||
domain.Nodes().size());
|
domain.Nodes().size());
|
||||||
for (const auto& frame : domain.ShellNodeInitialFrames()) {
|
for (const auto& frame : domain.ShellNodeInitialFrames()) {
|
||||||
if (frame.node_index >= directorsByNode.size() ||
|
if (frame.node_index >= directors_by_node.size() ||
|
||||||
directorsByNode[frame.node_index]) {
|
directors_by_node[frame.node_index]) {
|
||||||
return assemblyFailure(
|
return AssemblyFailure(
|
||||||
"invalid-assembly-element",
|
"invalid-assembly-element", {domain.SourcePath(), 0U},
|
||||||
{domain.SourcePath(), 0U},
|
|
||||||
std::to_string(frame.node_index),
|
std::to_string(frame.node_index),
|
||||||
"Shell initial frames must map uniquely to model nodes.");
|
"Shell initial frames must map uniquely to model nodes.");
|
||||||
}
|
}
|
||||||
directorsByNode[frame.node_index] = frame.director;
|
directors_by_node[frame.node_index] = frame.director;
|
||||||
}
|
}
|
||||||
|
|
||||||
struct ShellInput {
|
struct ShellInput {
|
||||||
@@ -103,15 +89,13 @@ Result<SparseMatrix> SparseAssembler::assembleStiffness(
|
|||||||
};
|
};
|
||||||
std::vector<ShellInput> inputs;
|
std::vector<ShellInput> inputs;
|
||||||
inputs.reserve(domain.ShellElements().size());
|
inputs.reserve(domain.ShellElements().size());
|
||||||
for (std::size_t elementOrder = 0U;
|
for (std::size_t element_order = 0U;
|
||||||
elementOrder < domain.ShellElements().size();
|
element_order < domain.ShellElements().size(); ++element_order) {
|
||||||
++elementOrder) {
|
const auto& element = domain.ShellElements()[element_order];
|
||||||
const auto& element = domain.ShellElements()[elementOrder];
|
|
||||||
if (element.material_index >= domain.Materials().size() ||
|
if (element.material_index >= domain.Materials().size() ||
|
||||||
element.section_index >= domain.ShellSections().size()) {
|
element.section_index >= domain.ShellSections().size()) {
|
||||||
return assemblyFailure(
|
return AssemblyFailure(
|
||||||
"invalid-assembly-element",
|
"invalid-assembly-element", element.location,
|
||||||
element.location,
|
|
||||||
element.source_id.source_label_text,
|
element.source_id.source_label_text,
|
||||||
"Shell element references an entity outside the Domain.");
|
"Shell element references an entity outside the Domain.");
|
||||||
}
|
}
|
||||||
@@ -120,42 +104,35 @@ Result<SparseMatrix> SparseAssembler::assembleStiffness(
|
|||||||
input.section = &domain.ShellSections()[element.section_index];
|
input.section = &domain.ShellSections()[element.section_index];
|
||||||
input.material = &domain.Materials()[element.material_index];
|
input.material = &domain.Materials()[element.material_index];
|
||||||
try {
|
try {
|
||||||
input.scatter = dofs.shellElementScatter(
|
input.scatter =
|
||||||
static_cast<EntityIndex>(elementOrder));
|
dofs.ShellElementScatter(static_cast<EntityIndex>(element_order));
|
||||||
} catch (const std::out_of_range&) {
|
} catch (const std::out_of_range&) {
|
||||||
return assemblyFailure(
|
return AssemblyFailure(
|
||||||
"invalid-assembly-scatter",
|
"invalid-assembly-scatter", element.location,
|
||||||
element.location,
|
|
||||||
element.source_id.source_label_text,
|
element.source_id.source_label_text,
|
||||||
"DofManager does not contain the active shell scatter.");
|
"DofManager does not contain the active shell scatter.");
|
||||||
}
|
}
|
||||||
for (std::size_t nodePosition = 0U;
|
for (std::size_t node_position = 0U;
|
||||||
nodePosition < element.node_indices.size();
|
node_position < element.node_indices.size(); ++node_position) {
|
||||||
++nodePosition) {
|
const EntityIndex node_index = element.node_indices[node_position];
|
||||||
const EntityIndex nodeIndex = element.node_indices[nodePosition];
|
if (node_index >= domain.Nodes().size() ||
|
||||||
if (nodeIndex >= domain.Nodes().size() ||
|
!directors_by_node[node_index]) {
|
||||||
!directorsByNode[nodeIndex]) {
|
return AssemblyFailure(
|
||||||
return assemblyFailure(
|
"invalid-assembly-element", element.location,
|
||||||
"invalid-assembly-element",
|
|
||||||
element.location,
|
|
||||||
element.source_id.source_label_text,
|
element.source_id.source_label_text,
|
||||||
"Shell element requires a valid node and initial director.");
|
"Shell element requires a valid node and initial director.");
|
||||||
}
|
}
|
||||||
input.nodes[nodePosition] = &domain.Nodes()[nodeIndex];
|
input.nodes[node_position] = &domain.Nodes()[node_index];
|
||||||
input.directors[nodePosition] = *directorsByNode[nodeIndex];
|
input.directors[node_position] = *directors_by_node[node_index];
|
||||||
for (std::size_t component = 0U;
|
for (std::size_t component = 0U; component < kDofsPerNode;
|
||||||
component < kDofsPerNode;
|
|
||||||
++component) {
|
++component) {
|
||||||
const std::size_t local =
|
const std::size_t local = node_position * kDofsPerNode + component;
|
||||||
nodePosition * kDofsPerNode + component;
|
|
||||||
const std::size_t expected =
|
const std::size_t expected =
|
||||||
static_cast<std::size_t>(nodeIndex) * kDofsPerNode +
|
static_cast<std::size_t>(node_index) * kDofsPerNode + component;
|
||||||
component;
|
|
||||||
if (input.scatter[local] != expected ||
|
if (input.scatter[local] != expected ||
|
||||||
input.scatter[local] >= dofs.fullDofCount()) {
|
input.scatter[local] >= dofs.FullDofCount()) {
|
||||||
return assemblyFailure(
|
return AssemblyFailure(
|
||||||
"invalid-assembly-scatter",
|
"invalid-assembly-scatter", element.location,
|
||||||
element.location,
|
|
||||||
element.source_id.source_label_text,
|
element.source_id.source_label_text,
|
||||||
"Shell scatter does not match the active model topology.");
|
"Shell scatter does not match the active model topology.");
|
||||||
}
|
}
|
||||||
@@ -164,128 +141,104 @@ Result<SparseMatrix> SparseAssembler::assembleStiffness(
|
|||||||
inputs.push_back(input);
|
inputs.push_back(input);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<ShellElementBuffer> localBuffers(inputs.size());
|
std::vector<ShellElementBuffer> local_buffers(inputs.size());
|
||||||
std::vector<std::optional<Status>> localFailures(inputs.size());
|
std::vector<std::optional<Status>> local_failures(inputs.size());
|
||||||
parallelFor.execute(
|
parallel_for.Execute(inputs.size(), [&](const std::size_t element_order) {
|
||||||
inputs.size(),
|
const auto& input = inputs[element_order];
|
||||||
[&](const std::size_t elementOrder) {
|
const auto shell = Mitc4Shell::Create(input.nodes, input.directors,
|
||||||
const auto& input = inputs[elementOrder];
|
*input.section, *input.material);
|
||||||
const auto shell = Mitc4Shell::Create(
|
|
||||||
input.nodes,
|
|
||||||
input.directors,
|
|
||||||
*input.section,
|
|
||||||
*input.material);
|
|
||||||
if (!shell.HasValue()) {
|
if (!shell.HasValue()) {
|
||||||
localFailures[elementOrder] = shell.GetStatus();
|
local_failures[element_order] = shell.GetStatus();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const auto stiffness = shell.Value().Stiffness();
|
const auto stiffness = shell.Value().Stiffness();
|
||||||
if (!stiffness.HasValue()) {
|
if (!stiffness.HasValue()) {
|
||||||
localFailures[elementOrder] = stiffness.GetStatus();
|
local_failures[element_order] = stiffness.GetStatus();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto& buffer = localBuffers[elementOrder];
|
auto& buffer = local_buffers[element_order];
|
||||||
for (std::size_t localRow = 0U;
|
for (std::size_t local_row = 0U; local_row < kShellElementDofCount;
|
||||||
localRow < kShellElementDofCount;
|
++local_row) {
|
||||||
++localRow) {
|
for (std::size_t local_column = 0U;
|
||||||
for (std::size_t localColumn = 0U;
|
local_column < kShellElementDofCount; ++local_column) {
|
||||||
localColumn < kShellElementDofCount;
|
const std::size_t local_order =
|
||||||
++localColumn) {
|
local_row * kShellElementDofCount + local_column;
|
||||||
const std::size_t localOrder =
|
buffer[local_order] = {
|
||||||
localRow * kShellElementDofCount + localColumn;
|
input.scatter[local_row], input.scatter[local_column],
|
||||||
buffer[localOrder] = {
|
stiffness.Value().stabilized_global24(local_row, local_column),
|
||||||
input.scatter[localRow],
|
element_order, local_order};
|
||||||
input.scatter[localColumn],
|
|
||||||
stiffness.Value().stabilized_global24(
|
|
||||||
localRow, localColumn),
|
|
||||||
elementOrder,
|
|
||||||
localOrder};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
for (std::size_t elementOrder = 0U;
|
for (std::size_t element_order = 0U; element_order < local_failures.size();
|
||||||
elementOrder < localFailures.size();
|
++element_order) {
|
||||||
++elementOrder) {
|
if (local_failures[element_order]) {
|
||||||
if (localFailures[elementOrder]) {
|
return Result<SparseMatrix>::Failure(*local_failures[element_order]);
|
||||||
return Result<SparseMatrix>::Failure(
|
|
||||||
*localFailures[elementOrder]);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<CooContribution> contributions;
|
std::vector<CooContribution> contributions;
|
||||||
contributions.reserve(
|
contributions.reserve(local_buffers.size() * kShellContributionCount);
|
||||||
localBuffers.size() * kShellContributionCount);
|
|
||||||
// Flatten in source-element order after workers complete. The canonical
|
// Flatten in source-element order after workers complete. The canonical
|
||||||
// COO reduction remains the sole writer of global CSR values.
|
// COO reduction remains the sole writer of global CSR values.
|
||||||
for (const auto& buffer : localBuffers) {
|
for (const auto& buffer : local_buffers) {
|
||||||
contributions.insert(
|
contributions.insert(contributions.end(), buffer.begin(), buffer.end());
|
||||||
contributions.end(), buffer.begin(), buffer.end());
|
|
||||||
}
|
}
|
||||||
return SparseMatrix::FromCoo(
|
return SparseMatrix::FromCoo(dofs.FullDofCount(), dofs.FullDofCount(),
|
||||||
dofs.fullDofCount(),
|
|
||||||
dofs.fullDofCount(),
|
|
||||||
std::move(contributions),
|
std::move(contributions),
|
||||||
dofs.sparsePattern());
|
dofs.GetSparsePattern());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (model.activeElements().size() >
|
if (model.ActiveElements().size() >
|
||||||
(std::numeric_limits<std::size_t>::max)() /
|
(std::numeric_limits<std::size_t>::max)() / kBeamContributionCount) {
|
||||||
kBeamContributionCount) {
|
return AssemblyFailure(
|
||||||
return assemblyFailure(
|
"invalid-assembly-dimensions", {domain.SourcePath(), 0U},
|
||||||
"invalid-assembly-dimensions",
|
std::to_string(model.ActiveElements().size()),
|
||||||
{domain.SourcePath(), 0U},
|
|
||||||
std::to_string(model.activeElements().size()),
|
|
||||||
"Element contribution storage exceeds the addressable range.");
|
"Element contribution storage exceeds the addressable range.");
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<std::array<std::size_t, kBeamElementDofCount>> scatters;
|
std::vector<std::array<std::size_t, kBeamElementDofCount>> scatters;
|
||||||
scatters.reserve(model.activeElements().size());
|
scatters.reserve(model.ActiveElements().size());
|
||||||
for (const EntityIndex elementIndex : model.activeElements()) {
|
for (const EntityIndex element_index : model.ActiveElements()) {
|
||||||
if (elementIndex >= domain.Elements().size()) {
|
if (element_index >= domain.Elements().size()) {
|
||||||
return assemblyFailure(
|
return AssemblyFailure("invalid-assembly-element",
|
||||||
"invalid-assembly-element",
|
|
||||||
{domain.SourcePath(), 0U},
|
{domain.SourcePath(), 0U},
|
||||||
std::to_string(elementIndex),
|
std::to_string(element_index),
|
||||||
"Active element index is outside the Domain.");
|
"Active element index is outside the Domain.");
|
||||||
}
|
}
|
||||||
const auto& element = domain.Elements()[elementIndex];
|
const auto& element = domain.Elements()[element_index];
|
||||||
if (element.node_indices[0U] >= domain.Nodes().size() ||
|
if (element.node_indices[0U] >= domain.Nodes().size() ||
|
||||||
element.node_indices[1U] >= domain.Nodes().size() ||
|
element.node_indices[1U] >= domain.Nodes().size() ||
|
||||||
element.material_index >= domain.Materials().size() ||
|
element.material_index >= domain.Materials().size() ||
|
||||||
element.section_index >= domain.Sections().size()) {
|
element.section_index >= domain.Sections().size()) {
|
||||||
return assemblyFailure(
|
return AssemblyFailure(
|
||||||
"invalid-assembly-element",
|
"invalid-assembly-element", element.location,
|
||||||
element.location,
|
|
||||||
element.source_id.source_label_text,
|
element.source_id.source_label_text,
|
||||||
"Element references an entity outside the Domain.");
|
"Element references an entity outside the Domain.");
|
||||||
}
|
}
|
||||||
|
|
||||||
std::array<std::size_t, kBeamElementDofCount> scatter{};
|
std::array<std::size_t, kBeamElementDofCount> scatter{};
|
||||||
try {
|
try {
|
||||||
scatter = dofs.elementScatter(elementIndex);
|
scatter = dofs.ElementScatter(element_index);
|
||||||
} catch (const std::out_of_range&) {
|
} catch (const std::out_of_range&) {
|
||||||
return assemblyFailure(
|
return AssemblyFailure(
|
||||||
"invalid-assembly-scatter",
|
"invalid-assembly-scatter", element.location,
|
||||||
element.location,
|
|
||||||
element.source_id.source_label_text,
|
element.source_id.source_label_text,
|
||||||
"DofManager does not contain the active element scatter.");
|
"DofManager does not contain the active element scatter.");
|
||||||
}
|
}
|
||||||
for (std::size_t endpoint = 0U; endpoint < 2U; ++endpoint) {
|
for (std::size_t endpoint = 0U; endpoint < 2U; ++endpoint) {
|
||||||
for (std::size_t component = 0U;
|
for (std::size_t component = 0U; component < kDofsPerNode; ++component) {
|
||||||
component < kDofsPerNode;
|
|
||||||
++component) {
|
|
||||||
const std::size_t local = endpoint * kDofsPerNode + component;
|
const std::size_t local = endpoint * kDofsPerNode + component;
|
||||||
const std::size_t expected =
|
const std::size_t expected =
|
||||||
static_cast<std::size_t>(element.node_indices[endpoint]) *
|
static_cast<std::size_t>(element.node_indices[endpoint]) *
|
||||||
kDofsPerNode +
|
kDofsPerNode +
|
||||||
component;
|
component;
|
||||||
if (scatter[local] != expected ||
|
if (scatter[local] != expected ||
|
||||||
scatter[local] >= dofs.fullDofCount()) {
|
scatter[local] >= dofs.FullDofCount()) {
|
||||||
return assemblyFailure(
|
return AssemblyFailure(
|
||||||
"invalid-assembly-scatter",
|
"invalid-assembly-scatter", element.location,
|
||||||
element.location,
|
|
||||||
element.source_id.source_label_text,
|
element.source_id.source_label_text,
|
||||||
"Element scatter does not match the active model topology.");
|
"Element scatter does not match the active model topology.");
|
||||||
}
|
}
|
||||||
@@ -294,67 +247,55 @@ Result<SparseMatrix> SparseAssembler::assembleStiffness(
|
|||||||
scatters.push_back(scatter);
|
scatters.push_back(scatter);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<BeamElementBuffer> localBuffers(model.activeElements().size());
|
std::vector<BeamElementBuffer> local_buffers(model.ActiveElements().size());
|
||||||
std::vector<std::optional<Status>> localFailures(
|
std::vector<std::optional<Status>> local_failures(
|
||||||
model.activeElements().size());
|
model.ActiveElements().size());
|
||||||
parallelFor.execute(
|
parallel_for.Execute(
|
||||||
model.activeElements().size(),
|
model.ActiveElements().size(), [&](const std::size_t element_order) {
|
||||||
[&](const std::size_t elementOrder) {
|
const EntityIndex element_index = model.ActiveElements()[element_order];
|
||||||
const EntityIndex elementIndex = model.activeElements()[elementOrder];
|
const auto& definition = domain.Elements()[element_index];
|
||||||
const auto& definition = domain.Elements()[elementIndex];
|
const auto beam =
|
||||||
const auto beam = EulerBeam3D::Create(
|
EulerBeam3D::Create(domain.Nodes()[definition.node_indices[0U]],
|
||||||
domain.Nodes()[definition.node_indices[0U]],
|
|
||||||
domain.Nodes()[definition.node_indices[1U]],
|
domain.Nodes()[definition.node_indices[1U]],
|
||||||
domain.Sections()[definition.section_index],
|
domain.Sections()[definition.section_index],
|
||||||
domain.Materials()[definition.material_index]);
|
domain.Materials()[definition.material_index]);
|
||||||
if (!beam.HasValue()) {
|
if (!beam.HasValue()) {
|
||||||
localFailures[elementOrder] = beam.GetStatus();
|
local_failures[element_order] = beam.GetStatus();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const Matrix stiffness = beam.Value().GlobalStiffness();
|
const Matrix stiffness = beam.Value().GlobalStiffness();
|
||||||
auto& buffer = localBuffers[elementOrder];
|
auto& buffer = local_buffers[element_order];
|
||||||
const auto& scatter = scatters[elementOrder];
|
const auto& scatter = scatters[element_order];
|
||||||
for (std::size_t localRow = 0U;
|
for (std::size_t local_row = 0U; local_row < kBeamElementDofCount;
|
||||||
localRow < kBeamElementDofCount;
|
++local_row) {
|
||||||
++localRow) {
|
for (std::size_t local_column = 0U;
|
||||||
for (std::size_t localColumn = 0U;
|
local_column < kBeamElementDofCount; ++local_column) {
|
||||||
localColumn < kBeamElementDofCount;
|
const std::size_t local_order =
|
||||||
++localColumn) {
|
local_row * kBeamElementDofCount + local_column;
|
||||||
const std::size_t localOrder =
|
buffer[local_order] = {scatter[local_row], scatter[local_column],
|
||||||
localRow * kBeamElementDofCount + localColumn;
|
stiffness(local_row, local_column),
|
||||||
buffer[localOrder] = {
|
element_order, local_order};
|
||||||
scatter[localRow],
|
|
||||||
scatter[localColumn],
|
|
||||||
stiffness(localRow, localColumn),
|
|
||||||
elementOrder,
|
|
||||||
localOrder};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
for (std::size_t elementOrder = 0U;
|
for (std::size_t element_order = 0U; element_order < local_failures.size();
|
||||||
elementOrder < localFailures.size();
|
++element_order) {
|
||||||
++elementOrder) {
|
if (local_failures[element_order]) {
|
||||||
if (localFailures[elementOrder]) {
|
return Result<SparseMatrix>::Failure(*local_failures[element_order]);
|
||||||
return Result<SparseMatrix>::Failure(
|
|
||||||
*localFailures[elementOrder]);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<CooContribution> contributions;
|
std::vector<CooContribution> contributions;
|
||||||
contributions.reserve(
|
contributions.reserve(local_buffers.size() * kBeamContributionCount);
|
||||||
localBuffers.size() * kBeamContributionCount);
|
|
||||||
// Flatten only after all workers complete; workers never share CSR state.
|
// Flatten only after all workers complete; workers never share CSR state.
|
||||||
for (const auto& buffer : localBuffers) {
|
for (const auto& buffer : local_buffers) {
|
||||||
contributions.insert(
|
contributions.insert(contributions.end(), buffer.begin(), buffer.end());
|
||||||
contributions.end(), buffer.begin(), buffer.end());
|
|
||||||
}
|
}
|
||||||
return SparseMatrix::FromCoo(
|
return SparseMatrix::FromCoo(dofs.FullDofCount(), dofs.FullDofCount(),
|
||||||
dofs.fullDofCount(),
|
|
||||||
dofs.fullDofCount(),
|
|
||||||
std::move(contributions),
|
std::move(contributions),
|
||||||
dofs.sparsePattern());
|
dofs.GetSparsePattern());
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace fesa
|
} // namespace fesa
|
||||||
|
|||||||
@@ -1,6 +1,4 @@
|
|||||||
#include "fesa/constraints/essential_constraints.hpp"
|
#include "fesa/constraints/essential_constraints.h"
|
||||||
|
|
||||||
#include "fesa/fem/dof_manager.hpp"
|
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <limits>
|
#include <limits>
|
||||||
@@ -9,16 +7,14 @@
|
|||||||
#include <utility>
|
#include <utility>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
|
#include "fesa/fem/dof_manager.h"
|
||||||
|
|
||||||
namespace fesa {
|
namespace fesa {
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
Status constraintFailure(
|
Status ConstraintFailure(const std::string& code, const std::string& identity,
|
||||||
const std::string& code,
|
|
||||||
const std::string& identity,
|
|
||||||
const std::string& message) {
|
const std::string& message) {
|
||||||
return Status::Failure(
|
return Status::Failure(FailureCategory::kModel, {{Severity::kError,
|
||||||
FailureCategory::kModel,
|
|
||||||
{{Severity::kError,
|
|
||||||
code,
|
code,
|
||||||
{{}, 0U},
|
{{}, 0U},
|
||||||
"ESSENTIAL_CONSTRAINTS",
|
"ESSENTIAL_CONSTRAINTS",
|
||||||
@@ -26,125 +22,108 @@ Status constraintFailure(
|
|||||||
message}});
|
message}});
|
||||||
}
|
}
|
||||||
|
|
||||||
bool isStrictlyIncreasing(const std::vector<std::size_t>& values) {
|
bool IsStrictlyIncreasing(const std::vector<std::size_t>& values) {
|
||||||
return std::adjacent_find(
|
return std::adjacent_find(
|
||||||
values.begin(),
|
values.begin(), values.end(),
|
||||||
values.end(),
|
|
||||||
[](const std::size_t left, const std::size_t right) {
|
[](const std::size_t left, const std::size_t right) {
|
||||||
return left >= right;
|
return left >= right;
|
||||||
}) == values.end();
|
}) == values.end();
|
||||||
}
|
}
|
||||||
|
|
||||||
Status validateDofOrder(const DofManager& dofs) {
|
/// @brief Validates the stable full/free/constrained numbering invariant.
|
||||||
const std::size_t fullCount = dofs.fullDofCount();
|
Status ValidateDofOrder(const DofManager& dofs) {
|
||||||
const auto& freeDofs = dofs.freeDofs();
|
const std::size_t full_count = dofs.FullDofCount();
|
||||||
const auto& constrainedDofs = dofs.constrainedDofs();
|
const auto& free_dofs = dofs.FreeDofs();
|
||||||
if (freeDofs.size() != dofs.freeDofCount() ||
|
const auto& constrained_dofs = dofs.ConstrainedDofs();
|
||||||
constrainedDofs.size() != dofs.constrainedDofCount() ||
|
if (free_dofs.size() != dofs.FreeDofCount() ||
|
||||||
dofs.prescribedValues().Size() != constrainedDofs.size() ||
|
constrained_dofs.size() != dofs.ConstrainedDofCount() ||
|
||||||
constrainedDofs.size() > fullCount ||
|
dofs.PrescribedValues().Size() != constrained_dofs.size() ||
|
||||||
freeDofs.size() != fullCount - constrainedDofs.size()) {
|
constrained_dofs.size() > full_count ||
|
||||||
return constraintFailure(
|
free_dofs.size() != full_count - constrained_dofs.size()) {
|
||||||
"invalid-constraint-dimensions",
|
return ConstraintFailure("invalid-constraint-dimensions",
|
||||||
std::to_string(fullCount),
|
std::to_string(full_count),
|
||||||
"DofManager full, free, constrained, and prescribed dimensions must agree.");
|
"DofManager full, free, constrained, and "
|
||||||
|
"prescribed dimensions must agree.");
|
||||||
}
|
}
|
||||||
if (!isStrictlyIncreasing(freeDofs) ||
|
if (!IsStrictlyIncreasing(free_dofs) ||
|
||||||
!isStrictlyIncreasing(constrainedDofs)) {
|
!IsStrictlyIncreasing(constrained_dofs)) {
|
||||||
return constraintFailure(
|
return ConstraintFailure(
|
||||||
"invalid-constraint-order",
|
"invalid-constraint-order", std::to_string(full_count),
|
||||||
std::to_string(fullCount),
|
|
||||||
"Free and constrained DOFs must use stable increasing full-DOF order.");
|
"Free and constrained DOFs must use stable increasing full-DOF order.");
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<unsigned char> ownership(fullCount, 0U);
|
std::vector<unsigned char> ownership(full_count, 0U);
|
||||||
try {
|
try {
|
||||||
for (std::size_t equation = 0U;
|
for (std::size_t equation = 0U; equation < free_dofs.size(); ++equation) {
|
||||||
equation < freeDofs.size();
|
const std::size_t full_dof = free_dofs[equation];
|
||||||
++equation) {
|
if (full_dof >= full_count || ownership[full_dof] != 0U ||
|
||||||
const std::size_t fullDof = freeDofs[equation];
|
dofs.FreeEquation(full_dof) != equation) {
|
||||||
if (fullDof >= fullCount || ownership[fullDof] != 0U ||
|
return ConstraintFailure(
|
||||||
dofs.freeEquation(fullDof) != equation) {
|
"invalid-constraint-order", std::to_string(full_dof),
|
||||||
return constraintFailure(
|
|
||||||
"invalid-constraint-order",
|
|
||||||
std::to_string(fullDof),
|
|
||||||
"Free equation numbering must match the stable free-DOF order.");
|
"Free equation numbering must match the stable free-DOF order.");
|
||||||
}
|
}
|
||||||
ownership[fullDof] = 1U;
|
ownership[full_dof] = 1U;
|
||||||
}
|
}
|
||||||
for (const std::size_t fullDof : constrainedDofs) {
|
for (const std::size_t full_dof : constrained_dofs) {
|
||||||
if (fullDof >= fullCount || ownership[fullDof] != 0U ||
|
if (full_dof >= full_count || ownership[full_dof] != 0U ||
|
||||||
dofs.freeEquation(fullDof).has_value()) {
|
dofs.FreeEquation(full_dof).has_value()) {
|
||||||
return constraintFailure(
|
return ConstraintFailure(
|
||||||
"invalid-constraint-order",
|
"invalid-constraint-order", std::to_string(full_dof),
|
||||||
std::to_string(fullDof),
|
|
||||||
"Constrained DOFs must be unique and absent from free equations.");
|
"Constrained DOFs must be unique and absent from free equations.");
|
||||||
}
|
}
|
||||||
ownership[fullDof] = 2U;
|
ownership[full_dof] = 2U;
|
||||||
}
|
}
|
||||||
} catch (const std::out_of_range&) {
|
} catch (const std::out_of_range&) {
|
||||||
return constraintFailure(
|
return ConstraintFailure(
|
||||||
"invalid-constraint-dimensions",
|
"invalid-constraint-dimensions", std::to_string(full_count),
|
||||||
std::to_string(fullCount),
|
|
||||||
"DofManager equation storage does not cover every full DOF.");
|
"DofManager equation storage does not cover every full DOF.");
|
||||||
}
|
}
|
||||||
if (std::find(ownership.begin(), ownership.end(), 0U) != ownership.end()) {
|
if (std::find(ownership.begin(), ownership.end(), 0U) != ownership.end()) {
|
||||||
return constraintFailure(
|
return ConstraintFailure("invalid-constraint-order",
|
||||||
"invalid-constraint-order",
|
std::to_string(full_count),
|
||||||
std::to_string(fullCount),
|
"Free and constrained DOFs must partition the "
|
||||||
"Free and constrained DOFs must partition the complete full-DOF range.");
|
"complete full-DOF range.");
|
||||||
}
|
}
|
||||||
return Status::Ok();
|
return Status::Ok();
|
||||||
}
|
}
|
||||||
|
|
||||||
Result<SparseMatrix> extractBlock(
|
/// @brief Extracts one partition without changing the supplied DOF order.
|
||||||
const SparseMatrix& full,
|
Result<SparseMatrix> ExtractBlock(const SparseMatrix& full,
|
||||||
const std::vector<std::size_t>& rowDofs,
|
const std::vector<std::size_t>& row_dofs,
|
||||||
const std::vector<std::size_t>& columnDofs) {
|
const std::vector<std::size_t>& column_dofs) {
|
||||||
const std::size_t absent = (std::numeric_limits<std::size_t>::max)();
|
const std::size_t absent = (std::numeric_limits<std::size_t>::max)();
|
||||||
std::vector<std::size_t> localColumn(full.Columns(), absent);
|
std::vector<std::size_t> local_column(full.Columns(), absent);
|
||||||
for (std::size_t column = 0U; column < columnDofs.size(); ++column) {
|
for (std::size_t column = 0U; column < column_dofs.size(); ++column) {
|
||||||
localColumn[columnDofs[column]] = column;
|
local_column[column_dofs[column]] = column;
|
||||||
}
|
}
|
||||||
|
|
||||||
SparsePattern pattern;
|
SparsePattern pattern;
|
||||||
pattern.rowOffsets.reserve(rowDofs.size() + 1U);
|
pattern.row_offsets.reserve(row_dofs.size() + 1U);
|
||||||
pattern.rowOffsets.push_back(0U);
|
pattern.row_offsets.push_back(0U);
|
||||||
std::vector<CooContribution> contributions;
|
std::vector<CooContribution> contributions;
|
||||||
contributions.reserve(full.Values().size());
|
contributions.reserve(full.Values().size());
|
||||||
for (std::size_t localRow = 0U;
|
for (std::size_t local_row = 0U; local_row < row_dofs.size(); ++local_row) {
|
||||||
localRow < rowDofs.size();
|
const std::size_t full_row = row_dofs[local_row];
|
||||||
++localRow) {
|
for (std::size_t position = full.RowOffsets()[full_row];
|
||||||
const std::size_t fullRow = rowDofs[localRow];
|
position < full.RowOffsets()[full_row + 1U]; ++position) {
|
||||||
for (std::size_t position = full.RowOffsets()[fullRow];
|
const std::size_t column = local_column[full.ColumnIndices()[position]];
|
||||||
position < full.RowOffsets()[fullRow + 1U];
|
|
||||||
++position) {
|
|
||||||
const std::size_t column =
|
|
||||||
localColumn[full.ColumnIndices()[position]];
|
|
||||||
if (column == absent) {
|
if (column == absent) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
pattern.columnIndices.push_back(column);
|
pattern.column_indices.push_back(column);
|
||||||
// One source CSR entry maps to one block slot, so exact numeric
|
// One source CSR entry maps to one block slot, so exact numeric
|
||||||
// values and structural zeros survive without a new reduction.
|
// values and structural zeros survive without a new reduction.
|
||||||
contributions.push_back({
|
contributions.push_back(
|
||||||
localRow,
|
{local_row, column, full.Values()[position], local_row, position});
|
||||||
column,
|
|
||||||
full.Values()[position],
|
|
||||||
localRow,
|
|
||||||
position});
|
|
||||||
}
|
}
|
||||||
pattern.rowOffsets.push_back(pattern.columnIndices.size());
|
pattern.row_offsets.push_back(pattern.column_indices.size());
|
||||||
}
|
}
|
||||||
return SparseMatrix::FromCoo(
|
return SparseMatrix::FromCoo(row_dofs.size(), column_dofs.size(),
|
||||||
rowDofs.size(),
|
std::move(contributions), pattern);
|
||||||
columnDofs.size(),
|
|
||||||
std::move(contributions),
|
|
||||||
pattern);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void requireDofOrder(const DofManager& dofs) {
|
void RequireDofOrder(const DofManager& dofs) {
|
||||||
if (!validateDofOrder(dofs).IsOk()) {
|
if (!ValidateDofOrder(dofs).IsOk()) {
|
||||||
throw std::invalid_argument{
|
throw std::invalid_argument{
|
||||||
"DofManager constraint dimensions or order are invalid."};
|
"DofManager constraint dimensions or order are invalid."};
|
||||||
}
|
}
|
||||||
@@ -152,108 +131,94 @@ void requireDofOrder(const DofManager& dofs) {
|
|||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
Result<PartitionedStiffness> EssentialConstraints::partition(
|
Result<PartitionedStiffness> EssentialConstraints::Partition(
|
||||||
const SparseMatrix& full,
|
const SparseMatrix& full, const DofManager& dofs) {
|
||||||
const DofManager& dofs) {
|
const Status matrix_status = full.Validate();
|
||||||
const Status matrixStatus = full.Validate();
|
if (!matrix_status.IsOk()) {
|
||||||
if (!matrixStatus.IsOk()) {
|
return Result<PartitionedStiffness>::Failure(matrix_status);
|
||||||
return Result<PartitionedStiffness>::Failure(matrixStatus);
|
|
||||||
}
|
}
|
||||||
if (full.Rows() != full.Columns() ||
|
if (full.Rows() != full.Columns() || full.Rows() != dofs.FullDofCount()) {
|
||||||
full.Rows() != dofs.fullDofCount()) {
|
return Result<PartitionedStiffness>::Failure(ConstraintFailure(
|
||||||
return Result<PartitionedStiffness>::Failure(constraintFailure(
|
|
||||||
"invalid-constraint-dimensions",
|
"invalid-constraint-dimensions",
|
||||||
std::to_string(full.Rows()) + "x" +
|
std::to_string(full.Rows()) + "x" + std::to_string(full.Columns()),
|
||||||
std::to_string(full.Columns()),
|
"Full stiffness must be square and match the DofManager full "
|
||||||
"Full stiffness must be square and match the DofManager full dimension."));
|
"dimension."));
|
||||||
}
|
}
|
||||||
const Status dofStatus = validateDofOrder(dofs);
|
const Status dof_status = ValidateDofOrder(dofs);
|
||||||
if (!dofStatus.IsOk()) {
|
if (!dof_status.IsOk()) {
|
||||||
return Result<PartitionedStiffness>::Failure(dofStatus);
|
return Result<PartitionedStiffness>::Failure(dof_status);
|
||||||
}
|
}
|
||||||
|
|
||||||
auto kff = extractBlock(full, dofs.freeDofs(), dofs.freeDofs());
|
auto kff = ExtractBlock(full, dofs.FreeDofs(), dofs.FreeDofs());
|
||||||
if (!kff.HasValue()) {
|
if (!kff.HasValue()) {
|
||||||
return Result<PartitionedStiffness>::Failure(kff.GetStatus());
|
return Result<PartitionedStiffness>::Failure(kff.GetStatus());
|
||||||
}
|
}
|
||||||
auto kfc = extractBlock(full, dofs.freeDofs(), dofs.constrainedDofs());
|
auto kfc = ExtractBlock(full, dofs.FreeDofs(), dofs.ConstrainedDofs());
|
||||||
if (!kfc.HasValue()) {
|
if (!kfc.HasValue()) {
|
||||||
return Result<PartitionedStiffness>::Failure(kfc.GetStatus());
|
return Result<PartitionedStiffness>::Failure(kfc.GetStatus());
|
||||||
}
|
}
|
||||||
auto kcf = extractBlock(full, dofs.constrainedDofs(), dofs.freeDofs());
|
auto kcf = ExtractBlock(full, dofs.ConstrainedDofs(), dofs.FreeDofs());
|
||||||
if (!kcf.HasValue()) {
|
if (!kcf.HasValue()) {
|
||||||
return Result<PartitionedStiffness>::Failure(kcf.GetStatus());
|
return Result<PartitionedStiffness>::Failure(kcf.GetStatus());
|
||||||
}
|
}
|
||||||
auto kcc = extractBlock(
|
auto kcc = ExtractBlock(full, dofs.ConstrainedDofs(), dofs.ConstrainedDofs());
|
||||||
full, dofs.constrainedDofs(), dofs.constrainedDofs());
|
|
||||||
if (!kcc.HasValue()) {
|
if (!kcc.HasValue()) {
|
||||||
return Result<PartitionedStiffness>::Failure(kcc.GetStatus());
|
return Result<PartitionedStiffness>::Failure(kcc.GetStatus());
|
||||||
}
|
}
|
||||||
|
|
||||||
return Result<PartitionedStiffness>::Success({
|
return Result<PartitionedStiffness>::Success(
|
||||||
std::move(kff.Value()),
|
{std::move(kff.Value()), std::move(kfc.Value()), std::move(kcf.Value()),
|
||||||
std::move(kfc.Value()),
|
|
||||||
std::move(kcf.Value()),
|
|
||||||
std::move(kcc.Value())});
|
std::move(kcc.Value())});
|
||||||
}
|
}
|
||||||
|
|
||||||
Vector EssentialConstraints::gatherFree(
|
Vector EssentialConstraints::GatherFree(const Vector& full,
|
||||||
const Vector& full,
|
|
||||||
const DofManager& dofs) {
|
const DofManager& dofs) {
|
||||||
requireDofOrder(dofs);
|
RequireDofOrder(dofs);
|
||||||
if (full.Size() != dofs.fullDofCount()) {
|
if (full.Size() != dofs.FullDofCount()) {
|
||||||
throw std::invalid_argument{
|
throw std::invalid_argument{
|
||||||
"Full vector size must match the DofManager full dimension."};
|
"Full vector size must match the DofManager full dimension."};
|
||||||
}
|
}
|
||||||
Vector reduced{dofs.freeDofCount()};
|
Vector reduced{dofs.FreeDofCount()};
|
||||||
for (std::size_t equation = 0U;
|
for (std::size_t equation = 0U; equation < dofs.FreeDofs().size();
|
||||||
equation < dofs.freeDofs().size();
|
|
||||||
++equation) {
|
++equation) {
|
||||||
reduced[equation] = full[dofs.freeDofs()[equation]];
|
reduced[equation] = full[dofs.FreeDofs()[equation]];
|
||||||
}
|
}
|
||||||
return reduced;
|
return reduced;
|
||||||
}
|
}
|
||||||
|
|
||||||
Vector EssentialConstraints::gatherConstrained(
|
Vector EssentialConstraints::GatherConstrained(const Vector& full,
|
||||||
const Vector& full,
|
|
||||||
const DofManager& dofs) {
|
const DofManager& dofs) {
|
||||||
requireDofOrder(dofs);
|
RequireDofOrder(dofs);
|
||||||
if (full.Size() != dofs.fullDofCount()) {
|
if (full.Size() != dofs.FullDofCount()) {
|
||||||
throw std::invalid_argument{
|
throw std::invalid_argument{
|
||||||
"Full vector size must match the DofManager full dimension."};
|
"Full vector size must match the DofManager full dimension."};
|
||||||
}
|
}
|
||||||
Vector reduced{dofs.constrainedDofCount()};
|
Vector reduced{dofs.ConstrainedDofCount()};
|
||||||
for (std::size_t index = 0U;
|
for (std::size_t index = 0U; index < dofs.ConstrainedDofs().size(); ++index) {
|
||||||
index < dofs.constrainedDofs().size();
|
reduced[index] = full[dofs.ConstrainedDofs()[index]];
|
||||||
++index) {
|
|
||||||
reduced[index] = full[dofs.constrainedDofs()[index]];
|
|
||||||
}
|
}
|
||||||
return reduced;
|
return reduced;
|
||||||
}
|
}
|
||||||
|
|
||||||
Vector EssentialConstraints::reconstructFull(
|
Vector EssentialConstraints::ReconstructFull(const Vector& free_values,
|
||||||
const Vector& freeValues,
|
const Vector& constrained_values,
|
||||||
const Vector& constrainedValues,
|
|
||||||
const DofManager& dofs) {
|
const DofManager& dofs) {
|
||||||
requireDofOrder(dofs);
|
RequireDofOrder(dofs);
|
||||||
if (freeValues.Size() != dofs.freeDofCount() ||
|
if (free_values.Size() != dofs.FreeDofCount() ||
|
||||||
constrainedValues.Size() != dofs.constrainedDofCount()) {
|
constrained_values.Size() != dofs.ConstrainedDofCount()) {
|
||||||
throw std::invalid_argument{
|
throw std::invalid_argument{
|
||||||
"Reduced vector sizes must match the DofManager order."};
|
"Reduced vector sizes must match the DofManager order."};
|
||||||
}
|
}
|
||||||
|
|
||||||
Vector full{dofs.fullDofCount()};
|
Vector full{dofs.FullDofCount()};
|
||||||
for (std::size_t equation = 0U;
|
for (std::size_t equation = 0U; equation < dofs.FreeDofs().size();
|
||||||
equation < dofs.freeDofs().size();
|
|
||||||
++equation) {
|
++equation) {
|
||||||
full[dofs.freeDofs()[equation]] = freeValues[equation];
|
full[dofs.FreeDofs()[equation]] = free_values[equation];
|
||||||
}
|
}
|
||||||
// Preserve caller-supplied dc exactly; nonzero prescribed displacement is
|
// Preserve caller-supplied dc exactly; nonzero prescribed displacement is
|
||||||
// never replaced with an implicit homogeneous constraint.
|
// never replaced with an implicit homogeneous constraint.
|
||||||
for (std::size_t index = 0U;
|
for (std::size_t index = 0U; index < dofs.ConstrainedDofs().size(); ++index) {
|
||||||
index < dofs.constrainedDofs().size();
|
full[dofs.ConstrainedDofs()[index]] = constrained_values[index];
|
||||||
++index) {
|
|
||||||
full[dofs.constrainedDofs()[index]] = constrainedValues[index];
|
|
||||||
}
|
}
|
||||||
return full;
|
return full;
|
||||||
}
|
}
|
||||||
|
|||||||
+133
-148
@@ -1,4 +1,4 @@
|
|||||||
#include "fesa/fem/dof_manager.hpp"
|
#include "fesa/fem/dof_manager.h"
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <charconv>
|
#include <charconv>
|
||||||
@@ -10,43 +10,42 @@
|
|||||||
namespace fesa {
|
namespace fesa {
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
constexpr std::size_t dofsPerNode = 6U;
|
constexpr std::size_t kDofsPerNode = 6U;
|
||||||
|
|
||||||
char asciiLower(char value) {
|
char AsciiLower(char value) {
|
||||||
if (value >= 'A' && value <= 'Z') {
|
if (value >= 'A' && value <= 'Z') {
|
||||||
return static_cast<char>(value + ('a' - 'A'));
|
return static_cast<char>(value + ('a' - 'A'));
|
||||||
}
|
}
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool equalName(const std::string& left, const std::string& right) {
|
bool EqualName(const std::string& left, const std::string& right) {
|
||||||
return left.size() == right.size() &&
|
return left.size() == right.size() &&
|
||||||
std::equal(
|
std::equal(left.begin(), left.end(), right.begin(),
|
||||||
left.begin(), left.end(), right.begin(),
|
[](char left_value, char right_value) {
|
||||||
[](char leftValue, char rightValue) {
|
return AsciiLower(left_value) == AsciiLower(right_value);
|
||||||
return asciiLower(leftValue) == asciiLower(rightValue);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
bool tryPositiveInteger(const std::string& text, std::int64_t& value) {
|
bool TryPositiveInteger(const std::string& text, std::int64_t& value) {
|
||||||
const char* const first = text.data();
|
const char* const first = text.data();
|
||||||
const char* const last = first + text.size();
|
const char* const last = first + text.size();
|
||||||
const auto parsed = std::from_chars(first, last, value);
|
const auto parsed = std::from_chars(first, last, value);
|
||||||
return parsed.ec == std::errc{} && parsed.ptr == last && value > 0;
|
return parsed.ec == std::errc{} && parsed.ptr == last && value > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<EntityIndex> expandBoundaryTarget(
|
std::vector<EntityIndex> ExpandBoundaryTarget(
|
||||||
const Domain& domain, const BoundaryCondition& boundary) {
|
const Domain& domain, const BoundaryCondition& boundary) {
|
||||||
for (const auto& set : domain.NodeSets()) {
|
for (const auto& set : domain.NodeSets()) {
|
||||||
if (equalName(set.name, boundary.target)) {
|
if (EqualName(set.name, boundary.target)) {
|
||||||
return set.node_indices;
|
return set.node_indices;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
std::int64_t sourceLabel = 0;
|
std::int64_t source_label = 0;
|
||||||
if (tryPositiveInteger(boundary.target, sourceLabel)) {
|
if (TryPositiveInteger(boundary.target, source_label)) {
|
||||||
for (std::size_t node = 0U; node < domain.Nodes().size(); ++node) {
|
for (std::size_t node = 0U; node < domain.Nodes().size(); ++node) {
|
||||||
if (domain.Nodes()[node].source_id.source_label == sourceLabel) {
|
if (domain.Nodes()[node].source_id.source_label == source_label) {
|
||||||
return {static_cast<EntityIndex>(node)};
|
return {static_cast<EntityIndex>(node)};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -54,219 +53,205 @@ std::vector<EntityIndex> expandBoundaryTarget(
|
|||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
template <std::size_t scatterSize>
|
template <std::size_t scatter_size>
|
||||||
void appendScatter(
|
void AppendScatter(std::vector<std::vector<std::size_t>>& columns_by_row,
|
||||||
std::vector<std::vector<std::size_t>>& columnsByRow,
|
const std::array<std::size_t, scatter_size>& scatter) {
|
||||||
const std::array<std::size_t, scatterSize>& scatter) {
|
|
||||||
for (const std::size_t row : scatter) {
|
for (const std::size_t row : scatter) {
|
||||||
auto& columns = columnsByRow[row];
|
auto& columns = columns_by_row[row];
|
||||||
columns.insert(columns.end(), scatter.begin(), scatter.end());
|
columns.insert(columns.end(), scatter.begin(), scatter.end());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
SparsePattern buildSparsePattern(
|
/// @brief Builds sorted unique CSR columns by deterministic scatter traversal.
|
||||||
std::size_t fullDofCount,
|
SparsePattern BuildSparsePattern(
|
||||||
const std::vector<EntityIndex>& activeElements,
|
std::size_t full_dof_count, const std::vector<EntityIndex>& active_elements,
|
||||||
const std::vector<std::array<std::size_t, 12>>& elementScatters,
|
const std::vector<std::array<std::size_t, 12>>& element_scatters,
|
||||||
const std::vector<std::array<std::size_t, 24>>& shellElementScatters) {
|
const std::vector<std::array<std::size_t, 24>>& shell_element_scatters) {
|
||||||
std::vector<std::vector<std::size_t>> columnsByRow(fullDofCount);
|
std::vector<std::vector<std::size_t>> columns_by_row(full_dof_count);
|
||||||
for (const EntityIndex element : activeElements) {
|
for (const EntityIndex element : active_elements) {
|
||||||
appendScatter(columnsByRow, elementScatters.at(element));
|
AppendScatter(columns_by_row, element_scatters.at(element));
|
||||||
}
|
}
|
||||||
// Every shell in the approved single-step shell subset is active.
|
// Every shell in the approved single-step shell subset is active.
|
||||||
for (const auto& scatter : shellElementScatters) {
|
for (const auto& scatter : shell_element_scatters) {
|
||||||
appendScatter(columnsByRow, scatter);
|
AppendScatter(columns_by_row, scatter);
|
||||||
}
|
}
|
||||||
|
|
||||||
SparsePattern pattern;
|
SparsePattern pattern;
|
||||||
pattern.rowOffsets.reserve(fullDofCount + 1U);
|
pattern.row_offsets.reserve(full_dof_count + 1U);
|
||||||
pattern.rowOffsets.push_back(0U);
|
pattern.row_offsets.push_back(0U);
|
||||||
for (auto& columns : columnsByRow) {
|
for (auto& columns : columns_by_row) {
|
||||||
// Stable CSR structure is independent of element traversal duplicates.
|
// Stable CSR structure is independent of element traversal duplicates.
|
||||||
std::sort(columns.begin(), columns.end());
|
std::sort(columns.begin(), columns.end());
|
||||||
columns.erase(std::unique(columns.begin(), columns.end()), columns.end());
|
columns.erase(std::unique(columns.begin(), columns.end()), columns.end());
|
||||||
pattern.columnIndices.insert(
|
pattern.column_indices.insert(pattern.column_indices.end(), columns.begin(),
|
||||||
pattern.columnIndices.end(), columns.begin(), columns.end());
|
columns.end());
|
||||||
pattern.rowOffsets.push_back(pattern.columnIndices.size());
|
pattern.row_offsets.push_back(pattern.column_indices.size());
|
||||||
}
|
}
|
||||||
return pattern;
|
return pattern;
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
Result<DofManager> DofManager::create(const AnalysisModel& model) {
|
Result<DofManager> DofManager::Create(const AnalysisModel& model) {
|
||||||
const Domain& domain = model.domain();
|
const Domain& domain = model.GetDomain();
|
||||||
const std::size_t fullCount = domain.Nodes().size() * dofsPerNode;
|
const std::size_t full_count = domain.Nodes().size() * kDofsPerNode;
|
||||||
|
|
||||||
std::vector<std::optional<double>> prescribedByFullDof(fullCount);
|
std::vector<std::optional<double>> prescribed_by_full_dof(full_count);
|
||||||
for (const EntityIndex boundaryIndex : model.activeBoundaryConditions()) {
|
for (const EntityIndex boundary_index : model.ActiveBoundaryConditions()) {
|
||||||
const auto& boundary = model.step().boundaries.at(boundaryIndex);
|
const auto& boundary = model.Step().boundaries.at(boundary_index);
|
||||||
const auto target = expandBoundaryTarget(domain, boundary);
|
const auto target = ExpandBoundaryTarget(domain, boundary);
|
||||||
for (const EntityIndex node : target) {
|
for (const EntityIndex node : target) {
|
||||||
for (int component = boundary.first_dof;
|
for (int component = boundary.first_dof; component <= boundary.last_dof;
|
||||||
component <= boundary.last_dof;
|
|
||||||
++component) {
|
++component) {
|
||||||
const std::size_t fullDof =
|
const std::size_t full_dof =
|
||||||
static_cast<std::size_t>(node) * dofsPerNode +
|
static_cast<std::size_t>(node) * kDofsPerNode +
|
||||||
static_cast<std::size_t>(component - 1);
|
static_cast<std::size_t>(component - 1);
|
||||||
auto& prescribed = prescribedByFullDof[fullDof];
|
auto& prescribed = prescribed_by_full_dof[full_dof];
|
||||||
if (prescribed && *prescribed != boundary.value) {
|
if (prescribed && *prescribed != boundary.value) {
|
||||||
return Result<DofManager>::Failure(Status::Failure(
|
return Result<DofManager>::Failure(Status::Failure(
|
||||||
FailureCategory::kInput,
|
FailureCategory::kInput,
|
||||||
{{Severity::kError,
|
{{Severity::kError, "conflicting-boundary-condition",
|
||||||
"conflicting-boundary-condition",
|
boundary.location, "BOUNDARY", boundary.target,
|
||||||
boundary.location,
|
"Expanded boundary rows prescribe different values to one "
|
||||||
"BOUNDARY",
|
"node/DOF."}}));
|
||||||
boundary.target,
|
|
||||||
"Expanded boundary rows prescribe different values to one node/DOF."}}));
|
|
||||||
}
|
}
|
||||||
prescribed = boundary.value;
|
prescribed = boundary.value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<std::size_t> freeDofs;
|
std::vector<std::size_t> free_dofs;
|
||||||
std::vector<std::size_t> constrainedDofs;
|
std::vector<std::size_t> constrained_dofs;
|
||||||
std::vector<double> constrainedValues;
|
std::vector<double> constrained_values;
|
||||||
std::vector<std::optional<std::size_t>> freeEquations(fullCount);
|
std::vector<std::optional<std::size_t>> free_equations(full_count);
|
||||||
freeDofs.reserve(fullCount);
|
free_dofs.reserve(full_count);
|
||||||
constrainedDofs.reserve(fullCount);
|
constrained_dofs.reserve(full_count);
|
||||||
constrainedValues.reserve(fullCount);
|
constrained_values.reserve(full_count);
|
||||||
// A full-DOF scan fixes free equations, constrained DOFs, and dc in the
|
// A full-DOF scan fixes free equations, constrained DOFs, and dc in the
|
||||||
// same stable order regardless of boundary declaration overlap.
|
// same stable order regardless of boundary declaration overlap.
|
||||||
for (std::size_t fullDof = 0U; fullDof < fullCount; ++fullDof) {
|
for (std::size_t full_dof = 0U; full_dof < full_count; ++full_dof) {
|
||||||
if (prescribedByFullDof[fullDof]) {
|
if (prescribed_by_full_dof[full_dof]) {
|
||||||
constrainedDofs.push_back(fullDof);
|
constrained_dofs.push_back(full_dof);
|
||||||
constrainedValues.push_back(*prescribedByFullDof[fullDof]);
|
constrained_values.push_back(*prescribed_by_full_dof[full_dof]);
|
||||||
} else {
|
} else {
|
||||||
freeEquations[fullDof] = freeDofs.size();
|
free_equations[full_dof] = free_dofs.size();
|
||||||
freeDofs.push_back(fullDof);
|
free_dofs.push_back(full_dof);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Vector prescribedValues{constrainedValues.size()};
|
Vector prescribed_values{constrained_values.size()};
|
||||||
for (std::size_t index = 0U; index < constrainedValues.size(); ++index) {
|
for (std::size_t index = 0U; index < constrained_values.size(); ++index) {
|
||||||
prescribedValues[index] = constrainedValues[index];
|
prescribed_values[index] = constrained_values[index];
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<std::array<std::size_t, 12>> elementScatters(
|
std::vector<std::array<std::size_t, 12>> element_scatters(
|
||||||
domain.Elements().size());
|
domain.Elements().size());
|
||||||
for (const EntityIndex elementIndex : model.activeElements()) {
|
for (const EntityIndex element_index : model.ActiveElements()) {
|
||||||
const auto& element = domain.Elements().at(elementIndex);
|
const auto& element = domain.Elements().at(element_index);
|
||||||
auto& scatter = elementScatters.at(elementIndex);
|
auto& scatter = element_scatters.at(element_index);
|
||||||
for (std::size_t endpoint = 0U; endpoint < element.node_indices.size(); ++endpoint) {
|
for (std::size_t endpoint = 0U; endpoint < element.node_indices.size();
|
||||||
|
++endpoint) {
|
||||||
const std::size_t node = element.node_indices[endpoint];
|
const std::size_t node = element.node_indices[endpoint];
|
||||||
for (std::size_t component = 0U; component < dofsPerNode; ++component) {
|
for (std::size_t component = 0U; component < kDofsPerNode; ++component) {
|
||||||
scatter[endpoint * dofsPerNode + component] =
|
scatter[endpoint * kDofsPerNode + component] =
|
||||||
node * dofsPerNode + component;
|
node * kDofsPerNode + component;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<std::array<std::size_t, 24>> shellElementScatters(
|
std::vector<std::array<std::size_t, 24>> shell_element_scatters(
|
||||||
domain.ShellElements().size());
|
domain.ShellElements().size());
|
||||||
for (std::size_t elementIndex = 0U;
|
for (std::size_t element_index = 0U;
|
||||||
elementIndex < domain.ShellElements().size();
|
element_index < domain.ShellElements().size(); ++element_index) {
|
||||||
++elementIndex) {
|
const auto& element = domain.ShellElements()[element_index];
|
||||||
const auto& element = domain.ShellElements()[elementIndex];
|
auto& scatter = shell_element_scatters[element_index];
|
||||||
auto& scatter = shellElementScatters[elementIndex];
|
for (std::size_t node_position = 0U;
|
||||||
for (std::size_t nodePosition = 0U;
|
node_position < element.node_indices.size(); ++node_position) {
|
||||||
nodePosition < element.node_indices.size();
|
const std::size_t node = element.node_indices[node_position];
|
||||||
++nodePosition) {
|
for (std::size_t component = 0U; component < kDofsPerNode; ++component) {
|
||||||
const std::size_t node = element.node_indices[nodePosition];
|
scatter[node_position * kDofsPerNode + component] =
|
||||||
for (std::size_t component = 0U;
|
node * kDofsPerNode + component;
|
||||||
component < dofsPerNode;
|
|
||||||
++component) {
|
|
||||||
scatter[nodePosition * dofsPerNode + component] =
|
|
||||||
node * dofsPerNode + component;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
auto pattern = buildSparsePattern(
|
auto pattern = BuildSparsePattern(full_count, model.ActiveElements(),
|
||||||
fullCount,
|
element_scatters, shell_element_scatters);
|
||||||
model.activeElements(),
|
return Result<DofManager>::Success(
|
||||||
elementScatters,
|
DofManager{full_count, std::move(free_equations),
|
||||||
shellElementScatters);
|
std::move(element_scatters), std::move(shell_element_scatters),
|
||||||
return Result<DofManager>::Success(DofManager{
|
std::move(free_dofs), std::move(constrained_dofs),
|
||||||
fullCount,
|
std::move(prescribed_values), std::move(pattern)});
|
||||||
std::move(freeEquations),
|
|
||||||
std::move(elementScatters),
|
|
||||||
std::move(shellElementScatters),
|
|
||||||
std::move(freeDofs),
|
|
||||||
std::move(constrainedDofs),
|
|
||||||
std::move(prescribedValues),
|
|
||||||
std::move(pattern)});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
std::size_t DofManager::fullDofCount() const noexcept {
|
std::size_t DofManager::FullDofCount() const noexcept {
|
||||||
return fullDofCount_;
|
return full_dof_count_;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::size_t DofManager::freeDofCount() const noexcept {
|
std::size_t DofManager::FreeDofCount() const noexcept {
|
||||||
return freeDofs_.size();
|
return free_dofs_.size();
|
||||||
}
|
}
|
||||||
|
|
||||||
std::size_t DofManager::constrainedDofCount() const noexcept {
|
std::size_t DofManager::ConstrainedDofCount() const noexcept {
|
||||||
return constrainedDofs_.size();
|
return constrained_dofs_.size();
|
||||||
}
|
}
|
||||||
|
|
||||||
std::size_t DofManager::fullDof(
|
std::size_t DofManager::FullDof(EntityIndex node,
|
||||||
EntityIndex node, DofComponent component) const {
|
DofComponent component) const {
|
||||||
const std::size_t componentIndex = static_cast<std::size_t>(component);
|
const std::size_t component_index = static_cast<std::size_t>(component);
|
||||||
if (node >= fullDofCount_ / dofsPerNode || componentIndex >= dofsPerNode) {
|
if (node >= full_dof_count_ / kDofsPerNode ||
|
||||||
|
component_index >= kDofsPerNode) {
|
||||||
throw std::out_of_range{"Node or DOF component is out of range."};
|
throw std::out_of_range{"Node or DOF component is out of range."};
|
||||||
}
|
}
|
||||||
return static_cast<std::size_t>(node) * dofsPerNode + componentIndex;
|
return static_cast<std::size_t>(node) * kDofsPerNode + component_index;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::optional<std::size_t> DofManager::freeEquation(
|
std::optional<std::size_t> DofManager::FreeEquation(
|
||||||
std::size_t fullDof) const {
|
std::size_t full_dof) const {
|
||||||
return freeEquations_.at(fullDof);
|
return free_equations_.at(full_dof);
|
||||||
}
|
}
|
||||||
|
|
||||||
const std::array<std::size_t, 12>& DofManager::elementScatter(
|
const std::array<std::size_t, 12>& DofManager::ElementScatter(
|
||||||
EntityIndex element) const {
|
EntityIndex element) const {
|
||||||
return elementScatters_.at(element);
|
return element_scatters_.at(element);
|
||||||
}
|
}
|
||||||
|
|
||||||
const std::array<std::size_t, 24>& DofManager::shellElementScatter(
|
const std::array<std::size_t, 24>& DofManager::ShellElementScatter(
|
||||||
EntityIndex element) const {
|
EntityIndex element) const {
|
||||||
return shellElementScatters_.at(element);
|
return shell_element_scatters_.at(element);
|
||||||
}
|
}
|
||||||
|
|
||||||
const std::vector<std::size_t>& DofManager::freeDofs() const noexcept {
|
const std::vector<std::size_t>& DofManager::FreeDofs() const noexcept {
|
||||||
return freeDofs_;
|
return free_dofs_;
|
||||||
}
|
}
|
||||||
|
|
||||||
const std::vector<std::size_t>& DofManager::constrainedDofs() const noexcept {
|
const std::vector<std::size_t>& DofManager::ConstrainedDofs() const noexcept {
|
||||||
return constrainedDofs_;
|
return constrained_dofs_;
|
||||||
}
|
}
|
||||||
|
|
||||||
const Vector& DofManager::prescribedValues() const noexcept {
|
const Vector& DofManager::PrescribedValues() const noexcept {
|
||||||
return prescribedValues_;
|
return prescribed_values_;
|
||||||
}
|
}
|
||||||
|
|
||||||
const SparsePattern& DofManager::sparsePattern() const noexcept {
|
const SparsePattern& DofManager::GetSparsePattern() const noexcept {
|
||||||
return sparsePattern_;
|
return sparse_pattern_;
|
||||||
}
|
}
|
||||||
|
|
||||||
DofManager::DofManager(
|
DofManager::DofManager(
|
||||||
std::size_t fullDofCount,
|
std::size_t full_dof_count,
|
||||||
std::vector<std::optional<std::size_t>> freeEquations,
|
std::vector<std::optional<std::size_t>> free_equations,
|
||||||
std::vector<std::array<std::size_t, 12>> elementScatters,
|
std::vector<std::array<std::size_t, 12>> element_scatters,
|
||||||
std::vector<std::array<std::size_t, 24>> shellElementScatters,
|
std::vector<std::array<std::size_t, 24>> shell_element_scatters,
|
||||||
std::vector<std::size_t> freeDofs,
|
std::vector<std::size_t> free_dofs,
|
||||||
std::vector<std::size_t> constrainedDofs,
|
std::vector<std::size_t> constrained_dofs, Vector prescribed_values,
|
||||||
Vector prescribedValues,
|
SparsePattern sparse_pattern)
|
||||||
SparsePattern sparsePattern)
|
: full_dof_count_{full_dof_count},
|
||||||
: fullDofCount_{fullDofCount},
|
free_equations_{std::move(free_equations)},
|
||||||
freeEquations_{std::move(freeEquations)},
|
element_scatters_{std::move(element_scatters)},
|
||||||
elementScatters_{std::move(elementScatters)},
|
shell_element_scatters_{std::move(shell_element_scatters)},
|
||||||
shellElementScatters_{std::move(shellElementScatters)},
|
free_dofs_{std::move(free_dofs)},
|
||||||
freeDofs_{std::move(freeDofs)},
|
constrained_dofs_{std::move(constrained_dofs)},
|
||||||
constrainedDofs_{std::move(constrainedDofs)},
|
prescribed_values_{std::move(prescribed_values)},
|
||||||
prescribedValues_{std::move(prescribedValues)},
|
sparse_pattern_{std::move(sparse_pattern)} {}
|
||||||
sparsePattern_{std::move(sparsePattern)} {}
|
|
||||||
|
|
||||||
} // namespace fesa
|
} // namespace fesa
|
||||||
|
|||||||
@@ -3,9 +3,9 @@
|
|||||||
|
|
||||||
#include "fesa/io/hdf5/hdf5_results_writer.hpp"
|
#include "fesa/io/hdf5/hdf5_results_writer.hpp"
|
||||||
|
|
||||||
#include "fesa/analysis/analysis_model.hpp"
|
#include "fesa/analysis/analysis_model.h"
|
||||||
#include "fesa/build_info.h"
|
#include "fesa/build_info.h"
|
||||||
#include "fesa/fem/dof_manager.hpp"
|
#include "fesa/fem/dof_manager.h"
|
||||||
|
|
||||||
#include <hdf5.h>
|
#include <hdf5.h>
|
||||||
|
|
||||||
@@ -389,38 +389,38 @@ Status validateShellWriterInput(
|
|||||||
std::size_t expectedRows = 0U;
|
std::size_t expectedRows = 0U;
|
||||||
if (!sizeProductFits(
|
if (!sizeProductFits(
|
||||||
domain.ShellElements().size(), kShellLocationCount, expectedRows) ||
|
domain.ShellElements().size(), kShellLocationCount, expectedRows) ||
|
||||||
state.shellResults().size() != expectedRows) {
|
state.ShellResults().size() != expectedRows) {
|
||||||
return outputFailure(
|
return outputFailure(
|
||||||
"invalid-result-rows",
|
"invalid-result-rows",
|
||||||
"Shell output requires exactly GP1 through GP4 for every shell element.");
|
"Shell output requires exactly GP1 through GP4 for every shell element.");
|
||||||
}
|
}
|
||||||
const double gauss = 1.0 / std::sqrt(3.0);
|
const double gauss = 1.0 / std::sqrt(3.0);
|
||||||
const std::array<ShellMidsurfaceLocation, kShellLocationCount> locations{
|
const std::array<ShellMidsurfaceLocation, kShellLocationCount> locations{
|
||||||
ShellMidsurfaceLocation::gp1,
|
ShellMidsurfaceLocation::kGp1,
|
||||||
ShellMidsurfaceLocation::gp2,
|
ShellMidsurfaceLocation::kGp2,
|
||||||
ShellMidsurfaceLocation::gp3,
|
ShellMidsurfaceLocation::kGp3,
|
||||||
ShellMidsurfaceLocation::gp4};
|
ShellMidsurfaceLocation::kGp4};
|
||||||
const std::array<std::array<double, 2>, kShellLocationCount> coordinates{{
|
const std::array<std::array<double, 2>, kShellLocationCount> coordinates{{
|
||||||
{-gauss, -gauss},
|
{-gauss, -gauss},
|
||||||
{gauss, -gauss},
|
{gauss, -gauss},
|
||||||
{gauss, gauss},
|
{gauss, gauss},
|
||||||
{-gauss, gauss}}};
|
{-gauss, gauss}}};
|
||||||
const std::array<ShellSectionPosition, kShellSectionPositionCount> positions{
|
const std::array<ShellSectionPosition, kShellSectionPositionCount> positions{
|
||||||
ShellSectionPosition::bottom,
|
ShellSectionPosition::kBottom,
|
||||||
ShellSectionPosition::middle,
|
ShellSectionPosition::kMiddle,
|
||||||
ShellSectionPosition::top};
|
ShellSectionPosition::kTop};
|
||||||
constexpr std::array<double, kShellSectionPositionCount> zeta{-1.0, 0.0, 1.0};
|
constexpr std::array<double, kShellSectionPositionCount> zeta{-1.0, 0.0, 1.0};
|
||||||
for (std::size_t rowIndex = 0U;
|
for (std::size_t rowIndex = 0U;
|
||||||
rowIndex < state.shellResults().size();
|
rowIndex < state.ShellResults().size();
|
||||||
++rowIndex) {
|
++rowIndex) {
|
||||||
const auto& row = state.shellResults()[rowIndex];
|
const auto& row = state.ShellResults()[rowIndex];
|
||||||
const std::size_t element = rowIndex / kShellLocationCount;
|
const std::size_t element = rowIndex / kShellLocationCount;
|
||||||
const std::size_t location = rowIndex % kShellLocationCount;
|
const std::size_t location = rowIndex % kShellLocationCount;
|
||||||
if (row.element != element || row.location != locations[location] ||
|
if (row.element != element || row.location != locations[location] ||
|
||||||
row.naturalCoordinates != coordinates[location] ||
|
row.natural_coordinates != coordinates[location] ||
|
||||||
!isOrthonormalRightHanded(row.localFrame) ||
|
!isOrthonormalRightHanded(row.local_frame) ||
|
||||||
!isFinite(row.generalizedStrain) ||
|
!isFinite(row.generalized_strain) ||
|
||||||
!isFinite(row.sectionResultant)) {
|
!isFinite(row.section_resultant)) {
|
||||||
return outputFailure(
|
return outputFailure(
|
||||||
"invalid-result-rows",
|
"invalid-result-rows",
|
||||||
"Shell result rows must be finite and preserve element/GP/frame identity.");
|
"Shell result rows must be finite and preserve element/GP/frame identity.");
|
||||||
@@ -437,9 +437,9 @@ Status validateShellWriterInput(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!std::isfinite(state.physicalStrainEnergy()) ||
|
if (!std::isfinite(state.PhysicalStrainEnergy()) ||
|
||||||
!isFinite(state.equilibrium()) ||
|
!isFinite(state.Equilibrium()) ||
|
||||||
!isFinite(state.verificationMetrics())) {
|
!isFinite(state.VerificationMetrics())) {
|
||||||
return outputFailure(
|
return outputFailure(
|
||||||
"invalid-result-rows",
|
"invalid-result-rows",
|
||||||
"Shell energy, equilibrium, and verification metrics must be finite.");
|
"Shell energy, equilibrium, and verification metrics must be finite.");
|
||||||
@@ -457,8 +457,8 @@ Status validateWriterInput(
|
|||||||
return outputFailure(
|
return outputFailure(
|
||||||
"invalid-output-path", "The HDF5 output path must name a file.");
|
"invalid-output-path", "The HDF5 output path must name a file.");
|
||||||
}
|
}
|
||||||
if (state.identity().stepName != kStepName ||
|
if (state.Identity().step_name != kStepName ||
|
||||||
state.identity().frameIndex != kFrameIndex) {
|
state.Identity().frame_index != kFrameIndex) {
|
||||||
return outputFailure(
|
return outputFailure(
|
||||||
"invalid-result-state",
|
"invalid-result-state",
|
||||||
"Schema v0 requires literal Step-1 and frame index 0.");
|
"Schema v0 requires literal Step-1 and frame index 0.");
|
||||||
@@ -470,7 +470,7 @@ Status validateWriterInput(
|
|||||||
if (!shellValidation.IsOk()) {
|
if (!shellValidation.IsOk()) {
|
||||||
return shellValidation;
|
return shellValidation;
|
||||||
}
|
}
|
||||||
} else if (!state.shellResults().empty()) {
|
} else if (!state.ShellResults().empty()) {
|
||||||
return outputFailure(
|
return outputFailure(
|
||||||
"invalid-result-rows",
|
"invalid-result-rows",
|
||||||
"Beam output cannot contain shell recovery rows.");
|
"Beam output cannot contain shell recovery rows.");
|
||||||
@@ -482,11 +482,11 @@ Status validateWriterInput(
|
|||||||
"invalid-result-state", "The nodal result shape overflows size_t.");
|
"invalid-result-state", "The nodal result shape overflows size_t.");
|
||||||
}
|
}
|
||||||
const std::array<const Vector*, 5> vectors = {
|
const std::array<const Vector*, 5> vectors = {
|
||||||
&state.displacement(),
|
&state.Displacement(),
|
||||||
&state.externalForce(),
|
&state.ExternalForce(),
|
||||||
&state.internalForce(),
|
&state.InternalForce(),
|
||||||
&state.residual(),
|
&state.Residual(),
|
||||||
&state.reaction()};
|
&state.Reaction()};
|
||||||
for (const Vector* vector : vectors) {
|
for (const Vector* vector : vectors) {
|
||||||
if (vector->Size() != fullDofCount) {
|
if (vector->Size() != fullDofCount) {
|
||||||
return outputFailure(
|
return outputFailure(
|
||||||
@@ -542,42 +542,42 @@ Status validateWriterInput(
|
|||||||
std::size_t gaussCount = 0U;
|
std::size_t gaussCount = 0U;
|
||||||
if (!sizeProductFits(domain.Elements().size(), kEndpointCount, endpointCount) ||
|
if (!sizeProductFits(domain.Elements().size(), kEndpointCount, endpointCount) ||
|
||||||
!sizeProductFits(domain.Elements().size(), kGaussPointCount, gaussCount) ||
|
!sizeProductFits(domain.Elements().size(), kGaussPointCount, gaussCount) ||
|
||||||
state.endpointResults().size() != endpointCount ||
|
state.EndpointResults().size() != endpointCount ||
|
||||||
state.gaussResults().size() != gaussCount) {
|
state.GaussResults().size() != gaussCount) {
|
||||||
return outputFailure(
|
return outputFailure(
|
||||||
"invalid-result-rows",
|
"invalid-result-rows",
|
||||||
"Endpoint and Gauss row counts must match every element and location.");
|
"Endpoint and Gauss row counts must match every element and location.");
|
||||||
}
|
}
|
||||||
for (std::size_t rowIndex = 0U;
|
for (std::size_t rowIndex = 0U;
|
||||||
rowIndex < state.endpointResults().size();
|
rowIndex < state.EndpointResults().size();
|
||||||
++rowIndex) {
|
++rowIndex) {
|
||||||
const EntityIndex expectedElement =
|
const EntityIndex expectedElement =
|
||||||
static_cast<EntityIndex>(rowIndex / kEndpointCount);
|
static_cast<EntityIndex>(rowIndex / kEndpointCount);
|
||||||
const int expectedEndpoint = static_cast<int>(rowIndex % kEndpointCount);
|
const int expectedEndpoint = static_cast<int>(rowIndex % kEndpointCount);
|
||||||
const EndpointResultRow& row = state.endpointResults()[rowIndex];
|
const EndpointResultRow& row = state.EndpointResults()[rowIndex];
|
||||||
const auto& element = domain.Elements()[expectedElement];
|
const auto& element = domain.Elements()[expectedElement];
|
||||||
const auto& expectedNode =
|
const auto& expectedNode =
|
||||||
domain.Nodes()[element.node_indices[static_cast<std::size_t>(expectedEndpoint)]];
|
domain.Nodes()[element.node_indices[static_cast<std::size_t>(expectedEndpoint)]];
|
||||||
if (row.element != expectedElement || row.endpoint != expectedEndpoint ||
|
if (row.element != expectedElement || row.endpoint != expectedEndpoint ||
|
||||||
!sameIdentity(row.node, expectedNode.source_id) ||
|
!sameIdentity(row.node, expectedNode.source_id) ||
|
||||||
!isFinite(row.endAction) || !isFinite(row.sectionResultant)) {
|
!isFinite(row.end_action) || !isFinite(row.section_resultant)) {
|
||||||
return outputFailure(
|
return outputFailure(
|
||||||
"invalid-result-rows",
|
"invalid-result-rows",
|
||||||
"Endpoint result rows must follow element/endpoint order and identity.");
|
"Endpoint result rows must follow element/endpoint order and identity.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (std::size_t rowIndex = 0U;
|
for (std::size_t rowIndex = 0U;
|
||||||
rowIndex < state.gaussResults().size();
|
rowIndex < state.GaussResults().size();
|
||||||
++rowIndex) {
|
++rowIndex) {
|
||||||
const EntityIndex expectedElement =
|
const EntityIndex expectedElement =
|
||||||
static_cast<EntityIndex>(rowIndex / kGaussPointCount);
|
static_cast<EntityIndex>(rowIndex / kGaussPointCount);
|
||||||
const int expectedGaussPoint =
|
const int expectedGaussPoint =
|
||||||
static_cast<int>(rowIndex % kGaussPointCount) + 1;
|
static_cast<int>(rowIndex % kGaussPointCount) + 1;
|
||||||
const GaussResultRow& row = state.gaussResults()[rowIndex];
|
const GaussResultRow& row = state.GaussResults()[rowIndex];
|
||||||
if (row.element != expectedElement ||
|
if (row.element != expectedElement ||
|
||||||
row.gaussPoint != expectedGaussPoint ||
|
row.gauss_point != expectedGaussPoint ||
|
||||||
!isFinite(row.generalizedStrain) ||
|
!isFinite(row.generalized_strain) ||
|
||||||
!isFinite(row.generalizedResultant)) {
|
!isFinite(row.generalized_resultant)) {
|
||||||
return outputFailure(
|
return outputFailure(
|
||||||
"invalid-result-rows",
|
"invalid-result-rows",
|
||||||
"Gauss result rows must follow element/Gauss order and identity.");
|
"Gauss result rows must follow element/Gauss order and identity.");
|
||||||
@@ -594,19 +594,19 @@ Status validateWriterInput(
|
|||||||
for (std::size_t gauss = 0U; gauss < kGaussPointCount; ++gauss) {
|
for (std::size_t gauss = 0U; gauss < kGaussPointCount; ++gauss) {
|
||||||
const std::size_t count = sectionPoints.empty() ? 1U : sectionPoints.size();
|
const std::size_t count = sectionPoints.empty() ? 1U : sectionPoints.size();
|
||||||
for (std::size_t point = 0U; point < count; ++point) {
|
for (std::size_t point = 0U; point < count; ++point) {
|
||||||
if (stressIndex >= state.stressResults().size()) {
|
if (stressIndex >= state.StressResults().size()) {
|
||||||
return outputFailure(
|
return outputFailure(
|
||||||
"invalid-result-rows",
|
"invalid-result-rows",
|
||||||
"Axial stress rows are missing required element/Gauss/section locations.");
|
"Axial stress rows are missing required element/Gauss/section locations.");
|
||||||
}
|
}
|
||||||
const StressS11Row& row = state.stressResults()[stressIndex++];
|
const StressS11Row& row = state.StressResults()[stressIndex++];
|
||||||
const std::size_t expectedPoint = sectionPoints.empty() ? 0U : point + 1U;
|
const std::size_t expectedPoint = sectionPoints.empty() ? 0U : point + 1U;
|
||||||
const double expectedX1 = sectionPoints.empty() ? 0.0 : sectionPoints[point][0U];
|
const double expectedX1 = sectionPoints.empty() ? 0.0 : sectionPoints[point][0U];
|
||||||
const double expectedX2 = sectionPoints.empty() ? 0.0 : sectionPoints[point][1U];
|
const double expectedX2 = sectionPoints.empty() ? 0.0 : sectionPoints[point][1U];
|
||||||
const char* expectedSource = sectionPoints.empty() ? "fesa-default" : "input";
|
const char* expectedSource = sectionPoints.empty() ? "fesa-default" : "input";
|
||||||
if (row.element != static_cast<EntityIndex>(elementIndex) ||
|
if (row.element != static_cast<EntityIndex>(elementIndex) ||
|
||||||
row.gaussPoint != static_cast<int>(gauss + 1U) ||
|
row.gauss_point != static_cast<int>(gauss + 1U) ||
|
||||||
row.sectionPoint != expectedPoint ||
|
row.section_point != expectedPoint ||
|
||||||
row.x1 != expectedX1 || row.x2 != expectedX2 ||
|
row.x1 != expectedX1 || row.x2 != expectedX2 ||
|
||||||
row.source != expectedSource || !isValidUtf8(row.source) ||
|
row.source != expectedSource || !isValidUtf8(row.source) ||
|
||||||
!std::isfinite(row.x1) || !std::isfinite(row.x2) ||
|
!std::isfinite(row.x1) || !std::isfinite(row.x2) ||
|
||||||
@@ -618,7 +618,7 @@ Status validateWriterInput(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (stressIndex != state.stressResults().size()) {
|
if (stressIndex != state.StressResults().size()) {
|
||||||
return outputFailure(
|
return outputFailure(
|
||||||
"invalid-result-rows", "Axial stress output contains extra rows.");
|
"invalid-result-rows", "Axial stress output contains extra rows.");
|
||||||
}
|
}
|
||||||
@@ -635,7 +635,7 @@ Status validateWriterInput(
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
auto analysisModelResult = AnalysisModel::create(domain);
|
auto analysisModelResult = AnalysisModel::Create(domain);
|
||||||
if (!analysisModelResult.HasValue()) {
|
if (!analysisModelResult.HasValue()) {
|
||||||
return outputFailure(
|
return outputFailure(
|
||||||
"invalid-result-state",
|
"invalid-result-state",
|
||||||
@@ -643,7 +643,7 @@ Status validateWriterInput(
|
|||||||
}
|
}
|
||||||
const AnalysisModel analysisModel =
|
const AnalysisModel analysisModel =
|
||||||
std::move(analysisModelResult.Value());
|
std::move(analysisModelResult.Value());
|
||||||
auto dofResult = DofManager::create(analysisModel);
|
auto dofResult = DofManager::Create(analysisModel);
|
||||||
if (!dofResult.HasValue()) {
|
if (!dofResult.HasValue()) {
|
||||||
return outputFailure(
|
return outputFailure(
|
||||||
"invalid-result-state",
|
"invalid-result-state",
|
||||||
@@ -652,16 +652,16 @@ Status validateWriterInput(
|
|||||||
const DofManager dofs = std::move(dofResult.Value());
|
const DofManager dofs = std::move(dofResult.Value());
|
||||||
modelData.constraintMask.assign(fullDofCount, 0U);
|
modelData.constraintMask.assign(fullDofCount, 0U);
|
||||||
modelData.prescribedDisplacement.assign(fullDofCount, 0.0);
|
modelData.prescribedDisplacement.assign(fullDofCount, 0.0);
|
||||||
if (dofs.constrainedDofs().size() != dofs.prescribedValues().Size()) {
|
if (dofs.ConstrainedDofs().size() != dofs.PrescribedValues().Size()) {
|
||||||
return outputFailure(
|
return outputFailure(
|
||||||
"invalid-result-state",
|
"invalid-result-state",
|
||||||
"Constraint identities and prescribed values have inconsistent sizes.");
|
"Constraint identities and prescribed values have inconsistent sizes.");
|
||||||
}
|
}
|
||||||
for (std::size_t index = 0U;
|
for (std::size_t index = 0U;
|
||||||
index < dofs.constrainedDofs().size();
|
index < dofs.ConstrainedDofs().size();
|
||||||
++index) {
|
++index) {
|
||||||
const std::size_t fullDof = dofs.constrainedDofs()[index];
|
const std::size_t fullDof = dofs.ConstrainedDofs()[index];
|
||||||
const double prescribed = dofs.prescribedValues()[index];
|
const double prescribed = dofs.PrescribedValues()[index];
|
||||||
if (fullDof >= fullDofCount || !std::isfinite(prescribed)) {
|
if (fullDof >= fullDofCount || !std::isfinite(prescribed)) {
|
||||||
return outputFailure(
|
return outputFailure(
|
||||||
"invalid-result-state",
|
"invalid-result-state",
|
||||||
@@ -1422,9 +1422,9 @@ std::vector<double> flattenEndpointValues(
|
|||||||
for (const auto& row : rows) {
|
for (const auto& row : rows) {
|
||||||
if (sectionResultants) {
|
if (sectionResultants) {
|
||||||
values.insert(
|
values.insert(
|
||||||
values.end(), row.sectionResultant.begin(), row.sectionResultant.end());
|
values.end(), row.section_resultant.begin(), row.section_resultant.end());
|
||||||
} else {
|
} else {
|
||||||
values.insert(values.end(), row.endAction.begin(), row.endAction.end());
|
values.insert(values.end(), row.end_action.begin(), row.end_action.end());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return values;
|
return values;
|
||||||
@@ -1437,7 +1437,7 @@ std::vector<double> flattenGaussValues(
|
|||||||
values.reserve(rows.size() * kGeneralizedComponentCount);
|
values.reserve(rows.size() * kGeneralizedComponentCount);
|
||||||
for (const auto& row : rows) {
|
for (const auto& row : rows) {
|
||||||
const auto& rowValues =
|
const auto& rowValues =
|
||||||
resultants ? row.generalizedResultant : row.generalizedStrain;
|
resultants ? row.generalized_resultant : row.generalized_strain;
|
||||||
values.insert(values.end(), rowValues.begin(), rowValues.end());
|
values.insert(values.end(), rowValues.begin(), rowValues.end());
|
||||||
}
|
}
|
||||||
return values;
|
return values;
|
||||||
@@ -1445,12 +1445,12 @@ std::vector<double> flattenGaussValues(
|
|||||||
|
|
||||||
void writeStress(const hid_t file, const AnalysisState& state) {
|
void writeStress(const hid_t file, const AnalysisState& state) {
|
||||||
std::vector<StressWriteRow> rows;
|
std::vector<StressWriteRow> rows;
|
||||||
rows.reserve(state.stressResults().size());
|
rows.reserve(state.StressResults().size());
|
||||||
for (const auto& row : state.stressResults()) {
|
for (const auto& row : state.StressResults()) {
|
||||||
rows.push_back({
|
rows.push_back({
|
||||||
static_cast<std::uint64_t>(row.element),
|
static_cast<std::uint64_t>(row.element),
|
||||||
static_cast<std::uint64_t>(row.gaussPoint),
|
static_cast<std::uint64_t>(row.gauss_point),
|
||||||
static_cast<std::uint64_t>(row.sectionPoint),
|
static_cast<std::uint64_t>(row.section_point),
|
||||||
row.x1,
|
row.x1,
|
||||||
row.x2,
|
row.x2,
|
||||||
row.source.c_str(),
|
row.source.c_str(),
|
||||||
@@ -1544,26 +1544,26 @@ void writeShellResultDatasets(
|
|||||||
std::vector<double> generalizedStrains;
|
std::vector<double> generalizedStrains;
|
||||||
std::vector<double> sectionResultants;
|
std::vector<double> sectionResultants;
|
||||||
std::vector<double> stresses;
|
std::vector<double> stresses;
|
||||||
localFrames.reserve(state.shellResults().size() * 9U);
|
localFrames.reserve(state.ShellResults().size() * 9U);
|
||||||
generalizedStrains.reserve(
|
generalizedStrains.reserve(
|
||||||
state.shellResults().size() * kShellGeneralizedComponentCount);
|
state.ShellResults().size() * kShellGeneralizedComponentCount);
|
||||||
sectionResultants.reserve(
|
sectionResultants.reserve(
|
||||||
state.shellResults().size() * kShellGeneralizedComponentCount);
|
state.ShellResults().size() * kShellGeneralizedComponentCount);
|
||||||
stresses.reserve(
|
stresses.reserve(
|
||||||
state.shellResults().size() * kShellSectionPositionCount *
|
state.ShellResults().size() * kShellSectionPositionCount *
|
||||||
kShellStressComponentCount);
|
kShellStressComponentCount);
|
||||||
for (const auto& row : state.shellResults()) {
|
for (const auto& row : state.ShellResults()) {
|
||||||
for (const auto& axis : row.localFrame) {
|
for (const auto& axis : row.local_frame) {
|
||||||
localFrames.insert(localFrames.end(), axis.begin(), axis.end());
|
localFrames.insert(localFrames.end(), axis.begin(), axis.end());
|
||||||
}
|
}
|
||||||
generalizedStrains.insert(
|
generalizedStrains.insert(
|
||||||
generalizedStrains.end(),
|
generalizedStrains.end(),
|
||||||
row.generalizedStrain.begin(),
|
row.generalized_strain.begin(),
|
||||||
row.generalizedStrain.end());
|
row.generalized_strain.end());
|
||||||
sectionResultants.insert(
|
sectionResultants.insert(
|
||||||
sectionResultants.end(),
|
sectionResultants.end(),
|
||||||
row.sectionResultant.begin(),
|
row.section_resultant.begin(),
|
||||||
row.sectionResultant.end());
|
row.section_resultant.end());
|
||||||
for (const auto& position : row.stress) {
|
for (const auto& position : row.stress) {
|
||||||
stresses.insert(
|
stresses.insert(
|
||||||
stresses.end(),
|
stresses.end(),
|
||||||
@@ -1624,22 +1624,22 @@ void writeShellResultDatasets(
|
|||||||
"shell-local", "section-position");
|
"shell-local", "section-position");
|
||||||
writeShellResultIdentity(file, stressPath, true);
|
writeShellResultIdentity(file, stressPath, true);
|
||||||
|
|
||||||
const double energy = state.physicalStrainEnergy();
|
const double energy = state.PhysicalStrainEnergy();
|
||||||
writeDoubleDataset(
|
writeDoubleDataset(
|
||||||
file, std::string{kStepRoot} + "/global/energy", {1U},
|
file, std::string{kStepRoot} + "/global/energy", {1U},
|
||||||
&energy, 1U, "PHYSICAL_STRAIN_ENERGY", "force*length",
|
&energy, 1U, "PHYSICAL_STRAIN_ENERGY", "force*length",
|
||||||
"global", "global");
|
"global", "global");
|
||||||
writeDoubleDataset(
|
writeDoubleDataset(
|
||||||
file, std::string{kStepRoot} + "/global/equilibrium", {6U},
|
file, std::string{kStepRoot} + "/global/equilibrium", {6U},
|
||||||
state.equilibrium().data(), state.equilibrium().size(),
|
state.Equilibrium().data(), state.Equilibrium().size(),
|
||||||
"FORCE_1,FORCE_2,FORCE_3,MOMENT_1,MOMENT_2,MOMENT_3",
|
"FORCE_1,FORCE_2,FORCE_3,MOMENT_1,MOMENT_2,MOMENT_3",
|
||||||
"force,force,force,force*length,force*length,force*length",
|
"force,force,force,force*length,force*length,force*length",
|
||||||
"global-cartesian", "global-origin");
|
"global-cartesian", "global-origin");
|
||||||
const std::string metricsPath =
|
const std::string metricsPath =
|
||||||
std::string{kStepRoot} + "/global/verification_metrics";
|
std::string{kStepRoot} + "/global/verification_metrics";
|
||||||
writeDoubleDataset(
|
writeDoubleDataset(
|
||||||
file, metricsPath, {3U}, state.verificationMetrics().data(),
|
file, metricsPath, {3U}, state.VerificationMetrics().data(),
|
||||||
state.verificationMetrics().size(),
|
state.VerificationMetrics().size(),
|
||||||
"FREE_RESIDUAL_NORMALIZED,FORCE_BALANCE_NORMALIZED,MOMENT_BALANCE_NORMALIZED",
|
"FREE_RESIDUAL_NORMALIZED,FORCE_BALANCE_NORMALIZED,MOMENT_BALANCE_NORMALIZED",
|
||||||
"1,1,1", "global", "verification");
|
"1,1,1", "global", "verification");
|
||||||
{
|
{
|
||||||
@@ -1735,8 +1735,8 @@ void writeResultDatasets(
|
|||||||
file,
|
file,
|
||||||
std::string{kStepRoot} + "/nodal/displacement",
|
std::string{kStepRoot} + "/nodal/displacement",
|
||||||
nodalDimensions,
|
nodalDimensions,
|
||||||
state.displacement().Data(),
|
state.Displacement().Data(),
|
||||||
state.displacement().Size(),
|
state.Displacement().Size(),
|
||||||
"UX,UY,UZ,URX,URY,URZ",
|
"UX,UY,UZ,URX,URY,URZ",
|
||||||
"length,length,length,radian,radian,radian",
|
"length,length,length,radian,radian,radian",
|
||||||
"global-cartesian",
|
"global-cartesian",
|
||||||
@@ -1745,8 +1745,8 @@ void writeResultDatasets(
|
|||||||
file,
|
file,
|
||||||
std::string{kStepRoot} + "/nodal/reaction",
|
std::string{kStepRoot} + "/nodal/reaction",
|
||||||
nodalDimensions,
|
nodalDimensions,
|
||||||
state.reaction().Data(),
|
state.Reaction().Data(),
|
||||||
state.reaction().Size(),
|
state.Reaction().Size(),
|
||||||
"RF1,RF2,RF3,RM1,RM2,RM3",
|
"RF1,RF2,RF3,RM1,RM2,RM3",
|
||||||
"force,force,force,force*length,force*length,force*length",
|
"force,force,force,force*length,force*length,force*length",
|
||||||
"global-cartesian",
|
"global-cartesian",
|
||||||
@@ -1765,7 +1765,7 @@ void writeResultDatasets(
|
|||||||
static_cast<hsize_t>(domain.Elements().size()),
|
static_cast<hsize_t>(domain.Elements().size()),
|
||||||
kEndpointCount,
|
kEndpointCount,
|
||||||
kGeneralizedComponentCount};
|
kGeneralizedComponentCount};
|
||||||
const auto endActions = flattenEndpointValues(state.endpointResults(), false);
|
const auto endActions = flattenEndpointValues(state.EndpointResults(), false);
|
||||||
writeDoubleDataset(
|
writeDoubleDataset(
|
||||||
file,
|
file,
|
||||||
std::string{kStepRoot} + "/element/end_force_local",
|
std::string{kStepRoot} + "/element/end_force_local",
|
||||||
@@ -1777,7 +1777,7 @@ void writeResultDatasets(
|
|||||||
"beam-local",
|
"beam-local",
|
||||||
"endpoint-outward-action");
|
"endpoint-outward-action");
|
||||||
const auto sectionResultants =
|
const auto sectionResultants =
|
||||||
flattenEndpointValues(state.endpointResults(), true);
|
flattenEndpointValues(state.EndpointResults(), true);
|
||||||
writeDoubleDataset(
|
writeDoubleDataset(
|
||||||
file,
|
file,
|
||||||
std::string{kStepRoot} + "/element/section_resultant",
|
std::string{kStepRoot} + "/element/section_resultant",
|
||||||
@@ -1789,7 +1789,7 @@ void writeResultDatasets(
|
|||||||
"beam-local",
|
"beam-local",
|
||||||
"endpoint-positive-local-x-section-cut");
|
"endpoint-positive-local-x-section-cut");
|
||||||
const auto generalizedStrains =
|
const auto generalizedStrains =
|
||||||
flattenGaussValues(state.gaussResults(), false);
|
flattenGaussValues(state.GaussResults(), false);
|
||||||
writeDoubleDataset(
|
writeDoubleDataset(
|
||||||
file,
|
file,
|
||||||
std::string{kStepRoot} + "/element/generalized_strain",
|
std::string{kStepRoot} + "/element/generalized_strain",
|
||||||
@@ -1801,7 +1801,7 @@ void writeResultDatasets(
|
|||||||
"beam-local",
|
"beam-local",
|
||||||
"integration-point");
|
"integration-point");
|
||||||
const auto generalizedResultants =
|
const auto generalizedResultants =
|
||||||
flattenGaussValues(state.gaussResults(), true);
|
flattenGaussValues(state.GaussResults(), true);
|
||||||
writeDoubleDataset(
|
writeDoubleDataset(
|
||||||
file,
|
file,
|
||||||
std::string{kStepRoot} + "/element/generalized_resultant",
|
std::string{kStepRoot} + "/element/generalized_resultant",
|
||||||
@@ -2494,7 +2494,7 @@ void selfCheckFile(
|
|||||||
"beam-local", "integration-point");
|
"beam-local", "integration-point");
|
||||||
requireCompoundDataset(
|
requireCompoundDataset(
|
||||||
file.get(), "/steps/Step-1/frames/0/element/stress_s11",
|
file.get(), "/steps/Step-1/frames/0/element/stress_s11",
|
||||||
static_cast<hsize_t>(state.stressResults().size()),
|
static_cast<hsize_t>(state.StressResults().size()),
|
||||||
{"internal_element_id", "gauss_point_index", "section_point_index",
|
{"internal_element_id", "gauss_point_index", "section_point_index",
|
||||||
"x1", "x2", "source", "S11"});
|
"x1", "x2", "source", "S11"});
|
||||||
auto stress = openDatasetForCheck(
|
auto stress = openDatasetForCheck(
|
||||||
@@ -2563,7 +2563,7 @@ bool finalizeFile(
|
|||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
Status Hdf5ResultsWriter::write(
|
Status Hdf5ResultsWriter::Write(
|
||||||
const std::filesystem::path& outputPath,
|
const std::filesystem::path& outputPath,
|
||||||
const Domain& domain,
|
const Domain& domain,
|
||||||
const AnalysisState& state,
|
const AnalysisState& state,
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
#include <tuple>
|
#include <tuple>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
|
|
||||||
#include "fesa/fem/dof_manager.hpp"
|
#include "fesa/fem/dof_manager.h"
|
||||||
|
|
||||||
namespace fesa {
|
namespace fesa {
|
||||||
namespace {
|
namespace {
|
||||||
@@ -79,8 +79,8 @@ Result<SparseMatrix> SparseMatrix::FromCoo(
|
|||||||
std::vector<CooContribution> contributions,
|
std::vector<CooContribution> contributions,
|
||||||
const SparsePattern& expected_pattern) {
|
const SparsePattern& expected_pattern) {
|
||||||
const Status pattern_status =
|
const Status pattern_status =
|
||||||
ValidateCsr(rows, columns, expected_pattern.rowOffsets,
|
ValidateCsr(rows, columns, expected_pattern.row_offsets,
|
||||||
expected_pattern.columnIndices, nullptr);
|
expected_pattern.column_indices, nullptr);
|
||||||
if (!pattern_status.IsOk()) {
|
if (!pattern_status.IsOk()) {
|
||||||
return Result<SparseMatrix>::Failure(pattern_status);
|
return Result<SparseMatrix>::Failure(pattern_status);
|
||||||
}
|
}
|
||||||
@@ -113,12 +113,12 @@ Result<SparseMatrix> SparseMatrix::FromCoo(
|
|||||||
right.local_order);
|
right.local_order);
|
||||||
});
|
});
|
||||||
|
|
||||||
std::vector<double> values(expected_pattern.columnIndices.size(), 0.0);
|
std::vector<double> values(expected_pattern.column_indices.size(), 0.0);
|
||||||
for (const auto& contribution : contributions) {
|
for (const auto& contribution : contributions) {
|
||||||
const std::size_t begin = expected_pattern.rowOffsets[contribution.row];
|
const std::size_t begin = expected_pattern.row_offsets[contribution.row];
|
||||||
const std::size_t end = expected_pattern.rowOffsets[contribution.row + 1U];
|
const std::size_t end = expected_pattern.row_offsets[contribution.row + 1U];
|
||||||
const auto first = expected_pattern.columnIndices.begin() + begin;
|
const auto first = expected_pattern.column_indices.begin() + begin;
|
||||||
const auto last = expected_pattern.columnIndices.begin() + end;
|
const auto last = expected_pattern.column_indices.begin() + end;
|
||||||
const auto found = std::lower_bound(first, last, contribution.column);
|
const auto found = std::lower_bound(first, last, contribution.column);
|
||||||
if (found == last || *found != contribution.column) {
|
if (found == last || *found != contribution.column) {
|
||||||
return Result<SparseMatrix>::Failure(SparseFailure(
|
return Result<SparseMatrix>::Failure(SparseFailure(
|
||||||
@@ -129,7 +129,7 @@ Result<SparseMatrix> SparseMatrix::FromCoo(
|
|||||||
}
|
}
|
||||||
|
|
||||||
const std::size_t position = static_cast<std::size_t>(
|
const std::size_t position = static_cast<std::size_t>(
|
||||||
std::distance(expected_pattern.columnIndices.begin(), found));
|
std::distance(expected_pattern.column_indices.begin(), found));
|
||||||
values[position] += contribution.value;
|
values[position] += contribution.value;
|
||||||
if (!std::isfinite(values[position])) {
|
if (!std::isfinite(values[position])) {
|
||||||
return Result<SparseMatrix>::Failure(SparseFailure(
|
return Result<SparseMatrix>::Failure(SparseFailure(
|
||||||
@@ -140,8 +140,8 @@ Result<SparseMatrix> SparseMatrix::FromCoo(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
SparseMatrix matrix{rows, columns, expected_pattern.rowOffsets,
|
SparseMatrix matrix{rows, columns, expected_pattern.row_offsets,
|
||||||
expected_pattern.columnIndices, std::move(values)};
|
expected_pattern.column_indices, std::move(values)};
|
||||||
const Status status = matrix.Validate();
|
const Status status = matrix.Validate();
|
||||||
if (!status.IsOk()) {
|
if (!status.IsOk()) {
|
||||||
return Result<SparseMatrix>::Failure(status);
|
return Result<SparseMatrix>::Failure(status);
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -1,8 +1,4 @@
|
|||||||
#include "fesa/analysis/linear_static_analysis.hpp"
|
#include "fesa/analysis/linear_static_analysis.h"
|
||||||
|
|
||||||
#include "fesa/assembly/parallel_for.hpp"
|
|
||||||
#include "fesa/results/results_writer.hpp"
|
|
||||||
#include "fesa/solvers/linear/mkl_pardiso_solver.h"
|
|
||||||
|
|
||||||
#include <gtest/gtest.h>
|
#include <gtest/gtest.h>
|
||||||
|
|
||||||
@@ -18,22 +14,25 @@
|
|||||||
#include <utility>
|
#include <utility>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
|
#include "fesa/assembly/parallel_for.h"
|
||||||
|
#include "fesa/results/results_writer.h"
|
||||||
|
#include "fesa/solvers/linear/mkl_pardiso_solver.h"
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
class TempDirectory {
|
class TempDirectory {
|
||||||
public:
|
public:
|
||||||
explicit TempDirectory(const std::string& label) {
|
explicit TempDirectory(const std::string& label) {
|
||||||
static std::atomic<unsigned long long> sequence{0U};
|
static std::atomic<unsigned long long> sequence{0U};
|
||||||
const auto tick = std::chrono::steady_clock::now()
|
const auto tick =
|
||||||
.time_since_epoch()
|
std::chrono::steady_clock::now().time_since_epoch().count();
|
||||||
.count();
|
|
||||||
path_ = std::filesystem::temp_directory_path() /
|
path_ = std::filesystem::temp_directory_path() /
|
||||||
("fesa-step24-analysis-" + label + "-" +
|
("fesa-step24-analysis-" + label + "-" + std::to_string(tick) +
|
||||||
std::to_string(tick) + "-" +
|
"-" + std::to_string(sequence.fetch_add(1U)));
|
||||||
std::to_string(sequence.fetch_add(1U)));
|
|
||||||
std::error_code error;
|
std::error_code error;
|
||||||
if (!std::filesystem::create_directory(path_, error) || error) {
|
if (!std::filesystem::create_directory(path_, error) || error) {
|
||||||
throw std::runtime_error{"Unable to create the Step 24 analysis fixture."};
|
throw std::runtime_error{
|
||||||
|
"Unable to create the Step 24 analysis fixture."};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -45,13 +44,13 @@ public:
|
|||||||
std::filesystem::remove_all(path_, ignored);
|
std::filesystem::remove_all(path_, ignored);
|
||||||
}
|
}
|
||||||
|
|
||||||
const std::filesystem::path& path() const noexcept { return path_; }
|
const std::filesystem::path& Path() const noexcept { return path_; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::filesystem::path path_;
|
std::filesystem::path path_;
|
||||||
};
|
};
|
||||||
|
|
||||||
void writeText(const std::filesystem::path& path, const std::string& text) {
|
void WriteText(const std::filesystem::path& path, const std::string& text) {
|
||||||
std::ofstream stream{path, std::ios::binary | std::ios::trunc};
|
std::ofstream stream{path, std::ios::binary | std::ios::trunc};
|
||||||
stream.write(text.data(), static_cast<std::streamsize>(text.size()));
|
stream.write(text.data(), static_cast<std::streamsize>(text.size()));
|
||||||
if (!stream) {
|
if (!stream) {
|
||||||
@@ -59,7 +58,7 @@ void writeText(const std::filesystem::path& path, const std::string& text) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string axialDeck(const double rootUx, const double tipForce) {
|
std::string AxialDeck(const double root_ux, const double tip_force) {
|
||||||
return R"inp(*Part, name=BeamPart
|
return R"inp(*Part, name=BeamPart
|
||||||
*Node
|
*Node
|
||||||
1, 0., 0., 0.
|
1, 0., 0., 0.
|
||||||
@@ -84,21 +83,22 @@ std::string axialDeck(const double rootUx, const double tipForce) {
|
|||||||
*Elastic
|
*Elastic
|
||||||
100., 0.25
|
100., 0.25
|
||||||
*Boundary
|
*Boundary
|
||||||
Root, 1, 1, )inp" + std::to_string(rootUx) + R"inp(
|
Root, 1, 1, )inp" +
|
||||||
|
std::to_string(root_ux) + R"inp(
|
||||||
Root, 2, 6
|
Root, 2, 6
|
||||||
Tip, 2, 6
|
Tip, 2, 6
|
||||||
*Step, name=Load, nlgeom=NO
|
*Step, name=Load, nlgeom=NO
|
||||||
*Static
|
*Static
|
||||||
0.1, 1., 0.01, 1.
|
0.1, 1., 0.01, 1.
|
||||||
*Cload
|
*Cload
|
||||||
Tip, 1, )inp" + std::to_string(tipForce) + R"inp(
|
Tip, 1, )inp" +
|
||||||
|
std::to_string(tip_force) + R"inp(
|
||||||
*End Step
|
*End Step
|
||||||
)inp";
|
)inp";
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string shellDeck(
|
std::string ShellDeck(const std::string& boundary_block,
|
||||||
const std::string& boundaryBlock,
|
const std::string& load_block = {}) {
|
||||||
const std::string& loadBlock = {}) {
|
|
||||||
return std::string{R"inp(*Part, name=ShellPart
|
return std::string{R"inp(*Part, name=ShellPart
|
||||||
*Node
|
*Node
|
||||||
1, 0., 0., 0.
|
1, 0., 0., 0.
|
||||||
@@ -129,19 +129,21 @@ std::string shellDeck(
|
|||||||
*Material, name=Steel
|
*Material, name=Steel
|
||||||
*Elastic
|
*Elastic
|
||||||
1000., 0.25
|
1000., 0.25
|
||||||
)inp"} + boundaryBlock + R"inp(*Step, name=Load, nlgeom=NO
|
)inp"} + boundary_block +
|
||||||
|
R"inp(*Step, name=Load, nlgeom=NO
|
||||||
*Static
|
*Static
|
||||||
0.1, 1., 0.01, 1.
|
0.1, 1., 0.01, 1.
|
||||||
)inp" + loadBlock + R"inp(*End Step
|
)inp" + load_block +
|
||||||
|
R"inp(*End Step
|
||||||
)inp";
|
)inp";
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string allConstrainedShellDeck() {
|
std::string AllConstrainedShellDeck() {
|
||||||
return shellDeck("*Boundary\nAll, 1, 6\n");
|
return ShellDeck("*Boundary\nAll, 1, 6\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string prescribedShellDeck() {
|
std::string PrescribedShellDeck() {
|
||||||
return shellDeck(R"inp(*Boundary
|
return ShellDeck(R"inp(*Boundary
|
||||||
N1, 1, 6
|
N1, 1, 6
|
||||||
N2, 1, 1, 0.1
|
N2, 1, 1, 0.1
|
||||||
N2, 2, 6
|
N2, 2, 6
|
||||||
@@ -150,38 +152,38 @@ N4, 1, 6
|
|||||||
)inp");
|
)inp");
|
||||||
}
|
}
|
||||||
|
|
||||||
// The pure Template Method spy makes the eight public lifecycle hooks observable
|
// The pure Template Method spy makes the eight public lifecycle hooks
|
||||||
// without coupling the ordering assertion to any solver backend.
|
// observable without coupling the ordering assertion to any solver backend.
|
||||||
class SpyAnalysis final : public fesa::Analysis {
|
class SpyAnalysis final : public fesa::Analysis {
|
||||||
public:
|
public:
|
||||||
const std::vector<std::string>& events() const noexcept { return events_; }
|
const std::vector<std::string>& Events() const noexcept { return events_; }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
fesa::Status initialize(const fesa::AnalysisRequest&) override {
|
fesa::Status Initialize(const fesa::AnalysisRequest&) override {
|
||||||
return record("initialize");
|
return Record("initialize");
|
||||||
}
|
}
|
||||||
fesa::Status buildAnalysisModel() override {
|
fesa::Status BuildAnalysisModel() override {
|
||||||
return record("build-analysis-model");
|
return Record("build-analysis-model");
|
||||||
}
|
}
|
||||||
fesa::Status buildDofMapAndSparsePattern() override {
|
fesa::Status BuildDofMapAndSparsePattern() override {
|
||||||
return record("build-dof-map-and-sparse-pattern");
|
return Record("build-dof-map-and-sparse-pattern");
|
||||||
}
|
}
|
||||||
fesa::Status assembleAndPartitionStiffness() override {
|
fesa::Status AssembleAndPartitionStiffness() override {
|
||||||
return record("assemble-and-partition-stiffness");
|
return Record("assemble-and-partition-stiffness");
|
||||||
}
|
}
|
||||||
fesa::Status factorize() override { return record("factorize"); }
|
fesa::Status Factorize() override { return Record("factorize"); }
|
||||||
fesa::Status assembleLoadsAndEffectiveRhs() override {
|
fesa::Status AssembleLoadsAndEffectiveRhs() override {
|
||||||
return record("assemble-loads-and-effective-rhs");
|
return Record("assemble-loads-and-effective-rhs");
|
||||||
}
|
}
|
||||||
fesa::Status substituteAndReconstruct() override {
|
fesa::Status SubstituteAndReconstruct() override {
|
||||||
return record("substitute-and-reconstruct");
|
return Record("substitute-and-reconstruct");
|
||||||
}
|
}
|
||||||
fesa::Status recoverAndWriteResults() override {
|
fesa::Status RecoverAndWriteResults() override {
|
||||||
return record("recover-and-write-results");
|
return Record("recover-and-write-results");
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
fesa::Status record(const char* event) {
|
fesa::Status Record(const char* event) {
|
||||||
events_.emplace_back(event);
|
events_.emplace_back(event);
|
||||||
return fesa::Status::Ok();
|
return fesa::Status::Ok();
|
||||||
}
|
}
|
||||||
@@ -192,51 +194,51 @@ private:
|
|||||||
// The solver spy records only the adapter-boundary operations. In particular,
|
// The solver spy records only the adapter-boundary operations. In particular,
|
||||||
// solve() cannot conceal a second factorization call.
|
// solve() cannot conceal a second factorization call.
|
||||||
class SpyLinearSolver final : public fesa::LinearSolver {
|
class SpyLinearSolver final : public fesa::LinearSolver {
|
||||||
public:
|
public:
|
||||||
explicit SpyLinearSolver(std::vector<std::string>& events)
|
explicit SpyLinearSolver(std::vector<std::string>& events)
|
||||||
: events_{events} {}
|
: events_{events} {}
|
||||||
|
|
||||||
fesa::Status Factorize(const fesa::SparseMatrix&) override {
|
fesa::Status Factorize(const fesa::SparseMatrix&) override {
|
||||||
++factorizeCalls_;
|
++factorize_calls_;
|
||||||
events_.emplace_back("solver-factorize");
|
events_.emplace_back("solver-factorize");
|
||||||
return fesa::Status::Ok();
|
return fesa::Status::Ok();
|
||||||
}
|
}
|
||||||
|
|
||||||
fesa::Status Solve(
|
fesa::Status Solve(const fesa::Vector& rhs,
|
||||||
const fesa::Vector& rhs, fesa::Vector& solution) const override {
|
fesa::Vector& solution) const override {
|
||||||
++solveCalls_;
|
++solve_calls_;
|
||||||
events_.emplace_back("solver-solve");
|
events_.emplace_back("solver-solve");
|
||||||
for (std::size_t index = 0U;
|
for (std::size_t index = 0U; index < rhs.Size() && index < solution.Size();
|
||||||
index < rhs.Size() && index < solution.Size(); ++index) {
|
++index) {
|
||||||
solution[index] = 0.0;
|
solution[index] = 0.0;
|
||||||
}
|
}
|
||||||
return fesa::Status::Ok();
|
return fesa::Status::Ok();
|
||||||
}
|
}
|
||||||
|
|
||||||
int factorizeCalls() const noexcept { return factorizeCalls_; }
|
int FactorizeCalls() const noexcept { return factorize_calls_; }
|
||||||
int solveCalls() const noexcept { return solveCalls_; }
|
int SolveCalls() const noexcept { return solve_calls_; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::vector<std::string>& events_;
|
std::vector<std::string>& events_;
|
||||||
int factorizeCalls_{0};
|
int factorize_calls_{0};
|
||||||
mutable int solveCalls_{0};
|
mutable int solve_calls_{0};
|
||||||
};
|
};
|
||||||
|
|
||||||
class RecordingMklSolver final : public fesa::LinearSolver {
|
class RecordingMklSolver final : public fesa::LinearSolver {
|
||||||
public:
|
public:
|
||||||
fesa::Status Factorize(const fesa::SparseMatrix& matrix) override {
|
fesa::Status Factorize(const fesa::SparseMatrix& matrix) override {
|
||||||
++factorizeCalls_;
|
++factorize_calls_;
|
||||||
factorizedDimension_ = matrix.Rows();
|
factorized_dimension_ = matrix.Rows();
|
||||||
if (matrix.Rows() == 1U && matrix.Columns() == 1U &&
|
if (matrix.Rows() == 1U && matrix.Columns() == 1U &&
|
||||||
matrix.Values().size() == 1U) {
|
matrix.Values().size() == 1U) {
|
||||||
scalarStiffness_ = matrix.Values()[0U];
|
scalar_stiffness_ = matrix.Values()[0U];
|
||||||
}
|
}
|
||||||
return backend_.Factorize(matrix);
|
return backend_.Factorize(matrix);
|
||||||
}
|
}
|
||||||
|
|
||||||
fesa::Status Solve(
|
fesa::Status Solve(const fesa::Vector& rhs,
|
||||||
const fesa::Vector& rhs, fesa::Vector& solution) const override {
|
fesa::Vector& solution) const override {
|
||||||
++solveCalls_;
|
++solve_calls_;
|
||||||
if (rhs.Size() == 0U) {
|
if (rhs.Size() == 0U) {
|
||||||
rhs_.clear();
|
rhs_.clear();
|
||||||
} else {
|
} else {
|
||||||
@@ -245,31 +247,31 @@ public:
|
|||||||
return backend_.Solve(rhs, solution);
|
return backend_.Solve(rhs, solution);
|
||||||
}
|
}
|
||||||
|
|
||||||
int factorizeCalls() const noexcept { return factorizeCalls_; }
|
int FactorizeCalls() const noexcept { return factorize_calls_; }
|
||||||
int solveCalls() const noexcept { return solveCalls_; }
|
int SolveCalls() const noexcept { return solve_calls_; }
|
||||||
std::size_t factorizedDimension() const noexcept {
|
std::size_t FactorizedDimension() const noexcept {
|
||||||
return factorizedDimension_;
|
return factorized_dimension_;
|
||||||
}
|
}
|
||||||
double scalarStiffness() const noexcept { return scalarStiffness_; }
|
double ScalarStiffness() const noexcept { return scalar_stiffness_; }
|
||||||
const std::vector<double>& rhs() const noexcept { return rhs_; }
|
const std::vector<double>& Rhs() const noexcept { return rhs_; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
fesa::MklPardisoSolver backend_;
|
fesa::MklPardisoSolver backend_;
|
||||||
int factorizeCalls_{0};
|
int factorize_calls_{0};
|
||||||
mutable int solveCalls_{0};
|
mutable int solve_calls_{0};
|
||||||
std::size_t factorizedDimension_{0U};
|
std::size_t factorized_dimension_{0U};
|
||||||
double scalarStiffness_{0.0};
|
double scalar_stiffness_{0.0};
|
||||||
mutable std::vector<double> rhs_;
|
mutable std::vector<double> rhs_;
|
||||||
};
|
};
|
||||||
|
|
||||||
class NonfiniteLinearSolver final : public fesa::LinearSolver {
|
class NonfiniteLinearSolver final : public fesa::LinearSolver {
|
||||||
public:
|
public:
|
||||||
fesa::Status Factorize(const fesa::SparseMatrix&) override {
|
fesa::Status Factorize(const fesa::SparseMatrix&) override {
|
||||||
return fesa::Status::Ok();
|
return fesa::Status::Ok();
|
||||||
}
|
}
|
||||||
|
|
||||||
fesa::Status Solve(
|
fesa::Status Solve(const fesa::Vector&,
|
||||||
const fesa::Vector&, fesa::Vector& solution) const override {
|
fesa::Vector& solution) const override {
|
||||||
for (std::size_t index = 0U; index < solution.Size(); ++index) {
|
for (std::size_t index = 0U; index < solution.Size(); ++index) {
|
||||||
solution[index] = (std::numeric_limits<double>::quiet_NaN)();
|
solution[index] = (std::numeric_limits<double>::quiet_NaN)();
|
||||||
}
|
}
|
||||||
@@ -278,64 +280,61 @@ public:
|
|||||||
};
|
};
|
||||||
|
|
||||||
class SpyResultsWriter final : public fesa::ResultsWriter {
|
class SpyResultsWriter final : public fesa::ResultsWriter {
|
||||||
public:
|
public:
|
||||||
explicit SpyResultsWriter(std::vector<std::string>& events)
|
explicit SpyResultsWriter(std::vector<std::string>& events)
|
||||||
: events_{events} {}
|
: events_{events} {}
|
||||||
|
|
||||||
fesa::Status write(
|
fesa::Status Write(const std::filesystem::path&, const fesa::Domain&,
|
||||||
const std::filesystem::path&,
|
|
||||||
const fesa::Domain&,
|
|
||||||
const fesa::AnalysisState&,
|
const fesa::AnalysisState&,
|
||||||
const std::vector<fesa::Diagnostic>&) override {
|
const std::vector<fesa::Diagnostic>&) override {
|
||||||
++writeCalls_;
|
++write_calls_;
|
||||||
events_.emplace_back("writer-write");
|
events_.emplace_back("writer-write");
|
||||||
return fesa::Status::Ok();
|
return fesa::Status::Ok();
|
||||||
}
|
}
|
||||||
|
|
||||||
int writeCalls() const noexcept { return writeCalls_; }
|
int WriteCalls() const noexcept { return write_calls_; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::vector<std::string>& events_;
|
std::vector<std::string>& events_;
|
||||||
int writeCalls_{0};
|
int write_calls_{0};
|
||||||
};
|
};
|
||||||
|
|
||||||
class CapturingResultsWriter final : public fesa::ResultsWriter {
|
class CapturingResultsWriter final : public fesa::ResultsWriter {
|
||||||
public:
|
public:
|
||||||
fesa::Status write(
|
fesa::Status Write(
|
||||||
const std::filesystem::path& outputPath,
|
const std::filesystem::path& output_path, const fesa::Domain& domain,
|
||||||
const fesa::Domain& domain,
|
|
||||||
const fesa::AnalysisState& state,
|
const fesa::AnalysisState& state,
|
||||||
const std::vector<fesa::Diagnostic>& diagnostics) override {
|
const std::vector<fesa::Diagnostic>& diagnostics) override {
|
||||||
outputPath_ = outputPath;
|
output_path_ = output_path;
|
||||||
nodeCount_ = domain.Nodes().size();
|
node_count_ = domain.Nodes().size();
|
||||||
shellElementCount_ = domain.ShellElements().size();
|
shell_element_count_ = domain.ShellElements().size();
|
||||||
state_ = std::make_unique<fesa::AnalysisState>(state);
|
state_ = std::make_unique<fesa::AnalysisState>(state);
|
||||||
diagnostics_ = diagnostics;
|
diagnostics_ = diagnostics;
|
||||||
return fesa::Status::Ok();
|
return fesa::Status::Ok();
|
||||||
}
|
}
|
||||||
|
|
||||||
const fesa::AnalysisState& state() const {
|
const fesa::AnalysisState& State() const {
|
||||||
if (!state_) {
|
if (!state_) {
|
||||||
throw std::logic_error{"No AnalysisState was captured."};
|
throw std::logic_error{"No AnalysisState was captured."};
|
||||||
}
|
}
|
||||||
return *state_;
|
return *state_;
|
||||||
}
|
}
|
||||||
|
|
||||||
const std::filesystem::path& outputPath() const noexcept {
|
const std::filesystem::path& OutputPath() const noexcept {
|
||||||
return outputPath_;
|
return output_path_;
|
||||||
}
|
}
|
||||||
std::size_t nodeCount() const noexcept { return nodeCount_; }
|
std::size_t NodeCount() const noexcept { return node_count_; }
|
||||||
std::size_t shellElementCount() const noexcept {
|
std::size_t ShellElementCount() const noexcept {
|
||||||
return shellElementCount_;
|
return shell_element_count_;
|
||||||
}
|
}
|
||||||
const std::vector<fesa::Diagnostic>& diagnostics() const noexcept {
|
const std::vector<fesa::Diagnostic>& Diagnostics() const noexcept {
|
||||||
return diagnostics_;
|
return diagnostics_;
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::filesystem::path outputPath_;
|
std::filesystem::path output_path_;
|
||||||
std::size_t nodeCount_{0U};
|
std::size_t node_count_{0U};
|
||||||
std::size_t shellElementCount_{0U};
|
std::size_t shell_element_count_{0U};
|
||||||
std::unique_ptr<fesa::AnalysisState> state_;
|
std::unique_ptr<fesa::AnalysisState> state_;
|
||||||
std::vector<fesa::Diagnostic> diagnostics_;
|
std::vector<fesa::Diagnostic> diagnostics_;
|
||||||
};
|
};
|
||||||
@@ -344,189 +343,182 @@ private:
|
|||||||
|
|
||||||
TEST(LinearStaticCli, FactorizesBeforeLoadAndSolvesWithoutRefactorization) {
|
TEST(LinearStaticCli, FactorizesBeforeLoadAndSolvesWithoutRefactorization) {
|
||||||
SpyAnalysis lifecycle;
|
SpyAnalysis lifecycle;
|
||||||
const fesa::AnalysisRequest emptyRequest{};
|
const fesa::AnalysisRequest empty_request{};
|
||||||
ASSERT_TRUE(lifecycle.run(emptyRequest).IsOk());
|
ASSERT_TRUE(lifecycle.Run(empty_request).IsOk());
|
||||||
EXPECT_EQ(
|
EXPECT_EQ(lifecycle.Events(),
|
||||||
lifecycle.events(),
|
|
||||||
(std::vector<std::string>{
|
(std::vector<std::string>{
|
||||||
"initialize",
|
"initialize", "build-analysis-model",
|
||||||
"build-analysis-model",
|
|
||||||
"build-dof-map-and-sparse-pattern",
|
"build-dof-map-and-sparse-pattern",
|
||||||
"assemble-and-partition-stiffness",
|
"assemble-and-partition-stiffness", "factorize",
|
||||||
"factorize",
|
|
||||||
"assemble-loads-and-effective-rhs",
|
"assemble-loads-and-effective-rhs",
|
||||||
"substitute-and-reconstruct",
|
"substitute-and-reconstruct", "recover-and-write-results"}));
|
||||||
"recover-and-write-results"}));
|
|
||||||
|
|
||||||
TempDirectory directory{"order"};
|
TempDirectory directory{"order"};
|
||||||
const auto input = directory.path() / "order.inp";
|
const auto input = directory.Path() / "order.inp";
|
||||||
const auto output = directory.path() / "results.h5";
|
const auto output = directory.Path() / "results.h5";
|
||||||
writeText(input, axialDeck(0.0, 0.0));
|
WriteText(input, AxialDeck(0.0, 0.0));
|
||||||
|
|
||||||
std::vector<std::string> adapterEvents;
|
std::vector<std::string> adapter_events;
|
||||||
fesa::SerialParallelFor serial;
|
fesa::SerialParallelFor serial;
|
||||||
SpyLinearSolver solver{adapterEvents};
|
SpyLinearSolver solver{adapter_events};
|
||||||
SpyResultsWriter writer{adapterEvents};
|
SpyResultsWriter writer{adapter_events};
|
||||||
fesa::LinearStaticAnalysis analysis{serial, solver, writer};
|
fesa::LinearStaticAnalysis analysis{serial, solver, writer};
|
||||||
|
|
||||||
ASSERT_TRUE(analysis.run({input, output}).IsOk());
|
ASSERT_TRUE(analysis.Run({input, output}).IsOk());
|
||||||
EXPECT_EQ(solver.factorizeCalls(), 1);
|
EXPECT_EQ(solver.FactorizeCalls(), 1);
|
||||||
EXPECT_EQ(solver.solveCalls(), 1);
|
EXPECT_EQ(solver.SolveCalls(), 1);
|
||||||
EXPECT_EQ(writer.writeCalls(), 1);
|
EXPECT_EQ(writer.WriteCalls(), 1);
|
||||||
EXPECT_EQ(
|
EXPECT_EQ(adapter_events,
|
||||||
adapterEvents,
|
(std::vector<std::string>{"solver-factorize", "solver-solve",
|
||||||
(std::vector<std::string>{
|
"writer-write"}));
|
||||||
"solver-factorize", "solver-solve", "writer-write"}));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(LinearStaticCli, RealPipelineHandlesAnalyticalAndNonzeroPrescription) {
|
TEST(LinearStaticCli, RealPipelineHandlesAnalyticalAndNonzeroPrescription) {
|
||||||
TempDirectory directory{"analytical"};
|
TempDirectory directory{"analytical"};
|
||||||
const auto input = directory.path() / "prescribed-axial.inp";
|
const auto input = directory.Path() / "prescribed-axial.inp";
|
||||||
const auto output = directory.path() / "captured-results.h5";
|
const auto output = directory.Path() / "captured-results.h5";
|
||||||
writeText(input, axialDeck(0.1, 10.0));
|
WriteText(input, AxialDeck(0.1, 10.0));
|
||||||
|
|
||||||
fesa::SerialParallelFor serial;
|
fesa::SerialParallelFor serial;
|
||||||
fesa::MklPardisoSolver solver;
|
fesa::MklPardisoSolver solver;
|
||||||
CapturingResultsWriter writer;
|
CapturingResultsWriter writer;
|
||||||
fesa::LinearStaticAnalysis analysis{serial, solver, writer};
|
fesa::LinearStaticAnalysis analysis{serial, solver, writer};
|
||||||
|
|
||||||
const auto status = analysis.run({input, output});
|
const auto status = analysis.Run({input, output});
|
||||||
ASSERT_TRUE(status.IsOk());
|
ASSERT_TRUE(status.IsOk());
|
||||||
EXPECT_EQ(writer.outputPath(), output);
|
EXPECT_EQ(writer.OutputPath(), output);
|
||||||
EXPECT_EQ(writer.nodeCount(), 2U);
|
EXPECT_EQ(writer.NodeCount(), 2U);
|
||||||
EXPECT_TRUE(writer.diagnostics().empty());
|
EXPECT_TRUE(writer.Diagnostics().empty());
|
||||||
|
|
||||||
const auto& state = writer.state();
|
const auto& state = writer.State();
|
||||||
ASSERT_EQ(state.displacement().Size(), 12U);
|
ASSERT_EQ(state.Displacement().Size(), 12U);
|
||||||
EXPECT_EQ(state.identity().stepName, "Step-1");
|
EXPECT_EQ(state.Identity().step_name, "Step-1");
|
||||||
EXPECT_EQ(state.identity().frameIndex, 0U);
|
EXPECT_EQ(state.Identity().frame_index, 0U);
|
||||||
|
|
||||||
// EA/L = 100 for this fixture, so u_tip = 0.1 + 10/100 = 0.2.
|
// EA/L = 100 for this fixture, so u_tip = 0.1 + 10/100 = 0.2.
|
||||||
EXPECT_NEAR(state.displacement()[0U], 0.1, 1.0e-12);
|
EXPECT_NEAR(state.Displacement()[0U], 0.1, 1.0e-12);
|
||||||
EXPECT_NEAR(state.displacement()[6U], 0.2, 2.0e-10);
|
EXPECT_NEAR(state.Displacement()[6U], 0.2, 2.0e-10);
|
||||||
EXPECT_NEAR(state.externalForce()[6U], 10.0, 1.0e-12);
|
EXPECT_NEAR(state.ExternalForce()[6U], 10.0, 1.0e-12);
|
||||||
EXPECT_NEAR(state.internalForce()[0U], -10.0, 1.0e-9);
|
EXPECT_NEAR(state.InternalForce()[0U], -10.0, 1.0e-9);
|
||||||
EXPECT_NEAR(state.internalForce()[6U], 10.0, 1.0e-9);
|
EXPECT_NEAR(state.InternalForce()[6U], 10.0, 1.0e-9);
|
||||||
EXPECT_NEAR(state.reaction()[0U], -10.0, 1.0e-9);
|
EXPECT_NEAR(state.Reaction()[0U], -10.0, 1.0e-9);
|
||||||
EXPECT_NEAR(state.residual()[6U], 0.0, 1.0e-9);
|
EXPECT_NEAR(state.Residual()[6U], 0.0, 1.0e-9);
|
||||||
EXPECT_EQ(state.endpointResults().size(), 2U);
|
EXPECT_EQ(state.EndpointResults().size(), 2U);
|
||||||
EXPECT_EQ(state.gaussResults().size(), 2U);
|
EXPECT_EQ(state.GaussResults().size(), 2U);
|
||||||
EXPECT_EQ(state.stressResults().size(), 2U);
|
EXPECT_EQ(state.StressResults().size(), 2U);
|
||||||
}
|
}
|
||||||
|
|
||||||
// MITC4-FLOW-001
|
// MITC4-FLOW-001
|
||||||
TEST(Mitc4ShellCli, UsesExistingLifecycleAndExactlyOneFactorization) {
|
TEST(Mitc4ShellCli, UsesExistingLifecycleAndExactlyOneFactorization) {
|
||||||
TempDirectory directory{"shell-order"};
|
TempDirectory directory{"shell-order"};
|
||||||
const auto input = directory.path() / "all-constrained-shell.inp";
|
const auto input = directory.Path() / "all-constrained-shell.inp";
|
||||||
const auto output = directory.path() / "results.h5";
|
const auto output = directory.Path() / "results.h5";
|
||||||
writeText(input, allConstrainedShellDeck());
|
WriteText(input, AllConstrainedShellDeck());
|
||||||
|
|
||||||
std::vector<std::string> adapterEvents;
|
std::vector<std::string> adapter_events;
|
||||||
fesa::SerialParallelFor serial;
|
fesa::SerialParallelFor serial;
|
||||||
SpyLinearSolver solver{adapterEvents};
|
SpyLinearSolver solver{adapter_events};
|
||||||
SpyResultsWriter writer{adapterEvents};
|
SpyResultsWriter writer{adapter_events};
|
||||||
fesa::LinearStaticAnalysis analysis{serial, solver, writer};
|
fesa::LinearStaticAnalysis analysis{serial, solver, writer};
|
||||||
|
|
||||||
ASSERT_TRUE(analysis.run({input, output}).IsOk());
|
ASSERT_TRUE(analysis.Run({input, output}).IsOk());
|
||||||
EXPECT_EQ(solver.factorizeCalls(), 1);
|
EXPECT_EQ(solver.FactorizeCalls(), 1);
|
||||||
EXPECT_EQ(solver.solveCalls(), 1);
|
EXPECT_EQ(solver.SolveCalls(), 1);
|
||||||
EXPECT_EQ(writer.writeCalls(), 1);
|
EXPECT_EQ(writer.WriteCalls(), 1);
|
||||||
EXPECT_EQ(
|
EXPECT_EQ(adapter_events,
|
||||||
adapterEvents,
|
(std::vector<std::string>{"solver-factorize", "solver-solve",
|
||||||
(std::vector<std::string>{
|
"writer-write"}));
|
||||||
"solver-factorize", "solver-solve", "writer-write"}));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// MITC4-FLOW-002
|
// MITC4-FLOW-002
|
||||||
TEST(Mitc4ShellCli, AppliesKfcForNonzeroPrescribedDisplacement) {
|
TEST(Mitc4ShellCli, AppliesKfcForNonzeroPrescribedDisplacement) {
|
||||||
TempDirectory directory{"shell-prescribed"};
|
TempDirectory directory{"shell-prescribed"};
|
||||||
const auto input = directory.path() / "prescribed-shell.inp";
|
const auto input = directory.Path() / "prescribed-shell.inp";
|
||||||
const auto output = directory.path() / "captured-results.h5";
|
const auto output = directory.Path() / "captured-results.h5";
|
||||||
writeText(input, prescribedShellDeck());
|
WriteText(input, PrescribedShellDeck());
|
||||||
|
|
||||||
fesa::SerialParallelFor serial;
|
fesa::SerialParallelFor serial;
|
||||||
RecordingMklSolver solver;
|
RecordingMklSolver solver;
|
||||||
CapturingResultsWriter writer;
|
CapturingResultsWriter writer;
|
||||||
fesa::LinearStaticAnalysis analysis{serial, solver, writer};
|
fesa::LinearStaticAnalysis analysis{serial, solver, writer};
|
||||||
|
|
||||||
const auto status = analysis.run({input, output});
|
const auto status = analysis.Run({input, output});
|
||||||
for (const auto& diagnostic : status.Diagnostics()) {
|
for (const auto& diagnostic : status.Diagnostics()) {
|
||||||
EXPECT_TRUE(status.IsOk())
|
EXPECT_TRUE(status.IsOk()) << diagnostic.code << ": " << diagnostic.message;
|
||||||
<< diagnostic.code << ": " << diagnostic.message;
|
|
||||||
}
|
}
|
||||||
ASSERT_TRUE(status.IsOk());
|
ASSERT_TRUE(status.IsOk());
|
||||||
ASSERT_EQ(solver.factorizeCalls(), 1);
|
ASSERT_EQ(solver.FactorizeCalls(), 1);
|
||||||
ASSERT_EQ(solver.solveCalls(), 1);
|
ASSERT_EQ(solver.SolveCalls(), 1);
|
||||||
ASSERT_EQ(solver.factorizedDimension(), 1U);
|
ASSERT_EQ(solver.FactorizedDimension(), 1U);
|
||||||
ASSERT_EQ(solver.rhs().size(), 1U);
|
ASSERT_EQ(solver.Rhs().size(), 1U);
|
||||||
EXPECT_NEAR(solver.scalarStiffness(), 440.0 / 9.0, 1.0e-12);
|
EXPECT_NEAR(solver.ScalarStiffness(), 440.0 / 9.0, 1.0e-12);
|
||||||
// Ff is exactly zero, so this nonzero RHS is solely -Kfc*dc.
|
// Ff is exactly zero, so this nonzero RHS is solely -Kfc*dc.
|
||||||
EXPECT_NEAR(solver.rhs()[0U], -4.0 / 9.0, 1.0e-12);
|
EXPECT_NEAR(solver.Rhs()[0U], -4.0 / 9.0, 1.0e-12);
|
||||||
|
|
||||||
EXPECT_EQ(writer.outputPath(), output);
|
EXPECT_EQ(writer.OutputPath(), output);
|
||||||
EXPECT_EQ(writer.nodeCount(), 4U);
|
EXPECT_EQ(writer.NodeCount(), 4U);
|
||||||
EXPECT_EQ(writer.shellElementCount(), 1U);
|
EXPECT_EQ(writer.ShellElementCount(), 1U);
|
||||||
const auto& state = writer.state();
|
const auto& state = writer.State();
|
||||||
ASSERT_EQ(state.displacement().Size(), 24U);
|
ASSERT_EQ(state.Displacement().Size(), 24U);
|
||||||
EXPECT_NEAR(state.displacement()[6U], 0.1, 1.0e-12);
|
EXPECT_NEAR(state.Displacement()[6U], 0.1, 1.0e-12);
|
||||||
EXPECT_NEAR(state.displacement()[12U], -1.0 / 110.0, 1.0e-12);
|
EXPECT_NEAR(state.Displacement()[12U], -1.0 / 110.0, 1.0e-12);
|
||||||
EXPECT_NEAR(state.verificationMetrics()[0U], 0.0, 1.0e-10);
|
EXPECT_NEAR(state.VerificationMetrics()[0U], 0.0, 1.0e-10);
|
||||||
EXPECT_EQ(state.shellResults().size(), 4U);
|
EXPECT_EQ(state.ShellResults().size(), 4U);
|
||||||
EXPECT_GT(state.physicalStrainEnergy(), 0.0);
|
EXPECT_GT(state.PhysicalStrainEnergy(), 0.0);
|
||||||
}
|
}
|
||||||
|
|
||||||
// MITC4-FLOW-003
|
// MITC4-FLOW-003
|
||||||
TEST(Mitc4ShellCli, RejectsSingularAndAcceptsZeroByZeroFreeSystem) {
|
TEST(Mitc4ShellCli, RejectsSingularAndAcceptsZeroByZeroFreeSystem) {
|
||||||
TempDirectory directory{"shell-singular-all"};
|
TempDirectory directory{"shell-singular-all"};
|
||||||
const auto singularInput = directory.path() / "singular-shell.inp";
|
const auto singular_input = directory.Path() / "singular-shell.inp";
|
||||||
const auto constrainedInput = directory.path() / "constrained-shell.inp";
|
const auto constrained_input = directory.Path() / "constrained-shell.inp";
|
||||||
writeText(singularInput, shellDeck(""));
|
WriteText(singular_input, ShellDeck(""));
|
||||||
writeText(constrainedInput, allConstrainedShellDeck());
|
WriteText(constrained_input, AllConstrainedShellDeck());
|
||||||
|
|
||||||
fesa::SerialParallelFor serial;
|
fesa::SerialParallelFor serial;
|
||||||
fesa::MklPardisoSolver singularSolver;
|
fesa::MklPardisoSolver singular_solver;
|
||||||
std::vector<std::string> singularEvents;
|
std::vector<std::string> singular_events;
|
||||||
SpyResultsWriter singularWriter{singularEvents};
|
SpyResultsWriter singular_writer{singular_events};
|
||||||
fesa::LinearStaticAnalysis singularAnalysis{
|
fesa::LinearStaticAnalysis singular_analysis{serial, singular_solver,
|
||||||
serial, singularSolver, singularWriter};
|
singular_writer};
|
||||||
const auto singular = singularAnalysis.run(
|
const auto singular =
|
||||||
{singularInput, directory.path() / "singular.h5"});
|
singular_analysis.Run({singular_input, directory.Path() / "singular.h5"});
|
||||||
ASSERT_FALSE(singular.IsOk());
|
ASSERT_FALSE(singular.IsOk());
|
||||||
EXPECT_EQ(singular.Category(), fesa::FailureCategory::kSolver);
|
EXPECT_EQ(singular.Category(), fesa::FailureCategory::kSolver);
|
||||||
EXPECT_EQ(singularWriter.writeCalls(), 0);
|
EXPECT_EQ(singular_writer.WriteCalls(), 0);
|
||||||
|
|
||||||
RecordingMklSolver constrainedSolver;
|
RecordingMklSolver constrained_solver;
|
||||||
CapturingResultsWriter constrainedWriter;
|
CapturingResultsWriter constrained_writer;
|
||||||
fesa::LinearStaticAnalysis constrainedAnalysis{
|
fesa::LinearStaticAnalysis constrained_analysis{serial, constrained_solver,
|
||||||
serial, constrainedSolver, constrainedWriter};
|
constrained_writer};
|
||||||
const auto constrained = constrainedAnalysis.run(
|
const auto constrained = constrained_analysis.Run(
|
||||||
{constrainedInput, directory.path() / "constrained.h5"});
|
{constrained_input, directory.Path() / "constrained.h5"});
|
||||||
ASSERT_TRUE(constrained.IsOk());
|
ASSERT_TRUE(constrained.IsOk());
|
||||||
EXPECT_EQ(constrainedSolver.factorizeCalls(), 1);
|
EXPECT_EQ(constrained_solver.FactorizeCalls(), 1);
|
||||||
EXPECT_EQ(constrainedSolver.factorizedDimension(), 0U);
|
EXPECT_EQ(constrained_solver.FactorizedDimension(), 0U);
|
||||||
EXPECT_EQ(constrainedSolver.solveCalls(), 1);
|
EXPECT_EQ(constrained_solver.SolveCalls(), 1);
|
||||||
EXPECT_TRUE(constrainedSolver.rhs().empty());
|
EXPECT_TRUE(constrained_solver.Rhs().empty());
|
||||||
EXPECT_EQ(constrainedWriter.state().shellResults().size(), 4U);
|
EXPECT_EQ(constrained_writer.State().ShellResults().size(), 4U);
|
||||||
}
|
}
|
||||||
|
|
||||||
// MITC4-FLOW-004
|
// MITC4-FLOW-004
|
||||||
TEST(Mitc4ShellCli, DoesNotWriteAnInvalidRecoveryCandidate) {
|
TEST(Mitc4ShellCli, DoesNotWriteAnInvalidRecoveryCandidate) {
|
||||||
TempDirectory directory{"shell-invalid-candidate"};
|
TempDirectory directory{"shell-invalid-candidate"};
|
||||||
const auto input = directory.path() / "invalid-recovery-shell.inp";
|
const auto input = directory.Path() / "invalid-recovery-shell.inp";
|
||||||
writeText(input, prescribedShellDeck());
|
WriteText(input, PrescribedShellDeck());
|
||||||
|
|
||||||
fesa::SerialParallelFor serial;
|
fesa::SerialParallelFor serial;
|
||||||
NonfiniteLinearSolver solver;
|
NonfiniteLinearSolver solver;
|
||||||
std::vector<std::string> adapterEvents;
|
std::vector<std::string> adapter_events;
|
||||||
SpyResultsWriter writer{adapterEvents};
|
SpyResultsWriter writer{adapter_events};
|
||||||
fesa::LinearStaticAnalysis analysis{serial, solver, writer};
|
fesa::LinearStaticAnalysis analysis{serial, solver, writer};
|
||||||
const auto status = analysis.run(
|
const auto status =
|
||||||
{input, directory.path() / "must-not-exist.h5"});
|
analysis.Run({input, directory.Path() / "must-not-exist.h5"});
|
||||||
|
|
||||||
ASSERT_FALSE(status.IsOk());
|
ASSERT_FALSE(status.IsOk());
|
||||||
EXPECT_EQ(status.Category(), fesa::FailureCategory::kModel);
|
EXPECT_EQ(status.Category(), fesa::FailureCategory::kModel);
|
||||||
ASSERT_FALSE(status.Diagnostics().empty());
|
ASSERT_FALSE(status.Diagnostics().empty());
|
||||||
EXPECT_EQ(status.Diagnostics().front().code, "nonfinite-recovery-value");
|
EXPECT_EQ(status.Diagnostics().front().code, "nonfinite-recovery-value");
|
||||||
EXPECT_EQ(writer.writeCalls(), 0);
|
EXPECT_EQ(writer.WriteCalls(), 0);
|
||||||
EXPECT_TRUE(adapterEvents.empty());
|
EXPECT_TRUE(adapter_events.empty());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
#include "reference_comparison.hpp"
|
#include "reference_comparison.hpp"
|
||||||
|
|
||||||
#include "fesa/analysis/analysis_model.hpp"
|
#include "fesa/analysis/analysis_model.h"
|
||||||
#include "fesa/assembly/load_assembler.hpp"
|
#include "fesa/assembly/load_assembler.h"
|
||||||
#include "fesa/fem/dof_manager.hpp"
|
#include "fesa/fem/dof_manager.h"
|
||||||
#include "fesa/io/abaqus/domain_mapper.hpp"
|
#include "fesa/io/abaqus/domain_mapper.hpp"
|
||||||
#include "fesa/io/abaqus/input_reader.hpp"
|
#include "fesa/io/abaqus/input_reader.hpp"
|
||||||
#include "fesa/results/result_recovery.hpp"
|
#include "fesa/results/result_recovery.h"
|
||||||
|
|
||||||
#include <hdf5.h>
|
#include <hdf5.h>
|
||||||
|
|
||||||
@@ -962,7 +962,7 @@ std::vector<NodeStationResultRow> normalizeStations(
|
|||||||
const Domain& domain,
|
const Domain& domain,
|
||||||
const HdfProjection& hdf,
|
const HdfProjection& hdf,
|
||||||
const ReferenceTable& sectionTable) {
|
const ReferenceTable& sectionTable) {
|
||||||
auto modelResult = AnalysisModel::create(domain);
|
auto modelResult = AnalysisModel::Create(domain);
|
||||||
if (!modelResult.HasValue()) {
|
if (!modelResult.HasValue()) {
|
||||||
fail("schema-mismatch", "The approved input cannot create an analysis view.");
|
fail("schema-mismatch", "The approved input cannot create an analysis view.");
|
||||||
}
|
}
|
||||||
@@ -991,7 +991,7 @@ std::vector<NodeStationResultRow> normalizeStations(
|
|||||||
values});
|
values});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
auto normalized = ResultRecovery::normalizeSectionResultantsToNodeStations(
|
auto normalized = ResultRecovery::NormalizeSectionResultantsToNodeStations(
|
||||||
model, endpoints, tolerances);
|
model, endpoints, tolerances);
|
||||||
if (!normalized.HasValue()) {
|
if (!normalized.HasValue()) {
|
||||||
const auto& diagnostics = normalized.GetStatus().Diagnostics();
|
const auto& diagnostics = normalized.GetStatus().Diagnostics();
|
||||||
@@ -1079,7 +1079,7 @@ void appendSectionRows(
|
|||||||
for (std::size_t component = 0U; component < components.size(); ++component) {
|
for (std::size_t component = 0U; component < components.size(); ++component) {
|
||||||
auto fesa = canonicalRow(
|
auto fesa = canonicalRow(
|
||||||
hdf.nodes[node], ComparisonQuantity::sectionResultant,
|
hdf.nodes[node], ComparisonQuantity::sectionResultant,
|
||||||
components[component], station.sectionResultant[component],
|
components[component], station.section_resultant[component],
|
||||||
units[component], "beam-local", kSectionPath);
|
units[component], "beam-local", kSectionPath);
|
||||||
auto abaqus = canonicalRow(
|
auto abaqus = canonicalRow(
|
||||||
hdf.nodes[node], ComparisonQuantity::sectionResultant,
|
hdf.nodes[node], ComparisonQuantity::sectionResultant,
|
||||||
@@ -1164,17 +1164,17 @@ void evaluateGroup(
|
|||||||
PhysicsEvidence makePhysicsEvidence(
|
PhysicsEvidence makePhysicsEvidence(
|
||||||
const Domain& domain,
|
const Domain& domain,
|
||||||
const HdfProjection& hdf) {
|
const HdfProjection& hdf) {
|
||||||
auto modelResult = AnalysisModel::create(domain);
|
auto modelResult = AnalysisModel::Create(domain);
|
||||||
if (!modelResult.HasValue()) {
|
if (!modelResult.HasValue()) {
|
||||||
fail("schema-mismatch", "The approved input cannot create physics evidence.");
|
fail("schema-mismatch", "The approved input cannot create physics evidence.");
|
||||||
}
|
}
|
||||||
const AnalysisModel model = std::move(modelResult.Value());
|
const AnalysisModel model = std::move(modelResult.Value());
|
||||||
auto dofsResult = DofManager::create(model);
|
auto dofsResult = DofManager::Create(model);
|
||||||
if (!dofsResult.HasValue()) {
|
if (!dofsResult.HasValue()) {
|
||||||
fail("schema-mismatch", "The approved input cannot create a DOF map.");
|
fail("schema-mismatch", "The approved input cannot create a DOF map.");
|
||||||
}
|
}
|
||||||
const DofManager dofs = std::move(dofsResult.Value());
|
const DofManager dofs = std::move(dofsResult.Value());
|
||||||
auto loadResult = LoadAssembler::assembleFullNodalLoad(model, dofs);
|
auto loadResult = LoadAssembler::AssembleFullNodalLoad(model, dofs);
|
||||||
if (!loadResult.HasValue()) {
|
if (!loadResult.HasValue()) {
|
||||||
fail("schema-mismatch", "The approved input load cannot be assembled.");
|
fail("schema-mismatch", "The approved input load cannot be assembled.");
|
||||||
}
|
}
|
||||||
@@ -1185,7 +1185,7 @@ PhysicsEvidence makePhysicsEvidence(
|
|||||||
|
|
||||||
PhysicsEvidence evidence{};
|
PhysicsEvidence evidence{};
|
||||||
long double residualSquared = 0.0L;
|
long double residualSquared = 0.0L;
|
||||||
for (const std::size_t freeDof : dofs.freeDofs()) {
|
for (const std::size_t freeDof : dofs.FreeDofs()) {
|
||||||
const long double value =
|
const long double value =
|
||||||
static_cast<long double>(hdf.reaction[freeDof]);
|
static_cast<long double>(hdf.reaction[freeDof]);
|
||||||
residualSquared += value * value;
|
residualSquared += value * value;
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
#include "reference_comparison.hpp"
|
#include "reference_comparison.hpp"
|
||||||
|
|
||||||
#include "fesa/analysis/analysis_model.hpp"
|
#include "fesa/analysis/analysis_model.h"
|
||||||
#include "fesa/analysis/analysis_state.hpp"
|
#include "fesa/analysis/analysis_state.h"
|
||||||
#include "fesa/fem/dof_manager.hpp"
|
#include "fesa/fem/dof_manager.h"
|
||||||
#include "fesa/io/abaqus/input_reader.hpp"
|
#include "fesa/io/abaqus/input_reader.hpp"
|
||||||
#include "fesa/io/hdf5/hdf5_results_writer.hpp"
|
#include "fesa/io/hdf5/hdf5_results_writer.hpp"
|
||||||
#include "fesa/model/domain.h"
|
#include "fesa/model/domain.h"
|
||||||
@@ -223,42 +223,42 @@ void writeResultsFixture(
|
|||||||
throw std::runtime_error{"Reference fixture Domain construction failed."};
|
throw std::runtime_error{"Reference fixture Domain construction failed."};
|
||||||
}
|
}
|
||||||
fesa::Domain domain = std::move(domainResult.Value());
|
fesa::Domain domain = std::move(domainResult.Value());
|
||||||
auto modelResult = fesa::AnalysisModel::create(domain);
|
auto modelResult = fesa::AnalysisModel::Create(domain);
|
||||||
if (!modelResult.HasValue()) {
|
if (!modelResult.HasValue()) {
|
||||||
throw std::runtime_error{"Reference fixture AnalysisModel construction failed."};
|
throw std::runtime_error{"Reference fixture AnalysisModel construction failed."};
|
||||||
}
|
}
|
||||||
fesa::AnalysisModel model = std::move(modelResult.Value());
|
fesa::AnalysisModel model = std::move(modelResult.Value());
|
||||||
auto dofsResult = fesa::DofManager::create(model);
|
auto dofsResult = fesa::DofManager::Create(model);
|
||||||
if (!dofsResult.HasValue()) {
|
if (!dofsResult.HasValue()) {
|
||||||
throw std::runtime_error{"Reference fixture DofManager construction failed."};
|
throw std::runtime_error{"Reference fixture DofManager construction failed."};
|
||||||
}
|
}
|
||||||
fesa::DofManager dofs = std::move(dofsResult.Value());
|
fesa::DofManager dofs = std::move(dofsResult.Value());
|
||||||
fesa::AnalysisState state =
|
fesa::AnalysisState state =
|
||||||
fesa::AnalysisState::create(dofs, {"Step-1", 0U});
|
fesa::AnalysisState::Create(dofs, {"Step-1", 0U});
|
||||||
|
|
||||||
for (std::size_t node = 0U; node < kNodeCount; ++node) {
|
for (std::size_t node = 0U; node < kNodeCount; ++node) {
|
||||||
for (std::size_t component = 0U; component < 6U; ++component) {
|
for (std::size_t component = 0U; component < 6U; ++component) {
|
||||||
const std::size_t index = node * 6U + component;
|
const std::size_t index = node * 6U + component;
|
||||||
state.displacement()[index] = values.displacement[node][component];
|
state.Displacement()[index] = values.displacement[node][component];
|
||||||
state.reaction()[index] = values.reaction[node][component];
|
state.Reaction()[index] = values.reaction[node][component];
|
||||||
state.residual()[index] = values.reaction[node][component];
|
state.Residual()[index] = values.reaction[node][component];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (std::size_t element = 0U; element < kElementCount; ++element) {
|
for (std::size_t element = 0U; element < kElementCount; ++element) {
|
||||||
for (std::size_t endpoint = 0U; endpoint < 2U; ++endpoint) {
|
for (std::size_t endpoint = 0U; endpoint < 2U; ++endpoint) {
|
||||||
const std::size_t node = element + endpoint;
|
const std::size_t node = element + endpoint;
|
||||||
state.endpointResults().push_back({
|
state.EndpointResults().push_back({
|
||||||
static_cast<fesa::EntityIndex>(element),
|
static_cast<fesa::EntityIndex>(element),
|
||||||
static_cast<int>(endpoint),
|
static_cast<int>(endpoint),
|
||||||
domain.Nodes()[node].source_id,
|
domain.Nodes()[node].source_id,
|
||||||
{},
|
{},
|
||||||
values.sectionResultants[element][endpoint]});
|
values.sectionResultants[element][endpoint]});
|
||||||
}
|
}
|
||||||
state.gaussResults().push_back(
|
state.GaussResults().push_back(
|
||||||
{static_cast<fesa::EntityIndex>(element), 1, {}, {}});
|
{static_cast<fesa::EntityIndex>(element), 1, {}, {}});
|
||||||
state.gaussResults().push_back(
|
state.GaussResults().push_back(
|
||||||
{static_cast<fesa::EntityIndex>(element), 2, {}, {}});
|
{static_cast<fesa::EntityIndex>(element), 2, {}, {}});
|
||||||
state.stressResults().push_back({
|
state.StressResults().push_back({
|
||||||
static_cast<fesa::EntityIndex>(element),
|
static_cast<fesa::EntityIndex>(element),
|
||||||
1,
|
1,
|
||||||
0U,
|
0U,
|
||||||
@@ -266,7 +266,7 @@ void writeResultsFixture(
|
|||||||
0.0,
|
0.0,
|
||||||
0.0,
|
0.0,
|
||||||
"fesa-default"});
|
"fesa-default"});
|
||||||
state.stressResults().push_back({
|
state.StressResults().push_back({
|
||||||
static_cast<fesa::EntityIndex>(element),
|
static_cast<fesa::EntityIndex>(element),
|
||||||
2,
|
2,
|
||||||
0U,
|
0U,
|
||||||
@@ -277,7 +277,7 @@ void writeResultsFixture(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fesa::Hdf5ResultsWriter writer;
|
fesa::Hdf5ResultsWriter writer;
|
||||||
const fesa::Status status = writer.write(output, domain, state, {});
|
const fesa::Status status = writer.Write(output, domain, state, {});
|
||||||
if (!status.IsOk()) {
|
if (!status.IsOk()) {
|
||||||
throw std::runtime_error{"Reference fixture HDF5 write failed."};
|
throw std::runtime_error{"Reference fixture HDF5 write failed."};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
#include "fesa/analysis/analysis_model.hpp"
|
#include "fesa/analysis/analysis_model.h"
|
||||||
|
|
||||||
#include <gtest/gtest.h>
|
#include <gtest/gtest.h>
|
||||||
|
|
||||||
@@ -9,40 +9,63 @@
|
|||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
fesa::ModelDefinition makeDefinition() {
|
fesa::ModelDefinition MakeDefinition() {
|
||||||
const std::filesystem::path source{"models/analysis-model.inp"};
|
const std::filesystem::path source{"models/analysis-model.inp"};
|
||||||
fesa::ModelDefinition definition{};
|
fesa::ModelDefinition definition{};
|
||||||
definition.source_path = source;
|
definition.source_path = source;
|
||||||
definition.source_content_identity = "fnv1a64:0123456789abcdef";
|
definition.source_content_identity = "fnv1a64:0123456789abcdef";
|
||||||
definition.nodes = {
|
definition.nodes = {{{"Beam-1", 1, "1"}, {0.0, 0.0, 0.0}, {source, 10U}},
|
||||||
{{"Beam-1", 1, "1"}, {0.0, 0.0, 0.0}, {source, 10U}},
|
|
||||||
{{"Beam-1", 2, "2"}, {1.0, 0.0, 0.0}, {source, 11U}},
|
{{"Beam-1", 2, "2"}, {1.0, 0.0, 0.0}, {source, 11U}},
|
||||||
{{"Beam-1", 3, "3"}, {2.0, 0.0, 0.0}, {source, 12U}},
|
{{"Beam-1", 3, "3"}, {2.0, 0.0, 0.0}, {source, 12U}},
|
||||||
{{"Beam-1", 4, "4"}, {3.0, 0.0, 0.0}, {source, 13U}},
|
{{"Beam-1", 4, "4"}, {3.0, 0.0, 0.0}, {source, 13U}},
|
||||||
{{"Beam-1", 5, "5"}, {4.0, 0.0, 0.0}, {source, 14U}}};
|
{{"Beam-1", 5, "5"}, {4.0, 0.0, 0.0}, {source, 14U}}};
|
||||||
definition.materials = {
|
definition.materials = {{"Material-0", 100.0, 0.20, {source, 20U}},
|
||||||
{"Material-0", 100.0, 0.20, {source, 20U}},
|
|
||||||
{"Material-1", 200.0, 0.25, {source, 21U}},
|
{"Material-1", 200.0, 0.25, {source, 21U}},
|
||||||
{"Material-2", 300.0, 0.30, {source, 22U}},
|
{"Material-2", 300.0, 0.30, {source, 22U}},
|
||||||
{"Unused-Material", 400.0, 0.35, {source, 23U}}};
|
{"Unused-Material", 400.0, 0.35, {source, 23U}}};
|
||||||
definition.sections = {
|
definition.sections = {{"Section-0",
|
||||||
{"Section-0", 1.0, 1.0, 0.0, 1.0, 1.0,
|
1.0,
|
||||||
{0.0, 1.0, 0.0}, {}, {source, 30U}},
|
1.0,
|
||||||
{"Section-1", 2.0, 2.0, 0.0, 2.0, 2.0,
|
0.0,
|
||||||
{0.0, 1.0, 0.0}, {}, {source, 31U}},
|
1.0,
|
||||||
{"Section-2", 3.0, 3.0, 0.0, 3.0, 3.0,
|
1.0,
|
||||||
{0.0, 1.0, 0.0}, {}, {source, 32U}},
|
{0.0, 1.0, 0.0},
|
||||||
{"Unused-Section", 4.0, 4.0, 0.0, 4.0, 4.0,
|
{},
|
||||||
{0.0, 1.0, 0.0}, {}, {source, 33U}}};
|
{source, 30U}},
|
||||||
definition.elements = {
|
{"Section-1",
|
||||||
{{"Beam-1", 1, "1"}, {0U, 1U}, 2U, 2U, {source, 40U}},
|
2.0,
|
||||||
|
2.0,
|
||||||
|
0.0,
|
||||||
|
2.0,
|
||||||
|
2.0,
|
||||||
|
{0.0, 1.0, 0.0},
|
||||||
|
{},
|
||||||
|
{source, 31U}},
|
||||||
|
{"Section-2",
|
||||||
|
3.0,
|
||||||
|
3.0,
|
||||||
|
0.0,
|
||||||
|
3.0,
|
||||||
|
3.0,
|
||||||
|
{0.0, 1.0, 0.0},
|
||||||
|
{},
|
||||||
|
{source, 32U}},
|
||||||
|
{"Unused-Section",
|
||||||
|
4.0,
|
||||||
|
4.0,
|
||||||
|
0.0,
|
||||||
|
4.0,
|
||||||
|
4.0,
|
||||||
|
{0.0, 1.0, 0.0},
|
||||||
|
{},
|
||||||
|
{source, 33U}}};
|
||||||
|
definition.elements = {{{"Beam-1", 1, "1"}, {0U, 1U}, 2U, 2U, {source, 40U}},
|
||||||
{{"Beam-1", 2, "2"}, {1U, 2U}, 0U, 1U, {source, 41U}},
|
{{"Beam-1", 2, "2"}, {1U, 2U}, 0U, 1U, {source, 41U}},
|
||||||
{{"Beam-1", 3, "3"}, {2U, 3U}, 2U, 2U, {source, 42U}},
|
{{"Beam-1", 3, "3"}, {2U, 3U}, 2U, 2U, {source, 42U}},
|
||||||
{{"Beam-1", 4, "4"}, {3U, 4U}, 1U, 0U, {source, 43U}}};
|
{{"Beam-1", 4, "4"}, {3U, 4U}, 1U, 0U, {source, 43U}}};
|
||||||
definition.steps = {{
|
definition.steps = {
|
||||||
"Step-1",
|
{"Step-1",
|
||||||
{{"Root", 1, 3, 0.0, {source, 51U}},
|
{{"Root", 1, 3, 0.0, {source, 51U}}, {"Root", 4, 6, 0.0, {source, 52U}}},
|
||||||
{"Root", 4, 6, 0.0, {source, 52U}}},
|
|
||||||
{{"Tip", 1, 10.0, {source, 53U}},
|
{{"Tip", 1, 10.0, {source, 53U}},
|
||||||
{"Tip", 2, -20.0, {source, 54U}},
|
{"Tip", 2, -20.0, {source, 54U}},
|
||||||
{"Tip", 6, 30.0, {source, 55U}}},
|
{"Tip", 6, 30.0, {source, 55U}}},
|
||||||
@@ -57,96 +80,81 @@ fesa::ModelDefinition makeDefinition() {
|
|||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
TEST(AnalysisModel, ClassifiesActiveEntitiesInStableOrder) {
|
TEST(AnalysisModel, ClassifiesActiveEntitiesInStableOrder) {
|
||||||
auto domainResult = fesa::Domain::Create(makeDefinition());
|
auto domain_result = fesa::Domain::Create(MakeDefinition());
|
||||||
ASSERT_TRUE(domainResult.HasValue());
|
ASSERT_TRUE(domain_result.HasValue());
|
||||||
|
|
||||||
auto modelResult = fesa::AnalysisModel::create(domainResult.Value());
|
auto model_result = fesa::AnalysisModel::Create(domain_result.Value());
|
||||||
ASSERT_TRUE(modelResult.HasValue());
|
ASSERT_TRUE(model_result.HasValue());
|
||||||
const auto& model = modelResult.Value();
|
const auto& model = model_result.Value();
|
||||||
|
|
||||||
EXPECT_EQ(
|
EXPECT_EQ(model.ActiveElements(),
|
||||||
model.activeElements(),
|
|
||||||
(std::vector<fesa::EntityIndex>{0U, 1U, 2U, 3U}));
|
(std::vector<fesa::EntityIndex>{0U, 1U, 2U, 3U}));
|
||||||
EXPECT_EQ(
|
EXPECT_EQ(model.ActiveMaterials(),
|
||||||
model.activeMaterials(),
|
|
||||||
(std::vector<fesa::EntityIndex>{0U, 1U, 2U}));
|
(std::vector<fesa::EntityIndex>{0U, 1U, 2U}));
|
||||||
EXPECT_EQ(
|
EXPECT_EQ(model.ActiveSections(),
|
||||||
model.activeSections(),
|
|
||||||
(std::vector<fesa::EntityIndex>{0U, 1U, 2U}));
|
(std::vector<fesa::EntityIndex>{0U, 1U, 2U}));
|
||||||
EXPECT_EQ(
|
EXPECT_EQ(model.ActiveBoundaryConditions(),
|
||||||
model.activeBoundaryConditions(),
|
|
||||||
(std::vector<fesa::EntityIndex>{0U, 1U}));
|
(std::vector<fesa::EntityIndex>{0U, 1U}));
|
||||||
EXPECT_EQ(
|
EXPECT_EQ(model.ActiveLoads(), (std::vector<fesa::EntityIndex>{0U, 1U, 2U}));
|
||||||
model.activeLoads(),
|
|
||||||
(std::vector<fesa::EntityIndex>{0U, 1U, 2U}));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(AnalysisModel, ReferencesWithoutCopyingOrMutatingDomain) {
|
TEST(AnalysisModel, ReferencesWithoutCopyingOrMutatingDomain) {
|
||||||
auto domainResult = fesa::Domain::Create(makeDefinition());
|
auto domain_result = fesa::Domain::Create(MakeDefinition());
|
||||||
ASSERT_TRUE(domainResult.HasValue());
|
ASSERT_TRUE(domain_result.HasValue());
|
||||||
const fesa::Domain& domain = domainResult.Value();
|
const fesa::Domain& domain = domain_result.Value();
|
||||||
const auto* const elementAddress = domain.Elements().data();
|
const auto* const element_address = domain.Elements().data();
|
||||||
const auto* const materialAddress = domain.Materials().data();
|
const auto* const material_address = domain.Materials().data();
|
||||||
const auto* const sectionAddress = domain.Sections().data();
|
const auto* const section_address = domain.Sections().data();
|
||||||
const std::string stepName = domain.Steps()[0].name;
|
const std::string step_name = domain.Steps()[0].name;
|
||||||
const double firstLoadMagnitude = domain.Steps()[0].loads[0].magnitude;
|
const double first_load_magnitude = domain.Steps()[0].loads[0].magnitude;
|
||||||
|
|
||||||
auto modelResult = fesa::AnalysisModel::create(domain);
|
auto model_result = fesa::AnalysisModel::Create(domain);
|
||||||
ASSERT_TRUE(modelResult.HasValue());
|
ASSERT_TRUE(model_result.HasValue());
|
||||||
const auto& model = modelResult.Value();
|
const auto& model = model_result.Value();
|
||||||
|
|
||||||
EXPECT_EQ(&model.domain(), &domain);
|
EXPECT_EQ(&model.GetDomain(), &domain);
|
||||||
EXPECT_EQ(&model.step(), &domain.Steps()[0]);
|
EXPECT_EQ(&model.Step(), &domain.Steps()[0]);
|
||||||
EXPECT_EQ(model.domain().Elements().data(), elementAddress);
|
EXPECT_EQ(model.GetDomain().Elements().data(), element_address);
|
||||||
EXPECT_EQ(model.domain().Materials().data(), materialAddress);
|
EXPECT_EQ(model.GetDomain().Materials().data(), material_address);
|
||||||
EXPECT_EQ(model.domain().Sections().data(), sectionAddress);
|
EXPECT_EQ(model.GetDomain().Sections().data(), section_address);
|
||||||
EXPECT_EQ(
|
EXPECT_EQ(&model.GetDomain().Elements()[model.ActiveElements()[1]],
|
||||||
&model.domain().Elements()[model.activeElements()[1]],
|
|
||||||
&domain.Elements()[1]);
|
&domain.Elements()[1]);
|
||||||
EXPECT_EQ(
|
EXPECT_EQ(&model.GetDomain().Materials()[model.ActiveMaterials()[2]],
|
||||||
&model.domain().Materials()[model.activeMaterials()[2]],
|
|
||||||
&domain.Materials()[2]);
|
&domain.Materials()[2]);
|
||||||
EXPECT_EQ(
|
EXPECT_EQ(&model.GetDomain().Sections()[model.ActiveSections()[1]],
|
||||||
&model.domain().Sections()[model.activeSections()[1]],
|
|
||||||
&domain.Sections()[1]);
|
&domain.Sections()[1]);
|
||||||
EXPECT_EQ(domain.Steps()[0].name, stepName);
|
EXPECT_EQ(domain.Steps()[0].name, step_name);
|
||||||
EXPECT_DOUBLE_EQ(domain.Steps()[0].loads[0].magnitude, firstLoadMagnitude);
|
EXPECT_DOUBLE_EQ(domain.Steps()[0].loads[0].magnitude, first_load_magnitude);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(AnalysisModel, RejectsMissingOrMultipleStep) {
|
TEST(AnalysisModel, RejectsMissingOrMultipleStep) {
|
||||||
auto missingDefinition = makeDefinition();
|
auto missing_definition = MakeDefinition();
|
||||||
missingDefinition.steps.clear();
|
missing_definition.steps.clear();
|
||||||
auto missingDomain = fesa::Domain::Create(std::move(missingDefinition));
|
auto missing_domain = fesa::Domain::Create(std::move(missing_definition));
|
||||||
ASSERT_TRUE(missingDomain.HasValue());
|
ASSERT_TRUE(missing_domain.HasValue());
|
||||||
|
|
||||||
auto missing = fesa::AnalysisModel::create(missingDomain.Value());
|
auto missing = fesa::AnalysisModel::Create(missing_domain.Value());
|
||||||
ASSERT_FALSE(missing.HasValue());
|
ASSERT_FALSE(missing.HasValue());
|
||||||
EXPECT_EQ(
|
EXPECT_EQ(missing.GetStatus().Category(), fesa::FailureCategory::kInput);
|
||||||
missing.GetStatus().Category(),
|
|
||||||
fesa::FailureCategory::kInput);
|
|
||||||
ASSERT_EQ(missing.GetStatus().Diagnostics().size(), 1U);
|
ASSERT_EQ(missing.GetStatus().Diagnostics().size(), 1U);
|
||||||
EXPECT_EQ(
|
EXPECT_EQ(missing.GetStatus().Diagnostics()[0].code,
|
||||||
missing.GetStatus().Diagnostics()[0].code,
|
|
||||||
"invalid-model-cardinality");
|
"invalid-model-cardinality");
|
||||||
EXPECT_EQ(missing.GetStatus().Diagnostics()[0].keyword, "STEP");
|
EXPECT_EQ(missing.GetStatus().Diagnostics()[0].keyword, "STEP");
|
||||||
EXPECT_EQ(missing.GetStatus().Diagnostics()[0].entity_identity, "0");
|
EXPECT_EQ(missing.GetStatus().Diagnostics()[0].entity_identity, "0");
|
||||||
|
|
||||||
auto multipleDefinition = makeDefinition();
|
auto multiple_definition = MakeDefinition();
|
||||||
auto secondStep = multipleDefinition.steps.front();
|
auto second_step = multiple_definition.steps.front();
|
||||||
secondStep.name = "Step-2";
|
second_step.name = "Step-2";
|
||||||
secondStep.location.line = 60U;
|
second_step.location.line = 60U;
|
||||||
multipleDefinition.steps.push_back(std::move(secondStep));
|
multiple_definition.steps.push_back(std::move(second_step));
|
||||||
auto multipleDomain = fesa::Domain::Create(std::move(multipleDefinition));
|
auto multiple_domain = fesa::Domain::Create(std::move(multiple_definition));
|
||||||
ASSERT_TRUE(multipleDomain.HasValue());
|
ASSERT_TRUE(multiple_domain.HasValue());
|
||||||
|
|
||||||
auto multiple = fesa::AnalysisModel::create(multipleDomain.Value());
|
auto multiple = fesa::AnalysisModel::Create(multiple_domain.Value());
|
||||||
ASSERT_FALSE(multiple.HasValue());
|
ASSERT_FALSE(multiple.HasValue());
|
||||||
EXPECT_EQ(
|
EXPECT_EQ(multiple.GetStatus().Category(), fesa::FailureCategory::kInput);
|
||||||
multiple.GetStatus().Category(),
|
|
||||||
fesa::FailureCategory::kInput);
|
|
||||||
ASSERT_EQ(multiple.GetStatus().Diagnostics().size(), 1U);
|
ASSERT_EQ(multiple.GetStatus().Diagnostics().size(), 1U);
|
||||||
EXPECT_EQ(
|
EXPECT_EQ(multiple.GetStatus().Diagnostics()[0].code,
|
||||||
multiple.GetStatus().Diagnostics()[0].code,
|
|
||||||
"unsupported-multiple-step");
|
"unsupported-multiple-step");
|
||||||
EXPECT_EQ(multiple.GetStatus().Diagnostics()[0].keyword, "STEP");
|
EXPECT_EQ(multiple.GetStatus().Diagnostics()[0].keyword, "STEP");
|
||||||
EXPECT_EQ(multiple.GetStatus().Diagnostics()[0].entity_identity, "Step-2");
|
EXPECT_EQ(multiple.GetStatus().Diagnostics()[0].entity_identity, "Step-2");
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
#include "fesa/analysis/analysis_state.hpp"
|
#include "fesa/analysis/analysis_state.h"
|
||||||
|
|
||||||
#include <gtest/gtest.h>
|
#include <gtest/gtest.h>
|
||||||
|
|
||||||
@@ -9,53 +9,53 @@
|
|||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
template<class T, class = void>
|
template <class T, class = void>
|
||||||
struct HasVelocity : std::false_type {};
|
struct HasVelocity : std::false_type {};
|
||||||
|
|
||||||
template<class T>
|
template <class T>
|
||||||
struct HasVelocity<T, std::void_t<decltype(std::declval<T&>().velocity())>>
|
struct HasVelocity<T, std::void_t<decltype(std::declval<T&>().Velocity())>>
|
||||||
: std::true_type {};
|
: std::true_type {};
|
||||||
|
|
||||||
template<class T, class = void>
|
template <class T, class = void>
|
||||||
struct HasAcceleration : std::false_type {};
|
struct HasAcceleration : std::false_type {};
|
||||||
|
|
||||||
template<class T>
|
template <class T>
|
||||||
struct HasAcceleration<
|
struct HasAcceleration<T,
|
||||||
T, std::void_t<decltype(std::declval<T&>().acceleration())>>
|
std::void_t<decltype(std::declval<T&>().Acceleration())>>
|
||||||
: std::true_type {};
|
: std::true_type {};
|
||||||
|
|
||||||
template<class T, class = void>
|
template <class T, class = void>
|
||||||
struct HasTemperature : std::false_type {};
|
struct HasTemperature : std::false_type {};
|
||||||
|
|
||||||
template<class T>
|
template <class T>
|
||||||
struct HasTemperature<T, std::void_t<decltype(std::declval<T&>().temperature())>>
|
struct HasTemperature<T,
|
||||||
|
std::void_t<decltype(std::declval<T&>().Temperature())>>
|
||||||
: std::true_type {};
|
: std::true_type {};
|
||||||
|
|
||||||
template<class T, class = void>
|
template <class T, class = void>
|
||||||
struct HasIterationHistory : std::false_type {};
|
struct HasIterationHistory : std::false_type {};
|
||||||
|
|
||||||
template<class T>
|
template <class T>
|
||||||
struct HasIterationHistory<
|
struct HasIterationHistory<
|
||||||
T, std::void_t<decltype(std::declval<T&>().iterationHistory())>>
|
T, std::void_t<decltype(std::declval<T&>().IterationHistory())>>
|
||||||
: std::true_type {};
|
: std::true_type {};
|
||||||
|
|
||||||
template<class T, class = void>
|
template <class T, class = void>
|
||||||
struct HasNonlinearState : std::false_type {};
|
struct HasNonlinearState : std::false_type {};
|
||||||
|
|
||||||
template<class T>
|
template <class T>
|
||||||
struct HasNonlinearState<
|
struct HasNonlinearState<
|
||||||
T, std::void_t<decltype(std::declval<T&>().nonlinearState())>>
|
T, std::void_t<decltype(std::declval<T&>().NonlinearState())>>
|
||||||
: std::true_type {};
|
: std::true_type {};
|
||||||
|
|
||||||
fesa::DofManager makeDofs() {
|
fesa::DofManager MakeDofs() {
|
||||||
fesa::ModelDefinition definition{};
|
fesa::ModelDefinition definition{};
|
||||||
definition.source_path = "models/analysis-state.inp";
|
definition.source_path = "models/analysis-state.inp";
|
||||||
definition.source_content_identity = "fnv1a64:0123456789abcdef";
|
definition.source_content_identity = "fnv1a64:0123456789abcdef";
|
||||||
definition.nodes = {
|
definition.nodes = {
|
||||||
{{"Beam-1", 1, "1"}, {0.0, 0.0, 0.0}, {definition.source_path, 10U}},
|
{{"Beam-1", 1, "1"}, {0.0, 0.0, 0.0}, {definition.source_path, 10U}},
|
||||||
{{"Beam-1", 2, "2"}, {1.0, 0.0, 0.0}, {definition.source_path, 11U}}};
|
{{"Beam-1", 2, "2"}, {1.0, 0.0, 0.0}, {definition.source_path, 11U}}};
|
||||||
definition.steps = {{
|
definition.steps = {{"Step-1",
|
||||||
"Step-1",
|
|
||||||
{{"1", 1, 6, 0.0, {definition.source_path, 20U}}},
|
{{"1", 1, 6, 0.0, {definition.source_path, 20U}}},
|
||||||
{},
|
{},
|
||||||
0.1,
|
0.1,
|
||||||
@@ -66,14 +66,14 @@ fesa::DofManager makeDofs() {
|
|||||||
|
|
||||||
auto domain = fesa::Domain::Create(std::move(definition));
|
auto domain = fesa::Domain::Create(std::move(definition));
|
||||||
EXPECT_TRUE(domain.HasValue());
|
EXPECT_TRUE(domain.HasValue());
|
||||||
auto model = fesa::AnalysisModel::create(domain.Value());
|
auto model = fesa::AnalysisModel::Create(domain.Value());
|
||||||
EXPECT_TRUE(model.HasValue());
|
EXPECT_TRUE(model.HasValue());
|
||||||
auto dofs = fesa::DofManager::create(model.Value());
|
auto dofs = fesa::DofManager::Create(model.Value());
|
||||||
EXPECT_TRUE(dofs.HasValue());
|
EXPECT_TRUE(dofs.HasValue());
|
||||||
return std::move(dofs.Value());
|
return std::move(dofs.Value());
|
||||||
}
|
}
|
||||||
|
|
||||||
void expectAllZero(const fesa::Vector& vector) {
|
void ExpectAllZero(const fesa::Vector& vector) {
|
||||||
for (std::size_t index = 0U; index < vector.Size(); ++index) {
|
for (std::size_t index = 0U; index < vector.Size(); ++index) {
|
||||||
EXPECT_DOUBLE_EQ(vector[index], 0.0);
|
EXPECT_DOUBLE_EQ(vector[index], 0.0);
|
||||||
}
|
}
|
||||||
@@ -82,22 +82,20 @@ void expectAllZero(const fesa::Vector& vector) {
|
|||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
TEST(AnalysisState, AllocatesOnlyV0FullVectors) {
|
TEST(AnalysisState, AllocatesOnlyV0FullVectors) {
|
||||||
const auto dofs = makeDofs();
|
const auto dofs = MakeDofs();
|
||||||
ASSERT_EQ(dofs.fullDofCount(), 12U);
|
ASSERT_EQ(dofs.FullDofCount(), 12U);
|
||||||
ASSERT_EQ(dofs.constrainedDofCount(), 6U);
|
ASSERT_EQ(dofs.ConstrainedDofCount(), 6U);
|
||||||
|
|
||||||
auto state = fesa::AnalysisState::create(dofs, {"Step-1", 0U});
|
auto state = fesa::AnalysisState::Create(dofs, {"Step-1", 0U});
|
||||||
const fesa::AnalysisState& constState = state;
|
const fesa::AnalysisState& const_state = state;
|
||||||
const fesa::Vector* const vectors[] = {
|
const fesa::Vector* const vectors[] = {
|
||||||
&constState.displacement(),
|
&const_state.Displacement(), &const_state.ExternalForce(),
|
||||||
&constState.externalForce(),
|
&const_state.InternalForce(), &const_state.Residual(),
|
||||||
&constState.internalForce(),
|
&const_state.Reaction()};
|
||||||
&constState.residual(),
|
|
||||||
&constState.reaction()};
|
|
||||||
|
|
||||||
for (const auto* vector : vectors) {
|
for (const auto* vector : vectors) {
|
||||||
EXPECT_EQ(vector->Size(), dofs.fullDofCount());
|
EXPECT_EQ(vector->Size(), dofs.FullDofCount());
|
||||||
expectAllZero(*vector);
|
ExpectAllZero(*vector);
|
||||||
}
|
}
|
||||||
for (std::size_t left = 0U; left < std::size(vectors); ++left) {
|
for (std::size_t left = 0U; left < std::size(vectors); ++left) {
|
||||||
for (std::size_t right = left + 1U; right < std::size(vectors); ++right) {
|
for (std::size_t right = left + 1U; right < std::size(vectors); ++right) {
|
||||||
@@ -105,13 +103,13 @@ TEST(AnalysisState, AllocatesOnlyV0FullVectors) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
state.displacement()[0] = 4.0;
|
state.Displacement()[0] = 4.0;
|
||||||
state.reaction()[11] = -9.0;
|
state.Reaction()[11] = -9.0;
|
||||||
EXPECT_DOUBLE_EQ(state.displacement()[0], 4.0);
|
EXPECT_DOUBLE_EQ(state.Displacement()[0], 4.0);
|
||||||
EXPECT_DOUBLE_EQ(state.reaction()[11], -9.0);
|
EXPECT_DOUBLE_EQ(state.Reaction()[11], -9.0);
|
||||||
EXPECT_DOUBLE_EQ(state.externalForce()[0], 0.0);
|
EXPECT_DOUBLE_EQ(state.ExternalForce()[0], 0.0);
|
||||||
EXPECT_DOUBLE_EQ(state.internalForce()[0], 0.0);
|
EXPECT_DOUBLE_EQ(state.InternalForce()[0], 0.0);
|
||||||
EXPECT_DOUBLE_EQ(state.residual()[0], 0.0);
|
EXPECT_DOUBLE_EQ(state.Residual()[0], 0.0);
|
||||||
|
|
||||||
EXPECT_FALSE(HasVelocity<fesa::AnalysisState>::value);
|
EXPECT_FALSE(HasVelocity<fesa::AnalysisState>::value);
|
||||||
EXPECT_FALSE(HasAcceleration<fesa::AnalysisState>::value);
|
EXPECT_FALSE(HasAcceleration<fesa::AnalysisState>::value);
|
||||||
@@ -126,48 +124,47 @@ TEST(AnalysisState, CopiesOrMovesWithoutAliasing) {
|
|||||||
static_assert(std::is_move_constructible_v<fesa::AnalysisState>);
|
static_assert(std::is_move_constructible_v<fesa::AnalysisState>);
|
||||||
static_assert(std::is_move_assignable_v<fesa::AnalysisState>);
|
static_assert(std::is_move_assignable_v<fesa::AnalysisState>);
|
||||||
|
|
||||||
const auto dofs = makeDofs();
|
const auto dofs = MakeDofs();
|
||||||
auto original = fesa::AnalysisState::create(dofs, {"Step-1", 0U});
|
auto original = fesa::AnalysisState::Create(dofs, {"Step-1", 0U});
|
||||||
original.displacement()[0] = 3.0;
|
original.Displacement()[0] = 3.0;
|
||||||
original.reaction()[11] = -2.0;
|
original.Reaction()[11] = -2.0;
|
||||||
original.endpointResults().push_back({
|
original.EndpointResults().push_back({0U,
|
||||||
0U,
|
|
||||||
-1,
|
-1,
|
||||||
{"Beam-1", 1, "1"},
|
{"Beam-1", 1, "1"},
|
||||||
{1.0, 0.0, 0.0, 0.0, 0.0, 0.0},
|
{1.0, 0.0, 0.0, 0.0, 0.0, 0.0},
|
||||||
{2.0, 0.0, 0.0, 0.0}});
|
{2.0, 0.0, 0.0, 0.0}});
|
||||||
original.gaussResults().push_back(
|
original.GaussResults().push_back(
|
||||||
{0U, 1, {0.1, 0.0, 0.0, 0.0}, {10.0, 0.0, 0.0, 0.0}});
|
{0U, 1, {0.1, 0.0, 0.0, 0.0}, {10.0, 0.0, 0.0, 0.0}});
|
||||||
original.stressResults().push_back(
|
original.StressResults().push_back(
|
||||||
{0U, 1, 0U, 0.0, 0.0, 10.0, "fesa-default"});
|
{0U, 1, 0U, 0.0, 0.0, 10.0, "fesa-default"});
|
||||||
|
|
||||||
auto copied = original;
|
auto copied = original;
|
||||||
EXPECT_NE(copied.displacement().Data(), original.displacement().Data());
|
EXPECT_NE(copied.Displacement().Data(), original.Displacement().Data());
|
||||||
EXPECT_NE(copied.reaction().Data(), original.reaction().Data());
|
EXPECT_NE(copied.Reaction().Data(), original.Reaction().Data());
|
||||||
EXPECT_NE(copied.endpointResults().data(), original.endpointResults().data());
|
EXPECT_NE(copied.EndpointResults().data(), original.EndpointResults().data());
|
||||||
EXPECT_NE(copied.gaussResults().data(), original.gaussResults().data());
|
EXPECT_NE(copied.GaussResults().data(), original.GaussResults().data());
|
||||||
EXPECT_NE(copied.stressResults().data(), original.stressResults().data());
|
EXPECT_NE(copied.StressResults().data(), original.StressResults().data());
|
||||||
copied.displacement()[0] = 30.0;
|
copied.Displacement()[0] = 30.0;
|
||||||
copied.endpointResults()[0].endAction[0] = 20.0;
|
copied.EndpointResults()[0].end_action[0] = 20.0;
|
||||||
EXPECT_DOUBLE_EQ(original.displacement()[0], 3.0);
|
EXPECT_DOUBLE_EQ(original.Displacement()[0], 3.0);
|
||||||
EXPECT_DOUBLE_EQ(original.endpointResults()[0].endAction[0], 1.0);
|
EXPECT_DOUBLE_EQ(original.EndpointResults()[0].end_action[0], 1.0);
|
||||||
|
|
||||||
auto moved = std::move(copied);
|
auto moved = std::move(copied);
|
||||||
EXPECT_EQ(moved.identity().stepName, "Step-1");
|
EXPECT_EQ(moved.Identity().step_name, "Step-1");
|
||||||
EXPECT_DOUBLE_EQ(moved.displacement()[0], 30.0);
|
EXPECT_DOUBLE_EQ(moved.Displacement()[0], 30.0);
|
||||||
EXPECT_DOUBLE_EQ(moved.endpointResults()[0].endAction[0], 20.0);
|
EXPECT_DOUBLE_EQ(moved.EndpointResults()[0].end_action[0], 20.0);
|
||||||
EXPECT_NE(moved.displacement().Data(), original.displacement().Data());
|
EXPECT_NE(moved.Displacement().Data(), original.Displacement().Data());
|
||||||
EXPECT_NE(moved.endpointResults().data(), original.endpointResults().data());
|
EXPECT_NE(moved.EndpointResults().data(), original.EndpointResults().data());
|
||||||
|
|
||||||
auto copyAssigned = fesa::AnalysisState::create(dofs, {"Other", 3U});
|
auto copy_assigned = fesa::AnalysisState::Create(dofs, {"Other", 3U});
|
||||||
copyAssigned = original;
|
copy_assigned = original;
|
||||||
copyAssigned.reaction()[11] = -20.0;
|
copy_assigned.Reaction()[11] = -20.0;
|
||||||
EXPECT_DOUBLE_EQ(original.reaction()[11], -2.0);
|
EXPECT_DOUBLE_EQ(original.Reaction()[11], -2.0);
|
||||||
EXPECT_EQ(copyAssigned.identity().stepName, "Step-1");
|
EXPECT_EQ(copy_assigned.Identity().step_name, "Step-1");
|
||||||
|
|
||||||
auto moveAssigned = fesa::AnalysisState::create(dofs, {"Other", 4U});
|
auto move_assigned = fesa::AnalysisState::Create(dofs, {"Other", 4U});
|
||||||
moveAssigned = std::move(copyAssigned);
|
move_assigned = std::move(copy_assigned);
|
||||||
EXPECT_EQ(moveAssigned.identity().stepName, "Step-1");
|
EXPECT_EQ(move_assigned.Identity().step_name, "Step-1");
|
||||||
EXPECT_DOUBLE_EQ(moveAssigned.reaction()[11], -20.0);
|
EXPECT_DOUBLE_EQ(move_assigned.Reaction()[11], -20.0);
|
||||||
EXPECT_NE(moveAssigned.reaction().Data(), original.reaction().Data());
|
EXPECT_NE(move_assigned.Reaction().Data(), original.Reaction().Data());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,4 @@
|
|||||||
#include "fesa/assembly/load_assembler.hpp"
|
#include "fesa/assembly/load_assembler.h"
|
||||||
|
|
||||||
#include "fesa/analysis/analysis_model.hpp"
|
|
||||||
#include "fesa/fem/dof_manager.hpp"
|
|
||||||
#include "fesa/model/domain.h"
|
|
||||||
|
|
||||||
#include <gtest/gtest.h>
|
#include <gtest/gtest.h>
|
||||||
|
|
||||||
@@ -18,6 +14,10 @@
|
|||||||
#include <utility>
|
#include <utility>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
|
#include "fesa/analysis/analysis_model.h"
|
||||||
|
#include "fesa/fem/dof_manager.h"
|
||||||
|
#include "fesa/model/domain.h"
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
struct LoadFixture {
|
struct LoadFixture {
|
||||||
@@ -26,25 +26,22 @@ struct LoadFixture {
|
|||||||
std::unique_ptr<fesa::DofManager> dofs;
|
std::unique_ptr<fesa::DofManager> dofs;
|
||||||
};
|
};
|
||||||
|
|
||||||
LoadFixture makeFixture(
|
LoadFixture MakeFixture(const std::size_t node_count,
|
||||||
const std::size_t nodeCount,
|
std::vector<fesa::NodeSet> node_sets,
|
||||||
std::vector<fesa::NodeSet> nodeSets,
|
|
||||||
std::vector<fesa::BoundaryCondition> boundaries,
|
std::vector<fesa::BoundaryCondition> boundaries,
|
||||||
std::vector<fesa::NodalLoad> loads) {
|
std::vector<fesa::NodalLoad> loads) {
|
||||||
const std::filesystem::path source{"models/load-assembly.inp"};
|
const std::filesystem::path source{"models/load-assembly.inp"};
|
||||||
fesa::ModelDefinition definition{};
|
fesa::ModelDefinition definition{};
|
||||||
definition.source_path = source;
|
definition.source_path = source;
|
||||||
definition.source_content_identity = "fnv1a64:abcdef0123456789";
|
definition.source_content_identity = "fnv1a64:abcdef0123456789";
|
||||||
for (std::size_t index = 0U; index < nodeCount; ++index) {
|
for (std::size_t index = 0U; index < node_count; ++index) {
|
||||||
const auto label = static_cast<std::int64_t>((index + 1U) * 10U);
|
const auto label = static_cast<std::int64_t>((index + 1U) * 10U);
|
||||||
definition.nodes.push_back({
|
definition.nodes.push_back({{"Beam-1", label, std::to_string(label)},
|
||||||
{"Beam-1", label, std::to_string(label)},
|
|
||||||
{static_cast<double>(index), 0.0, 0.0},
|
{static_cast<double>(index), 0.0, 0.0},
|
||||||
{source, index + 2U}});
|
{source, index + 2U}});
|
||||||
}
|
}
|
||||||
definition.node_sets = std::move(nodeSets);
|
definition.node_sets = std::move(node_sets);
|
||||||
definition.steps = {{
|
definition.steps = {{"Step-1",
|
||||||
"Step-1",
|
|
||||||
std::move(boundaries),
|
std::move(boundaries),
|
||||||
std::move(loads),
|
std::move(loads),
|
||||||
0.1,
|
0.1,
|
||||||
@@ -53,61 +50,54 @@ LoadFixture makeFixture(
|
|||||||
1.0,
|
1.0,
|
||||||
{source, 20U}}};
|
{source, 20U}}};
|
||||||
|
|
||||||
auto domainResult = fesa::Domain::Create(std::move(definition));
|
auto domain_result = fesa::Domain::Create(std::move(definition));
|
||||||
if (!domainResult.HasValue()) {
|
if (!domain_result.HasValue()) {
|
||||||
throw std::runtime_error{"Load fixture Domain construction failed."};
|
throw std::runtime_error{"Load fixture Domain construction failed."};
|
||||||
}
|
}
|
||||||
auto domain = std::make_unique<fesa::Domain>(
|
auto domain =
|
||||||
std::move(domainResult.Value()));
|
std::make_unique<fesa::Domain>(std::move(domain_result.Value()));
|
||||||
|
|
||||||
auto modelResult = fesa::AnalysisModel::create(*domain);
|
auto model_result = fesa::AnalysisModel::Create(*domain);
|
||||||
if (!modelResult.HasValue()) {
|
if (!model_result.HasValue()) {
|
||||||
throw std::runtime_error{"Load fixture AnalysisModel construction failed."};
|
throw std::runtime_error{"Load fixture AnalysisModel construction failed."};
|
||||||
}
|
}
|
||||||
auto model = std::make_unique<fesa::AnalysisModel>(
|
auto model =
|
||||||
std::move(modelResult.Value()));
|
std::make_unique<fesa::AnalysisModel>(std::move(model_result.Value()));
|
||||||
|
|
||||||
auto dofResult = fesa::DofManager::create(*model);
|
auto dof_result = fesa::DofManager::Create(*model);
|
||||||
if (!dofResult.HasValue()) {
|
if (!dof_result.HasValue()) {
|
||||||
throw std::runtime_error{"Load fixture DofManager construction failed."};
|
throw std::runtime_error{"Load fixture DofManager construction failed."};
|
||||||
}
|
}
|
||||||
auto dofs = std::make_unique<fesa::DofManager>(
|
auto dofs = std::make_unique<fesa::DofManager>(std::move(dof_result.Value()));
|
||||||
std::move(dofResult.Value()));
|
|
||||||
return {std::move(domain), std::move(model), std::move(dofs)};
|
return {std::move(domain), std::move(model), std::move(dofs)};
|
||||||
}
|
}
|
||||||
|
|
||||||
LoadFixture makeShellFixture(
|
LoadFixture MakeShellFixture(std::vector<fesa::BoundaryCondition> boundaries,
|
||||||
std::vector<fesa::BoundaryCondition> boundaries,
|
|
||||||
std::vector<fesa::NodalLoad> loads) {
|
std::vector<fesa::NodalLoad> loads) {
|
||||||
const std::filesystem::path source{"models/shell-load-assembly.inp"};
|
const std::filesystem::path source{"models/shell-load-assembly.inp"};
|
||||||
fesa::ModelDefinition definition{};
|
fesa::ModelDefinition definition{};
|
||||||
definition.source_path = source;
|
definition.source_path = source;
|
||||||
definition.source_content_identity = "fnv1a64:1122334455667788";
|
definition.source_content_identity = "fnv1a64:1122334455667788";
|
||||||
definition.nodes = {
|
definition.nodes = {{{"Shell-1", 10, "10"}, {0.0, 0.0, 0.0}, {source, 2U}},
|
||||||
{{"Shell-1", 10, "10"}, {0.0, 0.0, 0.0}, {source, 2U}},
|
|
||||||
{{"Shell-1", 20, "20"}, {1.0, 0.0, 0.0}, {source, 3U}},
|
{{"Shell-1", 20, "20"}, {1.0, 0.0, 0.0}, {source, 3U}},
|
||||||
{{"Shell-1", 30, "30"}, {1.0, 1.0, 0.0}, {source, 4U}},
|
{{"Shell-1", 30, "30"}, {1.0, 1.0, 0.0}, {source, 4U}},
|
||||||
{{"Shell-1", 40, "40"}, {0.0, 1.0, 0.0}, {source, 5U}}};
|
{{"Shell-1", 40, "40"}, {0.0, 1.0, 0.0}, {source, 5U}}};
|
||||||
definition.materials = {
|
definition.materials = {{"Material", 1000.0, 0.25, {source, 6U}}};
|
||||||
{"Material", 1000.0, 0.25, {source, 6U}}};
|
definition.shell_sections = {{"ShellSection", 0.1, 0U, {source, 7U}}};
|
||||||
definition.shell_sections = {
|
definition.shell_elements = {{{"Shell-1", 1, "1"},
|
||||||
{"ShellSection", 0.1, 0U, {source, 7U}}};
|
|
||||||
definition.shell_elements = {{
|
|
||||||
{"Shell-1", 1, "1"},
|
|
||||||
fesa::ShellSourceElementType::kS4,
|
fesa::ShellSourceElementType::kS4,
|
||||||
{0U, 1U, 2U, 3U},
|
{0U, 1U, 2U, 3U},
|
||||||
0U,
|
0U,
|
||||||
0U,
|
0U,
|
||||||
{source, 8U}}};
|
{source, 8U}}};
|
||||||
for (std::size_t node = 0U; node < definition.nodes.size(); ++node) {
|
for (std::size_t node = 0U; node < definition.nodes.size(); ++node) {
|
||||||
definition.shell_node_initial_frames.push_back({
|
definition.shell_node_initial_frames.push_back(
|
||||||
static_cast<fesa::EntityIndex>(node),
|
{static_cast<fesa::EntityIndex>(node),
|
||||||
{0.0, 0.0, 1.0},
|
{0.0, 0.0, 1.0},
|
||||||
{1.0, 0.0, 0.0},
|
{1.0, 0.0, 0.0},
|
||||||
{0.0, 1.0, 0.0}});
|
{0.0, 1.0, 0.0}});
|
||||||
}
|
}
|
||||||
definition.steps = {{
|
definition.steps = {{"Step-1",
|
||||||
"Step-1",
|
|
||||||
std::move(boundaries),
|
std::move(boundaries),
|
||||||
std::move(loads),
|
std::move(loads),
|
||||||
0.1,
|
0.1,
|
||||||
@@ -116,64 +106,60 @@ LoadFixture makeShellFixture(
|
|||||||
1.0,
|
1.0,
|
||||||
{source, 20U}}};
|
{source, 20U}}};
|
||||||
|
|
||||||
auto domainResult = fesa::Domain::Create(std::move(definition));
|
auto domain_result = fesa::Domain::Create(std::move(definition));
|
||||||
if (!domainResult.HasValue()) {
|
if (!domain_result.HasValue()) {
|
||||||
throw std::runtime_error{"Shell load fixture Domain construction failed."};
|
throw std::runtime_error{"Shell load fixture Domain construction failed."};
|
||||||
}
|
}
|
||||||
auto domain = std::make_unique<fesa::Domain>(
|
auto domain =
|
||||||
std::move(domainResult.Value()));
|
std::make_unique<fesa::Domain>(std::move(domain_result.Value()));
|
||||||
|
|
||||||
auto modelResult = fesa::AnalysisModel::create(*domain);
|
auto model_result = fesa::AnalysisModel::Create(*domain);
|
||||||
if (!modelResult.HasValue()) {
|
if (!model_result.HasValue()) {
|
||||||
throw std::runtime_error{"Shell load fixture AnalysisModel construction failed."};
|
throw std::runtime_error{
|
||||||
|
"Shell load fixture AnalysisModel construction failed."};
|
||||||
}
|
}
|
||||||
auto model = std::make_unique<fesa::AnalysisModel>(
|
auto model =
|
||||||
std::move(modelResult.Value()));
|
std::make_unique<fesa::AnalysisModel>(std::move(model_result.Value()));
|
||||||
|
|
||||||
auto dofResult = fesa::DofManager::create(*model);
|
auto dof_result = fesa::DofManager::Create(*model);
|
||||||
if (!dofResult.HasValue()) {
|
if (!dof_result.HasValue()) {
|
||||||
throw std::runtime_error{"Shell load fixture DofManager construction failed."};
|
throw std::runtime_error{
|
||||||
|
"Shell load fixture DofManager construction failed."};
|
||||||
}
|
}
|
||||||
auto dofs = std::make_unique<fesa::DofManager>(
|
auto dofs = std::make_unique<fesa::DofManager>(std::move(dof_result.Value()));
|
||||||
std::move(dofResult.Value()));
|
|
||||||
return {std::move(domain), std::move(model), std::move(dofs)};
|
return {std::move(domain), std::move(model), std::move(dofs)};
|
||||||
}
|
}
|
||||||
|
|
||||||
fesa::SparseMatrix makeDenseSparse(
|
fesa::SparseMatrix MakeDenseSparse(const std::size_t rows,
|
||||||
const std::size_t rows,
|
|
||||||
const std::size_t columns,
|
const std::size_t columns,
|
||||||
const std::vector<double>& values) {
|
const std::vector<double>& values) {
|
||||||
if (values.size() != rows * columns) {
|
if (values.size() != rows * columns) {
|
||||||
throw std::invalid_argument{"Dense sparse fixture has the wrong value count."};
|
throw std::invalid_argument{
|
||||||
|
"Dense sparse fixture has the wrong value count."};
|
||||||
}
|
}
|
||||||
|
|
||||||
fesa::SparsePattern pattern;
|
fesa::SparsePattern pattern;
|
||||||
std::vector<fesa::CooContribution> contributions;
|
std::vector<fesa::CooContribution> contributions;
|
||||||
pattern.rowOffsets.reserve(rows + 1U);
|
pattern.row_offsets.reserve(rows + 1U);
|
||||||
pattern.rowOffsets.push_back(0U);
|
pattern.row_offsets.push_back(0U);
|
||||||
for (std::size_t row = 0U; row < rows; ++row) {
|
for (std::size_t row = 0U; row < rows; ++row) {
|
||||||
for (std::size_t column = 0U; column < columns; ++column) {
|
for (std::size_t column = 0U; column < columns; ++column) {
|
||||||
pattern.columnIndices.push_back(column);
|
pattern.column_indices.push_back(column);
|
||||||
contributions.push_back({
|
contributions.push_back(
|
||||||
row,
|
{row, column, values[row * columns + column], row, column});
|
||||||
column,
|
|
||||||
values[row * columns + column],
|
|
||||||
row,
|
|
||||||
column});
|
|
||||||
}
|
}
|
||||||
pattern.rowOffsets.push_back(pattern.columnIndices.size());
|
pattern.row_offsets.push_back(pattern.column_indices.size());
|
||||||
}
|
}
|
||||||
|
|
||||||
auto result = fesa::SparseMatrix::FromCoo(
|
auto result = fesa::SparseMatrix::FromCoo(rows, columns,
|
||||||
rows, columns, std::move(contributions), pattern);
|
std::move(contributions), pattern);
|
||||||
if (!result.HasValue()) {
|
if (!result.HasValue()) {
|
||||||
throw std::runtime_error{"Sparse fixture construction failed."};
|
throw std::runtime_error{"Sparse fixture construction failed."};
|
||||||
}
|
}
|
||||||
return std::move(result.Value());
|
return std::move(result.Value());
|
||||||
}
|
}
|
||||||
|
|
||||||
void expectFailureCode(
|
void ExpectFailureCode(const fesa::Result<fesa::Vector>& result,
|
||||||
const fesa::Result<fesa::Vector>& result,
|
|
||||||
const std::string& code) {
|
const std::string& code) {
|
||||||
ASSERT_FALSE(result.HasValue());
|
ASSERT_FALSE(result.HasValue());
|
||||||
EXPECT_EQ(result.GetStatus().Category(), fesa::FailureCategory::kModel);
|
EXPECT_EQ(result.GetStatus().Category(), fesa::FailureCategory::kModel);
|
||||||
@@ -185,9 +171,8 @@ void expectFailureCode(
|
|||||||
|
|
||||||
TEST(LoadAssembly, AssemblesNodeSetAndSixComponentLoads) {
|
TEST(LoadAssembly, AssemblesNodeSetAndSixComponentLoads) {
|
||||||
const std::filesystem::path source{"models/load-assembly.inp"};
|
const std::filesystem::path source{"models/load-assembly.inp"};
|
||||||
auto fixture = makeFixture(
|
auto fixture =
|
||||||
2U,
|
MakeFixture(2U, {{"Pair", std::nullopt, {0U, 1U}, {source, 10U}}},
|
||||||
{{"Pair", std::nullopt, {0U, 1U}, {source, 10U}}},
|
|
||||||
{{"10", 1, 1, 0.0, {source, 21U}}},
|
{{"10", 1, 1, 0.0, {source, 21U}}},
|
||||||
{{"pair", 1, 1.0, {source, 30U}},
|
{{"pair", 1, 1.0, {source, 30U}},
|
||||||
{"10", 2, 2.0, {source, 31U}},
|
{"10", 2, 2.0, {source, 31U}},
|
||||||
@@ -196,41 +181,33 @@ TEST(LoadAssembly, AssemblesNodeSetAndSixComponentLoads) {
|
|||||||
{"PAIR", 5, 5.0, {source, 34U}},
|
{"PAIR", 5, 5.0, {source, 34U}},
|
||||||
{"20", 6, 6.0, {source, 35U}}});
|
{"20", 6, 6.0, {source, 35U}}});
|
||||||
|
|
||||||
auto result = fesa::LoadAssembler::assembleFullNodalLoad(
|
auto result =
|
||||||
*fixture.model, *fixture.dofs);
|
fesa::LoadAssembler::AssembleFullNodalLoad(*fixture.model, *fixture.dofs);
|
||||||
ASSERT_TRUE(result.HasValue());
|
ASSERT_TRUE(result.HasValue());
|
||||||
ASSERT_EQ(result.Value().Size(), 12U);
|
ASSERT_EQ(result.Value().Size(), 12U);
|
||||||
EXPECT_EQ(
|
EXPECT_EQ(
|
||||||
std::vector<double>(result.Value().Data(), result.Value().Data() + 12U),
|
std::vector<double>(result.Value().Data(), result.Value().Data() + 12U),
|
||||||
(std::vector<double>{
|
(std::vector<double>{1.0, 2.0, 0.0, -4.0, 5.0, 0.0, 1.0, 0.0, 3.0, 0.0,
|
||||||
1.0, 2.0, 0.0, -4.0, 5.0, 0.0,
|
5.0, 6.0}));
|
||||||
1.0, 0.0, 3.0, 0.0, 5.0, 6.0}));
|
EXPECT_EQ(fixture.dofs->ConstrainedDofs(), (std::vector<std::size_t>{0U}));
|
||||||
EXPECT_EQ(fixture.dofs->constrainedDofs(),
|
|
||||||
(std::vector<std::size_t>{0U}));
|
|
||||||
EXPECT_DOUBLE_EQ(result.Value()[0U], 1.0);
|
EXPECT_DOUBLE_EQ(result.Value()[0U], 1.0);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(LoadAssembly, AccumulatesSignedLoadsInSourceOrder) {
|
TEST(LoadAssembly, AccumulatesSignedLoadsInSourceOrder) {
|
||||||
const std::filesystem::path source{"models/load-assembly.inp"};
|
const std::filesystem::path source{"models/load-assembly.inp"};
|
||||||
auto firstOrder = makeFixture(
|
auto first_order = MakeFixture(1U, {}, {},
|
||||||
1U,
|
|
||||||
{},
|
|
||||||
{},
|
|
||||||
{{"10", 1, 1.0e16, {source, 30U}},
|
{{"10", 1, 1.0e16, {source, 30U}},
|
||||||
{"10", 1, -1.0e16, {source, 31U}},
|
{"10", 1, -1.0e16, {source, 31U}},
|
||||||
{"10", 1, 1.0, {source, 32U}}});
|
{"10", 1, 1.0, {source, 32U}}});
|
||||||
auto secondOrder = makeFixture(
|
auto second_order = MakeFixture(1U, {}, {},
|
||||||
1U,
|
|
||||||
{},
|
|
||||||
{},
|
|
||||||
{{"10", 1, 1.0e16, {source, 30U}},
|
{{"10", 1, 1.0e16, {source, 30U}},
|
||||||
{"10", 1, 1.0, {source, 31U}},
|
{"10", 1, 1.0, {source, 31U}},
|
||||||
{"10", 1, -1.0e16, {source, 32U}}});
|
{"10", 1, -1.0e16, {source, 32U}}});
|
||||||
|
|
||||||
auto first = fesa::LoadAssembler::assembleFullNodalLoad(
|
auto first = fesa::LoadAssembler::AssembleFullNodalLoad(*first_order.model,
|
||||||
*firstOrder.model, *firstOrder.dofs);
|
*first_order.dofs);
|
||||||
auto second = fesa::LoadAssembler::assembleFullNodalLoad(
|
auto second = fesa::LoadAssembler::AssembleFullNodalLoad(*second_order.model,
|
||||||
*secondOrder.model, *secondOrder.dofs);
|
*second_order.dofs);
|
||||||
ASSERT_TRUE(first.HasValue());
|
ASSERT_TRUE(first.HasValue());
|
||||||
ASSERT_TRUE(second.HasValue());
|
ASSERT_TRUE(second.HasValue());
|
||||||
EXPECT_DOUBLE_EQ(first.Value()[0U], 1.0);
|
EXPECT_DOUBLE_EQ(first.Value()[0U], 1.0);
|
||||||
@@ -240,9 +217,7 @@ TEST(LoadAssembly, AccumulatesSignedLoadsInSourceOrder) {
|
|||||||
// MITC4-LOAD-001
|
// MITC4-LOAD-001
|
||||||
TEST(LoadAssembly, AggregatesAllSixGlobalShellLoadComponentsInSourceOrder) {
|
TEST(LoadAssembly, AggregatesAllSixGlobalShellLoadComponentsInSourceOrder) {
|
||||||
const std::filesystem::path source{"models/shell-load-assembly.inp"};
|
const std::filesystem::path source{"models/shell-load-assembly.inp"};
|
||||||
auto fixture = makeShellFixture(
|
auto fixture = MakeShellFixture({}, {{"10", 1, 1.0e16, {source, 30U}},
|
||||||
{},
|
|
||||||
{{"10", 1, 1.0e16, {source, 30U}},
|
|
||||||
{"10", 1, -1.0e16, {source, 31U}},
|
{"10", 1, -1.0e16, {source, 31U}},
|
||||||
{"10", 1, 1.0, {source, 32U}},
|
{"10", 1, 1.0, {source, 32U}},
|
||||||
{"10", 2, 2.0, {source, 33U}},
|
{"10", 2, 2.0, {source, 33U}},
|
||||||
@@ -254,8 +229,8 @@ TEST(LoadAssembly, AggregatesAllSixGlobalShellLoadComponentsInSourceOrder) {
|
|||||||
{"10", 6, 6.0, {source, 39U}},
|
{"10", 6, 6.0, {source, 39U}},
|
||||||
{"10", 6, -6.0, {source, 40U}}});
|
{"10", 6, -6.0, {source, 40U}}});
|
||||||
|
|
||||||
const auto result = fesa::LoadAssembler::assembleFullNodalLoad(
|
const auto result =
|
||||||
*fixture.model, *fixture.dofs);
|
fesa::LoadAssembler::AssembleFullNodalLoad(*fixture.model, *fixture.dofs);
|
||||||
|
|
||||||
ASSERT_TRUE(result.HasValue());
|
ASSERT_TRUE(result.HasValue());
|
||||||
ASSERT_EQ(result.Value().Size(), 24U);
|
ASSERT_EQ(result.Value().Size(), 24U);
|
||||||
@@ -267,17 +242,15 @@ TEST(LoadAssembly, AggregatesAllSixGlobalShellLoadComponentsInSourceOrder) {
|
|||||||
// MITC4-LOAD-002
|
// MITC4-LOAD-002
|
||||||
TEST(LoadAssembly, AcceptsExactlyZeroAggregateShellMoment) {
|
TEST(LoadAssembly, AcceptsExactlyZeroAggregateShellMoment) {
|
||||||
const std::filesystem::path source{"models/shell-load-assembly.inp"};
|
const std::filesystem::path source{"models/shell-load-assembly.inp"};
|
||||||
auto fixture = makeShellFixture(
|
auto fixture = MakeShellFixture({}, {{"10", 4, 3.0, {source, 30U}},
|
||||||
{},
|
|
||||||
{{"10", 4, 3.0, {source, 30U}},
|
|
||||||
{"10", 4, -3.0, {source, 31U}},
|
{"10", 4, -3.0, {source, 31U}},
|
||||||
{"10", 5, 4.0, {source, 32U}},
|
{"10", 5, 4.0, {source, 32U}},
|
||||||
{"10", 5, -4.0, {source, 33U}},
|
{"10", 5, -4.0, {source, 33U}},
|
||||||
{"10", 6, 5.0, {source, 34U}},
|
{"10", 6, 5.0, {source, 34U}},
|
||||||
{"10", 6, -5.0, {source, 35U}}});
|
{"10", 6, -5.0, {source, 35U}}});
|
||||||
|
|
||||||
const auto result = fesa::LoadAssembler::assembleFullNodalLoad(
|
const auto result =
|
||||||
*fixture.model, *fixture.dofs);
|
fesa::LoadAssembler::AssembleFullNodalLoad(*fixture.model, *fixture.dofs);
|
||||||
|
|
||||||
ASSERT_TRUE(result.HasValue());
|
ASSERT_TRUE(result.HasValue());
|
||||||
EXPECT_DOUBLE_EQ(result.Value()[3U], 0.0);
|
EXPECT_DOUBLE_EQ(result.Value()[3U], 0.0);
|
||||||
@@ -288,26 +261,21 @@ TEST(LoadAssembly, AcceptsExactlyZeroAggregateShellMoment) {
|
|||||||
// MITC4-LOAD-003
|
// MITC4-LOAD-003
|
||||||
TEST(LoadAssembly, EnforcesAggregateShellMomentDirectorProjectionThreshold) {
|
TEST(LoadAssembly, EnforcesAggregateShellMomentDirectorProjectionThreshold) {
|
||||||
const std::filesystem::path source{"models/shell-load-assembly.inp"};
|
const std::filesystem::path source{"models/shell-load-assembly.inp"};
|
||||||
auto acceptedFixture = makeShellFixture(
|
auto accepted_fixture = MakeShellFixture(
|
||||||
{},
|
{}, {{"10", 4, 1.0, {source, 30U}}, {"10", 6, 1.0e-12, {source, 31U}}});
|
||||||
{{"10", 4, 1.0, {source, 30U}},
|
auto rejected_fixture = MakeShellFixture(
|
||||||
{"10", 6, 1.0e-12, {source, 31U}}});
|
{}, {{"10", 4, 1.0, {source, 30U}}, {"10", 6, 2.0e-12, {source, 31U}}});
|
||||||
auto rejectedFixture = makeShellFixture(
|
|
||||||
{},
|
|
||||||
{{"10", 4, 1.0, {source, 30U}},
|
|
||||||
{"10", 6, 2.0e-12, {source, 31U}}});
|
|
||||||
|
|
||||||
const auto accepted = fesa::LoadAssembler::assembleFullNodalLoad(
|
const auto accepted = fesa::LoadAssembler::AssembleFullNodalLoad(
|
||||||
*acceptedFixture.model, *acceptedFixture.dofs);
|
*accepted_fixture.model, *accepted_fixture.dofs);
|
||||||
const auto rejected = fesa::LoadAssembler::assembleFullNodalLoad(
|
const auto rejected = fesa::LoadAssembler::AssembleFullNodalLoad(
|
||||||
*rejectedFixture.model, *rejectedFixture.dofs);
|
*rejected_fixture.model, *rejected_fixture.dofs);
|
||||||
|
|
||||||
ASSERT_TRUE(accepted.HasValue());
|
ASSERT_TRUE(accepted.HasValue());
|
||||||
ASSERT_FALSE(rejected.HasValue());
|
ASSERT_FALSE(rejected.HasValue());
|
||||||
EXPECT_EQ(rejected.GetStatus().Category(), fesa::FailureCategory::kModel);
|
EXPECT_EQ(rejected.GetStatus().Category(), fesa::FailureCategory::kModel);
|
||||||
ASSERT_EQ(rejected.GetStatus().Diagnostics().size(), 1U);
|
ASSERT_EQ(rejected.GetStatus().Diagnostics().size(), 1U);
|
||||||
EXPECT_EQ(
|
EXPECT_EQ(rejected.GetStatus().Diagnostics()[0U].code,
|
||||||
rejected.GetStatus().Diagnostics()[0U].code,
|
|
||||||
"unsupported-drilling-load");
|
"unsupported-drilling-load");
|
||||||
EXPECT_EQ(rejected.GetStatus().Diagnostics()[0U].keyword, "CLOAD");
|
EXPECT_EQ(rejected.GetStatus().Diagnostics()[0U].keyword, "CLOAD");
|
||||||
EXPECT_EQ(rejected.GetStatus().Diagnostics()[0U].entity_identity, "10");
|
EXPECT_EQ(rejected.GetStatus().Diagnostics()[0U].entity_identity, "10");
|
||||||
@@ -316,191 +284,140 @@ TEST(LoadAssembly, EnforcesAggregateShellMomentDirectorProjectionThreshold) {
|
|||||||
// MITC4-LOAD-004
|
// MITC4-LOAD-004
|
||||||
TEST(LoadAssembly, RejectsDrillingMomentBeforeEffectiveRhsCanBeFormed) {
|
TEST(LoadAssembly, RejectsDrillingMomentBeforeEffectiveRhsCanBeFormed) {
|
||||||
const std::filesystem::path source{"models/shell-load-assembly.inp"};
|
const std::filesystem::path source{"models/shell-load-assembly.inp"};
|
||||||
auto fixture = makeShellFixture(
|
auto fixture = MakeShellFixture({{"10", 1, 1, 2.0, {source, 21U}}},
|
||||||
{{"10", 1, 1, 2.0, {source, 21U}}},
|
|
||||||
{{"10", 6, 1.0, {source, 30U}}});
|
{{"10", 6, 1.0, {source, 30U}}});
|
||||||
|
|
||||||
const auto rejected = fesa::LoadAssembler::assembleFullNodalLoad(
|
const auto rejected =
|
||||||
*fixture.model, *fixture.dofs);
|
fesa::LoadAssembler::AssembleFullNodalLoad(*fixture.model, *fixture.dofs);
|
||||||
|
|
||||||
ASSERT_FALSE(rejected.HasValue());
|
ASSERT_FALSE(rejected.HasValue());
|
||||||
EXPECT_EQ(rejected.GetStatus().Category(), fesa::FailureCategory::kModel);
|
EXPECT_EQ(rejected.GetStatus().Category(), fesa::FailureCategory::kModel);
|
||||||
ASSERT_EQ(rejected.GetStatus().Diagnostics().size(), 1U);
|
ASSERT_EQ(rejected.GetStatus().Diagnostics().size(), 1U);
|
||||||
EXPECT_EQ(
|
EXPECT_EQ(rejected.GetStatus().Diagnostics()[0U].code,
|
||||||
rejected.GetStatus().Diagnostics()[0U].code,
|
|
||||||
"unsupported-drilling-load");
|
"unsupported-drilling-load");
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(LoadAssembly, FormsNonzeroPrescribedEffectiveRhs) {
|
TEST(LoadAssembly, FormsNonzeroPrescribedEffectiveRhs) {
|
||||||
const std::filesystem::path source{"models/load-assembly.inp"};
|
const std::filesystem::path source{"models/load-assembly.inp"};
|
||||||
auto fixture = makeFixture(
|
auto fixture = MakeFixture(
|
||||||
1U,
|
1U, {},
|
||||||
{},
|
{{"10", 2, 2, 2.0, {source, 21U}}, {"10", 5, 5, -1.0, {source, 22U}}},
|
||||||
{{"10", 2, 2, 2.0, {source, 21U}},
|
|
||||||
{"10", 5, 5, -1.0, {source, 22U}}},
|
|
||||||
{{"10", 1, 10.0, {source, 30U}},
|
{{"10", 1, 10.0, {source, 30U}},
|
||||||
{"10", 2, 900.0, {source, 31U}},
|
{"10", 2, 900.0, {source, 31U}},
|
||||||
{"10", 3, 20.0, {source, 32U}},
|
{"10", 3, 20.0, {source, 32U}},
|
||||||
{"10", 4, 30.0, {source, 33U}},
|
{"10", 4, 30.0, {source, 33U}},
|
||||||
{"10", 5, 800.0, {source, 34U}},
|
{"10", 5, 800.0, {source, 34U}},
|
||||||
{"10", 6, 40.0, {source, 35U}}});
|
{"10", 6, 40.0, {source, 35U}}});
|
||||||
auto full = fesa::LoadAssembler::assembleFullNodalLoad(
|
auto full =
|
||||||
*fixture.model, *fixture.dofs);
|
fesa::LoadAssembler::AssembleFullNodalLoad(*fixture.model, *fixture.dofs);
|
||||||
ASSERT_TRUE(full.HasValue());
|
ASSERT_TRUE(full.HasValue());
|
||||||
const auto kfc = makeDenseSparse(
|
const auto kfc =
|
||||||
4U,
|
MakeDenseSparse(4U, 2U, {1.0, 2.0, 3.0, 4.0, -2.0, 5.0, 0.5, -1.0});
|
||||||
2U,
|
|
||||||
{1.0, 2.0,
|
|
||||||
3.0, 4.0,
|
|
||||||
-2.0, 5.0,
|
|
||||||
0.5, -1.0});
|
|
||||||
|
|
||||||
auto rhs = fesa::LoadAssembler::effectiveFreeRhs(
|
auto rhs = fesa::LoadAssembler::EffectiveFreeRhs(
|
||||||
full.Value(), kfc, fixture.dofs->prescribedValues(), *fixture.dofs);
|
full.Value(), kfc, fixture.dofs->PrescribedValues(), *fixture.dofs);
|
||||||
ASSERT_TRUE(rhs.HasValue());
|
ASSERT_TRUE(rhs.HasValue());
|
||||||
ASSERT_EQ(rhs.Value().Size(), 4U);
|
ASSERT_EQ(rhs.Value().Size(), 4U);
|
||||||
EXPECT_EQ(
|
EXPECT_EQ(std::vector<double>(rhs.Value().Data(), rhs.Value().Data() + 4U),
|
||||||
std::vector<double>(rhs.Value().Data(), rhs.Value().Data() + 4U),
|
|
||||||
(std::vector<double>{10.0, 18.0, 39.0, 38.0}));
|
(std::vector<double>{10.0, 18.0, 39.0, 38.0}));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(LoadAssembly, RejectsNonfiniteOrDimensionMismatch) {
|
TEST(LoadAssembly, RejectsNonfiniteOrDimensionMismatch) {
|
||||||
const std::filesystem::path source{"models/load-assembly.inp"};
|
const std::filesystem::path source{"models/load-assembly.inp"};
|
||||||
const double maximum = (std::numeric_limits<double>::max)();
|
const double maximum = (std::numeric_limits<double>::max)();
|
||||||
auto nonfinite = makeFixture(
|
auto nonfinite = MakeFixture(
|
||||||
1U,
|
1U, {}, {},
|
||||||
{},
|
|
||||||
{},
|
|
||||||
{{"10", 1, std::numeric_limits<double>::quiet_NaN(), {source, 30U}}});
|
{{"10", 1, std::numeric_limits<double>::quiet_NaN(), {source, 30U}}});
|
||||||
expectFailureCode(
|
ExpectFailureCode(fesa::LoadAssembler::AssembleFullNodalLoad(*nonfinite.model,
|
||||||
fesa::LoadAssembler::assembleFullNodalLoad(
|
*nonfinite.dofs),
|
||||||
*nonfinite.model, *nonfinite.dofs),
|
|
||||||
"nonfinite-load-value");
|
"nonfinite-load-value");
|
||||||
|
|
||||||
auto overflow = makeFixture(
|
auto overflow = MakeFixture(
|
||||||
1U,
|
1U, {}, {},
|
||||||
{},
|
{{"10", 1, maximum, {source, 30U}}, {"10", 1, maximum, {source, 31U}}});
|
||||||
{},
|
ExpectFailureCode(fesa::LoadAssembler::AssembleFullNodalLoad(*overflow.model,
|
||||||
{{"10", 1, maximum, {source, 30U}},
|
*overflow.dofs),
|
||||||
{"10", 1, maximum, {source, 31U}}});
|
|
||||||
expectFailureCode(
|
|
||||||
fesa::LoadAssembler::assembleFullNodalLoad(
|
|
||||||
*overflow.model, *overflow.dofs),
|
|
||||||
"nonfinite-load-accumulation");
|
"nonfinite-load-accumulation");
|
||||||
|
|
||||||
auto oneNode = makeFixture(
|
auto one_node = MakeFixture(
|
||||||
1U,
|
1U, {},
|
||||||
{},
|
{{"10", 2, 2, 2.0, {source, 21U}}, {"10", 5, 5, -1.0, {source, 22U}}},
|
||||||
{{"10", 2, 2, 2.0, {source, 21U}},
|
|
||||||
{"10", 5, 5, -1.0, {source, 22U}}},
|
|
||||||
{});
|
{});
|
||||||
auto twoNodes = makeFixture(2U, {}, {}, {});
|
auto two_nodes = MakeFixture(2U, {}, {}, {});
|
||||||
expectFailureCode(
|
ExpectFailureCode(fesa::LoadAssembler::AssembleFullNodalLoad(*two_nodes.model,
|
||||||
fesa::LoadAssembler::assembleFullNodalLoad(
|
*one_node.dofs),
|
||||||
*twoNodes.model, *oneNode.dofs),
|
|
||||||
"invalid-load-dimensions");
|
"invalid-load-dimensions");
|
||||||
|
|
||||||
const auto validKfc = makeDenseSparse(4U, 2U, std::vector<double>(8U, 0.0));
|
const auto valid_kfc = MakeDenseSparse(4U, 2U, std::vector<double>(8U, 0.0));
|
||||||
expectFailureCode(
|
ExpectFailureCode(fesa::LoadAssembler::EffectiveFreeRhs(
|
||||||
fesa::LoadAssembler::effectiveFreeRhs(
|
fesa::Vector{5U}, valid_kfc,
|
||||||
fesa::Vector{5U},
|
one_node.dofs->PrescribedValues(), *one_node.dofs),
|
||||||
validKfc,
|
|
||||||
oneNode.dofs->prescribedValues(),
|
|
||||||
*oneNode.dofs),
|
|
||||||
"invalid-load-dimensions");
|
"invalid-load-dimensions");
|
||||||
expectFailureCode(
|
ExpectFailureCode(fesa::LoadAssembler::EffectiveFreeRhs(
|
||||||
fesa::LoadAssembler::effectiveFreeRhs(
|
|
||||||
fesa::Vector{6U},
|
fesa::Vector{6U},
|
||||||
makeDenseSparse(3U, 2U, std::vector<double>(6U, 0.0)),
|
MakeDenseSparse(3U, 2U, std::vector<double>(6U, 0.0)),
|
||||||
oneNode.dofs->prescribedValues(),
|
one_node.dofs->PrescribedValues(), *one_node.dofs),
|
||||||
*oneNode.dofs),
|
|
||||||
"invalid-load-dimensions");
|
"invalid-load-dimensions");
|
||||||
expectFailureCode(
|
ExpectFailureCode(fesa::LoadAssembler::EffectiveFreeRhs(
|
||||||
fesa::LoadAssembler::effectiveFreeRhs(
|
|
||||||
fesa::Vector{6U},
|
fesa::Vector{6U},
|
||||||
makeDenseSparse(4U, 1U, std::vector<double>(4U, 0.0)),
|
MakeDenseSparse(4U, 1U, std::vector<double>(4U, 0.0)),
|
||||||
oneNode.dofs->prescribedValues(),
|
one_node.dofs->PrescribedValues(), *one_node.dofs),
|
||||||
*oneNode.dofs),
|
|
||||||
"invalid-load-dimensions");
|
"invalid-load-dimensions");
|
||||||
expectFailureCode(
|
ExpectFailureCode(
|
||||||
fesa::LoadAssembler::effectiveFreeRhs(
|
fesa::LoadAssembler::EffectiveFreeRhs(fesa::Vector{6U}, valid_kfc,
|
||||||
fesa::Vector{6U}, validKfc, fesa::Vector{1U}, *oneNode.dofs),
|
fesa::Vector{1U}, *one_node.dofs),
|
||||||
"invalid-load-dimensions");
|
"invalid-load-dimensions");
|
||||||
|
|
||||||
fesa::Vector nonfiniteFull{6U};
|
fesa::Vector nonfinite_full{6U};
|
||||||
nonfiniteFull[0U] = std::numeric_limits<double>::infinity();
|
nonfinite_full[0U] = std::numeric_limits<double>::infinity();
|
||||||
expectFailureCode(
|
ExpectFailureCode(fesa::LoadAssembler::EffectiveFreeRhs(
|
||||||
fesa::LoadAssembler::effectiveFreeRhs(
|
nonfinite_full, valid_kfc,
|
||||||
nonfiniteFull,
|
one_node.dofs->PrescribedValues(), *one_node.dofs),
|
||||||
validKfc,
|
|
||||||
oneNode.dofs->prescribedValues(),
|
|
||||||
*oneNode.dofs),
|
|
||||||
"nonfinite-load-value");
|
"nonfinite-load-value");
|
||||||
|
|
||||||
fesa::Vector nonfinitePrescribed{2U};
|
fesa::Vector nonfinite_prescribed{2U};
|
||||||
nonfinitePrescribed[0U] = std::numeric_limits<double>::quiet_NaN();
|
nonfinite_prescribed[0U] = std::numeric_limits<double>::quiet_NaN();
|
||||||
expectFailureCode(
|
ExpectFailureCode(
|
||||||
fesa::LoadAssembler::effectiveFreeRhs(
|
fesa::LoadAssembler::EffectiveFreeRhs(
|
||||||
fesa::Vector{6U}, validKfc, nonfinitePrescribed, *oneNode.dofs),
|
fesa::Vector{6U}, valid_kfc, nonfinite_prescribed, *one_node.dofs),
|
||||||
"nonfinite-load-value");
|
"nonfinite-load-value");
|
||||||
|
|
||||||
const auto overflowingKfc = makeDenseSparse(
|
const auto overflowing_kfc =
|
||||||
4U,
|
MakeDenseSparse(4U, 2U, {maximum, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0});
|
||||||
2U,
|
ExpectFailureCode(fesa::LoadAssembler::EffectiveFreeRhs(
|
||||||
{maximum, 0.0,
|
fesa::Vector{6U}, overflowing_kfc,
|
||||||
0.0, 0.0,
|
one_node.dofs->PrescribedValues(), *one_node.dofs),
|
||||||
0.0, 0.0,
|
|
||||||
0.0, 0.0});
|
|
||||||
expectFailureCode(
|
|
||||||
fesa::LoadAssembler::effectiveFreeRhs(
|
|
||||||
fesa::Vector{6U},
|
|
||||||
overflowingKfc,
|
|
||||||
oneNode.dofs->prescribedValues(),
|
|
||||||
*oneNode.dofs),
|
|
||||||
"nonfinite-load-accumulation");
|
"nonfinite-load-accumulation");
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(LoadAssembly, ZeroLoadsRemainZero) {
|
TEST(LoadAssembly, ZeroLoadsRemainZero) {
|
||||||
const std::filesystem::path source{"models/load-assembly.inp"};
|
const std::filesystem::path source{"models/load-assembly.inp"};
|
||||||
auto freeFixture = makeFixture(
|
auto free_fixture = MakeFixture(1U, {}, {}, {{"10", 3, 0.0, {source, 30U}}});
|
||||||
1U,
|
auto full = fesa::LoadAssembler::AssembleFullNodalLoad(*free_fixture.model,
|
||||||
{},
|
*free_fixture.dofs);
|
||||||
{},
|
|
||||||
{{"10", 3, 0.0, {source, 30U}}});
|
|
||||||
auto full = fesa::LoadAssembler::assembleFullNodalLoad(
|
|
||||||
*freeFixture.model, *freeFixture.dofs);
|
|
||||||
ASSERT_TRUE(full.HasValue());
|
ASSERT_TRUE(full.HasValue());
|
||||||
EXPECT_TRUE(std::all_of(
|
EXPECT_TRUE(std::all_of(full.Value().Data(),
|
||||||
full.Value().Data(),
|
|
||||||
full.Value().Data() + full.Value().Size(),
|
full.Value().Data() + full.Value().Size(),
|
||||||
[](const double value) { return value == 0.0; }));
|
[](const double value) { return value == 0.0; }));
|
||||||
const auto noConstrainedColumns = makeDenseSparse(6U, 0U, {});
|
const auto no_constrained_columns = MakeDenseSparse(6U, 0U, {});
|
||||||
auto freeRhs = fesa::LoadAssembler::effectiveFreeRhs(
|
auto free_rhs = fesa::LoadAssembler::EffectiveFreeRhs(
|
||||||
full.Value(),
|
full.Value(), no_constrained_columns,
|
||||||
noConstrainedColumns,
|
free_fixture.dofs->PrescribedValues(), *free_fixture.dofs);
|
||||||
freeFixture.dofs->prescribedValues(),
|
ASSERT_TRUE(free_rhs.HasValue());
|
||||||
*freeFixture.dofs);
|
EXPECT_EQ(free_rhs.Value().Size(), 6U);
|
||||||
ASSERT_TRUE(freeRhs.HasValue());
|
EXPECT_TRUE(std::all_of(free_rhs.Value().Data(),
|
||||||
EXPECT_EQ(freeRhs.Value().Size(), 6U);
|
free_rhs.Value().Data() + free_rhs.Value().Size(),
|
||||||
EXPECT_TRUE(std::all_of(
|
|
||||||
freeRhs.Value().Data(),
|
|
||||||
freeRhs.Value().Data() + freeRhs.Value().Size(),
|
|
||||||
[](const double value) { return value == 0.0; }));
|
[](const double value) { return value == 0.0; }));
|
||||||
|
|
||||||
auto constrainedFixture = makeFixture(
|
auto constrained_fixture =
|
||||||
1U,
|
MakeFixture(1U, {}, {{"10", 1, 6, 0.0, {source, 21U}}}, {});
|
||||||
{},
|
auto constrained_full = fesa::LoadAssembler::AssembleFullNodalLoad(
|
||||||
{{"10", 1, 6, 0.0, {source, 21U}}},
|
*constrained_fixture.model, *constrained_fixture.dofs);
|
||||||
{});
|
ASSERT_TRUE(constrained_full.HasValue());
|
||||||
auto constrainedFull = fesa::LoadAssembler::assembleFullNodalLoad(
|
const auto no_free_rows = MakeDenseSparse(0U, 6U, {});
|
||||||
*constrainedFixture.model, *constrainedFixture.dofs);
|
auto constrained_rhs = fesa::LoadAssembler::EffectiveFreeRhs(
|
||||||
ASSERT_TRUE(constrainedFull.HasValue());
|
constrained_full.Value(), no_free_rows,
|
||||||
const auto noFreeRows = makeDenseSparse(0U, 6U, {});
|
constrained_fixture.dofs->PrescribedValues(), *constrained_fixture.dofs);
|
||||||
auto constrainedRhs = fesa::LoadAssembler::effectiveFreeRhs(
|
ASSERT_TRUE(constrained_rhs.HasValue());
|
||||||
constrainedFull.Value(),
|
EXPECT_EQ(constrained_rhs.Value().Size(), 0U);
|
||||||
noFreeRows,
|
|
||||||
constrainedFixture.dofs->prescribedValues(),
|
|
||||||
*constrainedFixture.dofs);
|
|
||||||
ASSERT_TRUE(constrainedRhs.HasValue());
|
|
||||||
EXPECT_EQ(constrainedRhs.Value().Size(), 0U);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
#include "fesa/assembly/parallel_for.hpp"
|
#include "fesa/assembly/parallel_for.h"
|
||||||
|
|
||||||
#include <gtest/gtest.h>
|
#include <gtest/gtest.h>
|
||||||
|
|
||||||
@@ -14,13 +14,12 @@ namespace fesa {
|
|||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
class ParallelForBodyError final : public std::runtime_error {
|
class ParallelForBodyError final : public std::runtime_error {
|
||||||
public:
|
public:
|
||||||
using std::runtime_error::runtime_error;
|
using std::runtime_error::runtime_error;
|
||||||
};
|
};
|
||||||
|
|
||||||
std::array<std::reference_wrapper<const ParallelFor>, 2> parallelForBackends(
|
std::array<std::reference_wrapper<const ParallelFor>, 2> ParallelForBackends(
|
||||||
const SerialParallelFor& serial,
|
const SerialParallelFor& serial, const TbbParallelFor& tbb) {
|
||||||
const TbbParallelFor& tbb) {
|
|
||||||
return {std::cref(serial), std::cref(tbb)};
|
return {std::cref(serial), std::cref(tbb)};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -28,19 +27,19 @@ TEST(ParallelFor, ZeroOneManyExecuteExactlyOnce) {
|
|||||||
const SerialParallelFor serial;
|
const SerialParallelFor serial;
|
||||||
const TbbParallelFor tbb;
|
const TbbParallelFor tbb;
|
||||||
|
|
||||||
for (const ParallelFor& parallelFor : parallelForBackends(serial, tbb)) {
|
for (const ParallelFor& parallel_for : ParallelForBackends(serial, tbb)) {
|
||||||
std::atomic<std::size_t> zeroVisits{0U};
|
std::atomic<std::size_t> zero_visits{0U};
|
||||||
parallelFor.execute(0U, [&zeroVisits](std::size_t) {
|
parallel_for.Execute(0U, [&zero_visits](std::size_t) {
|
||||||
zeroVisits.fetch_add(1U, std::memory_order_relaxed);
|
zero_visits.fetch_add(1U, std::memory_order_relaxed);
|
||||||
});
|
});
|
||||||
EXPECT_EQ(zeroVisits.load(std::memory_order_relaxed), 0U);
|
EXPECT_EQ(zero_visits.load(std::memory_order_relaxed), 0U);
|
||||||
|
|
||||||
for (const std::size_t count : {1U, 257U}) {
|
for (const std::size_t count : {1U, 257U}) {
|
||||||
std::vector<std::atomic<std::size_t>> visits(count);
|
std::vector<std::atomic<std::size_t>> visits(count);
|
||||||
for (auto& visit : visits) {
|
for (auto& visit : visits) {
|
||||||
visit.store(0U, std::memory_order_relaxed);
|
visit.store(0U, std::memory_order_relaxed);
|
||||||
}
|
}
|
||||||
parallelFor.execute(count, [&visits](std::size_t index) {
|
parallel_for.Execute(count, [&visits](std::size_t index) {
|
||||||
visits[index].fetch_add(1U, std::memory_order_relaxed);
|
visits[index].fetch_add(1U, std::memory_order_relaxed);
|
||||||
});
|
});
|
||||||
for (std::size_t index = 0; index < count; ++index) {
|
for (std::size_t index = 0; index < count; ++index) {
|
||||||
@@ -52,41 +51,42 @@ TEST(ParallelFor, ZeroOneManyExecuteExactlyOnce) {
|
|||||||
|
|
||||||
TEST(ParallelFor, SerialAndTbbProduceStableIndexedOutput) {
|
TEST(ParallelFor, SerialAndTbbProduceStableIndexedOutput) {
|
||||||
constexpr std::size_t count = 1024U;
|
constexpr std::size_t count = 1024U;
|
||||||
std::vector<std::atomic<std::size_t>> serialOutput(count);
|
std::vector<std::atomic<std::size_t>> serial_output(count);
|
||||||
std::vector<std::atomic<std::size_t>> tbbOutput(count);
|
std::vector<std::atomic<std::size_t>> tbb_output(count);
|
||||||
std::vector<std::atomic<std::size_t>> serialVisits(count);
|
std::vector<std::atomic<std::size_t>> serial_visits(count);
|
||||||
std::vector<std::atomic<std::size_t>> tbbVisits(count);
|
std::vector<std::atomic<std::size_t>> tbb_visits(count);
|
||||||
for (std::size_t index = 0; index < count; ++index) {
|
for (std::size_t index = 0; index < count; ++index) {
|
||||||
serialOutput[index].store(0U, std::memory_order_relaxed);
|
serial_output[index].store(0U, std::memory_order_relaxed);
|
||||||
tbbOutput[index].store(0U, std::memory_order_relaxed);
|
tbb_output[index].store(0U, std::memory_order_relaxed);
|
||||||
serialVisits[index].store(0U, std::memory_order_relaxed);
|
serial_visits[index].store(0U, std::memory_order_relaxed);
|
||||||
tbbVisits[index].store(0U, std::memory_order_relaxed);
|
tbb_visits[index].store(0U, std::memory_order_relaxed);
|
||||||
}
|
}
|
||||||
const auto valueForIndex = [](std::size_t index) {
|
const auto value_for_index = [](std::size_t index) {
|
||||||
return (index + 17U) * (index + 3U);
|
return (index + 17U) * (index + 3U);
|
||||||
};
|
};
|
||||||
|
|
||||||
const SerialParallelFor serial;
|
const SerialParallelFor serial;
|
||||||
serial.execute(count, [&serialOutput, &serialVisits, &valueForIndex](std::size_t index) {
|
serial.Execute(count, [&serial_output, &serial_visits,
|
||||||
serialOutput[index].store(valueForIndex(index), std::memory_order_relaxed);
|
&value_for_index](std::size_t index) {
|
||||||
serialVisits[index].fetch_add(1U, std::memory_order_relaxed);
|
serial_output[index].store(value_for_index(index),
|
||||||
|
std::memory_order_relaxed);
|
||||||
|
serial_visits[index].fetch_add(1U, std::memory_order_relaxed);
|
||||||
});
|
});
|
||||||
|
|
||||||
const TbbParallelFor tbb;
|
const TbbParallelFor tbb;
|
||||||
tbb.execute(count, [&tbbOutput, &tbbVisits, &valueForIndex](std::size_t index) {
|
tbb.Execute(count, [&tbb_output, &tbb_visits,
|
||||||
tbbOutput[index].store(valueForIndex(index), std::memory_order_relaxed);
|
&value_for_index](std::size_t index) {
|
||||||
tbbVisits[index].fetch_add(1U, std::memory_order_relaxed);
|
tbb_output[index].store(value_for_index(index), std::memory_order_relaxed);
|
||||||
|
tbb_visits[index].fetch_add(1U, std::memory_order_relaxed);
|
||||||
});
|
});
|
||||||
|
|
||||||
for (std::size_t index = 0; index < count; ++index) {
|
for (std::size_t index = 0; index < count; ++index) {
|
||||||
EXPECT_EQ(serialVisits[index].load(std::memory_order_relaxed), 1U);
|
EXPECT_EQ(serial_visits[index].load(std::memory_order_relaxed), 1U);
|
||||||
EXPECT_EQ(tbbVisits[index].load(std::memory_order_relaxed), 1U);
|
EXPECT_EQ(tbb_visits[index].load(std::memory_order_relaxed), 1U);
|
||||||
EXPECT_EQ(
|
EXPECT_EQ(tbb_output[index].load(std::memory_order_relaxed),
|
||||||
tbbOutput[index].load(std::memory_order_relaxed),
|
serial_output[index].load(std::memory_order_relaxed));
|
||||||
serialOutput[index].load(std::memory_order_relaxed));
|
EXPECT_EQ(tbb_output[index].load(std::memory_order_relaxed),
|
||||||
EXPECT_EQ(
|
value_for_index(index));
|
||||||
tbbOutput[index].load(std::memory_order_relaxed),
|
|
||||||
valueForIndex(index));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -94,11 +94,11 @@ TEST(ParallelFor, PropagatesBodyExceptionByContract) {
|
|||||||
const SerialParallelFor serial;
|
const SerialParallelFor serial;
|
||||||
const TbbParallelFor tbb;
|
const TbbParallelFor tbb;
|
||||||
|
|
||||||
for (const ParallelFor& parallelFor : parallelForBackends(serial, tbb)) {
|
for (const ParallelFor& parallel_for : ParallelForBackends(serial, tbb)) {
|
||||||
try {
|
try {
|
||||||
// Every iteration throws the same value so the assertion is independent
|
// Every iteration throws the same value so the assertion is independent
|
||||||
// of which oneTBB task reports the cancellation-triggering exception.
|
// of which oneTBB task reports the cancellation-triggering exception.
|
||||||
parallelFor.execute(64U, [](std::size_t) {
|
parallel_for.Execute(64U, [](std::size_t) {
|
||||||
throw ParallelForBodyError{"parallel-for-body-failure"};
|
throw ParallelForBodyError{"parallel-for-body-failure"};
|
||||||
});
|
});
|
||||||
ADD_FAILURE() << "ParallelFor swallowed the body exception.";
|
ADD_FAILURE() << "ParallelFor swallowed the body exception.";
|
||||||
|
|||||||
@@ -1,9 +1,4 @@
|
|||||||
#include "fesa/analysis/analysis_model.hpp"
|
#include "fesa/assembly/sparse_assembler.h"
|
||||||
#include "fesa/assembly/parallel_for.hpp"
|
|
||||||
#include "fesa/assembly/sparse_assembler.hpp"
|
|
||||||
#include "fesa/elements/mitc4_shell.h"
|
|
||||||
#include "fesa/fem/dof_manager.hpp"
|
|
||||||
#include "fesa/model/domain.h"
|
|
||||||
|
|
||||||
#include <gtest/gtest.h>
|
#include <gtest/gtest.h>
|
||||||
|
|
||||||
@@ -14,21 +9,24 @@
|
|||||||
#include <utility>
|
#include <utility>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
|
#include "fesa/analysis/analysis_model.h"
|
||||||
|
#include "fesa/assembly/parallel_for.h"
|
||||||
|
#include "fesa/elements/mitc4_shell.h"
|
||||||
|
#include "fesa/fem/dof_manager.h"
|
||||||
|
#include "fesa/model/domain.h"
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
fesa::ModelDefinition makeDefinition() {
|
fesa::ModelDefinition MakeDefinition() {
|
||||||
const std::filesystem::path source{"models/sparse-assembly.inp"};
|
const std::filesystem::path source{"models/sparse-assembly.inp"};
|
||||||
fesa::ModelDefinition definition{};
|
fesa::ModelDefinition definition{};
|
||||||
definition.source_path = source;
|
definition.source_path = source;
|
||||||
definition.source_content_identity = "fnv1a64:0123456789abcdef";
|
definition.source_content_identity = "fnv1a64:0123456789abcdef";
|
||||||
definition.nodes = {
|
definition.nodes = {{{"Beam-1", 1, "1"}, {0.0, 0.0, 0.0}, {source, 10U}},
|
||||||
{{"Beam-1", 1, "1"}, {0.0, 0.0, 0.0}, {source, 10U}},
|
|
||||||
{{"Beam-1", 2, "2"}, {2.0, 0.0, 0.0}, {source, 11U}},
|
{{"Beam-1", 2, "2"}, {2.0, 0.0, 0.0}, {source, 11U}},
|
||||||
{{"Beam-1", 3, "3"}, {5.0, 0.0, 0.0}, {source, 12U}}};
|
{{"Beam-1", 3, "3"}, {5.0, 0.0, 0.0}, {source, 12U}}};
|
||||||
definition.materials = {
|
definition.materials = {{"Material", 120.0, 0.25, {source, 20U}}};
|
||||||
{"Material", 120.0, 0.25, {source, 20U}}};
|
definition.sections = {{"Section",
|
||||||
definition.sections = {{
|
|
||||||
"Section",
|
|
||||||
2.0,
|
2.0,
|
||||||
1.5,
|
1.5,
|
||||||
0.0,
|
0.0,
|
||||||
@@ -40,80 +38,81 @@ fesa::ModelDefinition makeDefinition() {
|
|||||||
definition.elements = {
|
definition.elements = {
|
||||||
{{"Beam-1", 10, "10"}, {0U, 1U}, 0U, 0U, {source, 40U}},
|
{{"Beam-1", 10, "10"}, {0U, 1U}, 0U, 0U, {source, 40U}},
|
||||||
{{"Beam-1", 20, "20"}, {1U, 2U}, 0U, 0U, {source, 41U}}};
|
{{"Beam-1", 20, "20"}, {1U, 2U}, 0U, 0U, {source, 41U}}};
|
||||||
definition.steps = {{
|
definition.steps = {{"Step-1", {}, {}, 0.1, 1.0, 0.01, 1.0, {source, 50U}}};
|
||||||
"Step-1", {}, {}, 0.1, 1.0, 0.01, 1.0, {source, 50U}}};
|
|
||||||
return definition;
|
return definition;
|
||||||
}
|
}
|
||||||
|
|
||||||
fesa::ModelDefinition makeShellDefinition(
|
fesa::ModelDefinition MakeShellDefinition(
|
||||||
const fesa::ShellSourceElementType sourceType,
|
const fesa::ShellSourceElementType source_type,
|
||||||
const bool twoElements = false) {
|
const bool two_elements = false) {
|
||||||
const std::filesystem::path source{"models/shell-sparse-assembly.inp"};
|
const std::filesystem::path source{"models/shell-sparse-assembly.inp"};
|
||||||
fesa::ModelDefinition definition{};
|
fesa::ModelDefinition definition{};
|
||||||
definition.source_path = source;
|
definition.source_path = source;
|
||||||
definition.source_content_identity = "fnv1a64:fedcba9876543210";
|
definition.source_content_identity = "fnv1a64:fedcba9876543210";
|
||||||
if (twoElements) {
|
if (two_elements) {
|
||||||
definition.nodes = {
|
definition.nodes = {{{"Shell-1", 1, "1"}, {0.0, 0.0, 0.0}, {source, 10U}},
|
||||||
{{"Shell-1", 1, "1"}, {0.0, 0.0, 0.0}, {source, 10U}},
|
|
||||||
{{"Shell-1", 2, "2"}, {1.0, 0.0, 0.0}, {source, 11U}},
|
{{"Shell-1", 2, "2"}, {1.0, 0.0, 0.0}, {source, 11U}},
|
||||||
{{"Shell-1", 3, "3"}, {2.0, 0.0, 0.0}, {source, 12U}},
|
{{"Shell-1", 3, "3"}, {2.0, 0.0, 0.0}, {source, 12U}},
|
||||||
{{"Shell-1", 4, "4"}, {0.0, 1.0, 0.0}, {source, 13U}},
|
{{"Shell-1", 4, "4"}, {0.0, 1.0, 0.0}, {source, 13U}},
|
||||||
{{"Shell-1", 5, "5"}, {1.0, 1.0, 0.0}, {source, 14U}},
|
{{"Shell-1", 5, "5"}, {1.0, 1.0, 0.0}, {source, 14U}},
|
||||||
{{"Shell-1", 6, "6"}, {2.0, 1.0, 0.0}, {source, 15U}}};
|
{{"Shell-1", 6, "6"}, {2.0, 1.0, 0.0}, {source, 15U}}};
|
||||||
for (std::size_t node = 0U; node < definition.nodes.size(); ++node) {
|
for (std::size_t node = 0U; node < definition.nodes.size(); ++node) {
|
||||||
definition.shell_node_initial_frames.push_back({
|
definition.shell_node_initial_frames.push_back(
|
||||||
static_cast<fesa::EntityIndex>(node),
|
{static_cast<fesa::EntityIndex>(node),
|
||||||
{0.0, 0.0, 1.0},
|
{0.0, 0.0, 1.0},
|
||||||
{1.0, 0.0, 0.0},
|
{1.0, 0.0, 0.0},
|
||||||
{0.0, 1.0, 0.0}});
|
{0.0, 1.0, 0.0}});
|
||||||
}
|
}
|
||||||
definition.shell_elements = {
|
definition.shell_elements = {{{"Shell-1", 10, "10"},
|
||||||
{{"Shell-1", 10, "10"}, sourceType, {0U, 1U, 4U, 3U},
|
source_type,
|
||||||
0U, 0U, {source, 40U}},
|
{0U, 1U, 4U, 3U},
|
||||||
{{"Shell-1", 20, "20"}, sourceType, {1U, 2U, 5U, 4U},
|
0U,
|
||||||
0U, 0U, {source, 41U}}};
|
0U,
|
||||||
|
{source, 40U}},
|
||||||
|
{{"Shell-1", 20, "20"},
|
||||||
|
source_type,
|
||||||
|
{1U, 2U, 5U, 4U},
|
||||||
|
0U,
|
||||||
|
0U,
|
||||||
|
{source, 41U}}};
|
||||||
} else {
|
} else {
|
||||||
// A YZ-plane fixture catches any accidental global-Z director assumption.
|
// A YZ-plane fixture catches any accidental global-Z director assumption.
|
||||||
definition.nodes = {
|
definition.nodes = {{{"Shell-1", 1, "1"}, {0.0, 0.0, 0.0}, {source, 10U}},
|
||||||
{{"Shell-1", 1, "1"}, {0.0, 0.0, 0.0}, {source, 10U}},
|
|
||||||
{{"Shell-1", 2, "2"}, {0.0, 1.0, 0.0}, {source, 11U}},
|
{{"Shell-1", 2, "2"}, {0.0, 1.0, 0.0}, {source, 11U}},
|
||||||
{{"Shell-1", 3, "3"}, {0.0, 1.0, 1.0}, {source, 12U}},
|
{{"Shell-1", 3, "3"}, {0.0, 1.0, 1.0}, {source, 12U}},
|
||||||
{{"Shell-1", 4, "4"}, {0.0, 0.0, 1.0}, {source, 13U}}};
|
{{"Shell-1", 4, "4"}, {0.0, 0.0, 1.0}, {source, 13U}}};
|
||||||
for (std::size_t node = 0U; node < definition.nodes.size(); ++node) {
|
for (std::size_t node = 0U; node < definition.nodes.size(); ++node) {
|
||||||
definition.shell_node_initial_frames.push_back({
|
definition.shell_node_initial_frames.push_back(
|
||||||
static_cast<fesa::EntityIndex>(node),
|
{static_cast<fesa::EntityIndex>(node),
|
||||||
{1.0, 0.0, 0.0},
|
{1.0, 0.0, 0.0},
|
||||||
{0.0, 1.0, 0.0},
|
{0.0, 1.0, 0.0},
|
||||||
{0.0, 0.0, 1.0}});
|
{0.0, 0.0, 1.0}});
|
||||||
}
|
}
|
||||||
definition.shell_elements = {{
|
definition.shell_elements = {{{"Shell-1", 10, "10"},
|
||||||
{"Shell-1", 10, "10"}, sourceType, {0U, 1U, 2U, 3U},
|
source_type,
|
||||||
0U, 0U, {source, 40U}}};
|
{0U, 1U, 2U, 3U},
|
||||||
|
0U,
|
||||||
|
0U,
|
||||||
|
{source, 40U}}};
|
||||||
}
|
}
|
||||||
definition.materials = {
|
definition.materials = {{"Material", 120.0, 0.25, {source, 20U}}};
|
||||||
{"Material", 120.0, 0.25, {source, 20U}}};
|
definition.shell_sections = {{"ShellSection", 0.2, 0U, {source, 30U}}};
|
||||||
definition.shell_sections = {
|
definition.steps = {{"Step-1", {}, {}, 0.1, 1.0, 0.01, 1.0, {source, 50U}}};
|
||||||
{"ShellSection", 0.2, 0U, {source, 30U}}};
|
|
||||||
definition.steps = {{
|
|
||||||
"Step-1", {}, {}, 0.1, 1.0, 0.01, 1.0, {source, 50U}}};
|
|
||||||
return definition;
|
return definition;
|
||||||
}
|
}
|
||||||
|
|
||||||
fesa::Result<fesa::Mitc4Stiffness> directShellStiffness(
|
fesa::Result<fesa::Mitc4Stiffness> DirectShellStiffness(
|
||||||
const fesa::Domain& domain,
|
const fesa::Domain& domain, const fesa::EntityIndex element_index) {
|
||||||
const fesa::EntityIndex elementIndex) {
|
const auto& definition = domain.ShellElements().at(element_index);
|
||||||
const auto& definition = domain.ShellElements().at(elementIndex);
|
|
||||||
std::array<const fesa::Node*, 4> nodes{};
|
std::array<const fesa::Node*, 4> nodes{};
|
||||||
std::array<std::array<double, 3>, 4> directors{};
|
std::array<std::array<double, 3>, 4> directors{};
|
||||||
for (std::size_t node = 0U; node < definition.node_indices.size(); ++node) {
|
for (std::size_t node = 0U; node < definition.node_indices.size(); ++node) {
|
||||||
const fesa::EntityIndex nodeIndex = definition.node_indices[node];
|
const fesa::EntityIndex node_index = definition.node_indices[node];
|
||||||
nodes[node] = &domain.Nodes().at(nodeIndex);
|
nodes[node] = &domain.Nodes().at(node_index);
|
||||||
directors[node] = domain.ShellNodeInitialFrames().at(nodeIndex).director;
|
directors[node] = domain.ShellNodeInitialFrames().at(node_index).director;
|
||||||
}
|
}
|
||||||
auto shell = fesa::Mitc4Shell::Create(
|
auto shell = fesa::Mitc4Shell::Create(
|
||||||
nodes,
|
nodes, directors, domain.ShellSections().at(definition.section_index),
|
||||||
directors,
|
|
||||||
domain.ShellSections().at(definition.section_index),
|
|
||||||
domain.Materials().at(definition.material_index));
|
domain.Materials().at(definition.material_index));
|
||||||
if (!shell.HasValue()) {
|
if (!shell.HasValue()) {
|
||||||
return fesa::Result<fesa::Mitc4Stiffness>::Failure(shell.GetStatus());
|
return fesa::Result<fesa::Mitc4Stiffness>::Failure(shell.GetStatus());
|
||||||
@@ -121,41 +120,38 @@ fesa::Result<fesa::Mitc4Stiffness> directShellStiffness(
|
|||||||
return shell.Value().Stiffness();
|
return shell.Value().Stiffness();
|
||||||
}
|
}
|
||||||
|
|
||||||
fesa::Result<fesa::SparseMatrix> assembleShell(
|
fesa::Result<fesa::SparseMatrix> AssembleShell(
|
||||||
const fesa::ShellSourceElementType sourceType,
|
const fesa::ShellSourceElementType source_type,
|
||||||
const fesa::ParallelFor& parallelFor,
|
const fesa::ParallelFor& parallel_for, const bool two_elements = false) {
|
||||||
const bool twoElements = false) {
|
auto domain =
|
||||||
auto domain = fesa::Domain::Create(
|
fesa::Domain::Create(MakeShellDefinition(source_type, two_elements));
|
||||||
makeShellDefinition(sourceType, twoElements));
|
|
||||||
if (!domain.HasValue()) {
|
if (!domain.HasValue()) {
|
||||||
return fesa::Result<fesa::SparseMatrix>::Failure(domain.GetStatus());
|
return fesa::Result<fesa::SparseMatrix>::Failure(domain.GetStatus());
|
||||||
}
|
}
|
||||||
auto model = fesa::AnalysisModel::create(domain.Value());
|
auto model = fesa::AnalysisModel::Create(domain.Value());
|
||||||
if (!model.HasValue()) {
|
if (!model.HasValue()) {
|
||||||
return fesa::Result<fesa::SparseMatrix>::Failure(model.GetStatus());
|
return fesa::Result<fesa::SparseMatrix>::Failure(model.GetStatus());
|
||||||
}
|
}
|
||||||
auto dofs = fesa::DofManager::create(model.Value());
|
auto dofs = fesa::DofManager::Create(model.Value());
|
||||||
if (!dofs.HasValue()) {
|
if (!dofs.HasValue()) {
|
||||||
return fesa::Result<fesa::SparseMatrix>::Failure(dofs.GetStatus());
|
return fesa::Result<fesa::SparseMatrix>::Failure(dofs.GetStatus());
|
||||||
}
|
}
|
||||||
return fesa::SparseAssembler::assembleStiffness(
|
return fesa::SparseAssembler::AssembleStiffness(model.Value(), dofs.Value(),
|
||||||
model.Value(), dofs.Value(), parallelFor);
|
parallel_for);
|
||||||
}
|
}
|
||||||
|
|
||||||
template<class T>
|
template <class T>
|
||||||
bool byteIdentical(const std::vector<T>& left, const std::vector<T>& right) {
|
bool ByteIdentical(const std::vector<T>& left, const std::vector<T>& right) {
|
||||||
return left.size() == right.size() &&
|
return left.size() == right.size() &&
|
||||||
(left.empty() ||
|
(left.empty() ||
|
||||||
std::memcmp(
|
std::memcmp(left.data(), right.data(), left.size() * sizeof(T)) == 0);
|
||||||
left.data(), right.data(), left.size() * sizeof(T)) == 0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
double entry(
|
double Entry(const fesa::SparseMatrix& matrix, const std::size_t row,
|
||||||
const fesa::SparseMatrix& matrix,
|
|
||||||
const std::size_t row,
|
|
||||||
const std::size_t column) {
|
const std::size_t column) {
|
||||||
const auto begin = matrix.ColumnIndices().begin() + matrix.RowOffsets()[row];
|
const auto begin = matrix.ColumnIndices().begin() + matrix.RowOffsets()[row];
|
||||||
const auto end = matrix.ColumnIndices().begin() + matrix.RowOffsets()[row + 1U];
|
const auto end =
|
||||||
|
matrix.ColumnIndices().begin() + matrix.RowOffsets()[row + 1U];
|
||||||
const auto found = std::lower_bound(begin, end, column);
|
const auto found = std::lower_bound(begin, end, column);
|
||||||
if (found == end || *found != column) {
|
if (found == end || *found != column) {
|
||||||
return 0.0;
|
return 0.0;
|
||||||
@@ -165,122 +161,110 @@ double entry(
|
|||||||
}
|
}
|
||||||
|
|
||||||
class ReverseParallelFor final : public fesa::ParallelFor {
|
class ReverseParallelFor final : public fesa::ParallelFor {
|
||||||
public:
|
public:
|
||||||
void execute(
|
void Execute(const std::size_t count,
|
||||||
const std::size_t count,
|
|
||||||
const std::function<void(std::size_t)>& body) const override {
|
const std::function<void(std::size_t)>& body) const override {
|
||||||
++calls_;
|
++calls_;
|
||||||
observedCount_ = count;
|
observed_count_ = count;
|
||||||
for (std::size_t index = count; index > 0U; --index) {
|
for (std::size_t index = count; index > 0U; --index) {
|
||||||
body(index - 1U);
|
body(index - 1U);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
std::size_t calls() const noexcept {
|
std::size_t Calls() const noexcept { return calls_; }
|
||||||
return calls_;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::size_t observedCount() const noexcept {
|
std::size_t ObservedCount() const noexcept { return observed_count_; }
|
||||||
return observedCount_;
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
mutable std::size_t calls_{0U};
|
mutable std::size_t calls_{0U};
|
||||||
mutable std::size_t observedCount_{0U};
|
mutable std::size_t observed_count_{0U};
|
||||||
};
|
};
|
||||||
|
|
||||||
void expectByteIdentical(
|
void ExpectByteIdentical(const fesa::SparseMatrix& actual,
|
||||||
const fesa::SparseMatrix& actual,
|
|
||||||
const fesa::SparseMatrix& expected) {
|
const fesa::SparseMatrix& expected) {
|
||||||
EXPECT_TRUE(byteIdentical(actual.RowOffsets(), expected.RowOffsets()));
|
EXPECT_TRUE(ByteIdentical(actual.RowOffsets(), expected.RowOffsets()));
|
||||||
EXPECT_TRUE(byteIdentical(actual.ColumnIndices(), expected.ColumnIndices()));
|
EXPECT_TRUE(ByteIdentical(actual.ColumnIndices(), expected.ColumnIndices()));
|
||||||
EXPECT_TRUE(byteIdentical(actual.Values(), expected.Values()));
|
EXPECT_TRUE(ByteIdentical(actual.Values(), expected.Values()));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(SparseAssembly, SerialTbbAndRepeatedRunsAreByteIdentical) {
|
TEST(SparseAssembly, SerialTbbAndRepeatedRunsAreByteIdentical) {
|
||||||
auto domainResult = fesa::Domain::Create(makeDefinition());
|
auto domain_result = fesa::Domain::Create(MakeDefinition());
|
||||||
ASSERT_TRUE(domainResult.HasValue());
|
ASSERT_TRUE(domain_result.HasValue());
|
||||||
auto modelResult = fesa::AnalysisModel::create(domainResult.Value());
|
auto model_result = fesa::AnalysisModel::Create(domain_result.Value());
|
||||||
ASSERT_TRUE(modelResult.HasValue());
|
ASSERT_TRUE(model_result.HasValue());
|
||||||
auto dofsResult = fesa::DofManager::create(modelResult.Value());
|
auto dofs_result = fesa::DofManager::Create(model_result.Value());
|
||||||
ASSERT_TRUE(dofsResult.HasValue());
|
ASSERT_TRUE(dofs_result.HasValue());
|
||||||
|
|
||||||
fesa::SerialParallelFor serialExecutor;
|
fesa::SerialParallelFor serial_executor;
|
||||||
fesa::TbbParallelFor tbbExecutor;
|
fesa::TbbParallelFor tbb_executor;
|
||||||
ReverseParallelFor reverseExecutor;
|
ReverseParallelFor reverse_executor;
|
||||||
auto serial = fesa::SparseAssembler::assembleStiffness(
|
auto serial = fesa::SparseAssembler::AssembleStiffness(
|
||||||
modelResult.Value(), dofsResult.Value(), serialExecutor);
|
model_result.Value(), dofs_result.Value(), serial_executor);
|
||||||
auto tbb = fesa::SparseAssembler::assembleStiffness(
|
auto tbb = fesa::SparseAssembler::AssembleStiffness(
|
||||||
modelResult.Value(), dofsResult.Value(), tbbExecutor);
|
model_result.Value(), dofs_result.Value(), tbb_executor);
|
||||||
auto reversed = fesa::SparseAssembler::assembleStiffness(
|
auto reversed = fesa::SparseAssembler::AssembleStiffness(
|
||||||
modelResult.Value(), dofsResult.Value(), reverseExecutor);
|
model_result.Value(), dofs_result.Value(), reverse_executor);
|
||||||
ASSERT_TRUE(serial.HasValue());
|
ASSERT_TRUE(serial.HasValue());
|
||||||
ASSERT_TRUE(tbb.HasValue());
|
ASSERT_TRUE(tbb.HasValue());
|
||||||
ASSERT_TRUE(reversed.HasValue());
|
ASSERT_TRUE(reversed.HasValue());
|
||||||
|
|
||||||
EXPECT_EQ(reverseExecutor.calls(), 1U);
|
EXPECT_EQ(reverse_executor.Calls(), 1U);
|
||||||
EXPECT_EQ(reverseExecutor.observedCount(), 2U);
|
EXPECT_EQ(reverse_executor.ObservedCount(), 2U);
|
||||||
EXPECT_EQ(serial.Value().Rows(), 18U);
|
EXPECT_EQ(serial.Value().Rows(), 18U);
|
||||||
EXPECT_EQ(serial.Value().Columns(), 18U);
|
EXPECT_EQ(serial.Value().Columns(), 18U);
|
||||||
EXPECT_EQ(serial.Value().RowOffsets(), dofsResult.Value().sparsePattern().rowOffsets);
|
EXPECT_EQ(serial.Value().RowOffsets(),
|
||||||
EXPECT_EQ(
|
dofs_result.Value().GetSparsePattern().row_offsets);
|
||||||
serial.Value().ColumnIndices(),
|
EXPECT_EQ(serial.Value().ColumnIndices(),
|
||||||
dofsResult.Value().sparsePattern().columnIndices);
|
dofs_result.Value().GetSparsePattern().column_indices);
|
||||||
EXPECT_TRUE(serial.Value().Validate().IsOk());
|
EXPECT_TRUE(serial.Value().Validate().IsOk());
|
||||||
expectByteIdentical(tbb.Value(), serial.Value());
|
ExpectByteIdentical(tbb.Value(), serial.Value());
|
||||||
expectByteIdentical(reversed.Value(), serial.Value());
|
ExpectByteIdentical(reversed.Value(), serial.Value());
|
||||||
|
|
||||||
for (std::size_t repetition = 0U; repetition < 8U; ++repetition) {
|
for (std::size_t repetition = 0U; repetition < 8U; ++repetition) {
|
||||||
auto repeated = fesa::SparseAssembler::assembleStiffness(
|
auto repeated = fesa::SparseAssembler::AssembleStiffness(
|
||||||
modelResult.Value(), dofsResult.Value(), tbbExecutor);
|
model_result.Value(), dofs_result.Value(), tbb_executor);
|
||||||
ASSERT_TRUE(repeated.HasValue());
|
ASSERT_TRUE(repeated.HasValue());
|
||||||
expectByteIdentical(repeated.Value(), serial.Value());
|
ExpectByteIdentical(repeated.Value(), serial.Value());
|
||||||
}
|
}
|
||||||
|
|
||||||
for (std::size_t row = 0U; row < serial.Value().Rows(); ++row) {
|
for (std::size_t row = 0U; row < serial.Value().Rows(); ++row) {
|
||||||
for (std::size_t column = 0U;
|
for (std::size_t column = 0U; column < serial.Value().Columns(); ++column) {
|
||||||
column < serial.Value().Columns();
|
EXPECT_DOUBLE_EQ(Entry(serial.Value(), row, column),
|
||||||
++column) {
|
Entry(serial.Value(), column, row));
|
||||||
EXPECT_DOUBLE_EQ(
|
|
||||||
entry(serial.Value(), row, column),
|
|
||||||
entry(serial.Value(), column, row));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
EXPECT_NEAR(entry(serial.Value(), 0U, 0U), 120.0, 1.0e-12);
|
EXPECT_NEAR(Entry(serial.Value(), 0U, 0U), 120.0, 1.0e-12);
|
||||||
EXPECT_NEAR(entry(serial.Value(), 0U, 6U), -120.0, 1.0e-12);
|
EXPECT_NEAR(Entry(serial.Value(), 0U, 6U), -120.0, 1.0e-12);
|
||||||
EXPECT_NEAR(entry(serial.Value(), 6U, 6U), 200.0, 1.0e-12);
|
EXPECT_NEAR(Entry(serial.Value(), 6U, 6U), 200.0, 1.0e-12);
|
||||||
EXPECT_NEAR(entry(serial.Value(), 6U, 12U), -80.0, 1.0e-12);
|
EXPECT_NEAR(Entry(serial.Value(), 6U, 12U), -80.0, 1.0e-12);
|
||||||
EXPECT_NEAR(entry(serial.Value(), 12U, 12U), 80.0, 1.0e-12);
|
EXPECT_NEAR(Entry(serial.Value(), 12U, 12U), 80.0, 1.0e-12);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(
|
TEST(SparseAssembly,
|
||||||
SparseAssembly,
|
|
||||||
AssemblesFourNodeTwentyFourDofKernelAndPreservesDiagonalSlots) {
|
AssemblesFourNodeTwentyFourDofKernelAndPreservesDiagonalSlots) {
|
||||||
auto domain = fesa::Domain::Create(
|
auto domain = fesa::Domain::Create(
|
||||||
makeShellDefinition(fesa::ShellSourceElementType::kS4));
|
MakeShellDefinition(fesa::ShellSourceElementType::kS4));
|
||||||
ASSERT_TRUE(domain.HasValue());
|
ASSERT_TRUE(domain.HasValue());
|
||||||
auto model = fesa::AnalysisModel::create(domain.Value());
|
auto model = fesa::AnalysisModel::Create(domain.Value());
|
||||||
ASSERT_TRUE(model.HasValue());
|
ASSERT_TRUE(model.HasValue());
|
||||||
auto dofs = fesa::DofManager::create(model.Value());
|
auto dofs = fesa::DofManager::Create(model.Value());
|
||||||
ASSERT_TRUE(dofs.HasValue());
|
ASSERT_TRUE(dofs.HasValue());
|
||||||
fesa::SerialParallelFor serialExecutor;
|
fesa::SerialParallelFor serial_executor;
|
||||||
|
|
||||||
auto assembled = fesa::SparseAssembler::assembleStiffness(
|
auto assembled = fesa::SparseAssembler::AssembleStiffness(
|
||||||
model.Value(), dofs.Value(), serialExecutor);
|
model.Value(), dofs.Value(), serial_executor);
|
||||||
auto expected = directShellStiffness(domain.Value(), 0U);
|
auto expected = DirectShellStiffness(domain.Value(), 0U);
|
||||||
ASSERT_TRUE(assembled.HasValue());
|
ASSERT_TRUE(assembled.HasValue());
|
||||||
ASSERT_TRUE(expected.HasValue());
|
ASSERT_TRUE(expected.HasValue());
|
||||||
|
|
||||||
EXPECT_EQ(assembled.Value().Rows(), 24U);
|
EXPECT_EQ(assembled.Value().Rows(), 24U);
|
||||||
EXPECT_EQ(assembled.Value().Columns(), 24U);
|
EXPECT_EQ(assembled.Value().Columns(), 24U);
|
||||||
EXPECT_EQ(assembled.Value().Values().size(), 24U * 24U);
|
EXPECT_EQ(assembled.Value().Values().size(), 24U * 24U);
|
||||||
EXPECT_EQ(
|
EXPECT_EQ(assembled.Value().RowOffsets(),
|
||||||
assembled.Value().RowOffsets(),
|
dofs.Value().GetSparsePattern().row_offsets);
|
||||||
dofs.Value().sparsePattern().rowOffsets);
|
EXPECT_EQ(assembled.Value().ColumnIndices(),
|
||||||
EXPECT_EQ(
|
dofs.Value().GetSparsePattern().column_indices);
|
||||||
assembled.Value().ColumnIndices(),
|
|
||||||
dofs.Value().sparsePattern().columnIndices);
|
|
||||||
for (std::size_t row = 0U; row < 24U; ++row) {
|
for (std::size_t row = 0U; row < 24U; ++row) {
|
||||||
const auto begin = assembled.Value().ColumnIndices().begin() +
|
const auto begin = assembled.Value().ColumnIndices().begin() +
|
||||||
assembled.Value().RowOffsets()[row];
|
assembled.Value().RowOffsets()[row];
|
||||||
@@ -288,52 +272,48 @@ TEST(
|
|||||||
assembled.Value().RowOffsets()[row + 1U];
|
assembled.Value().RowOffsets()[row + 1U];
|
||||||
EXPECT_NE(std::lower_bound(begin, end, row), end);
|
EXPECT_NE(std::lower_bound(begin, end, row), end);
|
||||||
for (std::size_t column = 0U; column < 24U; ++column) {
|
for (std::size_t column = 0U; column < 24U; ++column) {
|
||||||
EXPECT_DOUBLE_EQ(
|
EXPECT_DOUBLE_EQ(Entry(assembled.Value(), row, column),
|
||||||
entry(assembled.Value(), row, column),
|
|
||||||
expected.Value().stabilized_global24(row, column));
|
expected.Value().stabilized_global24(row, column));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(SparseAssembly, ShellSerialTbbReverseAndRepeatedRunsAreByteIdentical) {
|
TEST(SparseAssembly, ShellSerialTbbReverseAndRepeatedRunsAreByteIdentical) {
|
||||||
fesa::SerialParallelFor serialExecutor;
|
fesa::SerialParallelFor serial_executor;
|
||||||
fesa::TbbParallelFor tbbExecutor;
|
fesa::TbbParallelFor tbb_executor;
|
||||||
ReverseParallelFor reverseExecutor;
|
ReverseParallelFor reverse_executor;
|
||||||
auto serial = assembleShell(
|
auto serial =
|
||||||
fesa::ShellSourceElementType::kS4, serialExecutor, true);
|
AssembleShell(fesa::ShellSourceElementType::kS4, serial_executor, true);
|
||||||
auto tbb = assembleShell(
|
auto tbb =
|
||||||
fesa::ShellSourceElementType::kS4, tbbExecutor, true);
|
AssembleShell(fesa::ShellSourceElementType::kS4, tbb_executor, true);
|
||||||
auto reversed = assembleShell(
|
auto reversed =
|
||||||
fesa::ShellSourceElementType::kS4, reverseExecutor, true);
|
AssembleShell(fesa::ShellSourceElementType::kS4, reverse_executor, true);
|
||||||
ASSERT_TRUE(serial.HasValue());
|
ASSERT_TRUE(serial.HasValue());
|
||||||
ASSERT_TRUE(tbb.HasValue());
|
ASSERT_TRUE(tbb.HasValue());
|
||||||
ASSERT_TRUE(reversed.HasValue());
|
ASSERT_TRUE(reversed.HasValue());
|
||||||
|
|
||||||
EXPECT_EQ(reverseExecutor.calls(), 1U);
|
EXPECT_EQ(reverse_executor.Calls(), 1U);
|
||||||
EXPECT_EQ(reverseExecutor.observedCount(), 2U);
|
EXPECT_EQ(reverse_executor.ObservedCount(), 2U);
|
||||||
expectByteIdentical(tbb.Value(), serial.Value());
|
ExpectByteIdentical(tbb.Value(), serial.Value());
|
||||||
expectByteIdentical(reversed.Value(), serial.Value());
|
ExpectByteIdentical(reversed.Value(), serial.Value());
|
||||||
for (std::size_t repetition = 0U; repetition < 8U; ++repetition) {
|
for (std::size_t repetition = 0U; repetition < 8U; ++repetition) {
|
||||||
auto repeated = assembleShell(
|
auto repeated =
|
||||||
fesa::ShellSourceElementType::kS4, tbbExecutor, true);
|
AssembleShell(fesa::ShellSourceElementType::kS4, tbb_executor, true);
|
||||||
ASSERT_TRUE(repeated.HasValue());
|
ASSERT_TRUE(repeated.HasValue());
|
||||||
expectByteIdentical(repeated.Value(), serial.Value());
|
ExpectByteIdentical(repeated.Value(), serial.Value());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(SparseAssembly, S4AndS4rSemanticFixturesAssembleIdenticalStiffness) {
|
TEST(SparseAssembly, S4AndS4rSemanticFixturesAssembleIdenticalStiffness) {
|
||||||
fesa::SerialParallelFor serialExecutor;
|
fesa::SerialParallelFor serial_executor;
|
||||||
auto s4 = assembleShell(
|
auto s4 = AssembleShell(fesa::ShellSourceElementType::kS4, serial_executor);
|
||||||
fesa::ShellSourceElementType::kS4, serialExecutor);
|
auto s4r = AssembleShell(fesa::ShellSourceElementType::kS4r, serial_executor);
|
||||||
auto s4r = assembleShell(
|
|
||||||
fesa::ShellSourceElementType::kS4r, serialExecutor);
|
|
||||||
ASSERT_TRUE(s4.HasValue());
|
ASSERT_TRUE(s4.HasValue());
|
||||||
ASSERT_TRUE(s4r.HasValue());
|
ASSERT_TRUE(s4r.HasValue());
|
||||||
EXPECT_TRUE(std::any_of(
|
EXPECT_TRUE(std::any_of(s4.Value().Values().begin(),
|
||||||
s4.Value().Values().begin(),
|
|
||||||
s4.Value().Values().end(),
|
s4.Value().Values().end(),
|
||||||
[](const double value) { return value != 0.0; }));
|
[](const double value) { return value != 0.0; }));
|
||||||
expectByteIdentical(s4r.Value(), s4.Value());
|
ExpectByteIdentical(s4r.Value(), s4.Value());
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|||||||
@@ -1,8 +1,4 @@
|
|||||||
#include "fesa/constraints/essential_constraints.hpp"
|
#include "fesa/constraints/essential_constraints.h"
|
||||||
|
|
||||||
#include "fesa/analysis/analysis_model.hpp"
|
|
||||||
#include "fesa/fem/dof_manager.hpp"
|
|
||||||
#include "fesa/model/domain.h"
|
|
||||||
|
|
||||||
#include <gtest/gtest.h>
|
#include <gtest/gtest.h>
|
||||||
|
|
||||||
@@ -13,18 +9,19 @@
|
|||||||
#include <utility>
|
#include <utility>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
|
#include "fesa/analysis/analysis_model.h"
|
||||||
|
#include "fesa/fem/dof_manager.h"
|
||||||
|
#include "fesa/model/domain.h"
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
fesa::DofManager makeDofs(
|
fesa::DofManager MakeDofs(std::vector<fesa::BoundaryCondition> boundaries) {
|
||||||
std::vector<fesa::BoundaryCondition> boundaries) {
|
|
||||||
const std::filesystem::path source{"models/essential-constraints.inp"};
|
const std::filesystem::path source{"models/essential-constraints.inp"};
|
||||||
fesa::ModelDefinition definition{};
|
fesa::ModelDefinition definition{};
|
||||||
definition.source_path = source;
|
definition.source_path = source;
|
||||||
definition.source_content_identity = "fnv1a64:1234567890abcdef";
|
definition.source_content_identity = "fnv1a64:1234567890abcdef";
|
||||||
definition.nodes = {
|
definition.nodes = {{{"Beam-1", 1, "1"}, {0.0, 0.0, 0.0}, {source, 2U}}};
|
||||||
{{"Beam-1", 1, "1"}, {0.0, 0.0, 0.0}, {source, 2U}}};
|
definition.steps = {{"Step-1",
|
||||||
definition.steps = {{
|
|
||||||
"Step-1",
|
|
||||||
std::move(boundaries),
|
std::move(boundaries),
|
||||||
{},
|
{},
|
||||||
0.1,
|
0.1,
|
||||||
@@ -35,37 +32,32 @@ fesa::DofManager makeDofs(
|
|||||||
|
|
||||||
auto domain = fesa::Domain::Create(std::move(definition));
|
auto domain = fesa::Domain::Create(std::move(definition));
|
||||||
EXPECT_TRUE(domain.HasValue());
|
EXPECT_TRUE(domain.HasValue());
|
||||||
auto model = fesa::AnalysisModel::create(domain.Value());
|
auto model = fesa::AnalysisModel::Create(domain.Value());
|
||||||
EXPECT_TRUE(model.HasValue());
|
EXPECT_TRUE(model.HasValue());
|
||||||
auto dofs = fesa::DofManager::create(model.Value());
|
auto dofs = fesa::DofManager::Create(model.Value());
|
||||||
EXPECT_TRUE(dofs.HasValue());
|
EXPECT_TRUE(dofs.HasValue());
|
||||||
return std::move(dofs.Value());
|
return std::move(dofs.Value());
|
||||||
}
|
}
|
||||||
|
|
||||||
fesa::DofManager makeShellSizedDofs(
|
fesa::DofManager MakeShellSizedDofs(
|
||||||
std::vector<fesa::BoundaryCondition> boundaries) {
|
std::vector<fesa::BoundaryCondition> boundaries) {
|
||||||
const std::filesystem::path source{"models/shell-essential-constraints.inp"};
|
const std::filesystem::path source{"models/shell-essential-constraints.inp"};
|
||||||
fesa::ModelDefinition definition{};
|
fesa::ModelDefinition definition{};
|
||||||
definition.source_path = source;
|
definition.source_path = source;
|
||||||
definition.source_content_identity = "fnv1a64:8877665544332211";
|
definition.source_content_identity = "fnv1a64:8877665544332211";
|
||||||
definition.nodes = {
|
definition.nodes = {{{"Shell-1", 1, "1"}, {0.0, 0.0, 0.0}, {source, 2U}},
|
||||||
{{"Shell-1", 1, "1"}, {0.0, 0.0, 0.0}, {source, 2U}},
|
|
||||||
{{"Shell-1", 2, "2"}, {1.0, 0.0, 0.0}, {source, 3U}},
|
{{"Shell-1", 2, "2"}, {1.0, 0.0, 0.0}, {source, 3U}},
|
||||||
{{"Shell-1", 3, "3"}, {1.0, 1.0, 0.0}, {source, 4U}},
|
{{"Shell-1", 3, "3"}, {1.0, 1.0, 0.0}, {source, 4U}},
|
||||||
{{"Shell-1", 4, "4"}, {0.0, 1.0, 0.0}, {source, 5U}}};
|
{{"Shell-1", 4, "4"}, {0.0, 1.0, 0.0}, {source, 5U}}};
|
||||||
definition.materials = {
|
definition.materials = {{"Material", 1000.0, 0.25, {source, 6U}}};
|
||||||
{"Material", 1000.0, 0.25, {source, 6U}}};
|
definition.shell_sections = {{"ShellSection", 0.1, 0U, {source, 7U}}};
|
||||||
definition.shell_sections = {
|
definition.shell_elements = {{{"Shell-1", 1, "1"},
|
||||||
{"ShellSection", 0.1, 0U, {source, 7U}}};
|
|
||||||
definition.shell_elements = {{
|
|
||||||
{"Shell-1", 1, "1"},
|
|
||||||
fesa::ShellSourceElementType::kS4,
|
fesa::ShellSourceElementType::kS4,
|
||||||
{0U, 1U, 2U, 3U},
|
{0U, 1U, 2U, 3U},
|
||||||
0U,
|
0U,
|
||||||
0U,
|
0U,
|
||||||
{source, 8U}}};
|
{source, 8U}}};
|
||||||
definition.steps = {{
|
definition.steps = {{"Step-1",
|
||||||
"Step-1",
|
|
||||||
std::move(boundaries),
|
std::move(boundaries),
|
||||||
{},
|
{},
|
||||||
0.1,
|
0.1,
|
||||||
@@ -76,42 +68,36 @@ fesa::DofManager makeShellSizedDofs(
|
|||||||
|
|
||||||
auto domain = fesa::Domain::Create(std::move(definition));
|
auto domain = fesa::Domain::Create(std::move(definition));
|
||||||
EXPECT_TRUE(domain.HasValue());
|
EXPECT_TRUE(domain.HasValue());
|
||||||
auto model = fesa::AnalysisModel::create(domain.Value());
|
auto model = fesa::AnalysisModel::Create(domain.Value());
|
||||||
EXPECT_TRUE(model.HasValue());
|
EXPECT_TRUE(model.HasValue());
|
||||||
auto dofs = fesa::DofManager::create(model.Value());
|
auto dofs = fesa::DofManager::Create(model.Value());
|
||||||
EXPECT_TRUE(dofs.HasValue());
|
EXPECT_TRUE(dofs.HasValue());
|
||||||
return std::move(dofs.Value());
|
return std::move(dofs.Value());
|
||||||
}
|
}
|
||||||
|
|
||||||
fesa::SparseMatrix makeMatrix(
|
fesa::SparseMatrix MakeMatrix(const std::size_t rows, const std::size_t columns,
|
||||||
const std::size_t rows,
|
const std::vector<double>& dense_values) {
|
||||||
const std::size_t columns,
|
EXPECT_EQ(dense_values.size(), rows * columns);
|
||||||
const std::vector<double>& denseValues) {
|
|
||||||
EXPECT_EQ(denseValues.size(), rows * columns);
|
|
||||||
fesa::SparsePattern pattern;
|
fesa::SparsePattern pattern;
|
||||||
std::vector<fesa::CooContribution> contributions;
|
std::vector<fesa::CooContribution> contributions;
|
||||||
pattern.rowOffsets.reserve(rows + 1U);
|
pattern.row_offsets.reserve(rows + 1U);
|
||||||
pattern.rowOffsets.push_back(0U);
|
pattern.row_offsets.push_back(0U);
|
||||||
for (std::size_t row = 0U; row < rows; ++row) {
|
for (std::size_t row = 0U; row < rows; ++row) {
|
||||||
for (std::size_t column = 0U; column < columns; ++column) {
|
for (std::size_t column = 0U; column < columns; ++column) {
|
||||||
pattern.columnIndices.push_back(column);
|
pattern.column_indices.push_back(column);
|
||||||
contributions.push_back({
|
contributions.push_back(
|
||||||
row,
|
{row, column, dense_values[row * columns + column], row, column});
|
||||||
column,
|
|
||||||
denseValues[row * columns + column],
|
|
||||||
row,
|
|
||||||
column});
|
|
||||||
}
|
}
|
||||||
pattern.rowOffsets.push_back(pattern.columnIndices.size());
|
pattern.row_offsets.push_back(pattern.column_indices.size());
|
||||||
}
|
}
|
||||||
|
|
||||||
auto matrix = fesa::SparseMatrix::FromCoo(
|
auto matrix = fesa::SparseMatrix::FromCoo(rows, columns,
|
||||||
rows, columns, std::move(contributions), pattern);
|
std::move(contributions), pattern);
|
||||||
EXPECT_TRUE(matrix.HasValue());
|
EXPECT_TRUE(matrix.HasValue());
|
||||||
return std::move(matrix.Value());
|
return std::move(matrix.Value());
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<double> sequentialDense(const std::size_t size) {
|
std::vector<double> SequentialDense(const std::size_t size) {
|
||||||
std::vector<double> values(size * size);
|
std::vector<double> values(size * size);
|
||||||
for (std::size_t row = 0U; row < size; ++row) {
|
for (std::size_t row = 0U; row < size; ++row) {
|
||||||
for (std::size_t column = 0U; column < size; ++column) {
|
for (std::size_t column = 0U; column < size; ++column) {
|
||||||
@@ -122,9 +108,7 @@ std::vector<double> sequentialDense(const std::size_t size) {
|
|||||||
return values;
|
return values;
|
||||||
}
|
}
|
||||||
|
|
||||||
void expectShape(
|
void ExpectShape(const fesa::SparseMatrix& matrix, const std::size_t rows,
|
||||||
const fesa::SparseMatrix& matrix,
|
|
||||||
const std::size_t rows,
|
|
||||||
const std::size_t columns) {
|
const std::size_t columns) {
|
||||||
EXPECT_EQ(matrix.Rows(), rows);
|
EXPECT_EQ(matrix.Rows(), rows);
|
||||||
EXPECT_EQ(matrix.Columns(), columns);
|
EXPECT_EQ(matrix.Columns(), columns);
|
||||||
@@ -134,46 +118,39 @@ void expectShape(
|
|||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
TEST(EssentialConstraints, ExtractsHandComputedBlocksInStableOrder) {
|
TEST(EssentialConstraints, ExtractsHandComputedBlocksInStableOrder) {
|
||||||
const auto dofs = makeDofs({
|
const auto dofs =
|
||||||
{"1", 2, 2, 2.5, {{}, 12U}},
|
MakeDofs({{"1", 2, 2, 2.5, {{}, 12U}}, {"1", 5, 5, -3.25, {{}, 13U}}});
|
||||||
{"1", 5, 5, -3.25, {{}, 13U}}});
|
auto full_values = SequentialDense(6U);
|
||||||
auto fullValues = sequentialDense(6U);
|
full_values[2U * 6U + 4U] = 0.0;
|
||||||
fullValues[2U * 6U + 4U] = 0.0;
|
const auto full = MakeMatrix(6U, 6U, full_values);
|
||||||
const auto full = makeMatrix(6U, 6U, fullValues);
|
|
||||||
|
|
||||||
auto result = fesa::EssentialConstraints::partition(full, dofs);
|
auto result = fesa::EssentialConstraints::Partition(full, dofs);
|
||||||
ASSERT_TRUE(result.HasValue());
|
ASSERT_TRUE(result.HasValue());
|
||||||
const auto& blocks = result.Value();
|
const auto& blocks = result.Value();
|
||||||
|
|
||||||
EXPECT_EQ(blocks.kff.RowOffsets(), (std::vector<std::size_t>{0U, 4U, 8U, 12U, 16U}));
|
EXPECT_EQ(blocks.kff.RowOffsets(),
|
||||||
EXPECT_EQ(
|
(std::vector<std::size_t>{0U, 4U, 8U, 12U, 16U}));
|
||||||
blocks.kff.ColumnIndices(),
|
EXPECT_EQ(blocks.kff.ColumnIndices(),
|
||||||
(std::vector<std::size_t>{
|
(std::vector<std::size_t>{0U, 1U, 2U, 3U, 0U, 1U, 2U, 3U, 0U, 1U,
|
||||||
0U, 1U, 2U, 3U, 0U, 1U, 2U, 3U,
|
2U, 3U, 0U, 1U, 2U, 3U}));
|
||||||
0U, 1U, 2U, 3U, 0U, 1U, 2U, 3U}));
|
|
||||||
EXPECT_EQ(
|
EXPECT_EQ(
|
||||||
blocks.kff.Values(),
|
blocks.kff.Values(),
|
||||||
(std::vector<double>{
|
(std::vector<double>{1.0, 3.0, 4.0, 6.0, 21.0, 23.0, 24.0, 26.0, 31.0,
|
||||||
1.0, 3.0, 4.0, 6.0,
|
33.0, 34.0, 36.0, 51.0, 53.0, 54.0, 56.0}));
|
||||||
21.0, 23.0, 24.0, 26.0,
|
EXPECT_EQ(blocks.kfc.RowOffsets(),
|
||||||
31.0, 33.0, 34.0, 36.0,
|
(std::vector<std::size_t>{0U, 2U, 4U, 6U, 8U}));
|
||||||
51.0, 53.0, 54.0, 56.0}));
|
EXPECT_EQ(blocks.kfc.ColumnIndices(),
|
||||||
EXPECT_EQ(blocks.kfc.RowOffsets(), (std::vector<std::size_t>{0U, 2U, 4U, 6U, 8U}));
|
|
||||||
EXPECT_EQ(
|
|
||||||
blocks.kfc.ColumnIndices(),
|
|
||||||
(std::vector<std::size_t>{0U, 1U, 0U, 1U, 0U, 1U, 0U, 1U}));
|
(std::vector<std::size_t>{0U, 1U, 0U, 1U, 0U, 1U, 0U, 1U}));
|
||||||
EXPECT_EQ(
|
EXPECT_EQ(blocks.kfc.Values(),
|
||||||
blocks.kfc.Values(),
|
|
||||||
(std::vector<double>{2.0, 5.0, 22.0, 0.0, 32.0, 35.0, 52.0, 55.0}));
|
(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.RowOffsets(), (std::vector<std::size_t>{0U, 4U, 8U}));
|
||||||
EXPECT_EQ(
|
EXPECT_EQ(blocks.kcf.ColumnIndices(),
|
||||||
blocks.kcf.ColumnIndices(),
|
|
||||||
(std::vector<std::size_t>{0U, 1U, 2U, 3U, 0U, 1U, 2U, 3U}));
|
(std::vector<std::size_t>{0U, 1U, 2U, 3U, 0U, 1U, 2U, 3U}));
|
||||||
EXPECT_EQ(
|
EXPECT_EQ(blocks.kcf.Values(), (std::vector<double>{11.0, 13.0, 14.0, 16.0,
|
||||||
blocks.kcf.Values(),
|
41.0, 43.0, 44.0, 46.0}));
|
||||||
(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.RowOffsets(), (std::vector<std::size_t>{0U, 2U, 4U}));
|
||||||
EXPECT_EQ(blocks.kcc.ColumnIndices(), (std::vector<std::size_t>{0U, 1U, 0U, 1U}));
|
EXPECT_EQ(blocks.kcc.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.kcc.Values(), (std::vector<double>{12.0, 15.0, 42.0, 45.0}));
|
||||||
|
|
||||||
EXPECT_EQ(blocks.kfc.Values()[3U], 0.0);
|
EXPECT_EQ(blocks.kfc.Values()[3U], 0.0);
|
||||||
@@ -184,106 +161,100 @@ TEST(EssentialConstraints, ExtractsHandComputedBlocksInStableOrder) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
TEST(EssentialConstraints, HandlesNoAllAndMixedConstraints) {
|
TEST(EssentialConstraints, HandlesNoAllAndMixedConstraints) {
|
||||||
const auto full = makeMatrix(6U, 6U, sequentialDense(6U));
|
const auto full = MakeMatrix(6U, 6U, SequentialDense(6U));
|
||||||
|
|
||||||
const auto noConstraints = makeDofs({});
|
const auto no_constraints = MakeDofs({});
|
||||||
auto none = fesa::EssentialConstraints::partition(full, noConstraints);
|
auto none = fesa::EssentialConstraints::Partition(full, no_constraints);
|
||||||
ASSERT_TRUE(none.HasValue());
|
ASSERT_TRUE(none.HasValue());
|
||||||
expectShape(none.Value().kff, 6U, 6U);
|
ExpectShape(none.Value().kff, 6U, 6U);
|
||||||
expectShape(none.Value().kfc, 6U, 0U);
|
ExpectShape(none.Value().kfc, 6U, 0U);
|
||||||
expectShape(none.Value().kcf, 0U, 6U);
|
ExpectShape(none.Value().kcf, 0U, 6U);
|
||||||
expectShape(none.Value().kcc, 0U, 0U);
|
ExpectShape(none.Value().kcc, 0U, 0U);
|
||||||
EXPECT_EQ(none.Value().kff.Values(), full.Values());
|
EXPECT_EQ(none.Value().kff.Values(), full.Values());
|
||||||
|
|
||||||
const auto allConstraints = makeDofs({{"1", 1, 6, 1.0, {{}, 12U}}});
|
const auto all_constraints = MakeDofs({{"1", 1, 6, 1.0, {{}, 12U}}});
|
||||||
auto all = fesa::EssentialConstraints::partition(full, allConstraints);
|
auto all = fesa::EssentialConstraints::Partition(full, all_constraints);
|
||||||
ASSERT_TRUE(all.HasValue());
|
ASSERT_TRUE(all.HasValue());
|
||||||
expectShape(all.Value().kff, 0U, 0U);
|
ExpectShape(all.Value().kff, 0U, 0U);
|
||||||
expectShape(all.Value().kfc, 0U, 6U);
|
ExpectShape(all.Value().kfc, 0U, 6U);
|
||||||
expectShape(all.Value().kcf, 6U, 0U);
|
ExpectShape(all.Value().kcf, 6U, 0U);
|
||||||
expectShape(all.Value().kcc, 6U, 6U);
|
ExpectShape(all.Value().kcc, 6U, 6U);
|
||||||
EXPECT_EQ(all.Value().kcc.Values(), full.Values());
|
EXPECT_EQ(all.Value().kcc.Values(), full.Values());
|
||||||
|
|
||||||
const auto mixedConstraints = makeDofs({{"1", 3, 4, 0.0, {{}, 12U}}});
|
const auto mixed_constraints = MakeDofs({{"1", 3, 4, 0.0, {{}, 12U}}});
|
||||||
auto mixed = fesa::EssentialConstraints::partition(full, mixedConstraints);
|
auto mixed = fesa::EssentialConstraints::Partition(full, mixed_constraints);
|
||||||
ASSERT_TRUE(mixed.HasValue());
|
ASSERT_TRUE(mixed.HasValue());
|
||||||
expectShape(mixed.Value().kff, 4U, 4U);
|
ExpectShape(mixed.Value().kff, 4U, 4U);
|
||||||
expectShape(mixed.Value().kfc, 4U, 2U);
|
ExpectShape(mixed.Value().kfc, 4U, 2U);
|
||||||
expectShape(mixed.Value().kcf, 2U, 4U);
|
ExpectShape(mixed.Value().kcf, 2U, 4U);
|
||||||
expectShape(mixed.Value().kcc, 2U, 2U);
|
ExpectShape(mixed.Value().kcc, 2U, 2U);
|
||||||
}
|
}
|
||||||
|
|
||||||
// MITC4-DOF-003
|
// MITC4-DOF-003
|
||||||
TEST(EssentialConstraints, PreservesShellSizedNoMixedAndAllConstraintRoundTrips) {
|
TEST(EssentialConstraints,
|
||||||
const auto full = makeMatrix(24U, 24U, sequentialDense(24U));
|
PreservesShellSizedNoMixedAndAllConstraintRoundTrips) {
|
||||||
|
const auto full = MakeMatrix(24U, 24U, SequentialDense(24U));
|
||||||
|
|
||||||
const auto noConstraints = makeShellSizedDofs({});
|
const auto no_constraints = MakeShellSizedDofs({});
|
||||||
auto none = fesa::EssentialConstraints::partition(full, noConstraints);
|
auto none = fesa::EssentialConstraints::Partition(full, no_constraints);
|
||||||
ASSERT_TRUE(none.HasValue());
|
ASSERT_TRUE(none.HasValue());
|
||||||
expectShape(none.Value().kff, 24U, 24U);
|
ExpectShape(none.Value().kff, 24U, 24U);
|
||||||
expectShape(none.Value().kfc, 24U, 0U);
|
ExpectShape(none.Value().kfc, 24U, 0U);
|
||||||
expectShape(none.Value().kcf, 0U, 24U);
|
ExpectShape(none.Value().kcf, 0U, 24U);
|
||||||
expectShape(none.Value().kcc, 0U, 0U);
|
ExpectShape(none.Value().kcc, 0U, 0U);
|
||||||
|
|
||||||
const auto mixedConstraints = makeShellSizedDofs({
|
const auto mixed_constraints = MakeShellSizedDofs(
|
||||||
{"1", 1, 6, 0.0, {{}, 12U}},
|
{{"1", 1, 6, 0.0, {{}, 12U}}, {"4", 2, 2, 2.5, {{}, 13U}}});
|
||||||
{"4", 2, 2, 2.5, {{}, 13U}}});
|
auto mixed = fesa::EssentialConstraints::Partition(full, mixed_constraints);
|
||||||
auto mixed = fesa::EssentialConstraints::partition(full, mixedConstraints);
|
|
||||||
ASSERT_TRUE(mixed.HasValue());
|
ASSERT_TRUE(mixed.HasValue());
|
||||||
expectShape(mixed.Value().kff, 17U, 17U);
|
ExpectShape(mixed.Value().kff, 17U, 17U);
|
||||||
expectShape(mixed.Value().kfc, 17U, 7U);
|
ExpectShape(mixed.Value().kfc, 17U, 7U);
|
||||||
expectShape(mixed.Value().kcf, 7U, 17U);
|
ExpectShape(mixed.Value().kcf, 7U, 17U);
|
||||||
expectShape(mixed.Value().kcc, 7U, 7U);
|
ExpectShape(mixed.Value().kcc, 7U, 7U);
|
||||||
|
|
||||||
fesa::Vector mixedFull{24U};
|
fesa::Vector mixed_full{24U};
|
||||||
for (std::size_t index = 0U; index < mixedFull.Size(); ++index) {
|
for (std::size_t index = 0U; index < mixed_full.Size(); ++index) {
|
||||||
mixedFull[index] = static_cast<double>(index) + 0.5;
|
mixed_full[index] = static_cast<double>(index) + 0.5;
|
||||||
}
|
}
|
||||||
for (std::size_t index = 0U;
|
for (std::size_t index = 0U; index < mixed_constraints.ConstrainedDofCount();
|
||||||
index < mixedConstraints.constrainedDofCount();
|
|
||||||
++index) {
|
++index) {
|
||||||
mixedFull[mixedConstraints.constrainedDofs()[index]] =
|
mixed_full[mixed_constraints.ConstrainedDofs()[index]] =
|
||||||
mixedConstraints.prescribedValues()[index];
|
mixed_constraints.PrescribedValues()[index];
|
||||||
}
|
}
|
||||||
const auto mixedFree =
|
const auto mixed_free =
|
||||||
fesa::EssentialConstraints::gatherFree(mixedFull, mixedConstraints);
|
fesa::EssentialConstraints::GatherFree(mixed_full, mixed_constraints);
|
||||||
const auto mixedReconstructed =
|
const auto mixed_reconstructed = fesa::EssentialConstraints::ReconstructFull(
|
||||||
fesa::EssentialConstraints::reconstructFull(
|
mixed_free, mixed_constraints.PrescribedValues(), mixed_constraints);
|
||||||
mixedFree, mixedConstraints.prescribedValues(), mixedConstraints);
|
ASSERT_EQ(mixed_reconstructed.Size(), mixed_full.Size());
|
||||||
ASSERT_EQ(mixedReconstructed.Size(), mixedFull.Size());
|
for (std::size_t index = 0U; index < mixed_full.Size(); ++index) {
|
||||||
for (std::size_t index = 0U; index < mixedFull.Size(); ++index) {
|
EXPECT_DOUBLE_EQ(mixed_reconstructed[index], mixed_full[index]);
|
||||||
EXPECT_DOUBLE_EQ(mixedReconstructed[index], mixedFull[index]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto allConstraints = makeShellSizedDofs({
|
const auto all_constraints =
|
||||||
{"1", 1, 6, 1.0, {{}, 14U}},
|
MakeShellSizedDofs({{"1", 1, 6, 1.0, {{}, 14U}},
|
||||||
{"2", 1, 6, 2.0, {{}, 15U}},
|
{"2", 1, 6, 2.0, {{}, 15U}},
|
||||||
{"3", 1, 6, 3.0, {{}, 16U}},
|
{"3", 1, 6, 3.0, {{}, 16U}},
|
||||||
{"4", 1, 6, 4.0, {{}, 17U}}});
|
{"4", 1, 6, 4.0, {{}, 17U}}});
|
||||||
auto all = fesa::EssentialConstraints::partition(full, allConstraints);
|
auto all = fesa::EssentialConstraints::Partition(full, all_constraints);
|
||||||
ASSERT_TRUE(all.HasValue());
|
ASSERT_TRUE(all.HasValue());
|
||||||
expectShape(all.Value().kff, 0U, 0U);
|
ExpectShape(all.Value().kff, 0U, 0U);
|
||||||
expectShape(all.Value().kfc, 0U, 24U);
|
ExpectShape(all.Value().kfc, 0U, 24U);
|
||||||
expectShape(all.Value().kcf, 24U, 0U);
|
ExpectShape(all.Value().kcf, 24U, 0U);
|
||||||
expectShape(all.Value().kcc, 24U, 24U);
|
ExpectShape(all.Value().kcc, 24U, 24U);
|
||||||
|
|
||||||
const auto allReconstructed =
|
const auto all_reconstructed = fesa::EssentialConstraints::ReconstructFull(
|
||||||
fesa::EssentialConstraints::reconstructFull(
|
fesa::Vector{0U}, all_constraints.PrescribedValues(), all_constraints);
|
||||||
fesa::Vector{0U},
|
ASSERT_EQ(all_reconstructed.Size(), 24U);
|
||||||
allConstraints.prescribedValues(),
|
|
||||||
allConstraints);
|
|
||||||
ASSERT_EQ(allReconstructed.Size(), 24U);
|
|
||||||
for (std::size_t node = 0U; node < 4U; ++node) {
|
for (std::size_t node = 0U; node < 4U; ++node) {
|
||||||
for (std::size_t component = 0U; component < 6U; ++component) {
|
for (std::size_t component = 0U; component < 6U; ++component) {
|
||||||
EXPECT_DOUBLE_EQ(allReconstructed[node * 6U + component], node + 1.0);
|
EXPECT_DOUBLE_EQ(all_reconstructed[node * 6U + component], node + 1.0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(EssentialConstraints, ReconstructsNonzeroPrescribedValues) {
|
TEST(EssentialConstraints, ReconstructsNonzeroPrescribedValues) {
|
||||||
const auto dofs = makeDofs({
|
const auto dofs =
|
||||||
{"1", 2, 2, 2.5, {{}, 12U}},
|
MakeDofs({{"1", 2, 2, 2.5, {{}, 12U}}, {"1", 5, 5, -3.25, {{}, 13U}}});
|
||||||
{"1", 5, 5, -3.25, {{}, 13U}}});
|
|
||||||
fesa::Vector full{6U};
|
fesa::Vector full{6U};
|
||||||
full[0U] = 10.0;
|
full[0U] = 10.0;
|
||||||
full[1U] = 2.5;
|
full[1U] = 2.5;
|
||||||
@@ -292,9 +263,9 @@ TEST(EssentialConstraints, ReconstructsNonzeroPrescribedValues) {
|
|||||||
full[4U] = -3.25;
|
full[4U] = -3.25;
|
||||||
full[5U] = 40.0;
|
full[5U] = 40.0;
|
||||||
|
|
||||||
const auto free = fesa::EssentialConstraints::gatherFree(full, dofs);
|
const auto free = fesa::EssentialConstraints::GatherFree(full, dofs);
|
||||||
const auto constrained =
|
const auto constrained =
|
||||||
fesa::EssentialConstraints::gatherConstrained(full, dofs);
|
fesa::EssentialConstraints::GatherConstrained(full, dofs);
|
||||||
EXPECT_EQ(free.Size(), 4U);
|
EXPECT_EQ(free.Size(), 4U);
|
||||||
EXPECT_DOUBLE_EQ(free[0U], 10.0);
|
EXPECT_DOUBLE_EQ(free[0U], 10.0);
|
||||||
EXPECT_DOUBLE_EQ(free[1U], 20.0);
|
EXPECT_DOUBLE_EQ(free[1U], 20.0);
|
||||||
@@ -303,11 +274,11 @@ TEST(EssentialConstraints, ReconstructsNonzeroPrescribedValues) {
|
|||||||
EXPECT_EQ(constrained.Size(), 2U);
|
EXPECT_EQ(constrained.Size(), 2U);
|
||||||
EXPECT_DOUBLE_EQ(constrained[0U], 2.5);
|
EXPECT_DOUBLE_EQ(constrained[0U], 2.5);
|
||||||
EXPECT_DOUBLE_EQ(constrained[1U], -3.25);
|
EXPECT_DOUBLE_EQ(constrained[1U], -3.25);
|
||||||
EXPECT_EQ(constrained[0U], dofs.prescribedValues()[0U]);
|
EXPECT_EQ(constrained[0U], dofs.PrescribedValues()[0U]);
|
||||||
EXPECT_EQ(constrained[1U], dofs.prescribedValues()[1U]);
|
EXPECT_EQ(constrained[1U], dofs.PrescribedValues()[1U]);
|
||||||
|
|
||||||
const auto reconstructed = fesa::EssentialConstraints::reconstructFull(
|
const auto reconstructed = fesa::EssentialConstraints::ReconstructFull(
|
||||||
free, dofs.prescribedValues(), dofs);
|
free, dofs.PrescribedValues(), dofs);
|
||||||
ASSERT_EQ(reconstructed.Size(), full.Size());
|
ASSERT_EQ(reconstructed.Size(), full.Size());
|
||||||
for (std::size_t index = 0U; index < full.Size(); ++index) {
|
for (std::size_t index = 0U; index < full.Size(); ++index) {
|
||||||
EXPECT_DOUBLE_EQ(reconstructed[index], full[index]);
|
EXPECT_DOUBLE_EQ(reconstructed[index], full[index]);
|
||||||
@@ -315,37 +286,30 @@ TEST(EssentialConstraints, ReconstructsNonzeroPrescribedValues) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
TEST(EssentialConstraints, RejectsDimensionOrOrderMismatch) {
|
TEST(EssentialConstraints, RejectsDimensionOrOrderMismatch) {
|
||||||
const auto dofs = makeDofs({{"1", 2, 2, 1.0, {{}, 12U}}});
|
const auto dofs = MakeDofs({{"1", 2, 2, 1.0, {{}, 12U}}});
|
||||||
const auto wrongSquare = makeMatrix(5U, 5U, sequentialDense(5U));
|
const auto wrong_square = MakeMatrix(5U, 5U, SequentialDense(5U));
|
||||||
auto wrongDimension =
|
auto wrong_dimension =
|
||||||
fesa::EssentialConstraints::partition(wrongSquare, dofs);
|
fesa::EssentialConstraints::Partition(wrong_square, dofs);
|
||||||
ASSERT_FALSE(wrongDimension.HasValue());
|
ASSERT_FALSE(wrong_dimension.HasValue());
|
||||||
EXPECT_EQ(
|
EXPECT_EQ(wrong_dimension.GetStatus().Category(),
|
||||||
wrongDimension.GetStatus().Category(),
|
|
||||||
fesa::FailureCategory::kModel);
|
fesa::FailureCategory::kModel);
|
||||||
ASSERT_EQ(wrongDimension.GetStatus().Diagnostics().size(), 1U);
|
ASSERT_EQ(wrong_dimension.GetStatus().Diagnostics().size(), 1U);
|
||||||
EXPECT_EQ(
|
EXPECT_EQ(wrong_dimension.GetStatus().Diagnostics()[0U].code,
|
||||||
wrongDimension.GetStatus().Diagnostics()[0U].code,
|
|
||||||
"invalid-constraint-dimensions");
|
"invalid-constraint-dimensions");
|
||||||
|
|
||||||
const auto rectangular = makeMatrix(
|
const auto rectangular = MakeMatrix(6U, 5U, std::vector<double>(30U, 0.0));
|
||||||
6U, 5U, std::vector<double>(30U, 0.0));
|
auto wrong_order = fesa::EssentialConstraints::Partition(rectangular, dofs);
|
||||||
auto wrongOrder = fesa::EssentialConstraints::partition(rectangular, dofs);
|
ASSERT_FALSE(wrong_order.HasValue());
|
||||||
ASSERT_FALSE(wrongOrder.HasValue());
|
EXPECT_EQ(wrong_order.GetStatus().Diagnostics()[0U].code,
|
||||||
EXPECT_EQ(
|
|
||||||
wrongOrder.GetStatus().Diagnostics()[0U].code,
|
|
||||||
"invalid-constraint-dimensions");
|
"invalid-constraint-dimensions");
|
||||||
|
|
||||||
EXPECT_THROW(
|
EXPECT_THROW(static_cast<void>(fesa::EssentialConstraints::GatherFree(
|
||||||
static_cast<void>(fesa::EssentialConstraints::gatherFree(
|
|
||||||
fesa::Vector{5U}, dofs)),
|
fesa::Vector{5U}, dofs)),
|
||||||
std::invalid_argument);
|
std::invalid_argument);
|
||||||
EXPECT_THROW(
|
EXPECT_THROW(static_cast<void>(fesa::EssentialConstraints::GatherConstrained(
|
||||||
static_cast<void>(fesa::EssentialConstraints::gatherConstrained(
|
|
||||||
fesa::Vector{7U}, dofs)),
|
fesa::Vector{7U}, dofs)),
|
||||||
std::invalid_argument);
|
std::invalid_argument);
|
||||||
EXPECT_THROW(
|
EXPECT_THROW(static_cast<void>(fesa::EssentialConstraints::ReconstructFull(
|
||||||
static_cast<void>(fesa::EssentialConstraints::reconstructFull(
|
|
||||||
fesa::Vector{4U}, fesa::Vector{2U}, dofs)),
|
fesa::Vector{4U}, fesa::Vector{2U}, dofs)),
|
||||||
std::invalid_argument);
|
std::invalid_argument);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
#include "fesa/fem/dof_manager.hpp"
|
#include "fesa/fem/dof_manager.h"
|
||||||
|
|
||||||
#include <gtest/gtest.h>
|
#include <gtest/gtest.h>
|
||||||
|
|
||||||
@@ -11,28 +11,24 @@
|
|||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
fesa::ModelDefinition makeDefinition() {
|
fesa::ModelDefinition MakeDefinition() {
|
||||||
const std::filesystem::path source{"models/dof-manager.inp"};
|
const std::filesystem::path source{"models/dof-manager.inp"};
|
||||||
fesa::ModelDefinition definition{};
|
fesa::ModelDefinition definition{};
|
||||||
definition.source_path = source;
|
definition.source_path = source;
|
||||||
definition.source_content_identity = "fnv1a64:0123456789abcdef";
|
definition.source_content_identity = "fnv1a64:0123456789abcdef";
|
||||||
definition.nodes = {
|
definition.nodes = {{{"Beam-1", 10, "10"}, {0.0, 0.0, 0.0}, {source, 10U}},
|
||||||
{{"Beam-1", 10, "10"}, {0.0, 0.0, 0.0}, {source, 10U}},
|
|
||||||
{{"Beam-1", 20, "20"}, {1.0, 0.0, 0.0}, {source, 11U}},
|
{{"Beam-1", 20, "20"}, {1.0, 0.0, 0.0}, {source, 11U}},
|
||||||
{{"Beam-1", 30, "30"}, {2.0, 0.0, 0.0}, {source, 12U}}};
|
{{"Beam-1", 30, "30"}, {2.0, 0.0, 0.0}, {source, 12U}}};
|
||||||
definition.materials = {
|
definition.materials = {{"Material", 1000.0, 0.25, {source, 20U}}};
|
||||||
{"Material", 1000.0, 0.25, {source, 20U}}};
|
definition.sections = {
|
||||||
definition.sections = {{
|
{"Section", 1.0, 1.0, 0.0, 1.0, 1.0, {0.0, 1.0, 0.0}, {}, {source, 30U}}};
|
||||||
"Section", 1.0, 1.0, 0.0, 1.0, 1.0,
|
|
||||||
{0.0, 1.0, 0.0}, {}, {source, 30U}}};
|
|
||||||
definition.elements = {
|
definition.elements = {
|
||||||
{{"Beam-1", 100, "100"}, {0U, 1U}, 0U, 0U, {source, 40U}},
|
{{"Beam-1", 100, "100"}, {0U, 1U}, 0U, 0U, {source, 40U}},
|
||||||
{{"Beam-1", 200, "200"}, {1U, 2U}, 0U, 0U, {source, 41U}}};
|
{{"Beam-1", 200, "200"}, {1U, 2U}, 0U, 0U, {source, 41U}}};
|
||||||
definition.node_sets = {
|
definition.node_sets = {
|
||||||
{"Root", std::optional<std::string>{"Beam-1"}, {0U}, {source, 50U}},
|
{"Root", std::optional<std::string>{"Beam-1"}, {0U}, {source, 50U}},
|
||||||
{"Ends", std::optional<std::string>{"Beam-1"}, {0U, 2U}, {source, 51U}}};
|
{"Ends", std::optional<std::string>{"Beam-1"}, {0U, 2U}, {source, 51U}}};
|
||||||
definition.steps = {{
|
definition.steps = {{"Step-1",
|
||||||
"Step-1",
|
|
||||||
{{"Root", 1, 2, 0.0, {source, 60U}},
|
{{"Root", 1, 2, 0.0, {source, 60U}},
|
||||||
{"ends", 3, 3, 0.25, {source, 61U}},
|
{"ends", 3, 3, 0.25, {source, 61U}},
|
||||||
{"20", 6, 6, -0.5, {source, 62U}},
|
{"20", 6, 6, -0.5, {source, 62U}},
|
||||||
@@ -46,29 +42,24 @@ fesa::ModelDefinition makeDefinition() {
|
|||||||
return definition;
|
return definition;
|
||||||
}
|
}
|
||||||
|
|
||||||
fesa::ModelDefinition makeShellDefinition() {
|
fesa::ModelDefinition MakeShellDefinition() {
|
||||||
const std::filesystem::path source{"models/shell-dof-manager.inp"};
|
const std::filesystem::path source{"models/shell-dof-manager.inp"};
|
||||||
fesa::ModelDefinition definition{};
|
fesa::ModelDefinition definition{};
|
||||||
definition.source_path = source;
|
definition.source_path = source;
|
||||||
definition.source_content_identity = "fnv1a64:1122334455667788";
|
definition.source_content_identity = "fnv1a64:1122334455667788";
|
||||||
definition.nodes = {
|
definition.nodes = {{{"Shell-1", 10, "10"}, {0.0, 0.0, 0.0}, {source, 10U}},
|
||||||
{{"Shell-1", 10, "10"}, {0.0, 0.0, 0.0}, {source, 10U}},
|
|
||||||
{{"Shell-1", 20, "20"}, {1.0, 0.0, 0.0}, {source, 11U}},
|
{{"Shell-1", 20, "20"}, {1.0, 0.0, 0.0}, {source, 11U}},
|
||||||
{{"Shell-1", 30, "30"}, {1.0, 1.0, 0.0}, {source, 12U}},
|
{{"Shell-1", 30, "30"}, {1.0, 1.0, 0.0}, {source, 12U}},
|
||||||
{{"Shell-1", 40, "40"}, {0.0, 1.0, 0.0}, {source, 13U}}};
|
{{"Shell-1", 40, "40"}, {0.0, 1.0, 0.0}, {source, 13U}}};
|
||||||
definition.materials = {
|
definition.materials = {{"Material", 1000.0, 0.25, {source, 20U}}};
|
||||||
{"Material", 1000.0, 0.25, {source, 20U}}};
|
definition.shell_sections = {{"ShellSection", 0.1, 0U, {source, 30U}}};
|
||||||
definition.shell_sections = {
|
definition.shell_elements = {{{"Shell-1", 100, "100"},
|
||||||
{"ShellSection", 0.1, 0U, {source, 30U}}};
|
|
||||||
definition.shell_elements = {{
|
|
||||||
{"Shell-1", 100, "100"},
|
|
||||||
fesa::ShellSourceElementType::kS4,
|
fesa::ShellSourceElementType::kS4,
|
||||||
{2U, 0U, 3U, 1U},
|
{2U, 0U, 3U, 1U},
|
||||||
0U,
|
0U,
|
||||||
0U,
|
0U,
|
||||||
{source, 40U}}};
|
{source, 40U}}};
|
||||||
definition.steps = {{
|
definition.steps = {{"Step-1", {}, {}, 0.1, 1.0, 0.01, 1.0, {source, 50U}}};
|
||||||
"Step-1", {}, {}, 0.1, 1.0, 0.01, 1.0, {source, 50U}}};
|
|
||||||
return definition;
|
return definition;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -76,52 +67,49 @@ struct DofFixture {
|
|||||||
fesa::DofManager dofs;
|
fesa::DofManager dofs;
|
||||||
};
|
};
|
||||||
|
|
||||||
DofFixture makeDofFixture(fesa::ModelDefinition definition = makeDefinition()) {
|
DofFixture MakeDofFixture(fesa::ModelDefinition definition = MakeDefinition()) {
|
||||||
auto domain = fesa::Domain::Create(std::move(definition));
|
auto domain = fesa::Domain::Create(std::move(definition));
|
||||||
EXPECT_TRUE(domain.HasValue());
|
EXPECT_TRUE(domain.HasValue());
|
||||||
auto model = fesa::AnalysisModel::create(domain.Value());
|
auto model = fesa::AnalysisModel::Create(domain.Value());
|
||||||
EXPECT_TRUE(model.HasValue());
|
EXPECT_TRUE(model.HasValue());
|
||||||
auto dofs = fesa::DofManager::create(model.Value());
|
auto dofs = fesa::DofManager::Create(model.Value());
|
||||||
EXPECT_TRUE(dofs.HasValue());
|
EXPECT_TRUE(dofs.HasValue());
|
||||||
return {std::move(dofs.Value())};
|
return {std::move(dofs.Value())};
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<std::size_t> rowColumns(
|
std::vector<std::size_t> RowColumns(const fesa::SparsePattern& pattern,
|
||||||
const fesa::SparsePattern& pattern, std::size_t row) {
|
std::size_t row) {
|
||||||
return {
|
return {pattern.column_indices.begin() + pattern.row_offsets[row],
|
||||||
pattern.columnIndices.begin() + pattern.rowOffsets[row],
|
pattern.column_indices.begin() + pattern.row_offsets[row + 1U]};
|
||||||
pattern.columnIndices.begin() + pattern.rowOffsets[row + 1U]};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
TEST(DofManager, NumbersSixDofsAndFreeEquationsStably) {
|
TEST(DofManager, NumbersSixDofsAndFreeEquationsStably) {
|
||||||
const auto fixture = makeDofFixture();
|
const auto fixture = MakeDofFixture();
|
||||||
const auto& dofs = fixture.dofs;
|
const auto& dofs = fixture.dofs;
|
||||||
|
|
||||||
EXPECT_EQ(dofs.fullDofCount(), 18U);
|
EXPECT_EQ(dofs.FullDofCount(), 18U);
|
||||||
EXPECT_EQ(dofs.freeDofCount(), 13U);
|
EXPECT_EQ(dofs.FreeDofCount(), 13U);
|
||||||
EXPECT_EQ(dofs.constrainedDofCount(), 5U);
|
EXPECT_EQ(dofs.ConstrainedDofCount(), 5U);
|
||||||
EXPECT_EQ(dofs.fullDof(0U, fesa::DofComponent::ux), 0U);
|
EXPECT_EQ(dofs.FullDof(0U, fesa::DofComponent::kUx), 0U);
|
||||||
EXPECT_EQ(dofs.fullDof(0U, fesa::DofComponent::urz), 5U);
|
EXPECT_EQ(dofs.FullDof(0U, fesa::DofComponent::kUrz), 5U);
|
||||||
EXPECT_EQ(dofs.fullDof(1U, fesa::DofComponent::ux), 6U);
|
EXPECT_EQ(dofs.FullDof(1U, fesa::DofComponent::kUx), 6U);
|
||||||
EXPECT_EQ(dofs.fullDof(2U, fesa::DofComponent::urz), 17U);
|
EXPECT_EQ(dofs.FullDof(2U, fesa::DofComponent::kUrz), 17U);
|
||||||
EXPECT_EQ(
|
EXPECT_EQ(dofs.FreeDofs(),
|
||||||
dofs.freeDofs(),
|
(std::vector<std::size_t>{3U, 4U, 5U, 6U, 7U, 8U, 9U, 10U, 12U, 13U,
|
||||||
(std::vector<std::size_t>{
|
15U, 16U, 17U}));
|
||||||
3U, 4U, 5U, 6U, 7U, 8U, 9U, 10U, 12U, 13U, 15U, 16U, 17U}));
|
EXPECT_EQ(dofs.ConstrainedDofs(),
|
||||||
EXPECT_EQ(
|
|
||||||
dofs.constrainedDofs(),
|
|
||||||
(std::vector<std::size_t>{0U, 1U, 2U, 11U, 14U}));
|
(std::vector<std::size_t>{0U, 1U, 2U, 11U, 14U}));
|
||||||
EXPECT_EQ(dofs.freeEquation(0U), std::nullopt);
|
EXPECT_EQ(dofs.FreeEquation(0U), std::nullopt);
|
||||||
EXPECT_EQ(dofs.freeEquation(3U), std::optional<std::size_t>{0U});
|
EXPECT_EQ(dofs.FreeEquation(3U), std::optional<std::size_t>{0U});
|
||||||
EXPECT_EQ(dofs.freeEquation(10U), std::optional<std::size_t>{7U});
|
EXPECT_EQ(dofs.FreeEquation(10U), std::optional<std::size_t>{7U});
|
||||||
EXPECT_EQ(dofs.freeEquation(17U), std::optional<std::size_t>{12U});
|
EXPECT_EQ(dofs.FreeEquation(17U), std::optional<std::size_t>{12U});
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(DofManager, ExpandsAndValidatesPrescribedValues) {
|
TEST(DofManager, ExpandsAndValidatesPrescribedValues) {
|
||||||
const auto fixture = makeDofFixture();
|
const auto fixture = MakeDofFixture();
|
||||||
const auto& values = fixture.dofs.prescribedValues();
|
const auto& values = fixture.dofs.PrescribedValues();
|
||||||
ASSERT_EQ(values.Size(), 5U);
|
ASSERT_EQ(values.Size(), 5U);
|
||||||
EXPECT_DOUBLE_EQ(values[0], 0.0);
|
EXPECT_DOUBLE_EQ(values[0], 0.0);
|
||||||
EXPECT_DOUBLE_EQ(values[1], 0.0);
|
EXPECT_DOUBLE_EQ(values[1], 0.0);
|
||||||
@@ -129,15 +117,15 @@ TEST(DofManager, ExpandsAndValidatesPrescribedValues) {
|
|||||||
EXPECT_DOUBLE_EQ(values[3], -0.5);
|
EXPECT_DOUBLE_EQ(values[3], -0.5);
|
||||||
EXPECT_DOUBLE_EQ(values[4], 0.25);
|
EXPECT_DOUBLE_EQ(values[4], 0.25);
|
||||||
|
|
||||||
auto conflictingDefinition = makeDefinition();
|
auto conflicting_definition = MakeDefinition();
|
||||||
conflictingDefinition.steps[0].boundaries.push_back(
|
conflicting_definition.steps[0].boundaries.push_back(
|
||||||
{"root", 1, 1, 1.0, {conflictingDefinition.source_path, 77U}});
|
{"root", 1, 1, 1.0, {conflicting_definition.source_path, 77U}});
|
||||||
auto domain = fesa::Domain::Create(std::move(conflictingDefinition));
|
auto domain = fesa::Domain::Create(std::move(conflicting_definition));
|
||||||
ASSERT_TRUE(domain.HasValue());
|
ASSERT_TRUE(domain.HasValue());
|
||||||
auto model = fesa::AnalysisModel::create(domain.Value());
|
auto model = fesa::AnalysisModel::Create(domain.Value());
|
||||||
ASSERT_TRUE(model.HasValue());
|
ASSERT_TRUE(model.HasValue());
|
||||||
|
|
||||||
auto conflict = fesa::DofManager::create(model.Value());
|
auto conflict = fesa::DofManager::Create(model.Value());
|
||||||
ASSERT_FALSE(conflict.HasValue());
|
ASSERT_FALSE(conflict.HasValue());
|
||||||
EXPECT_EQ(conflict.GetStatus().Category(), fesa::FailureCategory::kInput);
|
EXPECT_EQ(conflict.GetStatus().Category(), fesa::FailureCategory::kInput);
|
||||||
ASSERT_EQ(conflict.GetStatus().Diagnostics().size(), 1U);
|
ASSERT_EQ(conflict.GetStatus().Diagnostics().size(), 1U);
|
||||||
@@ -145,105 +133,101 @@ TEST(DofManager, ExpandsAndValidatesPrescribedValues) {
|
|||||||
EXPECT_EQ(diagnostic.code, "conflicting-boundary-condition");
|
EXPECT_EQ(diagnostic.code, "conflicting-boundary-condition");
|
||||||
EXPECT_EQ(diagnostic.keyword, "BOUNDARY");
|
EXPECT_EQ(diagnostic.keyword, "BOUNDARY");
|
||||||
EXPECT_EQ(diagnostic.entity_identity, "root");
|
EXPECT_EQ(diagnostic.entity_identity, "root");
|
||||||
EXPECT_EQ(diagnostic.location.file, std::filesystem::path{"models/dof-manager.inp"});
|
EXPECT_EQ(diagnostic.location.file,
|
||||||
|
std::filesystem::path{"models/dof-manager.inp"});
|
||||||
EXPECT_EQ(diagnostic.location.line, 77U);
|
EXPECT_EQ(diagnostic.location.line, 77U);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(DofManager, BuildsTwelveDofScatterAndSortedUniquePattern) {
|
TEST(DofManager, BuildsTwelveDofScatterAndSortedUniquePattern) {
|
||||||
const auto fixture = makeDofFixture();
|
const auto fixture = MakeDofFixture();
|
||||||
const auto& dofs = fixture.dofs;
|
const auto& dofs = fixture.dofs;
|
||||||
|
|
||||||
EXPECT_EQ(
|
EXPECT_EQ(dofs.ElementScatter(0U),
|
||||||
dofs.elementScatter(0U),
|
(std::array<std::size_t, 12>{0U, 1U, 2U, 3U, 4U, 5U, 6U, 7U, 8U, 9U,
|
||||||
(std::array<std::size_t, 12>{
|
10U, 11U}));
|
||||||
0U, 1U, 2U, 3U, 4U, 5U, 6U, 7U, 8U, 9U, 10U, 11U}));
|
EXPECT_EQ(dofs.ElementScatter(1U),
|
||||||
EXPECT_EQ(
|
(std::array<std::size_t, 12>{6U, 7U, 8U, 9U, 10U, 11U, 12U, 13U,
|
||||||
dofs.elementScatter(1U),
|
14U, 15U, 16U, 17U}));
|
||||||
(std::array<std::size_t, 12>{
|
|
||||||
6U, 7U, 8U, 9U, 10U, 11U,
|
|
||||||
12U, 13U, 14U, 15U, 16U, 17U}));
|
|
||||||
|
|
||||||
const auto& pattern = dofs.sparsePattern();
|
const auto& pattern = dofs.GetSparsePattern();
|
||||||
EXPECT_EQ(
|
EXPECT_EQ(pattern.row_offsets,
|
||||||
pattern.rowOffsets,
|
(std::vector<std::size_t>{0U, 12U, 24U, 36U, 48U, 60U, 72U, 90U,
|
||||||
(std::vector<std::size_t>{
|
108U, 126U, 144U, 162U, 180U, 192U, 204U,
|
||||||
0U, 12U, 24U, 36U, 48U, 60U, 72U,
|
216U, 228U, 240U, 252U}));
|
||||||
90U, 108U, 126U, 144U, 162U, 180U,
|
const std::vector<std::size_t> first_block{0U, 1U, 2U, 3U, 4U, 5U,
|
||||||
192U, 204U, 216U, 228U, 240U, 252U}));
|
6U, 7U, 8U, 9U, 10U, 11U};
|
||||||
const std::vector<std::size_t> firstBlock{
|
const std::vector<std::size_t> shared_block{0U, 1U, 2U, 3U, 4U, 5U,
|
||||||
0U, 1U, 2U, 3U, 4U, 5U, 6U, 7U, 8U, 9U, 10U, 11U};
|
6U, 7U, 8U, 9U, 10U, 11U,
|
||||||
const std::vector<std::size_t> sharedBlock{
|
12U, 13U, 14U, 15U, 16U, 17U};
|
||||||
0U, 1U, 2U, 3U, 4U, 5U, 6U, 7U, 8U,
|
const std::vector<std::size_t> last_block{6U, 7U, 8U, 9U, 10U, 11U,
|
||||||
9U, 10U, 11U, 12U, 13U, 14U, 15U, 16U, 17U};
|
12U, 13U, 14U, 15U, 16U, 17U};
|
||||||
const std::vector<std::size_t> lastBlock{
|
EXPECT_EQ(RowColumns(pattern, 0U), first_block);
|
||||||
6U, 7U, 8U, 9U, 10U, 11U, 12U, 13U, 14U, 15U, 16U, 17U};
|
EXPECT_EQ(RowColumns(pattern, 7U), shared_block);
|
||||||
EXPECT_EQ(rowColumns(pattern, 0U), firstBlock);
|
EXPECT_EQ(RowColumns(pattern, 17U), last_block);
|
||||||
EXPECT_EQ(rowColumns(pattern, 7U), sharedBlock);
|
for (std::size_t row = 0U; row < dofs.FullDofCount(); ++row) {
|
||||||
EXPECT_EQ(rowColumns(pattern, 17U), lastBlock);
|
const auto columns = RowColumns(pattern, row);
|
||||||
for (std::size_t row = 0U; row < dofs.fullDofCount(); ++row) {
|
|
||||||
const auto columns = rowColumns(pattern, row);
|
|
||||||
EXPECT_TRUE(std::is_sorted(columns.begin(), columns.end()));
|
EXPECT_TRUE(std::is_sorted(columns.begin(), columns.end()));
|
||||||
EXPECT_EQ(std::adjacent_find(columns.begin(), columns.end()), columns.end());
|
EXPECT_EQ(std::adjacent_find(columns.begin(), columns.end()),
|
||||||
|
columns.end());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MITC4-DOF-001
|
// MITC4-DOF-001
|
||||||
TEST(DofManager, BuildsShellScatterInSourceNodeAndComponentOrder) {
|
TEST(DofManager, BuildsShellScatterInSourceNodeAndComponentOrder) {
|
||||||
const auto fixture = makeDofFixture(makeShellDefinition());
|
const auto fixture = MakeDofFixture(MakeShellDefinition());
|
||||||
const auto& dofs = fixture.dofs;
|
const auto& dofs = fixture.dofs;
|
||||||
|
|
||||||
EXPECT_EQ(dofs.fullDofCount(), 24U);
|
EXPECT_EQ(dofs.FullDofCount(), 24U);
|
||||||
EXPECT_EQ(
|
EXPECT_EQ(dofs.ShellElementScatter(0U),
|
||||||
dofs.shellElementScatter(0U),
|
|
||||||
(std::array<std::size_t, 24>{
|
(std::array<std::size_t, 24>{
|
||||||
12U, 13U, 14U, 15U, 16U, 17U,
|
12U, 13U, 14U, 15U, 16U, 17U, 0U, 1U, 2U, 3U, 4U, 5U,
|
||||||
0U, 1U, 2U, 3U, 4U, 5U,
|
18U, 19U, 20U, 21U, 22U, 23U, 6U, 7U, 8U, 9U, 10U, 11U}));
|
||||||
18U, 19U, 20U, 21U, 22U, 23U,
|
|
||||||
6U, 7U, 8U, 9U, 10U, 11U}));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// MITC4-DOF-002
|
// MITC4-DOF-002
|
||||||
TEST(DofManager, IncludesShellConnectivityInSortedUniqueSparsePattern) {
|
TEST(DofManager, IncludesShellConnectivityInSortedUniqueSparsePattern) {
|
||||||
const auto fixture = makeDofFixture(makeShellDefinition());
|
const auto fixture = MakeDofFixture(MakeShellDefinition());
|
||||||
const auto& dofs = fixture.dofs;
|
const auto& dofs = fixture.dofs;
|
||||||
const auto& pattern = dofs.sparsePattern();
|
const auto& pattern = dofs.GetSparsePattern();
|
||||||
|
|
||||||
ASSERT_EQ(pattern.rowOffsets.size(), 25U);
|
ASSERT_EQ(pattern.row_offsets.size(), 25U);
|
||||||
ASSERT_EQ(pattern.columnIndices.size(), 24U * 24U);
|
ASSERT_EQ(pattern.column_indices.size(), 24U * 24U);
|
||||||
for (std::size_t row = 0U; row < dofs.fullDofCount(); ++row) {
|
for (std::size_t row = 0U; row < dofs.FullDofCount(); ++row) {
|
||||||
EXPECT_EQ(pattern.rowOffsets[row], row * 24U);
|
EXPECT_EQ(pattern.row_offsets[row], row * 24U);
|
||||||
EXPECT_EQ(pattern.rowOffsets[row + 1U], (row + 1U) * 24U);
|
EXPECT_EQ(pattern.row_offsets[row + 1U], (row + 1U) * 24U);
|
||||||
const auto columns = rowColumns(pattern, row);
|
const auto columns = RowColumns(pattern, row);
|
||||||
ASSERT_EQ(columns.size(), 24U);
|
ASSERT_EQ(columns.size(), 24U);
|
||||||
for (std::size_t column = 0U; column < columns.size(); ++column) {
|
for (std::size_t column = 0U; column < columns.size(); ++column) {
|
||||||
EXPECT_EQ(columns[column], column);
|
EXPECT_EQ(columns[column], column);
|
||||||
}
|
}
|
||||||
EXPECT_TRUE(std::binary_search(columns.begin(), columns.end(), row));
|
EXPECT_TRUE(std::binary_search(columns.begin(), columns.end(), row));
|
||||||
EXPECT_EQ(std::adjacent_find(columns.begin(), columns.end()), columns.end());
|
EXPECT_EQ(std::adjacent_find(columns.begin(), columns.end()),
|
||||||
|
columns.end());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(DofManager, ReconstructsFullReducedRoundTrip) {
|
TEST(DofManager, ReconstructsFullReducedRoundTrip) {
|
||||||
const auto fixture = makeDofFixture();
|
const auto fixture = MakeDofFixture();
|
||||||
const auto& dofs = fixture.dofs;
|
const auto& dofs = fixture.dofs;
|
||||||
fesa::Vector full{dofs.fullDofCount()};
|
fesa::Vector full{dofs.FullDofCount()};
|
||||||
for (std::size_t index = 0U; index < full.Size(); ++index) {
|
for (std::size_t index = 0U; index < full.Size(); ++index) {
|
||||||
full[index] = static_cast<double>(index) + 0.5;
|
full[index] = static_cast<double>(index) + 0.5;
|
||||||
}
|
}
|
||||||
for (std::size_t index = 0U; index < dofs.constrainedDofCount(); ++index) {
|
for (std::size_t index = 0U; index < dofs.ConstrainedDofCount(); ++index) {
|
||||||
full[dofs.constrainedDofs()[index]] = dofs.prescribedValues()[index];
|
full[dofs.ConstrainedDofs()[index]] = dofs.PrescribedValues()[index];
|
||||||
}
|
}
|
||||||
|
|
||||||
fesa::Vector reduced{dofs.freeDofCount()};
|
fesa::Vector reduced{dofs.FreeDofCount()};
|
||||||
for (std::size_t equation = 0U; equation < reduced.Size(); ++equation) {
|
for (std::size_t equation = 0U; equation < reduced.Size(); ++equation) {
|
||||||
reduced[equation] = full[dofs.freeDofs()[equation]];
|
reduced[equation] = full[dofs.FreeDofs()[equation]];
|
||||||
}
|
}
|
||||||
fesa::Vector reconstructed{dofs.fullDofCount()};
|
fesa::Vector reconstructed{dofs.FullDofCount()};
|
||||||
for (std::size_t equation = 0U; equation < reduced.Size(); ++equation) {
|
for (std::size_t equation = 0U; equation < reduced.Size(); ++equation) {
|
||||||
reconstructed[dofs.freeDofs()[equation]] = reduced[equation];
|
reconstructed[dofs.FreeDofs()[equation]] = reduced[equation];
|
||||||
}
|
}
|
||||||
for (std::size_t index = 0U; index < dofs.constrainedDofCount(); ++index) {
|
for (std::size_t index = 0U; index < dofs.ConstrainedDofCount(); ++index) {
|
||||||
reconstructed[dofs.constrainedDofs()[index]] = dofs.prescribedValues()[index];
|
reconstructed[dofs.ConstrainedDofs()[index]] =
|
||||||
|
dofs.PrescribedValues()[index];
|
||||||
}
|
}
|
||||||
|
|
||||||
ASSERT_EQ(reconstructed.Size(), full.Size());
|
ASSERT_EQ(reconstructed.Size(), full.Size());
|
||||||
|
|||||||
@@ -3,10 +3,10 @@
|
|||||||
|
|
||||||
#include "fesa/io/hdf5/hdf5_results_writer.hpp"
|
#include "fesa/io/hdf5/hdf5_results_writer.hpp"
|
||||||
|
|
||||||
#include "fesa/analysis/analysis_model.hpp"
|
#include "fesa/analysis/analysis_model.h"
|
||||||
#include "fesa/analysis/analysis_state.hpp"
|
#include "fesa/analysis/analysis_state.h"
|
||||||
#include "fesa/build_info.h"
|
#include "fesa/build_info.h"
|
||||||
#include "fesa/fem/dof_manager.hpp"
|
#include "fesa/fem/dof_manager.h"
|
||||||
#include "fesa/model/domain.h"
|
#include "fesa/model/domain.h"
|
||||||
|
|
||||||
#include <hdf5.h>
|
#include <hdf5.h>
|
||||||
@@ -164,30 +164,30 @@ WriterFixture makeFixture(
|
|||||||
auto domain = std::make_unique<fesa::Domain>(
|
auto domain = std::make_unique<fesa::Domain>(
|
||||||
std::move(domainResult.Value()));
|
std::move(domainResult.Value()));
|
||||||
|
|
||||||
auto modelResult = fesa::AnalysisModel::create(*domain);
|
auto modelResult = fesa::AnalysisModel::Create(*domain);
|
||||||
if (!modelResult.HasValue()) {
|
if (!modelResult.HasValue()) {
|
||||||
throw std::runtime_error{"Writer fixture AnalysisModel construction failed."};
|
throw std::runtime_error{"Writer fixture AnalysisModel construction failed."};
|
||||||
}
|
}
|
||||||
const fesa::AnalysisModel model = std::move(modelResult.Value());
|
const fesa::AnalysisModel model = std::move(modelResult.Value());
|
||||||
auto dofsResult = fesa::DofManager::create(model);
|
auto dofsResult = fesa::DofManager::Create(model);
|
||||||
if (!dofsResult.HasValue()) {
|
if (!dofsResult.HasValue()) {
|
||||||
throw std::runtime_error{"Writer fixture DofManager construction failed."};
|
throw std::runtime_error{"Writer fixture DofManager construction failed."};
|
||||||
}
|
}
|
||||||
auto dofs = std::make_unique<fesa::DofManager>(
|
auto dofs = std::make_unique<fesa::DofManager>(
|
||||||
std::move(dofsResult.Value()));
|
std::move(dofsResult.Value()));
|
||||||
auto state = std::make_unique<fesa::AnalysisState>(
|
auto state = std::make_unique<fesa::AnalysisState>(
|
||||||
fesa::AnalysisState::create(*dofs, {"Step-1", 0U}));
|
fesa::AnalysisState::Create(*dofs, {"Step-1", 0U}));
|
||||||
|
|
||||||
for (std::size_t index = 0U; index < state->displacement().Size(); ++index) {
|
for (std::size_t index = 0U; index < state->Displacement().Size(); ++index) {
|
||||||
state->displacement()[index] = 0.25 + static_cast<double>(index);
|
state->Displacement()[index] = 0.25 + static_cast<double>(index);
|
||||||
state->externalForce()[index] = 100.0 + static_cast<double>(index);
|
state->ExternalForce()[index] = 100.0 + static_cast<double>(index);
|
||||||
state->internalForce()[index] = 200.0 + 2.0 * static_cast<double>(index);
|
state->InternalForce()[index] = 200.0 + 2.0 * static_cast<double>(index);
|
||||||
state->residual()[index] = 100.0 + static_cast<double>(index);
|
state->Residual()[index] = 100.0 + static_cast<double>(index);
|
||||||
state->reaction()[index] = 100.0 + static_cast<double>(index);
|
state->Reaction()[index] = 100.0 + static_cast<double>(index);
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto& nodes = domain->Nodes();
|
const auto& nodes = domain->Nodes();
|
||||||
state->endpointResults() = {
|
state->EndpointResults() = {
|
||||||
{0U,
|
{0U,
|
||||||
0,
|
0,
|
||||||
nodes[0U].source_id,
|
nodes[0U].source_id,
|
||||||
@@ -198,15 +198,15 @@ WriterFixture makeFixture(
|
|||||||
nodes[1U].source_id,
|
nodes[1U].source_id,
|
||||||
{7.0, 8.0, 9.0, 10.0, 11.0, 12.0},
|
{7.0, 8.0, 9.0, 10.0, 11.0, 12.0},
|
||||||
{15.0, 16.0, 17.0, 18.0}}};
|
{15.0, 16.0, 17.0, 18.0}}};
|
||||||
state->gaussResults() = {
|
state->GaussResults() = {
|
||||||
{0U, 1, {0.01, 0.02, 0.03, 0.04}, {21.0, 22.0, 23.0, 24.0}},
|
{0U, 1, {0.01, 0.02, 0.03, 0.04}, {21.0, 22.0, 23.0, 24.0}},
|
||||||
{0U, 2, {0.05, 0.06, 0.07, 0.08}, {25.0, 26.0, 27.0, 28.0}}};
|
{0U, 2, {0.05, 0.06, 0.07, 0.08}, {25.0, 26.0, 27.0, 28.0}}};
|
||||||
if (useDefaultCentroid) {
|
if (useDefaultCentroid) {
|
||||||
state->stressResults() = {
|
state->StressResults() = {
|
||||||
{0U, 1, 0U, 0.0, 0.0, 31.0, "fesa-default"},
|
{0U, 1, 0U, 0.0, 0.0, 31.0, "fesa-default"},
|
||||||
{0U, 2, 0U, 0.0, 0.0, 32.0, "fesa-default"}};
|
{0U, 2, 0U, 0.0, 0.0, 32.0, "fesa-default"}};
|
||||||
} else {
|
} else {
|
||||||
state->stressResults() = {
|
state->StressResults() = {
|
||||||
{0U, 1, 1U, -0.1, 0.2, 31.0, "input"},
|
{0U, 1, 1U, -0.1, 0.2, 31.0, "input"},
|
||||||
{0U, 1, 2U, 0.3, -0.4, 32.0, "input"},
|
{0U, 1, 2U, 0.3, -0.4, 32.0, "input"},
|
||||||
{0U, 2, 1U, -0.1, 0.2, 33.0, "input"},
|
{0U, 2, 1U, -0.1, 0.2, 33.0, "input"},
|
||||||
@@ -264,25 +264,25 @@ WriterFixture makeShellFixture(const std::filesystem::path& source) {
|
|||||||
}
|
}
|
||||||
auto domain = std::make_unique<fesa::Domain>(
|
auto domain = std::make_unique<fesa::Domain>(
|
||||||
std::move(domainResult.Value()));
|
std::move(domainResult.Value()));
|
||||||
auto modelResult = fesa::AnalysisModel::create(*domain);
|
auto modelResult = fesa::AnalysisModel::Create(*domain);
|
||||||
if (!modelResult.HasValue()) {
|
if (!modelResult.HasValue()) {
|
||||||
throw std::runtime_error{"Shell writer fixture AnalysisModel construction failed."};
|
throw std::runtime_error{"Shell writer fixture AnalysisModel construction failed."};
|
||||||
}
|
}
|
||||||
const fesa::AnalysisModel model = std::move(modelResult.Value());
|
const fesa::AnalysisModel model = std::move(modelResult.Value());
|
||||||
auto dofsResult = fesa::DofManager::create(model);
|
auto dofsResult = fesa::DofManager::Create(model);
|
||||||
if (!dofsResult.HasValue()) {
|
if (!dofsResult.HasValue()) {
|
||||||
throw std::runtime_error{"Shell writer fixture DofManager construction failed."};
|
throw std::runtime_error{"Shell writer fixture DofManager construction failed."};
|
||||||
}
|
}
|
||||||
auto dofs = std::make_unique<fesa::DofManager>(
|
auto dofs = std::make_unique<fesa::DofManager>(
|
||||||
std::move(dofsResult.Value()));
|
std::move(dofsResult.Value()));
|
||||||
auto state = std::make_unique<fesa::AnalysisState>(
|
auto state = std::make_unique<fesa::AnalysisState>(
|
||||||
fesa::AnalysisState::create(*dofs, {"Step-1", 0U}));
|
fesa::AnalysisState::Create(*dofs, {"Step-1", 0U}));
|
||||||
for (std::size_t index = 0U; index < state->displacement().Size(); ++index) {
|
for (std::size_t index = 0U; index < state->Displacement().Size(); ++index) {
|
||||||
state->displacement()[index] = 0.01 * static_cast<double>(index + 1U);
|
state->Displacement()[index] = 0.01 * static_cast<double>(index + 1U);
|
||||||
state->externalForce()[index] = 10.0 + static_cast<double>(index);
|
state->ExternalForce()[index] = 10.0 + static_cast<double>(index);
|
||||||
state->internalForce()[index] = 20.0 + static_cast<double>(index);
|
state->InternalForce()[index] = 20.0 + static_cast<double>(index);
|
||||||
state->residual()[index] = 30.0 + static_cast<double>(index);
|
state->Residual()[index] = 30.0 + static_cast<double>(index);
|
||||||
state->reaction()[index] = 40.0 + static_cast<double>(index);
|
state->Reaction()[index] = 40.0 + static_cast<double>(index);
|
||||||
}
|
}
|
||||||
|
|
||||||
const double gauss = 1.0 / std::sqrt(3.0);
|
const double gauss = 1.0 / std::sqrt(3.0);
|
||||||
@@ -292,10 +292,10 @@ WriterFixture makeShellFixture(const std::filesystem::path& source) {
|
|||||||
{gauss, gauss},
|
{gauss, gauss},
|
||||||
{-gauss, gauss}}};
|
{-gauss, gauss}}};
|
||||||
const std::array<fesa::ShellMidsurfaceLocation, 4> locations{
|
const std::array<fesa::ShellMidsurfaceLocation, 4> locations{
|
||||||
fesa::ShellMidsurfaceLocation::gp1,
|
fesa::ShellMidsurfaceLocation::kGp1,
|
||||||
fesa::ShellMidsurfaceLocation::gp2,
|
fesa::ShellMidsurfaceLocation::kGp2,
|
||||||
fesa::ShellMidsurfaceLocation::gp3,
|
fesa::ShellMidsurfaceLocation::kGp3,
|
||||||
fesa::ShellMidsurfaceLocation::gp4};
|
fesa::ShellMidsurfaceLocation::kGp4};
|
||||||
fesa::ShellStateCandidate candidate{};
|
fesa::ShellStateCandidate candidate{};
|
||||||
for (std::size_t point = 0U; point < locations.size(); ++point) {
|
for (std::size_t point = 0U; point < locations.size(); ++point) {
|
||||||
const double base = 100.0 * static_cast<double>(point + 1U);
|
const double base = 100.0 * static_cast<double>(point + 1U);
|
||||||
@@ -310,20 +310,20 @@ WriterFixture makeShellFixture(const std::filesystem::path& source) {
|
|||||||
base + 5.0, base + 6.0, base + 7.0, base + 8.0},
|
base + 5.0, base + 6.0, base + 7.0, base + 8.0},
|
||||||
{base + 11.0, base + 12.0, base + 13.0, base + 14.0,
|
{base + 11.0, base + 12.0, base + 13.0, base + 14.0,
|
||||||
base + 15.0, base + 16.0, base + 17.0, base + 18.0},
|
base + 15.0, base + 16.0, base + 17.0, base + 18.0},
|
||||||
{{{fesa::ShellSectionPosition::bottom,
|
{{{fesa::ShellSectionPosition::kBottom,
|
||||||
-1.0,
|
-1.0,
|
||||||
{base + 21.0, base + 22.0, base + 23.0}},
|
{base + 21.0, base + 22.0, base + 23.0}},
|
||||||
{fesa::ShellSectionPosition::middle,
|
{fesa::ShellSectionPosition::kMiddle,
|
||||||
0.0,
|
0.0,
|
||||||
{base + 24.0, base + 25.0, base + 26.0}},
|
{base + 24.0, base + 25.0, base + 26.0}},
|
||||||
{fesa::ShellSectionPosition::top,
|
{fesa::ShellSectionPosition::kTop,
|
||||||
1.0,
|
1.0,
|
||||||
{base + 27.0, base + 28.0, base + 29.0}}}}});
|
{base + 27.0, base + 28.0, base + 29.0}}}}});
|
||||||
}
|
}
|
||||||
candidate.physicalStrainEnergy = 123.5;
|
candidate.physical_strain_energy = 123.5;
|
||||||
candidate.equilibrium = {1.0, 2.0, 3.0, 4.0, 5.0, 6.0};
|
candidate.equilibrium = {1.0, 2.0, 3.0, 4.0, 5.0, 6.0};
|
||||||
candidate.verificationMetrics = {1.0e-13, 2.0e-13, 3.0e-13};
|
candidate.verification_metrics = {1.0e-13, 2.0e-13, 3.0e-13};
|
||||||
const fesa::Status commit = state->commitShellResults(
|
const fesa::Status commit = state->CommitShellResults(
|
||||||
{0U}, std::move(candidate));
|
{0U}, std::move(candidate));
|
||||||
if (!commit.IsOk()) {
|
if (!commit.IsOk()) {
|
||||||
throw std::runtime_error{"Shell writer fixture state commit failed."};
|
throw std::runtime_error{"Shell writer fixture state commit failed."};
|
||||||
@@ -846,7 +846,7 @@ TEST(Hdf5ResultsWriter, WritesExactSchemaShapesAttributesAndIdentity) {
|
|||||||
const auto output = directory.path() / "results.h5";
|
const auto output = directory.path() / "results.h5";
|
||||||
|
|
||||||
fesa::Hdf5ResultsWriter writer;
|
fesa::Hdf5ResultsWriter writer;
|
||||||
ASSERT_TRUE(writer.write(output, *fixture.domain, *fixture.state, {}).IsOk());
|
ASSERT_TRUE(writer.Write(output, *fixture.domain, *fixture.state, {}).IsOk());
|
||||||
ASSERT_GT(H5Fis_hdf5(output.string().c_str()), 0);
|
ASSERT_GT(H5Fis_hdf5(output.string().c_str()), 0);
|
||||||
|
|
||||||
const auto file = openFile(output);
|
const auto file = openFile(output);
|
||||||
@@ -1041,7 +1041,7 @@ TEST(Hdf5ResultsWriter, WritesMandatoryOutputsDespiteOutputRequests) {
|
|||||||
|
|
||||||
fesa::Hdf5ResultsWriter writer;
|
fesa::Hdf5ResultsWriter writer;
|
||||||
ASSERT_TRUE(
|
ASSERT_TRUE(
|
||||||
writer.write(output, *fixture.domain, *fixture.state, {ignoredRequest})
|
writer.Write(output, *fixture.domain, *fixture.state, {ignoredRequest})
|
||||||
.IsOk());
|
.IsOk());
|
||||||
const auto file = openFile(output);
|
const auto file = openFile(output);
|
||||||
for (const char* suffix : {
|
for (const char* suffix : {
|
||||||
@@ -1078,7 +1078,7 @@ TEST(Hdf5ResultsWriter, WritesWarningsAndDefaultCentroid) {
|
|||||||
const auto output = directory.path() / "results.h5";
|
const auto output = directory.path() / "results.h5";
|
||||||
|
|
||||||
fesa::Hdf5ResultsWriter writer;
|
fesa::Hdf5ResultsWriter writer;
|
||||||
ASSERT_TRUE(writer.write(output, *fixture.domain, *fixture.state, diagnostics).IsOk());
|
ASSERT_TRUE(writer.Write(output, *fixture.domain, *fixture.state, diagnostics).IsOk());
|
||||||
const auto file = openFile(output);
|
const auto file = openFile(output);
|
||||||
auto stressRows = readStressRows(file.get());
|
auto stressRows = readStressRows(file.get());
|
||||||
ASSERT_EQ(stressRows.size(), 2U);
|
ASSERT_EQ(stressRows.size(), 2U);
|
||||||
@@ -1116,15 +1116,15 @@ TEST(Hdf5ResultsWriter, FailureLeavesNoPartialAndPreservesExistingFinal) {
|
|||||||
auto fixture = makeFixture(directory.path() / "failure.inp");
|
auto fixture = makeFixture(directory.path() / "failure.inp");
|
||||||
fesa::Hdf5ResultsWriter writer;
|
fesa::Hdf5ResultsWriter writer;
|
||||||
|
|
||||||
fixture.state->displacement()[0U] =
|
fixture.state->Displacement()[0U] =
|
||||||
std::numeric_limits<double>::quiet_NaN();
|
std::numeric_limits<double>::quiet_NaN();
|
||||||
const auto invalidOutput = directory.path() / "invalid-results.h5";
|
const auto invalidOutput = directory.path() / "invalid-results.h5";
|
||||||
expectOutputFailure(
|
expectOutputFailure(
|
||||||
writer.write(invalidOutput, *fixture.domain, *fixture.state, {}),
|
writer.Write(invalidOutput, *fixture.domain, *fixture.state, {}),
|
||||||
"invalid-result-state");
|
"invalid-result-state");
|
||||||
EXPECT_FALSE(std::filesystem::exists(invalidOutput));
|
EXPECT_FALSE(std::filesystem::exists(invalidOutput));
|
||||||
EXPECT_EQ(entryCount(directory.path()), 0U);
|
EXPECT_EQ(entryCount(directory.path()), 0U);
|
||||||
fixture.state->displacement()[0U] = 0.25;
|
fixture.state->Displacement()[0U] = 0.25;
|
||||||
|
|
||||||
const auto final = directory.path() / "results.h5";
|
const auto final = directory.path() / "results.h5";
|
||||||
const std::vector<char> sentinel = {'p', 'r', 'e', 'v', 'i', 'o', 'u', 's'};
|
const std::vector<char> sentinel = {'p', 'r', 'e', 'v', 'i', 'o', 'u', 's'};
|
||||||
@@ -1140,7 +1140,7 @@ TEST(Hdf5ResultsWriter, FailureLeavesNoPartialAndPreservesExistingFinal) {
|
|||||||
ASSERT_NE(lock.get(), INVALID_HANDLE_VALUE);
|
ASSERT_NE(lock.get(), INVALID_HANDLE_VALUE);
|
||||||
|
|
||||||
expectOutputFailure(
|
expectOutputFailure(
|
||||||
writer.write(final, *fixture.domain, *fixture.state, {}),
|
writer.Write(final, *fixture.domain, *fixture.state, {}),
|
||||||
"hdf5-finalization-failure");
|
"hdf5-finalization-failure");
|
||||||
EXPECT_EQ(readBytes(final), sentinel);
|
EXPECT_EQ(readBytes(final), sentinel);
|
||||||
EXPECT_EQ(entryCount(directory.path()), 1U);
|
EXPECT_EQ(entryCount(directory.path()), 1U);
|
||||||
@@ -1153,7 +1153,7 @@ TEST(Hdf5ResultsWriter, SuccessfullyReplacesExistingFinal) {
|
|||||||
writeBytes(final, {'o', 'l', 'd'});
|
writeBytes(final, {'o', 'l', 'd'});
|
||||||
|
|
||||||
fesa::Hdf5ResultsWriter writer;
|
fesa::Hdf5ResultsWriter writer;
|
||||||
ASSERT_TRUE(writer.write(final, *fixture.domain, *fixture.state, {}).IsOk());
|
ASSERT_TRUE(writer.Write(final, *fixture.domain, *fixture.state, {}).IsOk());
|
||||||
EXPECT_GT(H5Fis_hdf5(final.string().c_str()), 0);
|
EXPECT_GT(H5Fis_hdf5(final.string().c_str()), 0);
|
||||||
EXPECT_EQ(entryCount(directory.path()), 1U);
|
EXPECT_EQ(entryCount(directory.path()), 1U);
|
||||||
const auto file = openFile(final);
|
const auto file = openFile(final);
|
||||||
@@ -1171,7 +1171,7 @@ TEST(Hdf5ResultsWriter, WritesExactShellMetadataAndModelIdentity) {
|
|||||||
const auto output = directory.path() / "results.h5";
|
const auto output = directory.path() / "results.h5";
|
||||||
|
|
||||||
fesa::Hdf5ResultsWriter writer;
|
fesa::Hdf5ResultsWriter writer;
|
||||||
ASSERT_TRUE(writer.write(output, *fixture.domain, *fixture.state, {}).IsOk());
|
ASSERT_TRUE(writer.Write(output, *fixture.domain, *fixture.state, {}).IsOk());
|
||||||
const auto file = openFile(output);
|
const auto file = openFile(output);
|
||||||
|
|
||||||
Hdf5Handle metadata{
|
Hdf5Handle metadata{
|
||||||
@@ -1261,7 +1261,7 @@ TEST(Hdf5ResultsWriter, WritesExactMandatoryShellResultInventory) {
|
|||||||
const auto output = directory.path() / "results.h5";
|
const auto output = directory.path() / "results.h5";
|
||||||
|
|
||||||
fesa::Hdf5ResultsWriter writer;
|
fesa::Hdf5ResultsWriter writer;
|
||||||
ASSERT_TRUE(writer.write(output, *fixture.domain, *fixture.state, {}).IsOk());
|
ASSERT_TRUE(writer.Write(output, *fixture.domain, *fixture.state, {}).IsOk());
|
||||||
const auto file = openFile(output);
|
const auto file = openFile(output);
|
||||||
const std::string shellRoot = std::string{kStepRoot} + "/element/shell";
|
const std::string shellRoot = std::string{kStepRoot} + "/element/shell";
|
||||||
expectNumericDataset(
|
expectNumericDataset(
|
||||||
@@ -1329,7 +1329,7 @@ TEST(Hdf5ResultsWriter, WritesShellInventoryDespiteRequestsAndOmitsForbiddenPath
|
|||||||
|
|
||||||
fesa::Hdf5ResultsWriter writer;
|
fesa::Hdf5ResultsWriter writer;
|
||||||
ASSERT_TRUE(
|
ASSERT_TRUE(
|
||||||
writer.write(output, *fixture.domain, *fixture.state, {ignoredRequest})
|
writer.Write(output, *fixture.domain, *fixture.state, {ignoredRequest})
|
||||||
.IsOk());
|
.IsOk());
|
||||||
const auto file = openFile(output);
|
const auto file = openFile(output);
|
||||||
for (const char* suffix : {
|
for (const char* suffix : {
|
||||||
@@ -1359,7 +1359,7 @@ TEST(Hdf5ResultsWriter, WritesShellInventoryDespiteRequestsAndOmitsForbiddenPath
|
|||||||
TEST(Hdf5ResultsWriter, InvalidShellInventoryPreservesExistingFinal) {
|
TEST(Hdf5ResultsWriter, InvalidShellInventoryPreservesExistingFinal) {
|
||||||
TempDirectory directory{"shell-atomic"};
|
TempDirectory directory{"shell-atomic"};
|
||||||
auto fixture = makeShellFixture(directory.path() / "shell.inp");
|
auto fixture = makeShellFixture(directory.path() / "shell.inp");
|
||||||
auto invalidState = fesa::AnalysisState::create(
|
auto invalidState = fesa::AnalysisState::Create(
|
||||||
*fixture.dofs, {"Step-1", 0U});
|
*fixture.dofs, {"Step-1", 0U});
|
||||||
const auto final = directory.path() / "results.h5";
|
const auto final = directory.path() / "results.h5";
|
||||||
const std::vector<char> sentinel = {'s', 'h', 'e', 'l', 'l'};
|
const std::vector<char> sentinel = {'s', 'h', 'e', 'l', 'l'};
|
||||||
@@ -1367,7 +1367,7 @@ TEST(Hdf5ResultsWriter, InvalidShellInventoryPreservesExistingFinal) {
|
|||||||
|
|
||||||
fesa::Hdf5ResultsWriter writer;
|
fesa::Hdf5ResultsWriter writer;
|
||||||
expectOutputFailure(
|
expectOutputFailure(
|
||||||
writer.write(final, *fixture.domain, invalidState, {}),
|
writer.Write(final, *fixture.domain, invalidState, {}),
|
||||||
"invalid-result-rows");
|
"invalid-result-rows");
|
||||||
EXPECT_EQ(readBytes(final), sentinel);
|
EXPECT_EQ(readBytes(final), sentinel);
|
||||||
EXPECT_EQ(entryCount(directory.path()), 1U);
|
EXPECT_EQ(entryCount(directory.path()), 1U);
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
#include <utility>
|
#include <utility>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include "fesa/fem/dof_manager.hpp"
|
#include "fesa/fem/dof_manager.h"
|
||||||
#include "fesa/math/matrix.h"
|
#include "fesa/math/matrix.h"
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
@@ -37,8 +37,8 @@ TEST(SparseAssembly, ValidatesKnownCsrAndMultiply) {
|
|||||||
|
|
||||||
EXPECT_EQ(matrix.Rows(), 4U);
|
EXPECT_EQ(matrix.Rows(), 4U);
|
||||||
EXPECT_EQ(matrix.Columns(), 4U);
|
EXPECT_EQ(matrix.Columns(), 4U);
|
||||||
EXPECT_EQ(matrix.RowOffsets(), pattern.rowOffsets);
|
EXPECT_EQ(matrix.RowOffsets(), pattern.row_offsets);
|
||||||
EXPECT_EQ(matrix.ColumnIndices(), pattern.columnIndices);
|
EXPECT_EQ(matrix.ColumnIndices(), pattern.column_indices);
|
||||||
EXPECT_EQ(matrix.Values(), (std::vector<double>{1.0, 2.0, 3.0, 4.0, 5.0}));
|
EXPECT_EQ(matrix.Values(), (std::vector<double>{1.0, 2.0, 3.0, 4.0, 5.0}));
|
||||||
EXPECT_TRUE(matrix.Validate().IsOk());
|
EXPECT_TRUE(matrix.Validate().IsOk());
|
||||||
|
|
||||||
@@ -122,8 +122,8 @@ TEST(SparseAssembly, PreservesExpectedStructuralZeros) {
|
|||||||
auto result =
|
auto result =
|
||||||
SparseMatrix::FromCoo(3U, 3U, std::move(contributions), pattern);
|
SparseMatrix::FromCoo(3U, 3U, std::move(contributions), pattern);
|
||||||
ASSERT_TRUE(result.HasValue());
|
ASSERT_TRUE(result.HasValue());
|
||||||
EXPECT_EQ(result.Value().RowOffsets(), pattern.rowOffsets);
|
EXPECT_EQ(result.Value().RowOffsets(), pattern.row_offsets);
|
||||||
EXPECT_EQ(result.Value().ColumnIndices(), pattern.columnIndices);
|
EXPECT_EQ(result.Value().ColumnIndices(), pattern.column_indices);
|
||||||
EXPECT_EQ(result.Value().Values(),
|
EXPECT_EQ(result.Value().Values(),
|
||||||
(std::vector<double>{2.0, 0.0, 0.0, 0.0, 0.0}));
|
(std::vector<double>{2.0, 0.0, 0.0, 0.0, 0.0}));
|
||||||
EXPECT_EQ(std::count(result.Value().Values().begin(),
|
EXPECT_EQ(std::count(result.Value().Values().begin(),
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
#include "fesa/analysis/analysis_state.hpp"
|
|
||||||
|
|
||||||
#include <gtest/gtest.h>
|
#include <gtest/gtest.h>
|
||||||
|
|
||||||
#include <array>
|
#include <array>
|
||||||
@@ -9,329 +7,281 @@
|
|||||||
#include <utility>
|
#include <utility>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
|
#include "fesa/analysis/analysis_state.h"
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
fesa::DofManager makeEmptyDofs() {
|
fesa::DofManager MakeEmptyDofs() {
|
||||||
fesa::ModelDefinition definition{};
|
fesa::ModelDefinition definition{};
|
||||||
definition.source_path = "models/result-records.inp";
|
definition.source_path = "models/result-records.inp";
|
||||||
definition.source_content_identity = "fnv1a64:0123456789abcdef";
|
definition.source_content_identity = "fnv1a64:0123456789abcdef";
|
||||||
definition.steps = {{
|
definition.steps = {
|
||||||
"Step-1", {}, {}, 0.1, 1.0, 0.01, 1.0,
|
{"Step-1", {}, {}, 0.1, 1.0, 0.01, 1.0, {definition.source_path, 10U}}};
|
||||||
{definition.source_path, 10U}}};
|
|
||||||
|
|
||||||
auto domain = fesa::Domain::Create(std::move(definition));
|
auto domain = fesa::Domain::Create(std::move(definition));
|
||||||
EXPECT_TRUE(domain.HasValue());
|
EXPECT_TRUE(domain.HasValue());
|
||||||
auto model = fesa::AnalysisModel::create(domain.Value());
|
auto model = fesa::AnalysisModel::Create(domain.Value());
|
||||||
EXPECT_TRUE(model.HasValue());
|
EXPECT_TRUE(model.HasValue());
|
||||||
auto dofs = fesa::DofManager::create(model.Value());
|
auto dofs = fesa::DofManager::Create(model.Value());
|
||||||
EXPECT_TRUE(dofs.HasValue());
|
EXPECT_TRUE(dofs.HasValue());
|
||||||
return std::move(dofs.Value());
|
return std::move(dofs.Value());
|
||||||
}
|
}
|
||||||
|
|
||||||
fesa::ShellResultRow makeShellRow(
|
fesa::ShellResultRow MakeShellRow(fesa::EntityIndex element,
|
||||||
fesa::EntityIndex element,
|
|
||||||
fesa::ShellMidsurfaceLocation location,
|
fesa::ShellMidsurfaceLocation location,
|
||||||
std::array<double, 2> naturalCoordinates,
|
std::array<double, 2> natural_coordinates,
|
||||||
double seed) {
|
double seed) {
|
||||||
return {
|
return {element,
|
||||||
element,
|
|
||||||
location,
|
location,
|
||||||
naturalCoordinates,
|
natural_coordinates,
|
||||||
{{{1.0, 0.0, 0.0}, {0.0, 1.0, 0.0}, {0.0, 0.0, 1.0}}},
|
{{{1.0, 0.0, 0.0}, {0.0, 1.0, 0.0}, {0.0, 0.0, 1.0}}},
|
||||||
{seed + 1.0,
|
{seed + 1.0, seed + 2.0, seed + 3.0, seed + 4.0, seed + 5.0,
|
||||||
seed + 2.0,
|
seed + 6.0, seed + 7.0, seed + 8.0},
|
||||||
seed + 3.0,
|
{seed + 11.0, seed + 12.0, seed + 13.0, seed + 14.0, seed + 15.0,
|
||||||
seed + 4.0,
|
seed + 16.0, seed + 17.0, seed + 18.0},
|
||||||
seed + 5.0,
|
{{{fesa::ShellSectionPosition::kBottom,
|
||||||
seed + 6.0,
|
|
||||||
seed + 7.0,
|
|
||||||
seed + 8.0},
|
|
||||||
{seed + 11.0,
|
|
||||||
seed + 12.0,
|
|
||||||
seed + 13.0,
|
|
||||||
seed + 14.0,
|
|
||||||
seed + 15.0,
|
|
||||||
seed + 16.0,
|
|
||||||
seed + 17.0,
|
|
||||||
seed + 18.0},
|
|
||||||
{{{fesa::ShellSectionPosition::bottom,
|
|
||||||
-1.0,
|
-1.0,
|
||||||
{seed + 21.0, seed + 22.0, seed + 23.0}},
|
{seed + 21.0, seed + 22.0, seed + 23.0}},
|
||||||
{fesa::ShellSectionPosition::middle,
|
{fesa::ShellSectionPosition::kMiddle,
|
||||||
0.0,
|
0.0,
|
||||||
{seed + 24.0, seed + 25.0, seed + 26.0}},
|
{seed + 24.0, seed + 25.0, seed + 26.0}},
|
||||||
{fesa::ShellSectionPosition::top,
|
{fesa::ShellSectionPosition::kTop,
|
||||||
1.0,
|
1.0,
|
||||||
{seed + 27.0, seed + 28.0, seed + 29.0}}}}};
|
{seed + 27.0, seed + 28.0, seed + 29.0}}}}};
|
||||||
}
|
}
|
||||||
|
|
||||||
fesa::ShellStateCandidate makeShellCandidate(
|
fesa::ShellStateCandidate MakeShellCandidate(
|
||||||
const std::vector<fesa::EntityIndex>& elements) {
|
const std::vector<fesa::EntityIndex>& elements) {
|
||||||
const double gauss = 1.0 / std::sqrt(3.0);
|
const double gauss = 1.0 / std::sqrt(3.0);
|
||||||
const std::array<fesa::ShellMidsurfaceLocation, 4> locations{
|
const std::array<fesa::ShellMidsurfaceLocation, 4> locations{
|
||||||
fesa::ShellMidsurfaceLocation::gp1,
|
fesa::ShellMidsurfaceLocation::kGp1, fesa::ShellMidsurfaceLocation::kGp2,
|
||||||
fesa::ShellMidsurfaceLocation::gp2,
|
fesa::ShellMidsurfaceLocation::kGp3, fesa::ShellMidsurfaceLocation::kGp4};
|
||||||
fesa::ShellMidsurfaceLocation::gp3,
|
|
||||||
fesa::ShellMidsurfaceLocation::gp4};
|
|
||||||
const std::array<std::array<double, 2>, 4> coordinates{
|
const std::array<std::array<double, 2>, 4> coordinates{
|
||||||
std::array<double, 2>{-gauss, -gauss},
|
std::array<double, 2>{-gauss, -gauss},
|
||||||
std::array<double, 2>{gauss, -gauss},
|
std::array<double, 2>{gauss, -gauss}, std::array<double, 2>{gauss, gauss},
|
||||||
std::array<double, 2>{gauss, gauss},
|
|
||||||
std::array<double, 2>{-gauss, gauss}};
|
std::array<double, 2>{-gauss, gauss}};
|
||||||
|
|
||||||
fesa::ShellStateCandidate candidate{};
|
fesa::ShellStateCandidate candidate{};
|
||||||
for (const auto element : elements) {
|
for (const auto element : elements) {
|
||||||
for (std::size_t point = 0U; point < locations.size(); ++point) {
|
for (std::size_t point = 0U; point < locations.size(); ++point) {
|
||||||
candidate.rows.push_back(makeShellRow(
|
candidate.rows.push_back(
|
||||||
element,
|
MakeShellRow(element, locations[point], coordinates[point],
|
||||||
locations[point],
|
|
||||||
coordinates[point],
|
|
||||||
100.0 * static_cast<double>(element) +
|
100.0 * static_cast<double>(element) +
|
||||||
10.0 * static_cast<double>(point)));
|
10.0 * static_cast<double>(point)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
candidate.physicalStrainEnergy = 35.5;
|
candidate.physical_strain_energy = 35.5;
|
||||||
candidate.equilibrium = {1.0, -2.0, 3.0, -4.0, 5.0, -6.0};
|
candidate.equilibrium = {1.0, -2.0, 3.0, -4.0, 5.0, -6.0};
|
||||||
candidate.verificationMetrics = {1.0e-11, 2.0e-11, 3.0e-11};
|
candidate.verification_metrics = {1.0e-11, 2.0e-11, 3.0e-11};
|
||||||
return candidate;
|
return candidate;
|
||||||
}
|
}
|
||||||
|
|
||||||
void expectShellStateEquals(
|
void ExpectShellStateEquals(const fesa::AnalysisState& state,
|
||||||
const fesa::AnalysisState& state,
|
|
||||||
const std::vector<fesa::ShellResultRow>& rows,
|
const std::vector<fesa::ShellResultRow>& rows,
|
||||||
double physicalStrainEnergy,
|
double physical_strain_energy,
|
||||||
const std::array<double, 6>& equilibrium,
|
const std::array<double, 6>& equilibrium,
|
||||||
const std::array<double, 3>& verificationMetrics) {
|
const std::array<double, 3>& verification_metrics) {
|
||||||
ASSERT_EQ(state.shellResults().size(), rows.size());
|
ASSERT_EQ(state.ShellResults().size(), rows.size());
|
||||||
for (std::size_t row = 0U; row < rows.size(); ++row) {
|
for (std::size_t row = 0U; row < rows.size(); ++row) {
|
||||||
EXPECT_EQ(state.shellResults()[row].element, rows[row].element);
|
EXPECT_EQ(state.ShellResults()[row].element, rows[row].element);
|
||||||
EXPECT_EQ(state.shellResults()[row].location, rows[row].location);
|
EXPECT_EQ(state.ShellResults()[row].location, rows[row].location);
|
||||||
EXPECT_EQ(
|
EXPECT_EQ(state.ShellResults()[row].natural_coordinates,
|
||||||
state.shellResults()[row].naturalCoordinates,
|
rows[row].natural_coordinates);
|
||||||
rows[row].naturalCoordinates);
|
EXPECT_EQ(state.ShellResults()[row].local_frame, rows[row].local_frame);
|
||||||
EXPECT_EQ(state.shellResults()[row].localFrame, rows[row].localFrame);
|
EXPECT_EQ(state.ShellResults()[row].generalized_strain,
|
||||||
EXPECT_EQ(
|
rows[row].generalized_strain);
|
||||||
state.shellResults()[row].generalizedStrain,
|
EXPECT_EQ(state.ShellResults()[row].section_resultant,
|
||||||
rows[row].generalizedStrain);
|
rows[row].section_resultant);
|
||||||
EXPECT_EQ(
|
for (std::size_t position = 0U; position < rows[row].stress.size();
|
||||||
state.shellResults()[row].sectionResultant,
|
|
||||||
rows[row].sectionResultant);
|
|
||||||
for (std::size_t position = 0U;
|
|
||||||
position < rows[row].stress.size();
|
|
||||||
++position) {
|
++position) {
|
||||||
EXPECT_EQ(
|
EXPECT_EQ(state.ShellResults()[row].stress[position].position,
|
||||||
state.shellResults()[row].stress[position].position,
|
|
||||||
rows[row].stress[position].position);
|
rows[row].stress[position].position);
|
||||||
EXPECT_DOUBLE_EQ(
|
EXPECT_DOUBLE_EQ(state.ShellResults()[row].stress[position].zeta,
|
||||||
state.shellResults()[row].stress[position].zeta,
|
|
||||||
rows[row].stress[position].zeta);
|
rows[row].stress[position].zeta);
|
||||||
EXPECT_EQ(
|
EXPECT_EQ(state.ShellResults()[row].stress[position].components,
|
||||||
state.shellResults()[row].stress[position].components,
|
|
||||||
rows[row].stress[position].components);
|
rows[row].stress[position].components);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
EXPECT_DOUBLE_EQ(state.physicalStrainEnergy(), physicalStrainEnergy);
|
EXPECT_DOUBLE_EQ(state.PhysicalStrainEnergy(), physical_strain_energy);
|
||||||
EXPECT_EQ(state.equilibrium(), equilibrium);
|
EXPECT_EQ(state.Equilibrium(), equilibrium);
|
||||||
EXPECT_EQ(state.verificationMetrics(), verificationMetrics);
|
EXPECT_EQ(state.VerificationMetrics(), verification_metrics);
|
||||||
}
|
}
|
||||||
|
|
||||||
void expectShellCandidateRejectedWithoutMutation(
|
void ExpectShellCandidateRejectedWithoutMutation(
|
||||||
fesa::AnalysisState& state,
|
fesa::AnalysisState& state,
|
||||||
const std::vector<fesa::EntityIndex>& expectedElements,
|
const std::vector<fesa::EntityIndex>& expected_elements,
|
||||||
const fesa::ShellStateCandidate& candidate,
|
const fesa::ShellStateCandidate& candidate,
|
||||||
const fesa::ShellStateCandidate& committed) {
|
const fesa::ShellStateCandidate& committed) {
|
||||||
const auto status = state.commitShellResults(expectedElements, candidate);
|
const auto status = state.CommitShellResults(expected_elements, candidate);
|
||||||
EXPECT_FALSE(status.IsOk());
|
EXPECT_FALSE(status.IsOk());
|
||||||
EXPECT_EQ(status.Category(), fesa::FailureCategory::kModel);
|
EXPECT_EQ(status.Category(), fesa::FailureCategory::kModel);
|
||||||
expectShellStateEquals(
|
ExpectShellStateEquals(state, committed.rows,
|
||||||
state,
|
committed.physical_strain_energy,
|
||||||
committed.rows,
|
committed.equilibrium, committed.verification_metrics);
|
||||||
committed.physicalStrainEnergy,
|
ASSERT_TRUE(state.CommitShellResults(expected_elements, committed).IsOk());
|
||||||
committed.equilibrium,
|
|
||||||
committed.verificationMetrics);
|
|
||||||
ASSERT_TRUE(state.commitShellResults(expectedElements, committed).IsOk());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
TEST(AnalysisState, PreservesStepFrameAndStableRowOrder) {
|
TEST(AnalysisState, PreservesStepFrameAndStableRowOrder) {
|
||||||
const auto dofs = makeEmptyDofs();
|
const auto dofs = MakeEmptyDofs();
|
||||||
auto state = fesa::AnalysisState::create(dofs, {"Step-1", 0U});
|
auto state = fesa::AnalysisState::Create(dofs, {"Step-1", 0U});
|
||||||
|
|
||||||
const fesa::EndpointResultRow firstEndpoint{
|
const fesa::EndpointResultRow first_endpoint{2U,
|
||||||
2U,
|
|
||||||
-1,
|
-1,
|
||||||
{"Beam-1", 10, "010"},
|
{"Beam-1", 10, "010"},
|
||||||
{1.0, 2.0, 3.0, 4.0, 5.0, 6.0},
|
{1.0, 2.0, 3.0, 4.0, 5.0, 6.0},
|
||||||
{7.0, 8.0, 9.0, 10.0}};
|
{7.0, 8.0, 9.0, 10.0}};
|
||||||
const fesa::EndpointResultRow secondEndpoint{
|
const fesa::EndpointResultRow second_endpoint{
|
||||||
2U,
|
2U,
|
||||||
1,
|
1,
|
||||||
{"Beam-1", 20, "020"},
|
{"Beam-1", 20, "020"},
|
||||||
{11.0, 12.0, 13.0, 14.0, 15.0, 16.0},
|
{11.0, 12.0, 13.0, 14.0, 15.0, 16.0},
|
||||||
{17.0, 18.0, 19.0, 20.0}};
|
{17.0, 18.0, 19.0, 20.0}};
|
||||||
const fesa::GaussResultRow firstGauss{
|
const fesa::GaussResultRow first_gauss{
|
||||||
2U, 1, {0.1, 0.2, 0.3, 0.4}, {1.1, 1.2, 1.3, 1.4}};
|
2U, 1, {0.1, 0.2, 0.3, 0.4}, {1.1, 1.2, 1.3, 1.4}};
|
||||||
const fesa::GaussResultRow secondGauss{
|
const fesa::GaussResultRow second_gauss{
|
||||||
2U, 2, {0.5, 0.6, 0.7, 0.8}, {1.5, 1.6, 1.7, 1.8}};
|
2U, 2, {0.5, 0.6, 0.7, 0.8}, {1.5, 1.6, 1.7, 1.8}};
|
||||||
const fesa::StressS11Row firstStress{
|
const fesa::StressS11Row first_stress{2U, 1, 1U, -0.25, 0.5, 12.5, "input"};
|
||||||
2U, 1, 1U, -0.25, 0.5, 12.5, "input"};
|
const fesa::StressS11Row second_stress{2U, 1, 2U, 0.25, -0.5, -7.5, "input"};
|
||||||
const fesa::StressS11Row secondStress{
|
|
||||||
2U, 1, 2U, 0.25, -0.5, -7.5, "input"};
|
|
||||||
|
|
||||||
state.endpointResults().push_back(firstEndpoint);
|
state.EndpointResults().push_back(first_endpoint);
|
||||||
state.endpointResults().push_back(secondEndpoint);
|
state.EndpointResults().push_back(second_endpoint);
|
||||||
state.gaussResults().push_back(firstGauss);
|
state.GaussResults().push_back(first_gauss);
|
||||||
state.gaussResults().push_back(secondGauss);
|
state.GaussResults().push_back(second_gauss);
|
||||||
state.stressResults().push_back(firstStress);
|
state.StressResults().push_back(first_stress);
|
||||||
state.stressResults().push_back(secondStress);
|
state.StressResults().push_back(second_stress);
|
||||||
|
|
||||||
const auto* const endpointStorage = state.endpointResults().data();
|
const auto* const endpoint_storage = state.EndpointResults().data();
|
||||||
const auto* const gaussStorage = state.gaussResults().data();
|
const auto* const gauss_storage = state.GaussResults().data();
|
||||||
const auto* const stressStorage = state.stressResults().data();
|
const auto* const stress_storage = state.StressResults().data();
|
||||||
const fesa::AnalysisState& constState = state;
|
const fesa::AnalysisState& const_state = state;
|
||||||
|
|
||||||
EXPECT_EQ(constState.identity().stepName, "Step-1");
|
EXPECT_EQ(const_state.Identity().step_name, "Step-1");
|
||||||
EXPECT_EQ(constState.identity().frameIndex, 0U);
|
EXPECT_EQ(const_state.Identity().frame_index, 0U);
|
||||||
ASSERT_EQ(constState.endpointResults().size(), 2U);
|
ASSERT_EQ(const_state.EndpointResults().size(), 2U);
|
||||||
EXPECT_EQ(constState.endpointResults().data(), endpointStorage);
|
EXPECT_EQ(const_state.EndpointResults().data(), endpoint_storage);
|
||||||
EXPECT_EQ(constState.endpointResults()[0].element, 2U);
|
EXPECT_EQ(const_state.EndpointResults()[0].element, 2U);
|
||||||
EXPECT_EQ(constState.endpointResults()[0].endpoint, -1);
|
EXPECT_EQ(const_state.EndpointResults()[0].endpoint, -1);
|
||||||
EXPECT_EQ(constState.endpointResults()[0].node.instance_name, "Beam-1");
|
EXPECT_EQ(const_state.EndpointResults()[0].node.instance_name, "Beam-1");
|
||||||
EXPECT_EQ(constState.endpointResults()[0].node.source_label, 10);
|
EXPECT_EQ(const_state.EndpointResults()[0].node.source_label, 10);
|
||||||
EXPECT_EQ(constState.endpointResults()[0].node.source_label_text, "010");
|
EXPECT_EQ(const_state.EndpointResults()[0].node.source_label_text, "010");
|
||||||
EXPECT_EQ(
|
EXPECT_EQ(const_state.EndpointResults()[0].end_action,
|
||||||
constState.endpointResults()[0].endAction,
|
|
||||||
(std::array<double, 6>{1.0, 2.0, 3.0, 4.0, 5.0, 6.0}));
|
(std::array<double, 6>{1.0, 2.0, 3.0, 4.0, 5.0, 6.0}));
|
||||||
EXPECT_EQ(
|
EXPECT_EQ(const_state.EndpointResults()[1].section_resultant,
|
||||||
constState.endpointResults()[1].sectionResultant,
|
|
||||||
(std::array<double, 4>{17.0, 18.0, 19.0, 20.0}));
|
(std::array<double, 4>{17.0, 18.0, 19.0, 20.0}));
|
||||||
|
|
||||||
ASSERT_EQ(constState.gaussResults().size(), 2U);
|
ASSERT_EQ(const_state.GaussResults().size(), 2U);
|
||||||
EXPECT_EQ(constState.gaussResults().data(), gaussStorage);
|
EXPECT_EQ(const_state.GaussResults().data(), gauss_storage);
|
||||||
EXPECT_EQ(constState.gaussResults()[0].gaussPoint, 1);
|
EXPECT_EQ(const_state.GaussResults()[0].gauss_point, 1);
|
||||||
EXPECT_EQ(constState.gaussResults()[1].gaussPoint, 2);
|
EXPECT_EQ(const_state.GaussResults()[1].gauss_point, 2);
|
||||||
EXPECT_EQ(
|
EXPECT_EQ(const_state.GaussResults()[0].generalized_strain,
|
||||||
constState.gaussResults()[0].generalizedStrain,
|
|
||||||
(std::array<double, 4>{0.1, 0.2, 0.3, 0.4}));
|
(std::array<double, 4>{0.1, 0.2, 0.3, 0.4}));
|
||||||
EXPECT_EQ(
|
EXPECT_EQ(const_state.GaussResults()[1].generalized_resultant,
|
||||||
constState.gaussResults()[1].generalizedResultant,
|
|
||||||
(std::array<double, 4>{1.5, 1.6, 1.7, 1.8}));
|
(std::array<double, 4>{1.5, 1.6, 1.7, 1.8}));
|
||||||
|
|
||||||
ASSERT_EQ(constState.stressResults().size(), 2U);
|
ASSERT_EQ(const_state.StressResults().size(), 2U);
|
||||||
EXPECT_EQ(constState.stressResults().data(), stressStorage);
|
EXPECT_EQ(const_state.StressResults().data(), stress_storage);
|
||||||
EXPECT_EQ(constState.stressResults()[0].sectionPoint, 1U);
|
EXPECT_EQ(const_state.StressResults()[0].section_point, 1U);
|
||||||
EXPECT_DOUBLE_EQ(constState.stressResults()[0].x1, -0.25);
|
EXPECT_DOUBLE_EQ(const_state.StressResults()[0].x1, -0.25);
|
||||||
EXPECT_DOUBLE_EQ(constState.stressResults()[0].x2, 0.5);
|
EXPECT_DOUBLE_EQ(const_state.StressResults()[0].x2, 0.5);
|
||||||
EXPECT_DOUBLE_EQ(constState.stressResults()[0].s11, 12.5);
|
EXPECT_DOUBLE_EQ(const_state.StressResults()[0].s11, 12.5);
|
||||||
EXPECT_EQ(constState.stressResults()[0].source, "input");
|
EXPECT_EQ(const_state.StressResults()[0].source, "input");
|
||||||
EXPECT_EQ(constState.stressResults()[1].sectionPoint, 2U);
|
EXPECT_EQ(const_state.StressResults()[1].section_point, 2U);
|
||||||
}
|
}
|
||||||
|
|
||||||
// MITC4-STATE-001
|
// MITC4-STATE-001
|
||||||
TEST(AnalysisState, OwnsExactShellRowsInStableElementAndLocationOrder) {
|
TEST(AnalysisState, OwnsExactShellRowsInStableElementAndLocationOrder) {
|
||||||
const auto dofs = makeEmptyDofs();
|
const auto dofs = MakeEmptyDofs();
|
||||||
auto state = fesa::AnalysisState::create(dofs, {"Step-1", 0U});
|
auto state = fesa::AnalysisState::Create(dofs, {"Step-1", 0U});
|
||||||
const std::vector<fesa::EntityIndex> expectedElements{3U, 7U};
|
const std::vector<fesa::EntityIndex> expected_elements{3U, 7U};
|
||||||
auto candidate = makeShellCandidate(expectedElements);
|
auto candidate = MakeShellCandidate(expected_elements);
|
||||||
|
|
||||||
const auto status = state.commitShellResults(expectedElements, candidate);
|
const auto status = state.CommitShellResults(expected_elements, candidate);
|
||||||
|
|
||||||
ASSERT_TRUE(status.IsOk());
|
ASSERT_TRUE(status.IsOk());
|
||||||
const fesa::AnalysisState& constState = state;
|
const fesa::AnalysisState& const_state = state;
|
||||||
ASSERT_EQ(constState.shellResults().size(), 8U);
|
ASSERT_EQ(const_state.ShellResults().size(), 8U);
|
||||||
EXPECT_EQ(constState.shellResults()[0].element, 3U);
|
EXPECT_EQ(const_state.ShellResults()[0].element, 3U);
|
||||||
|
EXPECT_EQ(const_state.ShellResults()[0].location,
|
||||||
|
fesa::ShellMidsurfaceLocation::kGp1);
|
||||||
|
EXPECT_EQ(const_state.ShellResults()[3].location,
|
||||||
|
fesa::ShellMidsurfaceLocation::kGp4);
|
||||||
|
EXPECT_EQ(const_state.ShellResults()[4].element, 7U);
|
||||||
|
EXPECT_EQ(const_state.ShellResults()[4].location,
|
||||||
|
fesa::ShellMidsurfaceLocation::kGp1);
|
||||||
EXPECT_EQ(
|
EXPECT_EQ(
|
||||||
constState.shellResults()[0].location,
|
const_state.ShellResults()[0].natural_coordinates,
|
||||||
fesa::ShellMidsurfaceLocation::gp1);
|
(std::array<double, 2>{-1.0 / std::sqrt(3.0), -1.0 / std::sqrt(3.0)}));
|
||||||
EXPECT_EQ(
|
EXPECT_EQ(const_state.ShellResults()[2].generalized_strain,
|
||||||
constState.shellResults()[3].location,
|
(std::array<double, 8>{321.0, 322.0, 323.0, 324.0, 325.0, 326.0,
|
||||||
fesa::ShellMidsurfaceLocation::gp4);
|
327.0, 328.0}));
|
||||||
EXPECT_EQ(constState.shellResults()[4].element, 7U);
|
EXPECT_EQ(const_state.ShellResults()[7].section_resultant,
|
||||||
EXPECT_EQ(
|
(std::array<double, 8>{741.0, 742.0, 743.0, 744.0, 745.0, 746.0,
|
||||||
constState.shellResults()[4].location,
|
747.0, 748.0}));
|
||||||
fesa::ShellMidsurfaceLocation::gp1);
|
EXPECT_EQ(const_state.ShellResults()[7].stress[0].position,
|
||||||
EXPECT_EQ(
|
fesa::ShellSectionPosition::kBottom);
|
||||||
constState.shellResults()[0].naturalCoordinates,
|
EXPECT_DOUBLE_EQ(const_state.ShellResults()[7].stress[0].zeta, -1.0);
|
||||||
(std::array<double, 2>{
|
EXPECT_EQ(const_state.ShellResults()[7].stress[2].components,
|
||||||
-1.0 / std::sqrt(3.0), -1.0 / std::sqrt(3.0)}));
|
|
||||||
EXPECT_EQ(
|
|
||||||
constState.shellResults()[2].generalizedStrain,
|
|
||||||
(std::array<double, 8>{
|
|
||||||
321.0, 322.0, 323.0, 324.0,
|
|
||||||
325.0, 326.0, 327.0, 328.0}));
|
|
||||||
EXPECT_EQ(
|
|
||||||
constState.shellResults()[7].sectionResultant,
|
|
||||||
(std::array<double, 8>{
|
|
||||||
741.0, 742.0, 743.0, 744.0,
|
|
||||||
745.0, 746.0, 747.0, 748.0}));
|
|
||||||
EXPECT_EQ(
|
|
||||||
constState.shellResults()[7].stress[0].position,
|
|
||||||
fesa::ShellSectionPosition::bottom);
|
|
||||||
EXPECT_DOUBLE_EQ(constState.shellResults()[7].stress[0].zeta, -1.0);
|
|
||||||
EXPECT_EQ(
|
|
||||||
constState.shellResults()[7].stress[2].components,
|
|
||||||
(std::array<double, 3>{757.0, 758.0, 759.0}));
|
(std::array<double, 3>{757.0, 758.0, 759.0}));
|
||||||
}
|
}
|
||||||
|
|
||||||
// MITC4-STATE-002
|
// MITC4-STATE-002
|
||||||
TEST(AnalysisState, CommitsFiniteShellGlobalEvidence) {
|
TEST(AnalysisState, CommitsFiniteShellGlobalEvidence) {
|
||||||
const auto dofs = makeEmptyDofs();
|
const auto dofs = MakeEmptyDofs();
|
||||||
auto state = fesa::AnalysisState::create(dofs, {"Step-1", 0U});
|
auto state = fesa::AnalysisState::Create(dofs, {"Step-1", 0U});
|
||||||
const std::vector<fesa::EntityIndex> expectedElements{5U};
|
const std::vector<fesa::EntityIndex> expected_elements{5U};
|
||||||
const auto candidate = makeShellCandidate(expectedElements);
|
const auto candidate = MakeShellCandidate(expected_elements);
|
||||||
|
|
||||||
const auto status = state.commitShellResults(expectedElements, candidate);
|
const auto status = state.CommitShellResults(expected_elements, candidate);
|
||||||
|
|
||||||
ASSERT_TRUE(status.IsOk());
|
ASSERT_TRUE(status.IsOk());
|
||||||
EXPECT_DOUBLE_EQ(state.physicalStrainEnergy(), 35.5);
|
EXPECT_DOUBLE_EQ(state.PhysicalStrainEnergy(), 35.5);
|
||||||
EXPECT_EQ(
|
EXPECT_EQ(state.Equilibrium(),
|
||||||
state.equilibrium(),
|
|
||||||
(std::array<double, 6>{1.0, -2.0, 3.0, -4.0, 5.0, -6.0}));
|
(std::array<double, 6>{1.0, -2.0, 3.0, -4.0, 5.0, -6.0}));
|
||||||
EXPECT_EQ(
|
EXPECT_EQ(state.VerificationMetrics(),
|
||||||
state.verificationMetrics(),
|
|
||||||
(std::array<double, 3>{1.0e-11, 2.0e-11, 3.0e-11}));
|
(std::array<double, 3>{1.0e-11, 2.0e-11, 3.0e-11}));
|
||||||
}
|
}
|
||||||
|
|
||||||
// MITC4-STATE-003
|
// MITC4-STATE-003
|
||||||
TEST(AnalysisState, InvalidShellCandidatesLeavePriorStateUnchanged) {
|
TEST(AnalysisState, InvalidShellCandidatesLeavePriorStateUnchanged) {
|
||||||
const auto dofs = makeEmptyDofs();
|
const auto dofs = MakeEmptyDofs();
|
||||||
auto state = fesa::AnalysisState::create(dofs, {"Step-1", 0U});
|
auto state = fesa::AnalysisState::Create(dofs, {"Step-1", 0U});
|
||||||
const std::vector<fesa::EntityIndex> expectedElements{5U};
|
const std::vector<fesa::EntityIndex> expected_elements{5U};
|
||||||
const auto committed = makeShellCandidate(expectedElements);
|
const auto committed = MakeShellCandidate(expected_elements);
|
||||||
ASSERT_TRUE(state.commitShellResults(expectedElements, committed).IsOk());
|
ASSERT_TRUE(state.CommitShellResults(expected_elements, committed).IsOk());
|
||||||
|
|
||||||
auto invalidLocation = makeShellCandidate(expectedElements);
|
auto invalid_location = MakeShellCandidate(expected_elements);
|
||||||
invalidLocation.rows[0].location = fesa::ShellMidsurfaceLocation::gp2;
|
invalid_location.rows[0].location = fesa::ShellMidsurfaceLocation::kGp2;
|
||||||
expectShellCandidateRejectedWithoutMutation(
|
ExpectShellCandidateRejectedWithoutMutation(state, expected_elements,
|
||||||
state, expectedElements, invalidLocation, committed);
|
invalid_location, committed);
|
||||||
|
|
||||||
auto nonfinite = makeShellCandidate(expectedElements);
|
auto nonfinite = MakeShellCandidate(expected_elements);
|
||||||
nonfinite.rows[2].generalizedStrain[6] =
|
nonfinite.rows[2].generalized_strain[6] =
|
||||||
(std::numeric_limits<double>::quiet_NaN)();
|
(std::numeric_limits<double>::quiet_NaN)();
|
||||||
expectShellCandidateRejectedWithoutMutation(
|
ExpectShellCandidateRejectedWithoutMutation(state, expected_elements,
|
||||||
state, expectedElements, nonfinite, committed);
|
nonfinite, committed);
|
||||||
|
|
||||||
auto nonfiniteFrame = makeShellCandidate(expectedElements);
|
auto nonfinite_frame = MakeShellCandidate(expected_elements);
|
||||||
nonfiniteFrame.rows[1].localFrame[2][0] =
|
nonfinite_frame.rows[1].local_frame[2][0] =
|
||||||
(std::numeric_limits<double>::infinity)();
|
(std::numeric_limits<double>::infinity)();
|
||||||
expectShellCandidateRejectedWithoutMutation(
|
ExpectShellCandidateRejectedWithoutMutation(state, expected_elements,
|
||||||
state, expectedElements, nonfiniteFrame, committed);
|
nonfinite_frame, committed);
|
||||||
|
|
||||||
auto invalidSectionPosition = makeShellCandidate(expectedElements);
|
auto invalid_section_position = MakeShellCandidate(expected_elements);
|
||||||
invalidSectionPosition.rows[3].stress[0].position =
|
invalid_section_position.rows[3].stress[0].position =
|
||||||
fesa::ShellSectionPosition::top;
|
fesa::ShellSectionPosition::kTop;
|
||||||
expectShellCandidateRejectedWithoutMutation(
|
ExpectShellCandidateRejectedWithoutMutation(
|
||||||
state, expectedElements, invalidSectionPosition, committed);
|
state, expected_elements, invalid_section_position, committed);
|
||||||
|
|
||||||
auto nonfiniteGlobalEvidence = makeShellCandidate(expectedElements);
|
auto nonfinite_global_evidence = MakeShellCandidate(expected_elements);
|
||||||
nonfiniteGlobalEvidence.verificationMetrics[1] =
|
nonfinite_global_evidence.verification_metrics[1] =
|
||||||
(std::numeric_limits<double>::infinity)();
|
(std::numeric_limits<double>::infinity)();
|
||||||
expectShellCandidateRejectedWithoutMutation(
|
ExpectShellCandidateRejectedWithoutMutation(
|
||||||
state, expectedElements, nonfiniteGlobalEvidence, committed);
|
state, expected_elements, nonfinite_global_evidence, committed);
|
||||||
|
|
||||||
auto incompleteInventory = makeShellCandidate(expectedElements);
|
auto incomplete_inventory = MakeShellCandidate(expected_elements);
|
||||||
incompleteInventory.rows.pop_back();
|
incomplete_inventory.rows.pop_back();
|
||||||
expectShellCandidateRejectedWithoutMutation(
|
ExpectShellCandidateRejectedWithoutMutation(state, expected_elements,
|
||||||
state, expectedElements, incompleteInventory, committed);
|
incomplete_inventory, committed);
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -1,24 +1,23 @@
|
|||||||
#include "fesa/results/results_writer.hpp"
|
#include "fesa/results/results_writer.h"
|
||||||
|
|
||||||
#include "fesa/analysis/analysis_state.hpp"
|
|
||||||
#include "fesa/core/diagnostic.h"
|
|
||||||
#include "fesa/core/status.h"
|
|
||||||
#include "fesa/model/domain.h"
|
|
||||||
|
|
||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
#include <type_traits>
|
#include <type_traits>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
|
#include "fesa/analysis/analysis_state.h"
|
||||||
|
#include "fesa/core/diagnostic.h"
|
||||||
|
#include "fesa/core/status.h"
|
||||||
|
#include "fesa/model/domain.h"
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
using WriteSignature = fesa::Status (fesa::ResultsWriter::*)(
|
using WriteSignature = fesa::Status (fesa::ResultsWriter::*)(
|
||||||
const std::filesystem::path&,
|
const std::filesystem::path&, const fesa::Domain&,
|
||||||
const fesa::Domain&,
|
const fesa::AnalysisState&, const std::vector<fesa::Diagnostic>&);
|
||||||
const fesa::AnalysisState&,
|
|
||||||
const std::vector<fesa::Diagnostic>&);
|
|
||||||
|
|
||||||
static_assert(std::has_virtual_destructor_v<fesa::ResultsWriter>);
|
static_assert(std::has_virtual_destructor_v<fesa::ResultsWriter>);
|
||||||
static_assert(std::is_abstract_v<fesa::ResultsWriter>);
|
static_assert(std::is_abstract_v<fesa::ResultsWriter>);
|
||||||
static_assert(std::is_same_v<decltype(&fesa::ResultsWriter::write), WriteSignature>);
|
static_assert(
|
||||||
|
std::is_same_v<decltype(&fesa::ResultsWriter::Write), WriteSignature>);
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
#include <utility>
|
#include <utility>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include "fesa/fem/dof_manager.hpp"
|
#include "fesa/fem/dof_manager.h"
|
||||||
#include "fesa/math/sparse_matrix.h"
|
#include "fesa/math/sparse_matrix.h"
|
||||||
#include "fesa/solvers/linear/mkl_pardiso_solver.h"
|
#include "fesa/solvers/linear/mkl_pardiso_solver.h"
|
||||||
|
|
||||||
@@ -19,15 +19,15 @@ fesa::SparseMatrix MakeDenseCsr(const std::size_t rows,
|
|||||||
|
|
||||||
fesa::SparsePattern pattern;
|
fesa::SparsePattern pattern;
|
||||||
std::vector<fesa::CooContribution> contributions;
|
std::vector<fesa::CooContribution> contributions;
|
||||||
pattern.rowOffsets.reserve(rows + 1U);
|
pattern.row_offsets.reserve(rows + 1U);
|
||||||
pattern.rowOffsets.push_back(0U);
|
pattern.row_offsets.push_back(0U);
|
||||||
for (std::size_t row = 0U; row < rows; ++row) {
|
for (std::size_t row = 0U; row < rows; ++row) {
|
||||||
for (std::size_t column = 0U; column < columns; ++column) {
|
for (std::size_t column = 0U; column < columns; ++column) {
|
||||||
pattern.columnIndices.push_back(column);
|
pattern.column_indices.push_back(column);
|
||||||
contributions.push_back(
|
contributions.push_back(
|
||||||
{row, column, values[row * columns + column], row, column});
|
{row, column, values[row * columns + column], row, column});
|
||||||
}
|
}
|
||||||
pattern.rowOffsets.push_back(pattern.columnIndices.size());
|
pattern.row_offsets.push_back(pattern.column_indices.size());
|
||||||
}
|
}
|
||||||
|
|
||||||
auto matrix = fesa::SparseMatrix::FromCoo(rows, columns,
|
auto matrix = fesa::SparseMatrix::FromCoo(rows, columns,
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
#include <utility>
|
#include <utility>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include "fesa/fem/dof_manager.hpp"
|
#include "fesa/fem/dof_manager.h"
|
||||||
#include "fesa/math/sparse_matrix.h"
|
#include "fesa/math/sparse_matrix.h"
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
@@ -20,15 +20,15 @@ fesa::SparseMatrix MakeDenseCsr(const std::size_t size,
|
|||||||
|
|
||||||
fesa::SparsePattern pattern;
|
fesa::SparsePattern pattern;
|
||||||
std::vector<fesa::CooContribution> contributions;
|
std::vector<fesa::CooContribution> contributions;
|
||||||
pattern.rowOffsets.reserve(size + 1U);
|
pattern.row_offsets.reserve(size + 1U);
|
||||||
pattern.rowOffsets.push_back(0U);
|
pattern.row_offsets.push_back(0U);
|
||||||
for (std::size_t row = 0U; row < size; ++row) {
|
for (std::size_t row = 0U; row < size; ++row) {
|
||||||
for (std::size_t column = 0U; column < size; ++column) {
|
for (std::size_t column = 0U; column < size; ++column) {
|
||||||
pattern.columnIndices.push_back(column);
|
pattern.column_indices.push_back(column);
|
||||||
contributions.push_back(
|
contributions.push_back(
|
||||||
{row, column, values[row * size + column], row, column});
|
{row, column, values[row * size + column], row, column});
|
||||||
}
|
}
|
||||||
pattern.rowOffsets.push_back(pattern.columnIndices.size());
|
pattern.row_offsets.push_back(pattern.column_indices.size());
|
||||||
}
|
}
|
||||||
|
|
||||||
auto matrix = fesa::SparseMatrix::FromCoo(size, size,
|
auto matrix = fesa::SparseMatrix::FromCoo(size, size,
|
||||||
|
|||||||
Reference in New Issue
Block a user