Integrate Refactor solver sources and dual MSVC CMake builds

This commit is contained in:
KOKO\Mimi
2026-10-18 02:10:51 +09:00
parent 7ee32e5837
commit 12d51480de
218 changed files with 17842 additions and 9689 deletions
+4
View File
@@ -27,3 +27,7 @@ phases/**/step*-output.json
# local document backups and initialization state
.dryforge/
# direct MSVC and CMake local build state
/.build/
/msvc/Fesa.Local.props
+5 -5
View File
@@ -1,13 +1,14 @@
cmake_minimum_required(VERSION 3.25)
project(FESA VERSION 0.1.0 LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)
set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Debug>:Debug>DLL")
# Pin the original backend ABI/link/thread choices rather than package defaults.
set(MKL_INTERFACE ilp64)
set(MKL_LINK dynamic)
set(MKL_THREADING intel_thread)
include(cmake/FesaDependencies.cmake)
find_package(Doxygen QUIET)
if(Doxygen_FOUND)
add_custom_target(fesa_docs
@@ -19,6 +20,5 @@ if(Doxygen_FOUND)
endif()
enable_testing()
add_subdirectory(src/fesa)
add_subdirectory(tests)
+21 -7
View File
@@ -3,11 +3,11 @@
"configurePresets": [
{
"name": "windows-msvc-ninja-debug",
"displayName": "Windows MSVC x64 Debug (Ninja)",
"generator": "Ninja",
"binaryDir": "${sourceDir}/build/windows-msvc-ninja-debug",
"binaryDir": "${sourceDir}/.build/cmake-debug",
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Debug",
"CMAKE_EXPORT_COMPILE_COMMANDS": "ON",
"FESA_GTEST_SOURCE_DIR": "$env{FESA_GTEST_SOURCE_DIR}",
"MKL_DIR": "$env{MKL_DIR}",
"TBB_DIR": "$env{TBB_DIR}",
@@ -16,11 +16,15 @@
},
{
"name": "windows-msvc-ninja-release",
"displayName": "Windows MSVC x64 Release with symbols (Ninja)",
"inherits": "windows-msvc-ninja-debug",
"binaryDir": "${sourceDir}/build/windows-msvc-ninja-release",
"generator": "Ninja",
"binaryDir": "${sourceDir}/.build/cmake-release",
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Release"
"CMAKE_BUILD_TYPE": "Release",
"CMAKE_EXPORT_COMPILE_COMMANDS": "ON",
"FESA_GTEST_SOURCE_DIR": "$env{FESA_GTEST_SOURCE_DIR}",
"MKL_DIR": "$env{MKL_DIR}",
"TBB_DIR": "$env{TBB_DIR}",
"HDF5_DIR": "$env{HDF5_DIR}"
}
}
],
@@ -37,7 +41,17 @@
"testPresets": [
{
"name": "windows-msvc-ninja-debug",
"configurePreset": "windows-msvc-ninja-debug"
"configurePreset": "windows-msvc-ninja-debug",
"output": {
"outputOnFailure": true
}
},
{
"name": "windows-msvc-ninja-release",
"configurePreset": "windows-msvc-ninja-release",
"output": {
"outputOnFailure": true
}
}
]
}
+48
View File
@@ -0,0 +1,48 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 18
VisualStudioVersion = 18.0.0.0
MinimumVisualStudioVersion = 10.0.40219.1
Project("{BC8A1FFA-BEE3-4634-8014-F334798102B3}") = "fesa_solver", "msvc\fesa_solver.vcxproj", "{3AEC39D9-4641-5161-9107-265EB50CA882}"
EndProject
Project("{BC8A1FFA-BEE3-4634-8014-F334798102B3}") = "fesa_cli", "msvc\fesa_cli.vcxproj", "{6DE2CFFA-D9B3-5E14-BEBD-5DFAB2B5290B}"
EndProject
Project("{BC8A1FFA-BEE3-4634-8014-F334798102B3}") = "fesa_unit_tests", "msvc\fesa_unit_tests.vcxproj", "{42558F77-73F2-5CA7-8E3C-BB0E1646CD45}"
EndProject
Project("{BC8A1FFA-BEE3-4634-8014-F334798102B3}") = "fesa_integration_tests", "msvc\fesa_integration_tests.vcxproj", "{7770C26B-DBFE-505D-8788-8B7EFD9A5A70}"
EndProject
Project("{BC8A1FFA-BEE3-4634-8014-F334798102B3}") = "fesa_reference_tests", "msvc\fesa_reference_tests.vcxproj", "{330D2DFB-E57D-517D-A795-F1EA620B69F0}"
EndProject
Project("{BC8A1FFA-BEE3-4634-8014-F334798102B3}") = "gtest", "msvc\gtest.vcxproj", "{03194BA4-3F3B-5399-BCAF-FB4FA47300CE}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|x64 = Debug|x64
Release|x64 = Release|x64
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{3AEC39D9-4641-5161-9107-265EB50CA882}.Debug|x64.ActiveCfg = Debug|x64
{3AEC39D9-4641-5161-9107-265EB50CA882}.Debug|x64.Build.0 = Debug|x64
{3AEC39D9-4641-5161-9107-265EB50CA882}.Release|x64.ActiveCfg = Release|x64
{3AEC39D9-4641-5161-9107-265EB50CA882}.Release|x64.Build.0 = Release|x64
{6DE2CFFA-D9B3-5E14-BEBD-5DFAB2B5290B}.Debug|x64.ActiveCfg = Debug|x64
{6DE2CFFA-D9B3-5E14-BEBD-5DFAB2B5290B}.Debug|x64.Build.0 = Debug|x64
{6DE2CFFA-D9B3-5E14-BEBD-5DFAB2B5290B}.Release|x64.ActiveCfg = Release|x64
{6DE2CFFA-D9B3-5E14-BEBD-5DFAB2B5290B}.Release|x64.Build.0 = Release|x64
{42558F77-73F2-5CA7-8E3C-BB0E1646CD45}.Debug|x64.ActiveCfg = Debug|x64
{42558F77-73F2-5CA7-8E3C-BB0E1646CD45}.Debug|x64.Build.0 = Debug|x64
{42558F77-73F2-5CA7-8E3C-BB0E1646CD45}.Release|x64.ActiveCfg = Release|x64
{42558F77-73F2-5CA7-8E3C-BB0E1646CD45}.Release|x64.Build.0 = Release|x64
{7770C26B-DBFE-505D-8788-8B7EFD9A5A70}.Debug|x64.ActiveCfg = Debug|x64
{7770C26B-DBFE-505D-8788-8B7EFD9A5A70}.Debug|x64.Build.0 = Debug|x64
{7770C26B-DBFE-505D-8788-8B7EFD9A5A70}.Release|x64.ActiveCfg = Release|x64
{7770C26B-DBFE-505D-8788-8B7EFD9A5A70}.Release|x64.Build.0 = Release|x64
{330D2DFB-E57D-517D-A795-F1EA620B69F0}.Debug|x64.ActiveCfg = Debug|x64
{330D2DFB-E57D-517D-A795-F1EA620B69F0}.Debug|x64.Build.0 = Debug|x64
{330D2DFB-E57D-517D-A795-F1EA620B69F0}.Release|x64.ActiveCfg = Release|x64
{330D2DFB-E57D-517D-A795-F1EA620B69F0}.Release|x64.Build.0 = Release|x64
{03194BA4-3F3B-5399-BCAF-FB4FA47300CE}.Debug|x64.ActiveCfg = Debug|x64
{03194BA4-3F3B-5399-BCAF-FB4FA47300CE}.Debug|x64.Build.0 = Debug|x64
{03194BA4-3F3B-5399-BCAF-FB4FA47300CE}.Release|x64.ActiveCfg = Release|x64
{03194BA4-3F3B-5399-BCAF-FB4FA47300CE}.Release|x64.Build.0 = Release|x64
EndGlobalSection
EndGlobal
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,23 @@
#ifndef FESA_ANALYSIS_ANALYSIS_PROCEDURE_H_
#define FESA_ANALYSIS_ANALYSIS_PROCEDURE_H_
#include "fesa/analysis/prepared_analysis_outcome.h"
#include "fesa/core/status.h"
namespace fesa {
class Domain;
/// @brief Executes one prepared Domain without opening result files.
/// @note The Domain and injected adapters outlive Execute. Accepted histories
/// remain procedure-specific and are returned only after validation.
class AnalysisProcedure {
public:
virtual ~AnalysisProcedure() = default;
/// @brief Returns the original typed completed payload or first failure.
virtual Result<PreparedAnalysisOutcome> Execute(const Domain& domain) = 0;
};
} // namespace fesa
#endif // FESA_ANALYSIS_ANALYSIS_PROCEDURE_H_
+2 -1
View File
@@ -6,12 +6,13 @@
#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 {
class DofManager;
/// @brief Owns mutable quantities required by the V0 linear-static frame.
class AnalysisState {
public:
@@ -0,0 +1,19 @@
#ifndef FESA_ANALYSIS_DYNAMIC_EXECUTION_RESULT_H_
#define FESA_ANALYSIS_DYNAMIC_EXECUTION_RESULT_H_
#include <vector>
#include "fesa/analysis/dynamic_analysis_state.h"
#include "fesa/core/diagnostic.h"
namespace fesa {
/// @brief Stores a complete HHT history before atomic result finalization.
struct DynamicExecutionResult {
DynamicAnalysisState state;
std::vector<Diagnostic> diagnostics;
};
} // namespace fesa
#endif // FESA_ANALYSIS_DYNAMIC_EXECUTION_RESULT_H_
+1 -1
View File
@@ -8,7 +8,7 @@
#include <vector>
#include "fesa/math/matrix.h"
#include "fesa/solvers/eigen/mkl_feast_eigen_solver.h"
#include "fesa/solvers/eigen/eigen_solver.h"
namespace fesa {
@@ -1,10 +1,14 @@
#ifndef FESA_ANALYSIS_HHT_DYNAMIC_PROCEDURE_H_
#define FESA_ANALYSIS_HHT_DYNAMIC_PROCEDURE_H_
#include "fesa/analysis/prepared_analysis.h"
#include "fesa/analysis/dynamic_execution_result.h"
#include "fesa/core/status.h"
namespace fesa {
class Domain;
class LinearSolver;
class NewtonIterationLogWriter;
class ParallelFor;
class TangentSolver;
@@ -6,13 +6,23 @@
namespace fesa {
class Domain;
class EigenSolver;
/// @brief Executes the supported linear MITC4 frequency procedure.
class LinearFrequencyProcedureExecutor final {
public:
/// @brief Preserves default FEAST construction for existing callers.
LinearFrequencyProcedureExecutor() = default;
/// @brief Borrows a solver that must outlive this executor and its calls.
explicit LinearFrequencyProcedureExecutor(const EigenSolver& solver) : solver_(&solver) {}
/// @brief Builds, solves, and validates one frequency modal candidate.
/// @return Requested physical modes plus all-c validation evidence.
Result<EigenAnalysisState> Execute(const Domain& domain) const;
private:
const EigenSolver* solver_{nullptr};
};
} // namespace fesa
@@ -0,0 +1,19 @@
#ifndef FESA_ANALYSIS_LINEAR_STATIC_EXECUTION_RESULT_H_
#define FESA_ANALYSIS_LINEAR_STATIC_EXECUTION_RESULT_H_
#include <vector>
#include "fesa/analysis/analysis_state.h"
#include "fesa/core/diagnostic.h"
namespace fesa {
/// @brief Stores a completed linear-static calculation before result writing.
struct LinearStaticExecutionResult {
AnalysisState state;
std::vector<Diagnostic> diagnostics;
};
} // namespace fesa
#endif // FESA_ANALYSIS_LINEAR_STATIC_EXECUTION_RESULT_H_
@@ -1,10 +1,13 @@
#ifndef FESA_ANALYSIS_LINEAR_STATIC_PROCEDURE_H_
#define FESA_ANALYSIS_LINEAR_STATIC_PROCEDURE_H_
#include "fesa/analysis/prepared_analysis.h"
#include "fesa/analysis/linear_static_execution_result.h"
#include "fesa/core/status.h"
namespace fesa {
class Domain;
class LinearSolver;
class ParallelFor;
@@ -0,0 +1,20 @@
#ifndef FESA_ANALYSIS_NONLINEAR_STATIC_EXECUTION_RESULT_H_
#define FESA_ANALYSIS_NONLINEAR_STATIC_EXECUTION_RESULT_H_
#include <vector>
#include "fesa/analysis/nonlinear_analysis_state.h"
#include "fesa/core/diagnostic.h"
namespace fesa {
/// @brief Stores a completed nonlinear-static calculation before result
/// writing.
struct NonlinearStaticExecutionResult {
NonlinearAnalysisState state;
std::vector<Diagnostic> diagnostics;
};
} // namespace fesa
#endif // FESA_ANALYSIS_NONLINEAR_STATIC_EXECUTION_RESULT_H_
@@ -1,10 +1,13 @@
#ifndef FESA_ANALYSIS_NONLINEAR_STATIC_PROCEDURE_H_
#define FESA_ANALYSIS_NONLINEAR_STATIC_PROCEDURE_H_
#include "fesa/analysis/prepared_analysis.h"
#include "fesa/analysis/nonlinear_static_execution_result.h"
#include "fesa/core/status.h"
namespace fesa {
class Domain;
class NewtonIterationLogWriter;
class ParallelFor;
class TangentSolver;
+19 -32
View File
@@ -2,23 +2,21 @@
#define FESA_ANALYSIS_PREPARED_ANALYSIS_H_
#include <filesystem>
#include <variant>
#include <memory>
#include <vector>
#include "fesa/analysis/analysis_state.h"
#include "fesa/analysis/dynamic_analysis_state.h"
#include "fesa/analysis/eigen_analysis_state.h"
#include "fesa/analysis/nonlinear_analysis_state.h"
#include "fesa/analysis/solid_frequency_state.h"
#include "fesa/analysis/solid_hht_dynamic_procedure.h"
#include "fesa/analysis/solid_static_procedure.h"
#include "fesa/analysis/transient_heat_procedure.h"
#include "fesa/analysis/analysis_procedure.h"
#include "fesa/analysis/prepared_analysis_outcome.h"
#include "fesa/core/diagnostic.h"
#include "fesa/core/status.h"
#include "fesa/model/domain.h"
namespace fesa {
class ParallelFor;
class LinearSolver;
class TangentSolver;
class EigenSolver;
class LinearStaticProcedureExecutor;
class LinearFrequencyProcedureExecutor;
class EigenResultsWriter;
@@ -31,6 +29,9 @@ class DynamicResultsWriter;
class ThermalResultsWriter;
class SolidResultsWriter;
class SolidFrequencyProcedureExecutor;
class SolidStaticProcedureExecutor;
class SolidHhtDynamicProcedureExecutor;
class TransientHeatProcedureExecutor;
struct DerivedOutputPaths;
/// @brief Owns one parsed and semantically mapped analysis input.
@@ -46,30 +47,16 @@ struct PreparedAnalysisInput {
Result<PreparedAnalysisInput> PrepareAnalysisInput(
const std::filesystem::path& input_path);
/// @brief Stores a completed linear-static calculation before result writing.
struct LinearStaticExecutionResult {
AnalysisState state;
std::vector<Diagnostic> diagnostics;
};
/// @brief Creates exactly one selected family with caller-owned adapters.
/// @note Domain and adapters remain alive and unmoved throughout execution.
Result<std::unique_ptr<AnalysisProcedure>> CreateAnalysisProcedure(
const Domain &domain, const ParallelFor &parallel_for,
LinearSolver &linear_solver, TangentSolver &tangent_solver,
const EigenSolver &eigen_solver, NewtonIterationLogWriter &log);
/// @brief Stores a completed nonlinear-static calculation before result
/// writing.
struct NonlinearStaticExecutionResult {
NonlinearAnalysisState state;
std::vector<Diagnostic> diagnostics;
};
/// @brief Stores a complete HHT history before atomic result finalization.
struct DynamicExecutionResult {
DynamicAnalysisState state;
std::vector<Diagnostic> diagnostics;
};
using PreparedAnalysisOutcome =
std::variant<LinearStaticExecutionResult, NonlinearStaticExecutionResult,
EigenAnalysisState, DynamicExecutionResult,
ThermalExecutionResult, SolidStaticExecutionResult,
SolidFrequencyState, SolidDynamicExecutionResult>;
/// @brief Executes a caller-selected procedure through the common interface.
Result<PreparedAnalysisOutcome>
ExecutePreparedAnalysis(const Domain &domain, AnalysisProcedure &procedure);
/// @brief Selects the approved procedure from a prepared Domain.
/// @pre The executors and log outlive this call.
@@ -0,0 +1,26 @@
#ifndef FESA_ANALYSIS_PREPARED_ANALYSIS_OUTCOME_H_
#define FESA_ANALYSIS_PREPARED_ANALYSIS_OUTCOME_H_
#include <variant>
#include "fesa/analysis/dynamic_execution_result.h"
#include "fesa/analysis/eigen_analysis_state.h"
#include "fesa/analysis/linear_static_execution_result.h"
#include "fesa/analysis/nonlinear_static_execution_result.h"
#include "fesa/analysis/solid_dynamic_execution_result.h"
#include "fesa/analysis/solid_frequency_state.h"
#include "fesa/analysis/solid_static_execution_result.h"
#include "fesa/analysis/thermal_execution_result.h"
namespace fesa {
/// @brief Owns a completed procedure result before result-file finalization.
using PreparedAnalysisOutcome =
std::variant<LinearStaticExecutionResult, NonlinearStaticExecutionResult,
EigenAnalysisState, DynamicExecutionResult,
ThermalExecutionResult, SolidStaticExecutionResult,
SolidFrequencyState, SolidDynamicExecutionResult>;
} // namespace fesa
#endif // FESA_ANALYSIS_PREPARED_ANALYSIS_OUTCOME_H_
@@ -0,0 +1,17 @@
#ifndef FESA_ANALYSIS_SOLID_DYNAMIC_EXECUTION_RESULT_H_
#define FESA_ANALYSIS_SOLID_DYNAMIC_EXECUTION_RESULT_H_
#include "fesa/analysis/solid_dynamic_analysis_state.h"
#include "fesa/fem/dof_manager.h"
namespace fesa {
/// @brief Owns the validated 3N dynamic result independently of serialization.
struct SolidDynamicExecutionResult {
DofManager dofs;
SolidDynamicAnalysisState state;
};
} // namespace fesa
#endif // FESA_ANALYSIS_SOLID_DYNAMIC_EXECUTION_RESULT_H_
@@ -5,14 +5,24 @@
namespace fesa {
class Domain;
class EigenSolver;
/// @brief Executes unloaded reference-state C3D8 generalized frequency
/// analysis.
class SolidFrequencyProcedureExecutor final {
public:
/// @brief Preserves default FEAST construction for existing callers.
SolidFrequencyProcedureExecutor() = default;
/// @brief Borrows a solver that must outlive this executor and its calls.
explicit SolidFrequencyProcedureExecutor(const EigenSolver& solver) : solver_(&solver) {}
/// @brief Returns lowest requested modes after validating every returned
/// pair.
/// @note Domain is immutable and must remain alive during execution.
Result<SolidFrequencyState> Execute(const Domain& domain) const;
private:
const EigenSolver* solver_{nullptr};
};
} // namespace fesa
#endif // FESA_ANALYSIS_SOLID_FREQUENCY_PROCEDURE_H_
@@ -8,7 +8,7 @@
#include <vector>
#include "fesa/math/matrix.h"
#include "fesa/solvers/eigen/mkl_feast_eigen_solver.h"
#include "fesa/solvers/eigen/eigen_solver.h"
namespace fesa {
/// @brief Identifies a source-ordered physical Solid eigenmode.
@@ -1,19 +1,13 @@
#ifndef FESA_ANALYSIS_SOLID_HHT_DYNAMIC_PROCEDURE_H_
#define FESA_ANALYSIS_SOLID_HHT_DYNAMIC_PROCEDURE_H_
#include "fesa/analysis/solid_dynamic_analysis_state.h"
#include "fesa/analysis/solid_dynamic_execution_result.h"
namespace fesa {
class LinearSolver;
class ParallelFor;
class TangentSolver;
/// @brief Owns the validated 3N dynamic result independently of serialization.
struct SolidDynamicExecutionResult {
DofManager dofs;
SolidDynamicAnalysisState state;
};
/// @brief Integrates linear or TL Solid mechanics with constant consistent
/// mass.
class SolidHhtDynamicProcedureExecutor {
@@ -0,0 +1,19 @@
#ifndef FESA_ANALYSIS_SOLID_STATIC_EXECUTION_RESULT_H_
#define FESA_ANALYSIS_SOLID_STATIC_EXECUTION_RESULT_H_
#include <vector>
#include "fesa/analysis/solid_static_state.h"
#include "fesa/core/diagnostic.h"
namespace fesa {
/// @brief Owns successful static frames and sorted model diagnostics.
struct SolidStaticExecutionResult {
SolidStaticState state;
std::vector<Diagnostic> diagnostics;
};
} // namespace fesa
#endif // FESA_ANALYSIS_SOLID_STATIC_EXECUTION_RESULT_H_
@@ -1,19 +1,13 @@
#ifndef FESA_ANALYSIS_SOLID_STATIC_PROCEDURE_H_
#define FESA_ANALYSIS_SOLID_STATIC_PROCEDURE_H_
#include "fesa/analysis/solid_static_state.h"
#include "fesa/analysis/solid_static_execution_result.h"
namespace fesa {
class LinearSolver;
class ParallelFor;
class TangentSolver;
/// @brief Owns successful static frames and sorted model diagnostics.
struct SolidStaticExecutionResult {
SolidStaticState state;
std::vector<Diagnostic> diagnostics;
};
/// @brief Executes linear K0 or fixed-factor TL StVK static analysis.
class SolidStaticProcedureExecutor {
public:
+1 -1
View File
@@ -99,7 +99,7 @@ class SolidStaticState {
const noexcept;
private:
friend class SolidStaticProcedureExecutor;
friend class SolidStaticCore;
bool initialized_{false};
bool nonlinear_{false};
std::vector<SolidIntegrationPointRow> reference_points_;
@@ -6,11 +6,15 @@
#include <vector>
#include "fesa/analysis/thermal_time_grid.h"
#include "fesa/assembly/thermal_assembler.h"
#include "fesa/core/status.h"
#include "fesa/results/thermal_result_records.h"
namespace fesa {
class AnalysisModel;
class DofManager;
class ThermalAssembly;
/// @brief Owns the immutable thermal result inventory and accepted history.
/// @note No Domain/model/assembly references survive Create; state may outlive
/// them. Numerical equilibrium and energy checks belong to the procedure before
@@ -0,0 +1,19 @@
#ifndef FESA_ANALYSIS_THERMAL_EXECUTION_RESULT_H_
#define FESA_ANALYSIS_THERMAL_EXECUTION_RESULT_H_
#include <vector>
#include "fesa/analysis/thermal_analysis_state.h"
#include "fesa/core/diagnostic.h"
namespace fesa {
/// @brief Owns complete thermal history before any result output is opened.
struct ThermalExecutionResult {
ThermalAnalysisState state;
std::vector<Diagnostic> diagnostics;
};
} // namespace fesa
#endif // FESA_ANALYSIS_THERMAL_EXECUTION_RESULT_H_
@@ -1,21 +1,14 @@
#ifndef FESA_ANALYSIS_TRANSIENT_HEAT_PROCEDURE_H_
#define FESA_ANALYSIS_TRANSIENT_HEAT_PROCEDURE_H_
#include <vector>
#include "fesa/analysis/thermal_analysis_state.h"
#include "fesa/analysis/thermal_execution_result.h"
#include "fesa/core/status.h"
namespace fesa {
class Domain;
class LinearSolver;
class ParallelFor;
/// @brief Owns complete thermal history before any result output is opened.
struct ThermalExecutionResult {
ThermalAnalysisState state;
std::vector<Diagnostic> diagnostics;
};
/// @brief Executes fixed-increment backward Euler with checked thermal
/// recovery.
class TransientHeatProcedureExecutor final {
+2 -1
View File
@@ -30,7 +30,8 @@ class LoadAssembler {
double step_time = 0.0);
/// @brief Forms Ff-Kfc*dc in stable free/constrained order.
/// @note This operation neither factorizes nor invokes a solver.
/// @note Compatibility facade delegates to EssentialConstraintPolicy; this
/// operation neither factorizes nor invokes a solver.
static Result<Vector> EffectiveFreeRhs(const Vector& full_load,
const SparseMatrix& kfc,
const Vector& prescribed_values,
@@ -2,10 +2,12 @@
#define FESA_ASSEMBLY_THERMAL_ASSEMBLER_H_
#include <array>
#include <memory>
#include <vector>
#include "fesa/analysis/analysis_model.h"
#include "fesa/assembly/parallel_for.h"
#include "fesa/elements/element_capabilities.h"
#include "fesa/elements/thermal_shell.h"
#include "fesa/fem/dof_manager.h"
#include "fesa/math/sparse_matrix.h"
@@ -50,6 +52,8 @@ class ThermalAssembly final {
const std::vector<EntityIndex>& ElementOrder() const noexcept;
/// @brief Returns checked kernels in stable active element order.
const std::vector<ThermalShell>& Kernels() const noexcept;
/// @brief Returns typed views bound after the final kernel buffer is placed.
const std::vector<std::unique_ptr<ThermalElement>>& Elements() const noexcept;
/// @brief Returns node-major twenty-entry full DOF scatters in kernel order.
const std::vector<std::array<std::size_t, 20>>& Scatters() const noexcept;
/// @brief Checks source Domain identity and the complete supplied equation
@@ -79,7 +83,10 @@ class ThermalAssembly final {
SparseMatrix capacity_error_;
Vector input_error_;
std::vector<EntityIndex> element_order_;
// Never resized after construction. Moving the owning vector transfers its
// buffer, so bound references survive ThermalAssembly moves.
std::vector<ThermalShell> kernels_;
std::vector<std::unique_ptr<ThermalElement>> elements_;
std::vector<std::array<std::size_t, 20>> scatters_;
};
@@ -24,6 +24,16 @@ class EssentialConstraintPolicy {
Result<PartitionedStiffness> Partition(const SparseMatrix& full_stiffness,
const DofManager& dof_manager) const;
/// @brief Forms Ff-Kfc*dc in stable free/constrained order.
/// @return A finite RHS candidate or the established load diagnostic.
/// @note Preserves independent CSR products and ordered row reductions;
/// no factorization or solver operation is performed. Inputs are borrowed
/// only for this call and remain unchanged on failure.
Result<Vector> EffectiveFreeRhs(const Vector& full_load,
const SparseMatrix& kfc,
const Vector& prescribed_values,
const DofManager& dofs) const;
/// @brief Gathers a full vector in stable free-equation order.
Vector GatherFree(const Vector& full_values,
const DofManager& dof_manager) const;
@@ -5,7 +5,7 @@
#include "fesa/constraints/boundary_condition.h"
#include "fesa/core/diagnostic.h"
#include "fesa/elements/element.h"
#include "fesa/elements/element_dof_layout.h"
#include "fesa/model/source_target_resolver.h"
namespace fesa {
+6
View File
@@ -2,6 +2,7 @@
#define FESA_CORE_ASCII_H_
#include <cstdint>
#include <string>
#include <string_view>
#include "fesa/core/status.h"
@@ -12,6 +13,11 @@ namespace fesa {
/// @return The lowercase ASCII byte, or the input byte when it is not A-Z.
char AsciiLower(char value) noexcept;
/// @brief Returns an uppercase ASCII copy without changing other source bytes.
/// @note Embedded nulls and non-ASCII bytes are preserved without locale
/// folding.
std::string AsciiUppercase(std::string value);
/// @brief Compares two byte strings with ASCII-only case folding.
/// @return True when the strings have equal length and equal ASCII-folded
/// bytes.
+8
View File
@@ -24,6 +24,14 @@ struct SourceEntityId {
std::string source_label_text;
};
/// @brief Compares all preserved source fields without canonicalizing spelling.
inline bool SourceIdentityEquals(const SourceEntityId& left,
const SourceEntityId& right) noexcept {
return left.instance_name == right.instance_name &&
left.source_label == right.source_label &&
left.source_label_text == right.source_label_text;
}
} // namespace fesa
#endif // FESA_CORE_SOURCE_IDENTITY_H_
+2 -2
View File
@@ -98,11 +98,11 @@ class Result {
struct FailureTag {};
/// @brief Constructs the successful value alternative.
Result(SuccessTag, T value)
Result(SuccessTag, T&& value)
: value_{std::move(value)}, status_{Status::Ok()} {}
/// @brief Constructs the failed status alternative.
Result(FailureTag, Status status)
Result(FailureTag, Status&& status)
: value_{std::nullopt}, status_{std::move(status)} {}
std::optional<T> value_;
+1 -24
View File
@@ -1,13 +1,11 @@
#ifndef FESA_ELEMENTS_ELEMENT_H_
#define FESA_ELEMENTS_ELEMENT_H_
#include <cstdint>
#include <functional>
#include <variant>
#include <vector>
#include "fesa/core/source_identity.h"
#include "fesa/core/status.h"
#include "fesa/elements/element_dof_layout.h"
#include "fesa/math/matrix.h"
#include "fesa/math/vector.h"
#include "fesa/results/result_records.h"
@@ -15,23 +13,6 @@
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 Describes one runtime element's stable node and component order.
struct ElementDofLayout {
SourceEntityId source_id;
std::vector<EntityIndex> node_indices;
std::vector<DofComponent> components_per_node;
};
/// @brief Carries one element-local stiffness in its declared DOF order.
struct ElementStiffnessContribution {
ElementDofLayout layout;
@@ -78,10 +59,6 @@ class Element {
const Vector& element_displacement) const = 0;
};
/// @brief Provides non-owning runtime elements in stable owner order.
/// @note Every referenced element must outlive this view.
using ElementView = std::vector<std::reference_wrapper<const Element>>;
} // namespace fesa
#endif // FESA_ELEMENTS_ELEMENT_H_
@@ -0,0 +1,200 @@
#ifndef FESA_ELEMENTS_ELEMENT_CAPABILITIES_H_
#define FESA_ELEMENTS_ELEMENT_CAPABILITIES_H_
#include <array>
#include <cstddef>
#include "fesa/elements/element.h"
#include "fesa/elements/thermal_element_layout.h"
#include "fesa/math/vector3.h"
namespace fesa {
class Hex8Element;
class Mitc4Shell;
class NonlinearMitc4Shell;
class NonlinearMitc4Inertia;
class ThermalShell;
struct Hex8Response;
struct Hex8HighLowResponse;
struct Mitc4Stiffness;
struct Mitc4Mass;
struct NonlinearMitc4TrialDofs;
struct NonlinearMitc4InternalEquilibrium;
struct NonlinearMitc4ExternalDrillingConstraint;
struct NonlinearMitc4InertiaEvaluation;
struct ThermalShellRecovery;
/// @brief Evaluates immutable shell trials; accepted history belongs to the procedure.
class ShellTrialElement {
public:
virtual ~ShellTrialElement() = default;
/// @brief Returns stable source-local node/component order, never equations.
virtual const ElementDofLayout& DofLayout() const noexcept = 0;
/// @brief Returns the complete force/tangent/recovery at a fixed-global trial.
/// @param coordinate_direction Optional 24-vector for D2(f_int)[v,v].
/// @note Failure preserves the kernel, caller inputs and earlier responses.
virtual Result<NonlinearMitc4InternalEquilibrium> EvaluateTrial(
const NonlinearMitc4TrialDofs& trial,
const Vector* coordinate_direction = nullptr) const = 0;
/// @copydoc NonlinearMitc4Shell::EvaluateExternalDrillingConstraint
virtual Result<NonlinearMitc4ExternalDrillingConstraint>
EvaluateExternalDrillingConstraint(
std::size_t node, const Vector3& phi, const Vector3& phi_dot,
const Vector3& force, const Vector3& force_rate,
const Vector3& force_acceleration) const = 0;
};
/// @brief Borrows the same immutable shell kernel for every trial operation.
/// @note The kernel must outlive this adapter and remain at its bound address.
class Mitc4TrialAdapter final : public ShellTrialElement {
public:
Mitc4TrialAdapter(ElementDofLayout layout, const NonlinearMitc4Shell& kernel);
const ElementDofLayout& DofLayout() const noexcept override;
Result<NonlinearMitc4InternalEquilibrium> EvaluateTrial(
const NonlinearMitc4TrialDofs& trial,
const Vector* coordinate_direction = nullptr) const override;
Result<NonlinearMitc4ExternalDrillingConstraint>
EvaluateExternalDrillingConstraint(
std::size_t node, const Vector3& phi, const Vector3& phi_dot,
const Vector3& force, const Vector3& force_rate,
const Vector3& force_acceleration) const override;
private:
ElementDofLayout layout_;
const NonlinearMitc4Shell& kernel_;
};
/// @brief Keeps complete paired solid responses and reference mass typed.
class SolidTrialElement : public Element {
public:
~SolidTrialElement() override = default;
/// @copydoc Hex8Element::EvaluateLinear
virtual Result<Hex8Response> EvaluateLinear(const Vector& u) const = 0;
/// @copydoc Hex8Element::EvaluateNonlinear
virtual Result<Hex8Response> EvaluateNonlinear(const Vector& u) const = 0;
/// @copydoc Hex8Element::EvaluateLinearHighLow
virtual Result<Hex8HighLowResponse> EvaluateLinearHighLow(
const Vector& high, const Vector& low) const = 0;
/// @copydoc Hex8Element::EvaluateNonlinearHighLow
virtual Result<Hex8HighLowResponse> EvaluateNonlinearHighLow(
const Vector& high, const Vector& low) const = 0;
/// @copydoc Hex8Element::ComputeMass
virtual Result<ElementStiffnessContribution> ComputeMass() const = 0;
/// @brief Returns fixed Gauss weights used by the frequency mass bound.
virtual std::array<double, 8> ReferenceWeights() const noexcept = 0;
};
/// @brief Borrows one stable Hex8 kernel shared by all local operations.
/// @note The kernel must outlive this adapter and remain at its bound address.
class Hex8TrialAdapter final : public SolidTrialElement {
public:
explicit Hex8TrialAdapter(const Hex8Element& kernel);
const ElementDofLayout& DofLayout() const noexcept override;
Result<ElementStiffnessContribution> ComputeStiffness() const override;
Result<ElementResultBundle> Recover(const Vector& u) const override;
Result<Hex8Response> EvaluateLinear(const Vector& u) const override;
Result<Hex8Response> EvaluateNonlinear(const Vector& u) const override;
Result<Hex8HighLowResponse> EvaluateLinearHighLow(
const Vector& high, const Vector& low) const override;
Result<Hex8HighLowResponse> EvaluateNonlinearHighLow(
const Vector& high, const Vector& low) const override;
Result<ElementStiffnessContribution> ComputeMass() const override;
std::array<double, 8> ReferenceWeights() const noexcept override;
private:
const Hex8Element& kernel_;
};
/// @brief Returns the complete physical inertia response in fixed chart DOFs.
class ShellInertiaElement {
public:
virtual ~ShellInertiaElement() = default;
virtual const ElementDofLayout& DofLayout() const noexcept = 0;
/// @copydoc NonlinearMitc4Inertia::Evaluate
virtual Result<NonlinearMitc4InertiaEvaluation> Evaluate(
const NonlinearMitc4TrialDofs& trial, const Vector& velocity24,
const Vector& physical_acceleration24) const = 0;
};
/// @brief Borrows immutable inertia geometry, never shared mutable scratch.
/// @note The kernel must outlive this adapter and remain at its bound address.
class Mitc4InertiaAdapter final : public ShellInertiaElement {
public:
Mitc4InertiaAdapter(ElementDofLayout layout,
const NonlinearMitc4Inertia& kernel);
const ElementDofLayout& DofLayout() const noexcept override;
Result<NonlinearMitc4InertiaEvaluation> Evaluate(
const NonlinearMitc4TrialDofs& trial, const Vector& velocity24,
const Vector& physical_acceleration24) const override;
private:
ElementDofLayout layout_;
const NonlinearMitc4Inertia& kernel_;
};
/// @brief Keeps DS4 DOFs 11-15 and all four-by-six flux traces independent.
class ThermalElement {
public:
virtual ~ThermalElement() = default;
/// @brief Returns node-major stations 11-15 and original source identity.
virtual const ThermalElementDofLayout& DofLayout() const noexcept = 0;
/// @copydoc ThermalShell::Conductivity
virtual const Matrix& Conductivity() const noexcept = 0;
/// @copydoc ThermalShell::Capacity
virtual const Matrix& Capacity() const noexcept = 0;
/// @copydoc ThermalShell::ConductivityError
virtual const Matrix& ConductivityError() const noexcept = 0;
/// @copydoc ThermalShell::CapacityError
virtual const Matrix& CapacityError() const noexcept = 0;
/// @copydoc ThermalShell::Recover
virtual Result<ThermalShellRecovery> Recover(
const std::array<double, 20>& temperature) const = 0;
};
/// @brief Borrows the same DS4 kernel for matrices, intervals and recovery.
/// @note The kernel must outlive this adapter and remain at its bound address.
class Ds4ThermalAdapter final : public ThermalElement {
public:
explicit Ds4ThermalAdapter(const ThermalShell& kernel);
const ThermalElementDofLayout& DofLayout() const noexcept override;
const Matrix& Conductivity() const noexcept override;
const Matrix& Capacity() const noexcept override;
const Matrix& ConductivityError() const noexcept override;
const Matrix& CapacityError() const noexcept override;
Result<ThermalShellRecovery> Recover(
const std::array<double, 20>& temperature) const override;
private:
ThermalElementDofLayout layout_;
const ThermalShell& kernel_;
};
/// @brief Supplies shell frequency operators with their distinct mass contract.
class ShellReferenceElement : public Element {
public:
~ShellReferenceElement() override = default;
/// @copydoc Mitc4Shell::Stiffness
virtual Result<Mitc4Stiffness> Stiffness() const = 0;
/// @copydoc Mitc4Shell::Mass
virtual Result<Mitc4Mass> Mass(double density) const = 0;
};
/// @brief Borrows the same linear MITC4 kernel for stiffness and recovery.
/// @note The kernel must outlive this adapter and remain at its bound address.
class Mitc4ReferenceAdapter final : public ShellReferenceElement {
public:
explicit Mitc4ReferenceAdapter(const Mitc4Shell& kernel);
const ElementDofLayout& DofLayout() const noexcept override;
Result<ElementStiffnessContribution> ComputeStiffness() const override;
Result<ElementResultBundle> Recover(const Vector& u) const override;
Result<Mitc4Stiffness> Stiffness() const override;
Result<Mitc4Mass> Mass(double density) const override;
private:
const Mitc4Shell& kernel_;
};
} // namespace fesa
#endif // FESA_ELEMENTS_ELEMENT_CAPABILITIES_H_
@@ -0,0 +1,38 @@
#ifndef FESA_ELEMENTS_ELEMENT_DOF_LAYOUT_H_
#define FESA_ELEMENTS_ELEMENT_DOF_LAYOUT_H_
#include <cstdint>
#include <functional>
#include <vector>
#include "fesa/core/source_identity.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 Describes one runtime element's stable node and component order.
struct ElementDofLayout {
SourceEntityId source_id;
std::vector<EntityIndex> node_indices;
std::vector<DofComponent> components_per_node;
};
class Element;
/// @brief Provides non-owning runtime elements in stable owner order.
/// @note Every referenced element must outlive this view.
using ElementView = std::vector<std::reference_wrapper<const Element>>;
} // namespace fesa
#endif // FESA_ELEMENTS_ELEMENT_DOF_LAYOUT_H_
+9
View File
@@ -5,6 +5,7 @@
#include "fesa/core/status.h"
#include "fesa/elements/element.h"
#include "fesa/elements/element_capabilities.h"
namespace fesa {
@@ -21,6 +22,14 @@ class ElementFactory {
Result<std::unique_ptr<Element>> Create(const ElementDefinition& definition,
const Domain& domain) const;
/// @brief Binds a typed view of an existing stable runtime kernel.
/// @note Returns null for a different family. The kernel must outlive the
/// returned adapter and remain at its address; no second kernel is created.
std::unique_ptr<SolidTrialElement> BindSolidTrial(const Element& kernel) const;
/// @copydoc BindSolidTrial
std::unique_ptr<ShellReferenceElement> BindShellReference(
const Element& kernel) const;
private:
/// @brief Creates one checked B33 runtime candidate.
Result<std::unique_ptr<Element>> CreateBeam(
+1 -34
View File
@@ -4,12 +4,11 @@
#include <array>
#include <cstddef>
#include <string>
#include <string_view>
#include <vector>
#include "fesa/core/status.h"
#include "fesa/elements/element.h"
#include "fesa/elements/element_definition.h"
#include "fesa/elements/euler_beam_3d_definition.h"
#include "fesa/materials/isotropic_linear_elastic_material.h"
#include "fesa/math/matrix.h"
#include "fesa/math/vector.h"
@@ -17,40 +16,8 @@
namespace fesa {
class Domain;
struct Node;
/// @brief Defines one two-node B33 semantic element.
class EulerBeam3DDefinition final : public ElementDefinition {
public:
/// @brief Constructs a parser-validated semantic definition.
EulerBeam3DDefinition(SourceEntityId source_id,
std::array<EntityIndex, 2> node_indices,
EntityIndex material_index, EntityIndex section_index,
SourceLocation location);
ElementDefinitionKind Kind() const noexcept override;
const SourceEntityId& SourceId() const noexcept override;
std::string_view SourceElementType() const noexcept override;
const std::vector<EntityIndex>& NodeIndices() const noexcept override;
EntityIndex MaterialIndex() const noexcept override;
EntityIndex PropertyIndex() const noexcept override;
SourceEntityId source_id;
std::array<EntityIndex, 2> node_indices;
EntityIndex material_index;
EntityIndex section_index;
SourceLocation location;
private:
friend class Domain;
/// @brief Synchronizes the base view after parser-candidate construction.
void SynchronizeNodeIndices();
std::vector<EntityIndex> node_indices_view_;
};
/// @brief Stores constant line-load components in the beam local frame.
struct ConstantLocalLineLoad {
double px;
@@ -0,0 +1,48 @@
#ifndef FESA_ELEMENTS_EULER_BEAM_3D_DEFINITION_H_
#define FESA_ELEMENTS_EULER_BEAM_3D_DEFINITION_H_
#include <array>
#include <string_view>
#include <vector>
#include "fesa/elements/element_definition.h"
namespace fesa {
class Domain;
/// @brief Defines one two-node B33 semantic element.
class EulerBeam3DDefinition final : public ElementDefinition {
public:
/// @brief Constructs a parser-validated semantic definition.
EulerBeam3DDefinition(SourceEntityId source_id,
std::array<EntityIndex, 2> node_indices,
EntityIndex material_index, EntityIndex section_index,
SourceLocation location);
ElementDefinitionKind Kind() const noexcept override;
const SourceEntityId& SourceId() const noexcept override;
std::string_view SourceElementType() const noexcept override;
const std::vector<EntityIndex>& NodeIndices() const noexcept override;
EntityIndex MaterialIndex() const noexcept override;
EntityIndex PropertyIndex() const noexcept override;
SourceEntityId source_id;
std::array<EntityIndex, 2> node_indices;
EntityIndex material_index;
EntityIndex section_index;
SourceLocation location;
private:
friend class Domain;
/// @brief Synchronizes the base view after parser-candidate construction.
void SynchronizeNodeIndices();
std::vector<EntityIndex> node_indices_view_;
};
} // namespace fesa
#endif // FESA_ELEMENTS_EULER_BEAM_3D_DEFINITION_H_
+6 -4
View File
@@ -33,11 +33,13 @@ Matrix ExpSkew(const Vector3& phi);
/// @pre phi is finite and lies inside the approved principal chart.
Matrix LeftJacobianSO3(const Vector3& phi);
/// @brief Computes the inverse transpose of the SO(3) left Jacobian.
/// @param phi Fixed-global rotation coordinate in radians.
/// @return The 3-by-3 inverse-transpose left Jacobian.
/// @brief Solves J(phi)^T * moment = generalized for a spatial moment.
/// @param phi Fixed-global principal-chart rotation coordinate in radians.
/// @param generalized Work-conjugate moment in fixed-global chart coordinates.
/// @return A finite spatial moment in the same force-length units, or failure.
/// @pre phi is finite and lies inside the approved principal chart.
Matrix InverseTransposeLeftJacobianSO3(const Vector3& phi);
/// @note Inputs remain unchanged; no inverse Jacobian is constructed.
Result<Vector3> SpatialMomentSO3(const Vector3& phi, const Vector3& generalized);
/// @brief Builds a checked finite-rotation state from its reference frame.
/// @param phi Fixed-global principal-chart rotation coordinate in radians.
+2 -48
View File
@@ -3,12 +3,11 @@
#include <array>
#include <string>
#include <string_view>
#include <vector>
#include "fesa/core/status.h"
#include "fesa/elements/element.h"
#include "fesa/elements/element_definition.h"
#include "fesa/elements/mitc4_shell_definition.h"
#include "fesa/materials/isotropic_linear_elastic_material.h"
#include "fesa/math/matrix.h"
#include "fesa/math/vector.h"
@@ -17,52 +16,8 @@
namespace fesa {
class Domain;
struct Node;
/// @brief Preserves the source shell type independently of formulation.
enum class ShellSourceElementType { kS4, kS4r };
/// @brief Names the internal shell formulation selected by S4 and S4R.
inline constexpr std::string_view kMitc4InternalFormulation{"FESA-MITC4"};
/// @brief Defines one four-node S4/S4R semantic element.
class Mitc4ShellDefinition final : public ElementDefinition {
public:
/// @brief Constructs a parser-validated semantic definition.
Mitc4ShellDefinition(SourceEntityId source_id,
ShellSourceElementType source_type,
std::array<EntityIndex, 4> node_indices,
EntityIndex material_index, EntityIndex section_index,
SourceLocation location);
ElementDefinitionKind Kind() const noexcept override;
const SourceEntityId& SourceId() const noexcept override;
std::string_view SourceElementType() const noexcept override;
const std::vector<EntityIndex>& NodeIndices() const noexcept override;
EntityIndex MaterialIndex() const noexcept override;
EntityIndex PropertyIndex() const noexcept override;
SourceEntityId source_id;
ShellSourceElementType source_type;
std::array<EntityIndex, 4> node_indices;
EntityIndex material_index;
EntityIndex section_index;
SourceLocation location;
private:
friend class Domain;
/// @brief Rebinds a shell-local section index to the unified property view.
void SetPropertyIndex(EntityIndex property_index) noexcept;
/// @brief Synchronizes the base view after parser-candidate construction.
void SynchronizeNodeIndices();
EntityIndex property_index_;
std::vector<EntityIndex> node_indices_view_;
};
/// @brief Stores bilinear shape values and natural-coordinate derivatives.
struct Mitc4ShapeFunctions {
std::array<double, 4> values;
@@ -213,11 +168,10 @@ class Mitc4Shell final : public Element {
void BindRuntime(ElementDofLayout layout, EntityIndex element_index,
SourceLocation location);
/// @brief Stores covariant, reciprocal, frame, and Jacobian data at one
/// @brief Stores covariant, frame, and Jacobian data at one
/// point.
struct GeometryData {
std::array<Vector3, 3> covariant;
std::array<Vector3, 3> reciprocal;
Mitc4LocalFrame frame;
double jacobian;
};
@@ -0,0 +1,60 @@
#ifndef FESA_ELEMENTS_MITC4_SHELL_DEFINITION_H_
#define FESA_ELEMENTS_MITC4_SHELL_DEFINITION_H_
#include <array>
#include <string_view>
#include <vector>
#include "fesa/elements/element_definition.h"
namespace fesa {
class Domain;
/// @brief Preserves the source shell type independently of formulation.
enum class ShellSourceElementType { kS4, kS4r };
/// @brief Names the internal shell formulation selected by S4 and S4R.
inline constexpr std::string_view kMitc4InternalFormulation{"FESA-MITC4"};
/// @brief Defines one four-node S4/S4R semantic element.
class Mitc4ShellDefinition final : public ElementDefinition {
public:
/// @brief Constructs a parser-validated semantic definition.
Mitc4ShellDefinition(SourceEntityId source_id,
ShellSourceElementType source_type,
std::array<EntityIndex, 4> node_indices,
EntityIndex material_index, EntityIndex section_index,
SourceLocation location);
ElementDefinitionKind Kind() const noexcept override;
const SourceEntityId& SourceId() const noexcept override;
std::string_view SourceElementType() const noexcept override;
const std::vector<EntityIndex>& NodeIndices() const noexcept override;
EntityIndex MaterialIndex() const noexcept override;
EntityIndex PropertyIndex() const noexcept override;
SourceEntityId source_id;
ShellSourceElementType source_type;
std::array<EntityIndex, 4> node_indices;
EntityIndex material_index;
EntityIndex section_index;
SourceLocation location;
private:
friend class Domain;
/// @brief Rebinds a shell-local section index to the unified property view.
void SetPropertyIndex(EntityIndex property_index) noexcept;
/// @brief Synchronizes the base view after parser-candidate construction.
void SynchronizeNodeIndices();
EntityIndex property_index_;
std::vector<EntityIndex> node_indices_view_;
};
} // namespace fesa
#endif // FESA_ELEMENTS_MITC4_SHELL_DEFINITION_H_
@@ -0,0 +1,20 @@
#ifndef FESA_ELEMENTS_THERMAL_DOF_COMPONENT_H_
#define FESA_ELEMENTS_THERMAL_DOF_COMPONENT_H_
namespace fesa {
/// @brief Identifies the fixed bottom-to-top thermal station source DOFs.
/// @note This is separate from the six-component structural DofComponent.
enum class ThermalDofComponent : int {
kBottom = 11,
kLower = 12,
kMiddle = 13,
kUpper = 14,
kTop = 15
};
} // namespace fesa
#endif // FESA_ELEMENTS_THERMAL_DOF_COMPONENT_H_
@@ -3,7 +3,8 @@
#include <array>
#include "fesa/model/model_types.h"
#include "fesa/core/source_identity.h"
#include "fesa/elements/thermal_dof_component.h"
namespace fesa {
+3 -8
View File
@@ -6,25 +6,20 @@
#include <optional>
#include <vector>
#include "fesa/analysis/analysis_model.h"
#include "fesa/constraints/boundary_condition.h"
#include "fesa/elements/element.h"
#include "fesa/elements/element_dof_layout.h"
#include "fesa/elements/thermal_element_layout.h"
#include "fesa/math/sparse_pattern.h"
#include "fesa/math/vector.h"
namespace fesa {
class AnalysisModel;
class DofManagerTestAccess;
/// @brief Distinguishes six-component, Solid translation, and thermal maps.
enum class DofProfile { kStructural, kThermal, kSolidStructural };
/// @brief Stores the stable full-space 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:
+20
View File
@@ -32,6 +32,26 @@ class Matrix {
/// @brief Returns the column count.
std::size_t Columns() const noexcept;
/// @brief Returns mutable row-major contiguous storage.
/// @note Borrows Rows()*Columns() entries until storage is moved, assigned
/// or destroyed.
double* Data() noexcept;
/// @brief Returns immutable row-major contiguous storage.
/// @note Borrows Rows()*Columns() entries until storage is moved, assigned
/// or destroyed.
const double* Data() const noexcept;
/// @brief Computes the robust Frobenius norm through the dense backend.
/// @note Empty storage has zero norm; nonfinite values propagate as in Vector.
/// @throws std::length_error if storage exceeds the backend integer range.
double Norm() const;
/// @brief Scales every entry by alpha through the dense backend.
/// @note Empty storage is unchanged; nonfinite values follow BLAS semantics.
/// @throws std::length_error if storage exceeds the backend integer range.
void Scale(double alpha);
/// @brief Returns a bounds-checked mutable entry.
/// @throws std::out_of_range if the index is outside the matrix.
double& operator()(std::size_t row, std::size_t column);
+1 -2
View File
@@ -5,12 +5,11 @@
#include <vector>
#include "fesa/core/status.h"
#include "fesa/math/sparse_pattern.h"
#include "fesa/math/vector.h"
namespace fesa {
struct SparsePattern;
/// @brief Carries one deterministic element-local COO contribution.
struct CooContribution {
std::size_t row;
+18
View File
@@ -0,0 +1,18 @@
#ifndef FESA_MATH_SPARSE_PATTERN_H_
#define FESA_MATH_SPARSE_PATTERN_H_
#include <cstddef>
#include <vector>
namespace fesa {
/// @brief Stores the stable full-space CSR pattern.
struct SparsePattern {
std::vector<std::size_t> row_offsets;
std::vector<std::size_t> column_indices;
};
} // namespace fesa
#endif // FESA_MATH_SPARSE_PATTERN_H_
+2
View File
@@ -11,6 +11,8 @@
#include "fesa/constraints/boundary_condition.h"
#include "fesa/constraints/prescribed_displacement.h"
#include "fesa/core/status.h"
#include "fesa/elements/ds4_thermal.h"
#include "fesa/materials/isotropic_thermal_material.h"
#include "fesa/loads/concentrated_nodal_load.h"
#include "fesa/loads/load.h"
#include "fesa/model/model_types.h"
+3 -14
View File
@@ -13,12 +13,11 @@
#include "fesa/core/diagnostic.h"
#include "fesa/core/source_identity.h"
#include "fesa/elements/ds4_thermal.h"
#include "fesa/elements/euler_beam_3d.h"
#include "fesa/elements/euler_beam_3d_definition.h"
#include "fesa/elements/hex8_definition.h"
#include "fesa/elements/mitc4_shell.h"
#include "fesa/elements/mitc4_shell_definition.h"
#include "fesa/elements/thermal_dof_component.h"
#include "fesa/materials/isotropic_linear_elastic_material.h"
#include "fesa/materials/isotropic_thermal_material.h"
#include "fesa/properties/general_beam_section.h"
#include "fesa/properties/shell_section.h"
#include "fesa/properties/solid_section.h"
@@ -302,16 +301,6 @@ struct ThermalInitialConditionDefinition {
SourceLocation location;
};
/// @brief Identifies the fixed bottom-to-top thermal station source DOFs.
/// @note This is separate from the six-component structural DofComponent.
enum class ThermalDofComponent : int {
kBottom = 11,
kLower = 12,
kMiddle = 13,
kUpper = 14,
kTop = 15
};
/// @brief Stores one prescribed thermal source DOF value.
struct ThermalBoundaryConditionDefinition {
EntityIndex node_index;
@@ -38,6 +38,14 @@ class SourceTargetIndex {
/// @return An owning index that preserves Domain declaration order.
static SourceTargetIndex FromDomain(const Domain& domain);
/// @brief Copies node and set identities before Domain ownership begins.
/// @param nodes Semantic nodes in stable expanded declaration order.
/// @param node_sets Node memberships in stable source order.
/// @return An owning node-only index preserving raw spelling and scope.
/// @note The returned index outlives its inputs; no Domain is constructed.
static SourceTargetIndex FromNodeDefinitions(
const std::vector<Node>& nodes, const std::vector<NodeSet>& node_sets);
/// @brief Returns owned entries without exposing mutable index state.
const std::vector<SourceTargetIndexEntry>& Entries() const noexcept;
@@ -0,0 +1,39 @@
#ifndef FESA_RESULTS_PREPARED_RESULTS_WRITER_H_
#define FESA_RESULTS_PREPARED_RESULTS_WRITER_H_
#include <filesystem>
#include <vector>
#include "fesa/analysis/prepared_analysis_outcome.h"
#include "fesa/core/diagnostic.h"
#include "fesa/core/status.h"
namespace fesa {
class Domain;
class ResultsWriter;
class NonlinearResultsWriter;
class EigenResultsWriter;
class DynamicResultsWriter;
class ThermalResultsWriter;
class SolidResultsWriter;
/// @brief Routes one completed typed outcome to its authoritative writer.
/// @pre Domain, outcome, diagnostics, and adapters remain alive for this call.
/// The caller has closed its analysis log successfully before publication.
/// @param results_path Explicit output path, retaining each writer's contract.
/// @note Optional adapters are required only for their matching outcome family.
/// @return The selected writer's Status after candidate validation/finalization,
/// or the existing missing-adapter output failure without invoking a writer.
Status WritePreparedAnalysisResult(
const std::filesystem::path& results_path, const Domain& domain,
const PreparedAnalysisOutcome& outcome,
const std::vector<Diagnostic>& diagnostics, ResultsWriter& linear_writer,
NonlinearResultsWriter& nonlinear_writer, EigenResultsWriter& eigen_writer,
DynamicResultsWriter* dynamic_writer = nullptr,
ThermalResultsWriter* thermal_writer = nullptr,
SolidResultsWriter* solid_writer = nullptr);
} // namespace fesa
#endif // FESA_RESULTS_PREPARED_RESULTS_WRITER_H_
+99
View File
@@ -0,0 +1,99 @@
#ifndef FESA_SOLVERS_EIGEN_EIGEN_SOLVER_H_
#define FESA_SOLVERS_EIGEN_EIGEN_SOLVER_H_
#include <cstddef>
#include <cstdint>
#include <string>
#include <vector>
#include "fesa/core/status.h"
#include "fesa/math/matrix.h"
namespace fesa {
class SparseMatrix;
/// @brief Records one deterministic private-backend invocation.
struct FeastBackendCall {
std::size_t ordinal;
std::string kind;
std::string purpose;
double lower;
double upper;
std::size_t allocated_m0;
int info;
bool count_available;
std::size_t count;
bool loop_available;
int loop;
bool epsout_available;
double epsout;
std::string outcome;
std::string decision_reason;
};
/// @brief Owns the complete validated FEAST interval result before public N
/// selection.
struct FeastEigenResult {
std::size_t requested_modes;
std::vector<double> eigenvalues;
Matrix eigenvectors;
double operator_scale;
std::vector<std::size_t> column_ids;
std::vector<bool> backend_res_available;
std::vector<double> backend_res;
std::vector<double> normalized_eigenvalues;
std::vector<double> whitened_residuals;
double gram_error;
std::vector<FeastBackendCall> backend_calls;
std::vector<std::int64_t> base_fpm;
std::string runtime_version;
std::string integer_abi;
std::size_t thread_count;
std::uint64_t seed;
double enclosure_lower;
double enclosure_upper;
double solve_lower;
double solve_upper;
std::string upper_search_termination;
};
/// @brief Solves symmetric standard or definite generalized eigenproblems.
/// @note Inputs remain caller-owned and are borrowed only for each call.
class EigenSolver {
public:
/// @brief Destroys a backend-neutral eigen solver.
virtual ~EigenSolver() = default;
/// @brief Computes all validated interval pairs for a requested lowest-mode
/// prefix.
/// @param whitened_operator Symmetric physical-lambda operator after
/// whitening.
/// @param requested_modes Number of lowest modes eventually exposed by the
/// caller.
/// @param zero_operator_scale Positive physical scale used only when A is
/// exactly zero.
/// @return Sorted all-c eigenpairs in physical lambda units or a solver
/// failure.
virtual Result<FeastEigenResult> Solve(
const SparseMatrix& whitened_operator, std::size_t requested_modes,
double zero_operator_scale) const = 0;
/// @brief Solves the original sparse symmetric definite K/M pencil.
/// @param mass_lower_bound Certified positive lower spectral bound of M,
/// including floating-point assembly uncertainty; never a diagonal estimate.
/// @param classification_scale Positive max |Kii|/Mii in physical units.
/// @note Eigenvectors are mass-normalized. For this API the historical
/// whitened_residuals field contains original-pencil eta_r; no whitening
/// is performed. Intervals/eigenvalues in normalized backend evidence use
/// classification_scale, while eigenvalues are returned in physical units.
virtual Result<FeastEigenResult> SolveGeneralized(
const SparseMatrix& stiffness, const SparseMatrix& mass,
std::size_t requested_modes, double mass_lower_bound,
double classification_scale) const = 0;
};
} // namespace fesa
#endif // FESA_SOLVERS_EIGEN_EIGEN_SOLVER_H_
@@ -1,65 +1,12 @@
#ifndef FESA_SOLVERS_EIGEN_MKL_FEAST_EIGEN_SOLVER_H_
#define FESA_SOLVERS_EIGEN_MKL_FEAST_EIGEN_SOLVER_H_
#include <cstddef>
#include <cstdint>
#include <string>
#include <vector>
#include "fesa/core/status.h"
#include "fesa/math/matrix.h"
#include "fesa/solvers/eigen/eigen_solver.h"
namespace fesa {
class SparseMatrix;
/// @brief Records one deterministic private-backend invocation.
struct FeastBackendCall {
std::size_t ordinal;
std::string kind;
std::string purpose;
double lower;
double upper;
std::size_t allocated_m0;
int info;
bool count_available;
std::size_t count;
bool loop_available;
int loop;
bool epsout_available;
double epsout;
std::string outcome;
std::string decision_reason;
};
/// @brief Owns the complete validated FEAST interval result before public N
/// selection.
struct FeastEigenResult {
std::size_t requested_modes;
std::vector<double> eigenvalues;
Matrix eigenvectors;
double operator_scale;
std::vector<std::size_t> column_ids;
std::vector<bool> backend_res_available;
std::vector<double> backend_res;
std::vector<double> normalized_eigenvalues;
std::vector<double> whitened_residuals;
double gram_error;
std::vector<FeastBackendCall> backend_calls;
std::vector<std::int64_t> base_fpm;
std::string runtime_version;
std::string integer_abi;
std::size_t thread_count;
std::uint64_t seed;
double enclosure_lower;
double enclosure_upper;
double solve_lower;
double solve_upper;
std::string upper_search_termination;
};
/// @brief Adapts standard and symmetric-definite sparse pencils to FEAST.
class MklFeastEigenSolver final {
class MklFeastEigenSolver final : public EigenSolver {
public:
/// @brief Computes all validated interval pairs for a requested lowest-mode
/// prefix.
@@ -74,8 +21,8 @@ class MklFeastEigenSolver final {
/// @note The implementation fixes oneMKL local thread count to one during
/// every call.
Result<FeastEigenResult> Solve(const SparseMatrix& whitened_operator,
std::size_t requested_modes,
double zero_operator_scale) const;
std::size_t requested_modes,
double zero_operator_scale) const override;
/// @brief Solves the original sparse symmetric definite K/M pencil.
/// @param mass_lower_bound Certified positive lower spectral bound of M,
/// including floating-point assembly uncertainty; never a diagonal estimate.
@@ -86,16 +33,16 @@ class MklFeastEigenSolver final {
/// classification_scale, while eigenvalues are returned in physical units.
Result<FeastEigenResult> SolveGeneralized(const SparseMatrix& stiffness,
const SparseMatrix& mass,
std::size_t requested_modes,
std::size_t requested_modes,
double mass_lower_bound,
double classification_scale) const;
double classification_scale) const override;
private:
/// @brief Shares deterministic count/search controls without changing
/// pencils.
Result<FeastEigenResult> SolveImpl(const SparseMatrix& stiffness,
const SparseMatrix* mass,
std::size_t requested_modes,
std::size_t requested_modes,
double physical_scale,
double mass_lower_bound) const;
};
+17
View File
@@ -0,0 +1,17 @@
# 직접 유지하는 MSVC 빌드
이 영역은 FESA 솔루션의 solver library·CLI·별도 테스트 프로젝트와 공통 옵션·DLL 준비를
소유한다. CMake가 생성한 프로젝트로 교체하거나 빌드 전에 CMake를 숨겨서 호출하지 않는다.
요소 수식·검사 본문·기준 데이터는 이 영역의 소유가 아니다.
- Source 추가·이동·제거는 대응하는 CMake target 목록과 같은 변경 단위로 처리한다.
한 owner가 두 목록을 대조하며 solver와 검사 본문을 빌드 경로별로 복제하지 않는다.
- `Fesa.Common.props`와 `Fesa.Runtime.targets`의 CRT·산술·define·link·runtime staging을
함께 확인한다. 개인 설치 경로는 환경변수 또는 ignored `Fesa.Local.props`로 주입한다.
- AVX2 옵션은 전용 Hex8 source에만 둔다. Dispatch source와 나머지 target으로 확장하지 않는다.
외부 GoogleTest source에 FESA 제품의 `/W4 /WX` 정책을 강제하지 않는다.
- `MSBuild FESA.sln /m:2 /p:Configuration=Debug /p:Platform=x64`는 저장소 root의
VS x64 개발 환경에서 실행한다. 별도 테스트 executable이 같은 solver library를 연결하고
실행 파일 곁의 configuration별 DLL도 준비됐는지 확인한다. 빌드 성공은 검사 통과가 아니다.
- 빌드 설정을 바꾸면 CMake와 실제 source·옵션·link·DLL 및 발견한 검사 identity를 대조한다.
기존 실행 로그와 바이너리를 덮어쓰지 말고 변경 전후 근거를 구분한다.
+75
View File
@@ -0,0 +1,75 @@
<?xml version="1.0" encoding="utf-8"?>
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<!-- Override installation roots with environment variables or this ignored sheet. -->
<Import Project="Fesa.Local.props" Condition="Exists('Fesa.Local.props')" />
<PropertyGroup>
<FesaRoot>$([System.IO.Path]::GetFullPath('$(MSBuildThisFileDirectory)..\'))</FesaRoot>
<FesaBuildRoot>$(FesaRoot).build\native\$(Configuration)\</FesaBuildRoot>
<FesaRootForward>$(FesaRoot.Replace('\','/').TrimEnd('/'))</FesaRootForward>
<FesaBuildRootForward>$(FesaBuildRoot.Replace('\','/').TrimEnd('/'))</FesaBuildRootForward>
<OutDir>$(FesaBuildRoot)tests\</OutDir>
<OutDir Condition="'$(MSBuildProjectName)'=='fesa_cli'">$(FesaBuildRoot)src\fesa\</OutDir>
<OutDir Condition="'$(ConfigurationType)'=='StaticLibrary'">$(FesaBuildRoot)lib\</OutDir>
<IntDir>$(FesaBuildRoot)obj\$(MSBuildProjectName)\</IntDir>
<TargetName Condition="'$(MSBuildProjectName)'=='fesa_cli'">fesa</TargetName>
<TbbSuffix Condition="'$(Configuration)'=='Debug'">_debug</TbbSuffix>
</PropertyGroup>
<PropertyGroup>
<FesaExternalOptions Condition="'$(MSBuildProjectName)'=='fesa_solver'">/external:I&quot;$(FESA_MKL_ROOT)\include&quot; /external:I&quot;$(FESA_TBB_ROOT)\include&quot; /external:I&quot;$(FESA_HDF5_ROOT)\include&quot; /external:W0</FesaExternalOptions>
<FesaExternalOptions Condition="'$(MSBuildProjectName)'=='fesa_unit_tests' Or '$(MSBuildProjectName)'=='fesa_integration_tests' Or '$(MSBuildProjectName)'=='fesa_reference_tests'">/external:I&quot;$(FESA_HDF5_ROOT)\include&quot; /external:I&quot;$(FESA_GTEST_SOURCE_DIR)\googletest\include&quot; /external:I&quot;$(FESA_GTEST_SOURCE_DIR)\googletest&quot; /external:W0</FesaExternalOptions>
<FesaExternalOptions Condition="'$(MSBuildProjectName)'=='fesa_unit_tests'">$(FesaExternalOptions) /external:I&quot;$(FESA_MKL_ROOT)\include&quot;</FesaExternalOptions>
</PropertyGroup>
<ItemDefinitionGroup>
<ClCompile>
<LanguageStandard>stdcpp17</LanguageStandard>
<SupportJustMyCode>false</SupportJustMyCode>
<ExceptionHandling>Sync</ExceptionHandling>
<WarningLevel>Level3</WarningLevel>
<TreatWarningAsError>false</TreatWarningAsError>
<WarningLevel Condition="'$(MSBuildProjectName)'=='fesa_solver' Or '$(MSBuildProjectName)'=='fesa_cli'">Level4</WarningLevel>
<TreatWarningAsError Condition="'$(MSBuildProjectName)'=='fesa_solver' Or '$(MSBuildProjectName)'=='fesa_cli'">true</TreatWarningAsError>
<RuntimeLibrary Condition="'$(Configuration)'=='Debug'">MultiThreadedDebugDLL</RuntimeLibrary>
<RuntimeLibrary Condition="'$(Configuration)'=='Release'">MultiThreadedDLL</RuntimeLibrary>
<Optimization Condition="'$(Configuration)'=='Debug'">Disabled</Optimization>
<InlineFunctionExpansion Condition="'$(Configuration)'=='Debug'">Disabled</InlineFunctionExpansion>
<BasicRuntimeChecks Condition="'$(Configuration)'=='Debug'">EnableFastChecks</BasicRuntimeChecks>
<Optimization Condition="'$(Configuration)'=='Release'">MaxSpeed</Optimization>
<InlineFunctionExpansion Condition="'$(Configuration)'=='Release'">AnySuitable</InlineFunctionExpansion>
<DebugInformationFormat Condition="'$(Configuration)'=='Debug'">ProgramDatabase</DebugInformationFormat>
<DebugInformationFormat Condition="'$(Configuration)'=='Release' And ('$(MSBuildProjectName)'=='fesa_solver' Or '$(MSBuildProjectName)'=='fesa_cli')">ProgramDatabase</DebugInformationFormat>
<FloatingPointModel>Precise</FloatingPointModel>
<PreprocessorDefinitions>WIN32;_WINDOWS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<PreprocessorDefinitions Condition="'$(Configuration)'=='Release'">NDEBUG;WIN32;_WINDOWS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<AdditionalIncludeDirectories>$(FesaRoot)include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<AdditionalIncludeDirectories Condition="'$(MSBuildProjectName)'=='fesa_solver' Or '$(MSBuildProjectName)'=='fesa_unit_tests' Or '$(MSBuildProjectName)'=='fesa_integration_tests'">$(FesaRoot)include;$(FesaRoot)src\fesa;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<AdditionalOptions>$(FesaExternalOptions) /MP2 %(AdditionalOptions)</AdditionalOptions>
<ExternalWarningLevel>TurnOffAllWarnings</ExternalWarningLevel>
</ClCompile>
<Link>
<TargetMachine>MachineX64</TargetMachine>
<SubSystem>Console</SubSystem>
<GenerateDebugInformation Condition="'$(Configuration)'=='Release' And '$(MSBuildProjectName)'=='fesa_cli'">true</GenerateDebugInformation>
<OptimizeReferences Condition="'$(Configuration)'=='Release' And '$(MSBuildProjectName)'=='fesa_cli'">true</OptimizeReferences>
<EnableCOMDATFolding Condition="'$(Configuration)'=='Release' And '$(MSBuildProjectName)'=='fesa_cli'">true</EnableCOMDATFolding>
<GenerateDebugInformation Condition="'$(Configuration)'=='Debug'">true</GenerateDebugInformation>
<AdditionalLibraryDirectories>$(FESA_MKL_ROOT)\lib;$(FESA_OMP_ROOT)\lib;$(FESA_TBB_ROOT)\lib;$(FESA_HDF5_ROOT)\lib;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
<AdditionalDependencies>mkl_intel_ilp64_dll.lib;mkl_intel_thread_dll.lib;mkl_core_dll.lib;libiomp5md.lib;tbb12$(TbbSuffix).lib;hdf5.lib;shlwapi.lib;%(AdditionalDependencies)</AdditionalDependencies>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(MSBuildProjectName)'=='fesa_solver'">
<ClCompile><PreprocessorDefinitions>MKL_ILP64;H5_BUILT_AS_DYNAMIC_LIB;__TBB_NO_IMPLICIT_LINKAGE=1;%(PreprocessorDefinitions)</PreprocessorDefinitions></ClCompile>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(MSBuildProjectName)'=='fesa_unit_tests'">
<ClCompile><PreprocessorDefinitions>MKL_ILP64;%(PreprocessorDefinitions)</PreprocessorDefinitions></ClCompile>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(MSBuildProjectName)'=='fesa_unit_tests' Or '$(MSBuildProjectName)'=='fesa_integration_tests' Or '$(MSBuildProjectName)'=='fesa_reference_tests'">
<ClCompile><PreprocessorDefinitions>H5_BUILT_AS_DYNAMIC_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions></ClCompile>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(MSBuildProjectName)'=='gtest'">
<ClCompile>
<AdditionalIncludeDirectories>$(FESA_GTEST_SOURCE_DIR)\googletest\include;$(FESA_GTEST_SOURCE_DIR)\googletest</AdditionalIncludeDirectories>
<AdditionalOptions>/utf-8 /J %(AdditionalOptions)</AdditionalOptions>
<PreprocessorDefinitions>GTEST_HAS_PTHREAD=0;_HAS_EXCEPTIONS=1;%(PreprocessorDefinitions)</PreprocessorDefinitions>
</ClCompile>
</ItemDefinitionGroup>
</Project>
+23
View File
@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="utf-8"?>
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Target Name="ValidateFesaDependencies" BeforeTargets="PrepareForBuild">
<Error Condition="'$(Platform)'!='x64'" Text="FESA requires MSVC x64." />
<Error Condition="!Exists('$(FESA_GTEST_SOURCE_DIR)\googletest\src\gtest-all.cc')" Text="Set FESA_GTEST_SOURCE_DIR to the approved local GoogleTest source checkout." />
<Error Condition="!Exists('$(FESA_MKL_ROOT)\lib\mkl_intel_ilp64_dll.lib')" Text="Set FESA_MKL_ROOT to the oneMKL installation root." />
<Error Condition="!Exists('$(FESA_TBB_ROOT)\lib\tbb12$(TbbSuffix).lib')" Text="Set FESA_TBB_ROOT to the oneTBB installation root." />
<Error Condition="!Exists('$(FESA_HDF5_ROOT)\lib\hdf5.lib')" Text="Set FESA_HDF5_ROOT to the shared HDF5 installation root." />
<Error Condition="!Exists('$(FESA_OMP_ROOT)\bin\libiomp5md.dll') Or !Exists('$(FESA_OMP_ROOT)\bin\libmmd.dll')" Text="Set FESA_OMP_ROOT to the Intel compiler runtime installation root." />
</Target>
<Target Name="StageFesaRuntime" AfterTargets="Build" Condition="'$(ConfigurationType)'=='Application'">
<ItemGroup>
<FesaMklThread Include="$(FESA_MKL_ROOT)\bin\mkl_intel_thread.*.dll" />
<FesaMklCore Include="$(FESA_MKL_ROOT)\bin\mkl_core.*.dll" />
<FesaMklDispatch Include="$(FESA_MKL_ROOT)\bin\mkl_def.*.dll" />
<FesaRuntime Include="@(FesaMklThread);@(FesaMklCore);@(FesaMklDispatch);$(FESA_TBB_ROOT)\bin\tbb12$(TbbSuffix).dll;$(FESA_OMP_ROOT)\bin\libiomp5md.dll;$(FESA_OMP_ROOT)\bin\libmmd.dll;$(FESA_HDF5_ROOT)\bin\hdf5.dll" />
</ItemGroup>
<Error Condition="'@(FesaMklThread)'==''" Text="The oneMKL Intel-thread runtime DLL was not resolved." />
<Error Condition="'@(FesaMklCore)'==''" Text="The oneMKL core runtime DLL was not resolved." />
<Error Condition="'@(FesaMklDispatch)'==''" Text="The oneMKL default-dispatch runtime DLL was not resolved." />
<Copy SourceFiles="@(FesaRuntime)" DestinationFolder="$(OutDir)" SkipUnchangedFiles="true" />
</Target>
</Project>
+18
View File
@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup Label="ProjectConfigurations"><ProjectConfiguration Include="Debug|x64"><Configuration>Debug</Configuration><Platform>x64</Platform></ProjectConfiguration><ProjectConfiguration Include="Release|x64"><Configuration>Release</Configuration><Platform>x64</Platform></ProjectConfiguration></ItemGroup>
<PropertyGroup Label="Globals"><ProjectGuid>{6DE2CFFA-D9B3-5E14-BEBD-5DFAB2B5290B}</ProjectGuid><Keyword>Win32Proj</Keyword><WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion></PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<PropertyGroup Label="Configuration"><ConfigurationType>Application</ConfigurationType><PlatformToolset>v145</PlatformToolset><UseDebugLibraries Condition="'$(Configuration)'=='Debug'">true</UseDebugLibraries></PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
<Import Project="Fesa.Common.props" />
<ItemGroup>
<ClCompile Include="..\src\fesa\app\main.cpp" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="fesa_solver.vcxproj"><Project>{3AEC39D9-4641-5161-9107-265EB50CA882}</Project><LinkLibraryDependencies>true</LinkLibraryDependencies></ProjectReference>
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<Import Project="Fesa.Runtime.targets" />
</Project>
+30
View File
@@ -0,0 +1,30 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup Label="ProjectConfigurations"><ProjectConfiguration Include="Debug|x64"><Configuration>Debug</Configuration><Platform>x64</Platform></ProjectConfiguration><ProjectConfiguration Include="Release|x64"><Configuration>Release</Configuration><Platform>x64</Platform></ProjectConfiguration></ItemGroup>
<PropertyGroup Label="Globals"><ProjectGuid>{7770C26B-DBFE-505D-8788-8B7EFD9A5A70}</ProjectGuid><Keyword>Win32Proj</Keyword><WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion></PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<PropertyGroup Label="Configuration"><ConfigurationType>Application</ConfigurationType><PlatformToolset>v145</PlatformToolset><UseDebugLibraries Condition="'$(Configuration)'=='Debug'">true</UseDebugLibraries></PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
<Import Project="Fesa.Common.props" />
<ItemGroup>
<ClCompile Include="..\tests\integration\analysis\linear_static_analysis_test.cpp" />
<ClCompile Include="..\tests\integration\analysis\linear_frequency_procedure_test.cpp" />
<ClCompile Include="..\tests\integration\analysis\shell_surface_pressure_linear_test.cpp" />
<ClCompile Include="..\tests\integration\analysis\shell_surface_pressure_nonlinear_test.cpp" />
<ClCompile Include="..\tests\integration\analysis\nonlinear_static_procedure_test.cpp" />
<ClCompile Include="..\tests\integration\analysis\thermal_physics_test.cpp" />
<ClCompile Include="..\tests\integration\app\fesa_application_test.cpp" />
<ClCompile Include="..\tests\integration\app\eigen_application_test.cpp" />
<ClCompile Include="..\tests\integration\app\shell_surface_pressure_application_test.cpp" />
<ClCompile Include="..\tests\integration\app\thermal_application_test.cpp" />
<ClCompile Include="..\tests\integration\app\solid_application_test.cpp" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="fesa_solver.vcxproj"><Project>{3AEC39D9-4641-5161-9107-265EB50CA882}</Project><LinkLibraryDependencies>true</LinkLibraryDependencies></ProjectReference>
<ProjectReference Include="gtest.vcxproj"><Project>{03194BA4-3F3B-5399-BCAF-FB4FA47300CE}</Project><LinkLibraryDependencies>true</LinkLibraryDependencies></ProjectReference>
<ProjectReference Include="fesa_cli.vcxproj"><Project>{6DE2CFFA-D9B3-5E14-BEBD-5DFAB2B5290B}</Project><LinkLibraryDependencies>false</LinkLibraryDependencies></ProjectReference>
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<Import Project="Fesa.Runtime.targets" />
</Project>
+35
View File
@@ -0,0 +1,35 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup Label="ProjectConfigurations"><ProjectConfiguration Include="Debug|x64"><Configuration>Debug</Configuration><Platform>x64</Platform></ProjectConfiguration><ProjectConfiguration Include="Release|x64"><Configuration>Release</Configuration><Platform>x64</Platform></ProjectConfiguration></ItemGroup>
<PropertyGroup Label="Globals"><ProjectGuid>{330D2DFB-E57D-517D-A795-F1EA620B69F0}</ProjectGuid><Keyword>Win32Proj</Keyword><WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion></PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<PropertyGroup Label="Configuration"><ConfigurationType>Application</ConfigurationType><PlatformToolset>v145</PlatformToolset><UseDebugLibraries Condition="'$(Configuration)'=='Debug'">true</UseDebugLibraries></PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
<Import Project="Fesa.Common.props" />
<ItemDefinitionGroup><ClCompile><PreprocessorDefinitions>FESA_TEST_SOURCE_DIR="$(FesaRootForward)";FESA_TEST_BINARY_DIR="$(FesaBuildRootForward)";%(PreprocessorDefinitions)</PreprocessorDefinitions></ClCompile></ItemDefinitionGroup>
<ItemGroup>
<ClCompile Include="..\tests\reference\reference_tolerance_policy.cpp" />
<ClCompile Include="..\tests\reference\eigen_reference_comparison.cpp" />
<ClCompile Include="..\tests\reference\eigen_reference_comparison_test.cpp" />
<ClCompile Include="..\tests\reference\eigen_reference_cases_test.cpp" />
<ClCompile Include="..\tests\reference\reference_tolerance_policy_test.cpp" />
<ClCompile Include="..\tests\reference\reference_comparison.cpp" />
<ClCompile Include="..\tests\reference\reference_comparison_test.cpp" />
<ClCompile Include="..\tests\reference\b33_reference_comparison_test.cpp" />
<ClCompile Include="..\tests\reference\mitc4_reference_comparison.cpp" />
<ClCompile Include="..\tests\reference\mitc4_reference_comparison_test.cpp" />
<ClCompile Include="..\tests\reference\mitc4_reference_cases_test.cpp" />
<ClCompile Include="..\tests\reference\nonlinear_static_reference_comparison.cpp" />
<ClCompile Include="..\tests\reference\nonlinear_static_reference_cases_test.cpp" />
<ClCompile Include="..\tests\reference\shell_surface_pressure_reference_test.cpp" />
<ClCompile Include="..\tests\reference\thermal_reference_comparison.cpp" />
<ClCompile Include="..\tests\reference\thermal_reference_comparison_test.cpp" />
<ClCompile Include="..\tests\reference\thermal_reference_cases_test.cpp" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="fesa_solver.vcxproj"><Project>{3AEC39D9-4641-5161-9107-265EB50CA882}</Project><LinkLibraryDependencies>true</LinkLibraryDependencies></ProjectReference>
<ProjectReference Include="gtest.vcxproj"><Project>{03194BA4-3F3B-5399-BCAF-FB4FA47300CE}</Project><LinkLibraryDependencies>true</LinkLibraryDependencies></ProjectReference>
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<Import Project="Fesa.Runtime.targets" />
</Project>
+118
View File
@@ -0,0 +1,118 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup Label="ProjectConfigurations"><ProjectConfiguration Include="Debug|x64"><Configuration>Debug</Configuration><Platform>x64</Platform></ProjectConfiguration><ProjectConfiguration Include="Release|x64"><Configuration>Release</Configuration><Platform>x64</Platform></ProjectConfiguration></ItemGroup>
<PropertyGroup Label="Globals"><ProjectGuid>{3AEC39D9-4641-5161-9107-265EB50CA882}</ProjectGuid><Keyword>Win32Proj</Keyword><WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion></PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<PropertyGroup Label="Configuration"><ConfigurationType>StaticLibrary</ConfigurationType><PlatformToolset>v145</PlatformToolset><UseDebugLibraries Condition="'$(Configuration)'=='Debug'">true</UseDebugLibraries></PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
<Import Project="Fesa.Common.props" />
<ItemDefinitionGroup><ClCompile><PreprocessorDefinitions>FESA_HEX8_PAIR_AVX2=1;%(PreprocessorDefinitions)</PreprocessorDefinitions></ClCompile></ItemDefinitionGroup>
<ItemGroup>
<ClCompile Include="..\src\fesa\analysis\analysis_model.cpp" />
<ClCompile Include="..\src\fesa\analysis\analysis_state.cpp" />
<ClCompile Include="..\src\fesa\analysis\dynamic_analysis_state.cpp" />
<ClCompile Include="..\src\fesa\analysis\eigen_reduction.cpp" />
<ClCompile Include="..\src\fesa\analysis\hht_time_integration.cpp" />
<ClCompile Include="..\src\fesa\analysis\hht_dynamic_algebra.cpp" />
<ClCompile Include="..\src\fesa\analysis\hht_dynamic_assembly.cpp" />
<ClCompile Include="..\src\fesa\analysis\hht_reduced_operators.cpp" />
<ClCompile Include="..\src\fesa\analysis\hht_reduced_procedure.cpp" />
<ClCompile Include="..\src\fesa\analysis\hht_dynamic_procedure.cpp" />
<ClCompile Include="..\src\fesa\analysis\linear_frequency_procedure.cpp" />
<ClCompile Include="..\src\fesa\analysis\linear_static_analysis.cpp" />
<ClCompile Include="..\src\fesa\analysis\linear_static_procedure.cpp" />
<ClCompile Include="..\src\fesa\analysis\newton_iteration_log.cpp" />
<ClCompile Include="..\src\fesa\analysis\nonlinear_analysis_state.cpp" />
<ClCompile Include="..\src\fesa\analysis\nonlinear_static_control.cpp" />
<ClCompile Include="..\src\fesa\analysis\nonlinear_static_procedure.cpp" />
<ClCompile Include="..\src\fesa\analysis\prepared_analysis.cpp" />
<ClCompile Include="..\src\fesa\analysis\solid_dynamic_analysis_state.cpp" />
<ClCompile Include="..\src\fesa\analysis\solid_frequency_procedure.cpp" />
<ClCompile Include="..\src\fesa\analysis\solid_hht_dynamic_procedure.cpp" />
<ClCompile Include="..\src\fesa\analysis\solid_static_procedure.cpp" />
<ClCompile Include="..\src\fesa\analysis\solid_static_state.cpp" />
<ClCompile Include="..\src\fesa\analysis\thermal_analysis_state.cpp" />
<ClCompile Include="..\src\fesa\analysis\thermal_time_grid.cpp" />
<ClCompile Include="..\src\fesa\analysis\thermal_transient_algebra.cpp" />
<ClCompile Include="..\src\fesa\analysis\transient_heat_procedure.cpp" />
<ClCompile Include="..\src\fesa\app\fesa_application.cpp" />
<ClCompile Include="..\src\fesa\app\output_paths.cpp" />
<ClCompile Include="..\src\fesa\assembly\load_assembler.cpp" />
<ClCompile Include="..\src\fesa\assembly\parallel_for.cpp" />
<ClCompile Include="..\src\fesa\assembly\sparse_assembler.cpp" />
<ClCompile Include="..\src\fesa\assembly\surface_pressure_assembler.cpp" />
<ClCompile Include="..\src\fesa\assembly\thermal_assembler.cpp" />
<ClCompile Include="..\src\fesa\build_info.cpp" />
<ClCompile Include="..\src\fesa\constraints\essential_constraint_policy.cpp" />
<ClCompile Include="..\src\fesa\constraints\prescribed_displacement.cpp" />
<ClCompile Include="..\src\fesa\core\ascii.cpp" />
<ClCompile Include="..\src\fesa\core\diagnostic.cpp" />
<ClCompile Include="..\src\fesa\core\status.cpp" />
<ClCompile Include="..\src\fesa\elements\element_capabilities.cpp" />
<ClCompile Include="..\src\fesa\elements\element_factory.cpp" />
<ClCompile Include="..\src\fesa\elements\hex8_element.cpp" />
<ClCompile Include="..\src\fesa\elements\hex8_high_low.cpp" />
<ClCompile Include="..\src\fesa\elements\hex8_pair_simd_dispatch.cpp" />
<ClCompile Include="..\src\fesa\elements\hex8_pair_simd_avx2.cpp"><EnableEnhancedInstructionSet>AdvancedVectorExtensions2</EnableEnhancedInstructionSet></ClCompile>
<ClCompile Include="..\src\fesa\elements\hex8_geometry.cpp" />
<ClCompile Include="..\src\fesa\elements\euler_beam_3d.cpp" />
<ClCompile Include="..\src\fesa\elements\finite_rotation.cpp" />
<ClCompile Include="..\src\fesa\elements\mitc4_shell.cpp" />
<ClCompile Include="..\src\fesa\elements\nonlinear_mitc4_shell.cpp" />
<ClCompile Include="..\src\fesa\elements\nonlinear_mitc4_inertia.cpp" />
<ClCompile Include="..\src\fesa\elements\thermal_shell.cpp" />
<ClCompile Include="..\src\fesa\elements\thermal_shell_geometry.cpp" />
<ClCompile Include="..\src\fesa\fem\dof_manager.cpp" />
<ClCompile Include="..\src\fesa\io\abaqus\domain_builder.cpp" />
<ClCompile Include="..\src\fesa\io\abaqus\domain_mapping_context.cpp" />
<ClCompile Include="..\src\fesa\io\abaqus\domain_mapper.cpp" />
<ClCompile Include="..\src\fesa\io\abaqus\input_reader.cpp" />
<ClCompile Include="..\src\fesa\io\abaqus\material_property_mapper.cpp" />
<ClCompile Include="..\src\fesa\io\abaqus\step_definition_mapper.cpp" />
<ClCompile Include="..\src\fesa\io\abaqus\surface_definition_mapper.cpp" />
<ClCompile Include="..\src\fesa\io\abaqus\topology_mapper.cpp" />
<ClCompile Include="..\src\fesa\io\hdf5\hdf5_atomic_file.cpp" />
<ClCompile Include="..\src\fesa\io\hdf5\dynamic_hdf5_results_writer.cpp" />
<ClCompile Include="..\src\fesa\io\hdf5\hdf5_model_writer.cpp" />
<ClCompile Include="..\src\fesa\io\hdf5\nonlinear_hdf5_results_writer.cpp" />
<ClCompile Include="..\src\fesa\io\hdf5\hdf5_primitives.cpp" />
<ClCompile Include="..\src\fesa\io\hdf5\hdf5_result_writer.cpp" />
<ClCompile Include="..\src\fesa\io\hdf5\hdf5_results_writer.cpp" />
<ClCompile Include="..\src\fesa\io\hdf5\eigen_hdf5_results_writer.cpp" />
<ClCompile Include="..\src\fesa\io\hdf5\eigen_hdf5_self_check.cpp" />
<ClCompile Include="..\src\fesa\io\hdf5\hdf5_self_check.cpp" />
<ClCompile Include="..\src\fesa\io\hdf5\thermal_hdf5_results_writer.cpp" />
<ClCompile Include="..\src\fesa\io\hdf5\thermal_hdf5_self_check.cpp" />
<ClCompile Include="..\src\fesa\io\hdf5\solid_hdf5_results_writer.cpp" />
<ClCompile Include="..\src\fesa\io\hdf5\solid_hdf5_self_check.cpp" />
<ClCompile Include="..\src\fesa\loads\concentrated_nodal_load.cpp" />
<ClCompile Include="..\src\fesa\loads\shell_surface_pressure.cpp" />
<ClCompile Include="..\src\fesa\materials\isotropic_linear_elastic_material.cpp" />
<ClCompile Include="..\src\fesa\materials\isotropic_thermal_material.cpp" />
<ClCompile Include="..\src\fesa\math\dense_blas_internal.cpp" />
<ClCompile Include="..\src\fesa\math\matrix.cpp" />
<ClCompile Include="..\src\fesa\math\sparse_matrix.cpp" />
<ClCompile Include="..\src\fesa\math\vector.cpp" />
<ClCompile Include="..\src\fesa\model\domain.cpp" />
<ClCompile Include="..\src\fesa\model\shell_geometry.cpp" />
<ClCompile Include="..\src\fesa\model\source_target_resolver.cpp" />
<ClCompile Include="..\src\fesa\properties\general_beam_section.cpp" />
<ClCompile Include="..\src\fesa\properties\shell_section.cpp" />
<ClCompile Include="..\src\fesa\results\analysis_state_commit.cpp" />
<ClCompile Include="..\src\fesa\results\beam_result_recovery.cpp" />
<ClCompile Include="..\src\fesa\results\global_equilibrium_recovery.cpp" />
<ClCompile Include="..\src\fesa\results\prepared_results_writer.cpp" />
<ClCompile Include="..\src\fesa\results\recovery_candidate.cpp" />
<ClCompile Include="..\src\fesa\results\result_recovery.cpp" />
<ClCompile Include="..\src\fesa\results\shell_result_recovery.cpp" />
<ClCompile Include="..\src\fesa\solvers\eigen\mkl_feast_eigen_solver.cpp" />
<ClCompile Include="..\src\fesa\solvers\linear\mkl_pardiso_solver.cpp" />
<ClCompile Include="..\src\fesa\solvers\linear\mkl_pardiso_tangent_solver.cpp" />
<ClCompile Include="..\src\fesa\solvers\linear\pardiso_session.cpp" />
</ItemGroup>
<ItemGroup>
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<Import Project="Fesa.Runtime.targets" />
</Project>
+108
View File
@@ -0,0 +1,108 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup Label="ProjectConfigurations"><ProjectConfiguration Include="Debug|x64"><Configuration>Debug</Configuration><Platform>x64</Platform></ProjectConfiguration><ProjectConfiguration Include="Release|x64"><Configuration>Release</Configuration><Platform>x64</Platform></ProjectConfiguration></ItemGroup>
<PropertyGroup Label="Globals"><ProjectGuid>{42558F77-73F2-5CA7-8E3C-BB0E1646CD45}</ProjectGuid><Keyword>Win32Proj</Keyword><WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion></PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<PropertyGroup Label="Configuration"><ConfigurationType>Application</ConfigurationType><PlatformToolset>v145</PlatformToolset><UseDebugLibraries Condition="'$(Configuration)'=='Debug'">true</UseDebugLibraries></PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
<Import Project="Fesa.Common.props" />
<ItemGroup>
<ClCompile Include="..\tests\unit\build_info_test.cpp" />
<ClCompile Include="..\tests\unit\analysis\analysis_test.cpp" />
<ClCompile Include="..\tests\unit\analysis\analysis_model_test.cpp" />
<ClCompile Include="..\tests\unit\analysis\analysis_state_test.cpp" />
<ClCompile Include="..\tests\unit\analysis\dynamic_analysis_state_test.cpp" />
<ClCompile Include="..\tests\unit\analysis\eigen_reduction_test.cpp" />
<ClCompile Include="..\tests\unit\analysis\eigen_mass_assembly_test.cpp" />
<ClCompile Include="..\tests\unit\analysis\hht_time_integration_test.cpp" />
<ClCompile Include="..\tests\unit\analysis\hht_shell_physics_test.cpp" />
<ClCompile Include="..\tests\unit\analysis\hht_energy_verification_test.cpp" />
<ClCompile Include="..\tests\unit\analysis\hht_temporal_verification_test.cpp" />
<ClCompile Include="..\tests\unit\analysis\hht_dynamic_algebra_test.cpp" />
<ClCompile Include="..\tests\unit\analysis\hht_reduced_shell_test.cpp" />
<ClCompile Include="..\tests\unit\analysis\hht_dynamic_procedure_test.cpp" />
<ClCompile Include="..\tests\unit\analysis\nonlinear_analysis_state_test.cpp" />
<ClCompile Include="..\tests\unit\analysis\nonlinear_static_control_test.cpp" />
<ClCompile Include="..\tests\unit\analysis\newton_iteration_log_test.cpp" />
<ClCompile Include="..\tests\unit\analysis\solid_frequency_procedure_test.cpp" />
<ClCompile Include="..\tests\unit\analysis\solid_hht_dynamic_procedure_test.cpp" />
<ClCompile Include="..\tests\unit\analysis\solid_hht_temporal_verification_test.cpp" />
<ClCompile Include="..\tests\unit\analysis\solid_static_procedure_test.cpp" />
<ClCompile Include="..\tests\unit\analysis\solid_static_state_test.cpp" />
<ClCompile Include="..\tests\unit\analysis\thermal_time_grid_test.cpp" />
<ClCompile Include="..\tests\unit\analysis\thermal_analysis_state_test.cpp" />
<ClCompile Include="..\tests\unit\analysis\transient_heat_procedure_test.cpp" />
<ClCompile Include="..\tests\unit\assembly\parallel_for_test.cpp" />
<ClCompile Include="..\tests\unit\assembly\load_assembler_test.cpp" />
<ClCompile Include="..\tests\unit\assembly\surface_pressure_assembler_test.cpp" />
<ClCompile Include="..\tests\unit\assembly\sparse_assembler_test.cpp" />
<ClCompile Include="..\tests\unit\assembly\thermal_assembler_test.cpp" />
<ClCompile Include="..\tests\unit\app\output_paths_test.cpp" />
<ClCompile Include="..\tests\unit\constraints\boundary_condition_test.cpp" />
<ClCompile Include="..\tests\unit\constraints\essential_constraints_test.cpp" />
<ClCompile Include="..\tests\unit\core\ascii_test.cpp" />
<ClCompile Include="..\tests\unit\core\diagnostic_test.cpp" />
<ClCompile Include="..\tests\unit\core\source_identity_test.cpp" />
<ClCompile Include="..\tests\unit\core\status_test.cpp" />
<ClCompile Include="..\tests\unit\elements\element_capabilities_test.cpp" />
<ClCompile Include="..\tests\unit\elements\element_factory_test.cpp" />
<ClCompile Include="..\tests\unit\elements\hex8_element_test.cpp" />
<ClCompile Include="..\tests\unit\elements\hex8_high_low_test.cpp" />
<ClCompile Include="..\tests\unit\elements\euler_beam_3d_test.cpp" />
<ClCompile Include="..\tests\unit\elements\finite_rotation_test.cpp" />
<ClCompile Include="..\tests\unit\elements\mitc4_shell_test.cpp" />
<ClCompile Include="..\tests\unit\elements\mitc4_shell_mass_contract_test.cpp" />
<ClCompile Include="..\tests\unit\elements\nonlinear_mitc4_shell_test.cpp" />
<ClCompile Include="..\tests\unit\elements\nonlinear_mitc4_inertia_test.cpp" />
<ClCompile Include="..\tests\unit\elements\nonlinear_mitc4_shell_tangent_test.cpp" />
<ClCompile Include="..\tests\unit\elements\thermal_shell_test.cpp" />
<ClCompile Include="..\tests\unit\fem\dof_manager_test.cpp" />
<ClCompile Include="..\tests\unit\math\dense_blas_internal_test.cpp" />
<ClCompile Include="..\tests\unit\math\high_low_arithmetic_test.cpp" />
<ClCompile Include="..\tests\unit\math\matrix_test.cpp" />
<ClCompile Include="..\tests\unit\math\sparse_matrix_test.cpp" />
<ClCompile Include="..\tests\unit\math\vector3_test.cpp" />
<ClCompile Include="..\tests\unit\math\vector_test.cpp" />
<ClCompile Include="..\tests\unit\io\abaqus\domain_mapping_components_test.cpp" />
<ClCompile Include="..\tests\unit\io\abaqus\domain_mapper_test.cpp" />
<ClCompile Include="..\tests\unit\io\abaqus\hex8_semantics_test.cpp" />
<ClCompile Include="..\tests\unit\io\abaqus\eigen_domain_test.cpp" />
<ClCompile Include="..\tests\unit\io\abaqus\hht_dynamic_domain_test.cpp" />
<ClCompile Include="..\tests\unit\io\abaqus\thermal_domain_test.cpp" />
<ClCompile Include="..\tests\unit\io\abaqus\input_reader_test.cpp" />
<ClCompile Include="..\tests\unit\io\abaqus\input_syntax_test.cpp" />
<ClCompile Include="..\tests\unit\io\abaqus\shell_surface_pressure_domain_test.cpp" />
<ClCompile Include="..\tests\unit\io\hdf5\hdf5_results_writer_test.cpp" />
<ClCompile Include="..\tests\unit\io\hdf5\dynamic_hdf5_results_writer_test.cpp" />
<ClCompile Include="..\tests\unit\io\hdf5\eigen_hdf5_results_writer_test.cpp" />
<ClCompile Include="..\tests\unit\io\hdf5\nonlinear_hdf5_results_writer_test.cpp" />
<ClCompile Include="..\tests\unit\io\hdf5\hdf5_writer_components_test.cpp" />
<ClCompile Include="..\tests\unit\io\hdf5\thermal_hdf5_results_writer_test.cpp" />
<ClCompile Include="..\tests\unit\io\hdf5\solid_hdf5_results_writer_test.cpp" />
<ClCompile Include="..\tests\unit\loads\load_test.cpp" />
<ClCompile Include="..\tests\unit\loads\shell_surface_pressure_test.cpp" />
<ClCompile Include="..\tests\unit\model\domain_test.cpp" />
<ClCompile Include="..\tests\unit\model\thermal_semantics_test.cpp" />
<ClCompile Include="..\tests\unit\model\thermal_shell_geometry_test.cpp" />
<ClCompile Include="..\tests\unit\model\model_types_test.cpp" />
<ClCompile Include="..\tests\unit\model\shell_geometry_test.cpp" />
<ClCompile Include="..\tests\unit\model\source_target_resolver_test.cpp" />
<ClCompile Include="..\tests\unit\materials\material_test.cpp" />
<ClCompile Include="..\tests\unit\properties\element_property_test.cpp" />
<ClCompile Include="..\tests\unit\results\result_records_test.cpp" />
<ClCompile Include="..\tests\unit\results\result_recovery_components_test.cpp" />
<ClCompile Include="..\tests\unit\results\result_recovery_test.cpp" />
<ClCompile Include="..\tests\unit\results\results_writer_test.cpp" />
<ClCompile Include="..\tests\unit\solvers\eigen\mkl_feast_eigen_solver_test.cpp" />
<ClCompile Include="..\tests\unit\solvers\linear\linear_solver_test.cpp" />
<ClCompile Include="..\tests\unit\solvers\linear\mkl_pardiso_solver_test.cpp" />
<ClCompile Include="..\tests\unit\solvers\linear\tangent_solver_test.cpp" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="fesa_solver.vcxproj"><Project>{3AEC39D9-4641-5161-9107-265EB50CA882}</Project><LinkLibraryDependencies>true</LinkLibraryDependencies></ProjectReference>
<ProjectReference Include="gtest.vcxproj"><Project>{03194BA4-3F3B-5399-BCAF-FB4FA47300CE}</Project><LinkLibraryDependencies>true</LinkLibraryDependencies></ProjectReference>
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<Import Project="Fesa.Runtime.targets" />
</Project>
+19
View File
@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup Label="ProjectConfigurations"><ProjectConfiguration Include="Debug|x64"><Configuration>Debug</Configuration><Platform>x64</Platform></ProjectConfiguration><ProjectConfiguration Include="Release|x64"><Configuration>Release</Configuration><Platform>x64</Platform></ProjectConfiguration></ItemGroup>
<PropertyGroup Label="Globals"><ProjectGuid>{03194BA4-3F3B-5399-BCAF-FB4FA47300CE}</ProjectGuid><Keyword>Win32Proj</Keyword><WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion></PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<PropertyGroup Label="Configuration"><ConfigurationType>StaticLibrary</ConfigurationType><PlatformToolset>v145</PlatformToolset><UseDebugLibraries Condition="'$(Configuration)'=='Debug'">true</UseDebugLibraries></PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
<Import Project="Fesa.Common.props" />
<ItemGroup>
<ClCompile Include="$(FESA_GTEST_SOURCE_DIR)\googletest\src\gtest-all.cc" />
<ClCompile Include="$(FESA_GTEST_SOURCE_DIR)\googletest\src\gtest_main.cc" />
</ItemGroup>
<ItemGroup>
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<Import Project="Fesa.Runtime.targets" />
</Project>
+3
View File
@@ -41,6 +41,7 @@ add_library(
core/ascii.cpp
core/diagnostic.cpp
core/status.cpp
elements/element_capabilities.cpp
elements/element_factory.cpp
elements/hex8_element.cpp
elements/hex8_high_low.cpp
@@ -93,12 +94,14 @@ add_library(
results/analysis_state_commit.cpp
results/beam_result_recovery.cpp
results/global_equilibrium_recovery.cpp
results/prepared_results_writer.cpp
results/recovery_candidate.cpp
results/result_recovery.cpp
results/shell_result_recovery.cpp
solvers/eigen/mkl_feast_eigen_solver.cpp
solvers/linear/mkl_pardiso_solver.cpp
solvers/linear/mkl_pardiso_tangent_solver.cpp
solvers/linear/pardiso_session.cpp
)
target_include_directories(
+170
View File
@@ -0,0 +1,170 @@
#ifndef FESA_ANALYSIS_ANALYSIS_PROCEDURES_H_
#define FESA_ANALYSIS_ANALYSIS_PROCEDURES_H_
#include "fesa/analysis/analysis_procedure.h"
#include "fesa/analysis/hht_dynamic_procedure.h"
#include "fesa/analysis/linear_frequency_procedure.h"
#include "fesa/analysis/linear_static_procedure.h"
#include "fesa/analysis/nonlinear_static_procedure.h"
#include "fesa/analysis/prepared_analysis_outcome.h"
#include "fesa/analysis/solid_frequency_procedure.h"
#include "fesa/analysis/solid_hht_dynamic_procedure.h"
#include "fesa/analysis/solid_static_procedure.h"
#include "fesa/analysis/transient_heat_procedure.h"
namespace fesa {
class EigenSolver;
/// @brief Adapts the LinearStatic lifecycle to prepared execution.
/// @note Referenced backend and log adapters must outlive this procedure.
class LinearStaticProcedure final : public AnalysisProcedure {
public:
/// @brief Injects only the adapters required by this family.
LinearStaticProcedure(const ParallelFor& parallel_for,
LinearSolver& linear_solver)
: executor_(parallel_for, linear_solver) {}
/// @brief Retains compatibility with an already configured typed executor.
explicit LinearStaticProcedure(LinearStaticProcedureExecutor executor)
: executor_(executor) {}
Result<PreparedAnalysisOutcome> Execute(const Domain& domain) override;
private:
LinearStaticProcedureExecutor executor_;
};
/// @brief Adapts the NonlinearStatic lifecycle to prepared execution.
/// @note Referenced backend and log adapters must outlive this procedure.
class NonlinearStaticProcedure final : public AnalysisProcedure {
public:
/// @brief Injects only the adapters required by this family.
NonlinearStaticProcedure(const ParallelFor& parallel_for,
TangentSolver& tangent_solver,
NewtonIterationLogWriter& log)
: executor_(parallel_for, tangent_solver), log_(log) {}
/// @brief Retains compatibility with an already configured typed executor.
explicit NonlinearStaticProcedure(NonlinearStaticProcedureExecutor executor,
NewtonIterationLogWriter& log)
: executor_(executor), log_(log) {}
Result<PreparedAnalysisOutcome> Execute(const Domain& domain) override;
private:
NonlinearStaticProcedureExecutor executor_;
NewtonIterationLogWriter& log_;
};
/// @brief Adapts the LinearFrequency lifecycle to prepared execution.
/// @note Referenced backend and log adapters must outlive this procedure.
class LinearFrequencyProcedure final : public AnalysisProcedure {
public:
/// @brief Injects only the adapters required by this family.
LinearFrequencyProcedure(const EigenSolver& eigen_solver)
: executor_(eigen_solver) {}
/// @brief Retains compatibility with an already configured typed executor.
explicit LinearFrequencyProcedure(LinearFrequencyProcedureExecutor executor)
: executor_(executor) {}
Result<PreparedAnalysisOutcome> Execute(const Domain& domain) override;
private:
LinearFrequencyProcedureExecutor executor_;
};
/// @brief Adapts the HhtDynamic lifecycle to prepared execution.
/// @note Referenced backend and log adapters must outlive this procedure.
class HhtDynamicProcedure final : public AnalysisProcedure {
public:
/// @brief Injects only the adapters required by this family.
HhtDynamicProcedure(const ParallelFor& parallel_for,
LinearSolver& positive_solver,
TangentSolver& tangent_solver,
NewtonIterationLogWriter& log)
: executor_(parallel_for, positive_solver, tangent_solver), log_(log) {}
/// @brief Retains compatibility with an already configured typed executor.
explicit HhtDynamicProcedure(HhtDynamicProcedureExecutor executor,
NewtonIterationLogWriter& log)
: executor_(executor), log_(log) {}
Result<PreparedAnalysisOutcome> Execute(const Domain& domain) override;
private:
HhtDynamicProcedureExecutor executor_;
NewtonIterationLogWriter& log_;
};
/// @brief Adapts the SolidStatic lifecycle to prepared execution.
/// @note Referenced backend and log adapters must outlive this procedure.
class SolidStaticProcedure final : public AnalysisProcedure {
public:
/// @brief Injects only the adapters required by this family.
SolidStaticProcedure(const ParallelFor& parallel_for,
LinearSolver& linear_solver,
TangentSolver& tangent_solver,
NewtonIterationLogWriter& log)
: executor_(parallel_for, linear_solver, tangent_solver), log_(log) {}
/// @brief Retains compatibility with an already configured typed executor.
explicit SolidStaticProcedure(SolidStaticProcedureExecutor executor,
NewtonIterationLogWriter& log)
: executor_(executor), log_(log) {}
Result<PreparedAnalysisOutcome> Execute(const Domain& domain) override;
private:
SolidStaticProcedureExecutor executor_;
NewtonIterationLogWriter& log_;
};
/// @brief Adapts the SolidFrequency lifecycle to prepared execution.
/// @note Referenced backend and log adapters must outlive this procedure.
class SolidFrequencyProcedure final : public AnalysisProcedure {
public:
/// @brief Injects only the adapters required by this family.
SolidFrequencyProcedure(const EigenSolver& eigen_solver)
: executor_(eigen_solver) {}
/// @brief Retains compatibility with an already configured typed executor.
explicit SolidFrequencyProcedure(SolidFrequencyProcedureExecutor executor)
: executor_(executor) {}
Result<PreparedAnalysisOutcome> Execute(const Domain& domain) override;
private:
SolidFrequencyProcedureExecutor executor_;
};
/// @brief Adapts the SolidHhtDynamic lifecycle to prepared execution.
/// @note Referenced backend and log adapters must outlive this procedure.
class SolidHhtDynamicProcedure final : public AnalysisProcedure {
public:
/// @brief Injects only the adapters required by this family.
SolidHhtDynamicProcedure(const ParallelFor& parallel_for,
LinearSolver& positive_solver,
TangentSolver& tangent_solver,
NewtonIterationLogWriter& log)
: executor_(parallel_for, positive_solver, tangent_solver), log_(log) {}
/// @brief Retains compatibility with an already configured typed executor.
explicit SolidHhtDynamicProcedure(SolidHhtDynamicProcedureExecutor executor,
NewtonIterationLogWriter& log)
: executor_(executor), log_(log) {}
Result<PreparedAnalysisOutcome> Execute(const Domain& domain) override;
private:
SolidHhtDynamicProcedureExecutor executor_;
NewtonIterationLogWriter& log_;
};
/// @brief Adapts the TransientHeat lifecycle to prepared execution.
/// @note Referenced backend and log adapters must outlive this procedure.
class TransientHeatProcedure final : public AnalysisProcedure {
public:
/// @brief Injects only the adapters required by this family.
TransientHeatProcedure(const ParallelFor& parallel_for,
LinearSolver& linear_solver)
: executor_(parallel_for, linear_solver) {}
/// @brief Retains compatibility with an already configured typed executor.
explicit TransientHeatProcedure(TransientHeatProcedureExecutor executor)
: executor_(executor) {}
Result<PreparedAnalysisOutcome> Execute(const Domain& domain) override;
private:
TransientHeatProcedureExecutor executor_;
};
} // namespace fesa
#endif // FESA_ANALYSIS_ANALYSIS_PROCEDURES_H_
+7 -26
View File
@@ -7,9 +7,15 @@
#include <string>
#include <utility>
#include "fesa/fem/dof_manager.h"
#include "math/finite_values.h"
#include "results/shell_result_recovery.h"
namespace fesa {
namespace {
using math_internal::IsFinite;
constexpr std::size_t kShellLocationsPerElement = 4U;
/// @brief Creates a structured failure without mutating existing state.
@@ -21,31 +27,6 @@ Status ShellCandidateFailure(const std::string& code,
{{Severity::kError, code, {}, "ANALYSIS_STATE", identity, message}});
}
template <std::size_t Size>
/// @brief Tests one fixed-size candidate component array for finite values.
bool IsFinite(const std::array<double, Size>& values) {
return std::all_of(values.begin(), values.end(),
[](const double value) { return std::isfinite(value); });
}
/// @brief Tests every physical shell row component for finite values.
bool IsFinite(const ShellResultRow& row) {
if (!IsFinite(row.natural_coordinates) || !IsFinite(row.generalized_strain) ||
!IsFinite(row.section_resultant)) {
return false;
}
for (const auto& axis : row.local_frame) {
if (!IsFinite(axis)) {
return false;
}
}
return std::all_of(row.stress.begin(), row.stress.end(),
[](const ShellSectionStressRow& stress) {
return std::isfinite(stress.zeta) &&
IsFinite(stress.components);
});
}
} // namespace
AnalysisState AnalysisState::Create(const DofManager& dofs,
@@ -170,7 +151,7 @@ Status AnalysisState::CommitShellResults(
"Shell stress rows require BOTTOM, MIDDLE, TOP identity.");
}
}
if (!IsFinite(row)) {
if (!results_internal::IsFiniteShellRow(row)) {
return ShellCandidateFailure(
"nonfinite-shell-state-value", std::to_string(row.element),
"Shell result rows must contain only finite values.");
+9 -56
View File
@@ -1,18 +1,24 @@
#include "fesa/analysis/dynamic_analysis_state.h"
#include <algorithm>
#include <cmath>
#include <limits>
#include <stdexcept>
#include <string>
#include <utility>
#include "fesa/core/source_identity.h"
#include "fesa/elements/element_definition.h"
#include "fesa/elements/finite_rotation.h"
#include "math/finite_values.h"
#include "results/shell_result_recovery.h"
#include "analysis/shell_state_inventory.h"
namespace fesa {
namespace {
using analysis_internal::ShellElementOrder;
using math_internal::IsFinite;
constexpr std::size_t kDofsPerNode = 6U;
constexpr std::size_t kAngularDofsPerNode = 3U;
constexpr std::size_t kShellLocationsPerElement = 4U;
@@ -28,59 +34,6 @@ Status Failure(const FailureCategory category, const std::string& message) {
message}});
}
/// @brief Compares canonical and raw source identity exactly.
bool SameId(const SourceEntityId& left, const SourceEntityId& right) {
return left.instance_name == right.instance_name &&
left.source_label == right.source_label &&
left.source_label_text == right.source_label_text;
}
/// @brief Checks all components of one fixed-size physical record.
template <std::size_t Size>
bool IsFinite(const std::array<double, Size>& values) {
return std::all_of(values.begin(), values.end(),
[](double value) { return std::isfinite(value); });
}
/// @brief Checks every vector component before state acceptance.
bool IsFinite(const Vector& values) {
for (std::size_t index = 0U; index < values.Size(); ++index) {
if (!std::isfinite(values[index])) {
return false;
}
}
return true;
}
/// @brief Checks all physical recovery quantities for finite values.
bool IsFinite(const ShellResultRow& row) {
if (!IsFinite(row.natural_coordinates) || !IsFinite(row.generalized_strain) ||
!IsFinite(row.section_resultant)) {
return false;
}
for (const auto& axis : row.local_frame) {
if (!IsFinite(axis)) {
return false;
}
}
return std::all_of(row.stress.begin(), row.stress.end(),
[](const ShellSectionStressRow& stress) {
return std::isfinite(stress.zeta) &&
IsFinite(stress.components);
});
}
/// @brief Collects stable Domain indices for recovery identity.
std::vector<EntityIndex> ShellElementOrder(const Domain& domain) {
std::vector<EntityIndex> order;
for (std::size_t index = 0U; index < domain.Elements().Size(); ++index) {
if (domain.Elements()[index].Kind() == ElementDefinitionKind::kMitc4Shell) {
order.push_back(static_cast<EntityIndex>(index));
}
}
return order;
}
/// @brief Requires complete ordered Gauss and section-position rows.
Status ValidateShellRows(const DynamicAcceptedFrameState& frame,
const std::vector<EntityIndex>& shell_order) {
@@ -110,7 +63,7 @@ Status ValidateShellRows(const DynamicAcceptedFrameState& frame,
frame.shell_results[element * kShellLocationsPerElement + point];
if (row.element != shell_order[element] ||
row.location != kLocations[point] ||
row.natural_coordinates != natural[point] || !IsFinite(row)) {
row.natural_coordinates != natural[point] || !results_internal::IsFiniteShellRow(row)) {
return Failure(FailureCategory::kSolver,
"Shell recovery rows have invalid identity or values.");
}
@@ -228,7 +181,7 @@ Status DynamicAnalysisState::CommitAcceptedFrame(
}
for (std::size_t node = 0U; node < node_count; ++node) {
if (frame.nodal_internal_order[node] != node ||
!SameId(frame.nodal_source_ids[node], nodal_source_ids_[node]) ||
!SourceIdentityEquals(frame.nodal_source_ids[node], nodal_source_ids_[node]) ||
!IsFinite(frame.nodal_triad[node][0U]) ||
!IsFinite(frame.nodal_triad[node][1U]) ||
!IsFinite(frame.nodal_triad[node][2U]) ||
+18 -23
View File
@@ -162,54 +162,49 @@ Result<NodalLift> MakeLift(const Matrix& mass,
Failure("eigen-mass-conditioning", node,
"A positive-space mass pivot is not resolvable."));
}
Matrix lower(positive, positive);
Matrix inverse_lower(positive, positive);
if (!dense_blas_internal::CholeskyLowerAndInverse(normalized_mp, lower,
inverse_lower)) {
const auto factor = dense_blas_internal::DenseFactor::Cholesky(normalized_mp);
if (!factor.HasValue()) {
return Result<NodalLift>::Failure(
Failure("eigen-mass-conditioning", node,
"A positive-space mass pivot is not resolvable."));
}
const Matrix lower = std::move(factor.Value().Lower().Value());
Matrix transposed_lower(positive, positive);
Matrix scaled_inverse_transpose(positive, positive);
const double root_scale = std::sqrt(scale);
for (std::size_t i = 0; i < positive; ++i)
for (std::size_t j = 0; j < positive; ++j) {
for (std::size_t j = 0; j < positive; ++j)
transposed_lower(i, j) = lower(j, i);
scaled_inverse_transpose(i, j) = inverse_lower(j, i) / root_scale;
}
const auto inverse_mass = dense_blas_internal::MultiplyTransposeLeft(
inverse_lower, inverse_lower);
const auto condition = factor.Value().ReciprocalConditionInfinity();
const auto rebuilt_mass = lower.Multiply(transposed_lower);
double norm_mp = 0.0;
double norm_inverse = 0.0;
double reconstruction = 0.0;
double scaled_mass_norm = 0.0;
double rebuilt_mass_norm = 0.0;
for (std::size_t i = 0; i < positive; ++i) {
double row_mp = 0.0;
double row_inverse = 0.0;
for (std::size_t j = 0; j < positive; ++j) {
const double inverse = inverse_mass(i, j);
const double rebuilt = rebuilt_mass(i, j);
row_inverse += std::abs(inverse);
row_mp += std::abs(mp(i, j) / scale);
reconstruction = std::hypot(reconstruction, rebuilt - mp(i, j) / scale);
scaled_mass_norm = std::hypot(scaled_mass_norm, mp(i, j) / scale);
rebuilt_mass_norm = std::hypot(rebuilt_mass_norm, rebuilt);
}
norm_mp = (std::max)(norm_mp, row_mp);
norm_inverse = (std::max)(norm_inverse, row_inverse);
}
result.record.rcond_inf = (1.0 / norm_mp) / norm_inverse;
if (!(result.record.rcond_inf >= kConditionLimit) ||
if (!condition.HasValue() || !(condition.Value() >= kConditionLimit) ||
!(Ratio(reconstruction, scaled_mass_norm + rebuilt_mass_norm) <=
kAlgebraLimit)) {
return Result<NodalLift>::Failure(Failure(
"eigen-mass-conditioning", node,
"Nodal mass fails rcond_inf >= 2^-32 or Cholesky reconstruction."));
}
result.scaled_lift = basis.Multiply(scaled_inverse_transpose);
result.record.rcond_inf = condition.Value();
// X L^T = basis/sqrt(scale), so X^T M_scaled X = I.
Matrix scaled_basis = basis;
const double root_scale = std::sqrt(scale);
for (std::size_t c = 0; c < 6U; ++c)
for (std::size_t j = 0; j < positive; ++j)
scaled_basis(c, j) /= root_scale;
auto whitened = factor.Value().SolveLowerRight(scaled_basis, true);
if (!whitened.HasValue())
return Result<NodalLift>::Failure(Failure(
"eigen-mass-conditioning", node, "Nodal whitening is nonfinite."));
result.scaled_lift = std::move(whitened.Value());
for (std::size_t c = 0; c < 6U; ++c) {
for (std::size_t j = 0; j < positive; ++j) {
if (!std::isfinite(result.scaled_lift(c, j))) {
+86
View File
@@ -0,0 +1,86 @@
#ifndef FESA_ANALYSIS_EQUILIBRIUM_ALGORITHM_H_
#define FESA_ANALYSIS_EQUILIBRIUM_ALGORITHM_H_
#include <cstdint>
#include <functional>
#include <optional>
#include <utility>
#include "fesa/core/status.h"
namespace fesa {
/// @brief Controls evaluated iteration order without owning a family candidate.
/// @note Evaluation and its referenced trial context remain alive and unmoved
/// throughout this call. A successful evaluation value means candidate ready.
class EquilibriumAlgorithm {
public:
using Evaluation = std::function<Result<bool>(std::uint32_t)>;
virtual ~EquilibriumAlgorithm() = default;
virtual Status SolveCurrentStep(std::uint32_t first, std::uint32_t last,
const Evaluation& evaluate) = 0;
};
/// @brief Preserves the original inclusive full-Newton evaluation sequence.
class NewtonStepAlgorithm final : public EquilibriumAlgorithm {
public:
Status SolveCurrentStep(std::uint32_t first, std::uint32_t last,
const Evaluation& evaluate) override {
for (std::uint32_t iteration = first; iteration <= last; ++iteration) {
auto result = evaluate(iteration);
if (!result.HasValue()) return result.GetStatus();
if (result.Value()) return Status::Ok();
}
// Family evaluators retain their original final-evaluation failure.
return Status::Ok();
}
};
/// @brief Owns one procedure-local typed evaluator and unpublished candidate.
/// @note This value is not an algorithm interface. The injected algorithm never
/// owns or type-erases Candidate, and neither callback nor context may escape.
template <typename Candidate, std::uint32_t kFirstIteration>
class EquilibriumStep {
public:
using Evaluation =
std::function<Result<std::optional<Candidate>>(std::uint32_t)>;
EquilibriumStep(Evaluation evaluate, std::uint32_t maximum)
: evaluate_(std::move(evaluate)), maximum_(maximum) {}
EquilibriumStep(const EquilibriumStep&) = delete;
EquilibriumStep& operator=(const EquilibriumStep&) = delete;
Status Solve(EquilibriumAlgorithm& algorithm) {
const EquilibriumAlgorithm::Evaluation evaluate =
[this](std::uint32_t iteration) -> Result<bool> {
auto result = evaluate_(iteration);
if (!result.HasValue()) return Result<bool>::Failure(result.GetStatus());
if (result.Value().has_value()) {
pending_ = std::move(result.Value());
return Result<bool>::Success(true);
}
return Result<bool>::Success(false);
};
return algorithm.SolveCurrentStep(kFirstIteration, maximum_, evaluate);
}
bool HasCandidate() const { return pending_.has_value(); }
/// @brief Transfers the validated candidate to the procedure commit boundary.
Candidate TakeCandidate() { return std::move(pending_.value()); }
private:
Evaluation evaluate_;
std::uint32_t maximum_;
std::optional<Candidate> pending_;
};
// Static evaluations start at one; mechanical HHT corrections start at zero.
// Evaluation at the maximum still performs the existing endpoint validation.
template <typename Candidate>
using ShellFullNewtonStep = EquilibriumStep<Candidate, 1U>;
template <typename Candidate>
using SolidFullNewtonStep = EquilibriumStep<Candidate, 1U>;
template <typename Candidate>
using ReducedShellHhtStep = EquilibriumStep<Candidate, 0U>;
template <typename Candidate>
using SolidHhtStep = EquilibriumStep<Candidate, 0U>;
} // namespace fesa
#endif // FESA_ANALYSIS_EQUILIBRIUM_ALGORITHM_H_
+36 -60
View File
@@ -16,11 +16,14 @@
#include "fesa/solvers/linear/linear_solver.h"
#include "fesa/solvers/linear/tangent_solver.h"
#include "math/dense_blas_internal.h"
#include "math/compensated_sum.h"
namespace fesa {
namespace hht_internal {
namespace {
using math_internal::AddCompensated;
/// @brief Attaches a fail-closed dynamic diagnostic to the source model.
Status Failure(const Domain& domain, const std::string& code,
const std::string& message) {
@@ -148,48 +151,23 @@ Status CheckLocalMass(const Domain& domain, const SparseMatrix& matrix,
return Failure(domain, "dynamic-positive-mass",
"A nonempty physical mass block has no positive scale.");
}
double norm = 0.0;
for (std::size_t i = 0; i < size; ++i) {
double sum = 0.0;
for (std::size_t j = 0; j < size; ++j) {
block(i, j) /= scale;
sum += std::abs(block(i, j));
}
norm = std::max(norm, sum);
}
Matrix lower{size, size};
Matrix inverse_lower{size, size};
auto factor_failure = dense_blas_internal::CholeskyFailure::kNone;
for (std::size_t i = 0; i < size; ++i)
for (std::size_t j = 0; j < size; ++j) block(i, j) /= scale;
try {
if (!dense_blas_internal::CholeskyLowerAndInverse(
block, lower, inverse_lower, &factor_failure)) {
if (factor_failure == dense_blas_internal::CholeskyFailure::kInverse)
return Failure(
domain, "dynamic-positive-mass-conditioning",
"A nodal free physical block reciprocal condition is below 2^-32.");
const auto factor = dense_blas_internal::DenseFactor::Cholesky(block);
if (!factor.HasValue())
return Failure(
domain, "dynamic-positive-mass",
"Physical mass requires strictly positive Cholesky pivots.");
}
const auto rcond = factor.Value().ReciprocalConditionInfinity();
if (!rcond.HasValue() || !std::isfinite(rcond.Value()) ||
!(rcond.Value() >= std::ldexp(1.0, -32)))
return Failure(
domain, "dynamic-positive-mass-conditioning",
"A nodal free physical block reciprocal condition is below 2^-32.");
} catch (const std::exception& error) {
return Failure(domain, "dynamic-positive-mass", error.what());
}
const Matrix inverse =
dense_blas_internal::MultiplyTransposeLeft(inverse_lower, inverse_lower);
std::array<double, 6> sums{};
for (std::size_t column = 0; column < size; ++column) {
for (std::size_t i = 0; i < size; ++i) {
sums[i] += std::abs(inverse(i, column));
}
}
const double inverse_norm = *std::max_element(sums.begin(), sums.end());
const double rcond = 1.0 / norm / inverse_norm;
if (!std::isfinite(inverse_norm) || !std::isfinite(rcond) ||
!(rcond >= std::ldexp(1.0, -32))) {
return Failure(
domain, "dynamic-positive-mass-conditioning",
"A nodal free physical block reciprocal condition is below 2^-32.");
}
return Status::Ok();
}
@@ -468,15 +446,6 @@ Result<SparseMatrix> ConstraintGradient(const DynamicAssembly& assembly,
std::move(entries));
}
/// @brief Accumulates one deterministic sparse contraction without lost sums.
void AddCompensated(const double term, double* sum, double* correction) {
const double next = *sum + term;
*correction += std::abs(*sum) >= std::abs(term)
? (*sum - next) + term
: (term - next) + *sum;
*sum = next;
}
/// @brief Stores W-scaled regularity with exact inverse-action infinity norms.
struct Regularity {
SparseMatrix matrix;
@@ -553,23 +522,10 @@ Result<Regularity> CheckRegularity(const Domain& domain,
sum += std::abs(matrix.Value().Values()[index]);
norm = std::max(norm, sum);
}
Vector inverse_sums{nodes.size()};
// Columns are discarded after their absolute row sums are accumulated;
// a global dense inverse is never retained.
for (std::size_t column = 0; column < nodes.size(); ++column) {
Vector rhs{nodes.size()};
rhs[column] = 1.0;
Vector solution{nodes.size()};
const Status solved = solver.SolveCorrection(rhs, solution);
if (!solved.IsOk()) return Result<Regularity>::Failure(solved);
const Status checked = CheckLinearSolve(matrix.Value(), rhs, solution);
if (!checked.IsOk()) return Result<Regularity>::Failure(checked);
for (std::size_t row = 0; row < nodes.size(); ++row)
inverse_sums[row] += std::abs(solution[row]);
}
double inverse_norm = 0.0;
for (std::size_t row = 0; row < nodes.size(); ++row)
inverse_norm = std::max(inverse_norm, inverse_sums[row]);
const Status accumulated =
AccumulateCheckedCanonicalNorm(matrix.Value(), solver, inverse_norm);
if (!accumulated.IsOk()) return Result<Regularity>::Failure(accumulated);
const double condition = 1.0 / norm / inverse_norm;
if (!std::isfinite(norm) || !std::isfinite(inverse_norm) ||
!std::isfinite(condition) || !(condition >= std::ldexp(1.0, -32))) {
@@ -662,6 +618,26 @@ Status HiddenEvidence(const Domain& domain, const DynamicAssembly& assembly,
} // namespace
Status AccumulateCheckedCanonicalNorm(const SparseMatrix& matrix,
TangentSolver& solver, double& norm) {
const auto size = matrix.Rows();
Vector row_sums{size};
for (std::size_t column = 0U; column < size; ++column) {
Vector rhs{size};
rhs[column] = 1.0;
Vector solution{size};
const Status solved = solver.SolveCorrection(rhs, solution);
if (!solved.IsOk()) return solved;
const Status checked = CheckLinearSolve(matrix, rhs, solution);
if (!checked.IsOk()) return checked;
for (std::size_t row = 0U; row < size; ++row)
row_sums[row] += std::abs(solution[row]);
}
for (std::size_t row = 0U; row < size; ++row)
norm = (std::max)(norm, row_sums[row]);
return Status::Ok();
}
double OrderedNullMetric(const SparseMatrix& metric, const std::size_t node,
const Vector3& direction) {
Vector left{9U};
+28 -11
View File
@@ -151,9 +151,24 @@ Result<std::vector<RuntimeShell>> BuildRuntimeShells(const Domain& domain,
*material, density);
if (!inertia.HasValue())
return Result<std::vector<RuntimeShell>>::Failure(inertia.GetStatus());
shells.push_back({static_cast<EntityIndex>(index),
dofs.ShellElementScatter(static_cast<EntityIndex>(index)),
std::move(internal.Value()), std::move(inertia.Value())});
auto internal_kernel =
std::make_unique<NonlinearMitc4Shell>(std::move(internal.Value()));
auto inertia_kernel =
std::make_unique<NonlinearMitc4Inertia>(std::move(inertia.Value()));
ElementDofLayout layout{
definition->SourceId(), definition->NodeIndices(),
{DofComponent::kUx, DofComponent::kUy, DofComponent::kUz,
DofComponent::kUrx, DofComponent::kUry, DofComponent::kUrz}};
auto trial = std::make_unique<Mitc4TrialAdapter>(layout, *internal_kernel);
auto kinetic = std::make_unique<Mitc4InertiaAdapter>(layout, *inertia_kernel);
auto mapped = dofs.ElementScatter(kinetic->DofLayout());
if (!mapped.HasValue())
return Result<std::vector<RuntimeShell>>::Failure(mapped.GetStatus());
std::array<std::size_t, 24> scatter{};
std::copy(mapped.Value().begin(), mapped.Value().end(), scatter.begin());
shells.push_back({static_cast<EntityIndex>(index), scatter,
std::move(internal_kernel), std::move(inertia_kernel),
std::move(trial), std::move(kinetic)});
}
return Result<std::vector<RuntimeShell>>::Success(std::move(shells));
}
@@ -431,8 +446,8 @@ Result<DynamicAssembly> EvaluateAssembly(
}
const auto before = retain_constraints ? ReadTrialEvaluationContext()
: TrialEvaluationContext{};
auto f = EvaluateInternalResidualAndTangent(
shells[e].internal, trial, directional_third ? &v : nullptr);
auto f = shells[e].internal->EvaluateTrial(
trial, directional_third ? &v : nullptr);
if (!f.HasValue()) {
failures[e] = f.GetStatus();
return;
@@ -441,7 +456,7 @@ Result<DynamicAssembly> EvaluateAssembly(
const auto after = ReadTrialEvaluationContext();
if (ReusableTrialContext(before, after)) evaluation_contexts[e] = after;
}
auto m = shells[e].inertia.Evaluate(trial, v, a);
auto m = shells[e].inertia->Evaluate(trial, v, a);
if (!m.HasValue()) {
failures[e] = m.GetStatus();
return;
@@ -499,7 +514,7 @@ Result<DynamicAssembly> EvaluateAssembly(
external_done[node] = true;
const auto first = 6U * node + 3U;
const auto external =
shells[e].internal.EvaluateExternalDrillingConstraint(
shells[e].internal->EvaluateExternalDrillingConstraint(
local, Triple(frame.generalized_coordinates, first),
Triple(frame.velocity, first),
Triple(frame.external_force, first), {}, {});
@@ -660,7 +675,7 @@ Result<DynamicAssembly> EvaluateAssembly(
record.scatter = shells[e].scatter;
record.configuration =
nonlinear_mitc4_internal::ConfigurationSnapshot::Read(
shells[e].internal);
*shells[e].internal_kernel);
record.evaluation_context = evaluation_contexts[e];
for (std::size_t i = 0U; i < 24U; ++i) {
record.q[i] = frame.generalized_coordinates[shells[e].scatter[i]];
@@ -703,11 +718,13 @@ Status CompleteEquilibrium(const Domain& domain, const DofManager& dofs,
const auto first = 6U * node;
const Vector3 arm = Vector3(domain.Nodes()[node].coordinates) +
Triple(frame->generalized_coordinates, first);
const Matrix transform = InverseTransposeLeftJacobianSO3(
Triple(frame->generalized_coordinates, first + 3U));
const Vector3 phi = Triple(frame->generalized_coordinates, first + 3U);
for (const Vector* values : {&frame->external_force, &frame->reaction}) {
const Vector3 force = Triple(*values, first);
const Vector3 moment = Transform(transform, Triple(*values, first + 3U));
const auto spatial = SpatialMomentSO3(phi, Triple(*values, first + 3U));
if (!spatial.HasValue())
return Failure("Dynamic physical force or moment balance exceeds 1e-8.");
const Vector3 moment = spatial.Value();
total_force = total_force + force;
total_moment = total_moment + arm.Cross(force) + moment;
force_scale += force.Norm();
+12 -2
View File
@@ -4,11 +4,13 @@
#include <array>
#include <cstdint>
#include <filesystem>
#include <memory>
#include <optional>
#include <vector>
#include "fesa/analysis/dynamic_analysis_state.h"
#include "fesa/analysis/hht_time_integration.h"
#include "fesa/elements/element_capabilities.h"
#include "fesa/elements/nonlinear_mitc4_inertia.h"
#include "fesa/elements/nonlinear_mitc4_shell.h"
#include "fesa/math/sparse_matrix.h"
@@ -25,8 +27,10 @@ namespace hht_internal {
struct RuntimeShell {
EntityIndex element_index;
std::array<std::size_t, 24> scatter;
NonlinearMitc4Shell internal;
NonlinearMitc4Inertia inertia;
std::unique_ptr<NonlinearMitc4Shell> internal_kernel;
std::unique_ptr<NonlinearMitc4Inertia> inertia_kernel;
std::unique_ptr<ShellTrialElement> internal;
std::unique_ptr<ShellInertiaElement> inertia;
};
/// @brief Captures only controls used by the current MSVC x64 local evaluator.
@@ -138,6 +142,12 @@ Result<SparseMatrix> ScaledFree(const SparseMatrix& matrix,
Status CheckLinearSolve(const SparseMatrix& matrix, const Vector& rhs,
const Vector& solution);
/// @brief Streams checked canonical solves into the caller's infinity norm.
/// @note The caller owns factorization and the initial norm. Each column is
/// discarded after ascending absolute row-sum accumulation; failures preserve norm.
Status AccumulateCheckedCanonicalNorm(const SparseMatrix& matrix,
TangentSolver& solver, double& norm);
/// @brief Finds analytic admissible node null directions without rank cuts.
std::vector<std::size_t> NullNodes(
const DofManager& dofs,
@@ -0,0 +1,74 @@
#ifndef FESA_ANALYSIS_HHT_REDUCED_INTEGRATOR_H_
#define FESA_ANALYSIS_HHT_REDUCED_INTEGRATOR_H_
#include <utility>
#include "analysis/hht_reduced_internal.h"
namespace fesa::hht_reduced_internal {
struct ReducedHistory {
Vector p{0U};
Vector velocity{0U};
Vector acceleration{0U};
Vector algorithmic_acceleration{0U};
Vector z{0U};
};
/// @brief Keeps every private predictor compensation channel before a trial.
struct ReducedShellPredictor {
Vector pp{0U};
Vector vpp{0U};
Vector coordinate_low{0U};
Vector increment_trace{0U};
Vector increment_trace_compensation{0U};
Vector increment_trace_absolute_sum{0U};
};
struct ReducedShellRateRequest {
const FixedChart& chart;
const ReducedHistory& history;
const Vector& trial_algorithmic;
const Vector& vpp;
bool copy_translation_rates;
const Vector& retained_velocity;
const Vector& retained_acceleration;
double h;
const HhtParameters& parameters;
};
struct ReducedShellTrialRates {
Vector velocity{0U};
Vector acceleration{0U};
};
/// @brief Handles private shell predictors and exact sparse rate
/// reconstruction.
/// @note Chart/root/solver are non-owning call views; no history is committed.
class ShellHhtIntegrator {
public:
virtual ~ShellHhtIntegrator() = default;
virtual ReducedShellPredictor
Predict(const ReducedHistory& history, double h,
const HhtParameters& parameters) const = 0;
/// @note Output buffers have history.p.Size() entries and do not alias any
/// request input or each other. The caller owns their allocation/lifetime.
virtual void EvaluateRates(const ReducedShellRateRequest& request,
Vector& velocity, Vector& acceleration) const = 0;
virtual Result<ReconstructedRates>
ReconstructPhysicalRates(const FixedChart& chart, const EmbeddingRoot& root,
const Vector& velocity, const Vector& acceleration,
TangentSolver& solver) const = 0;
};
/// @brief Sequences one private predictor, pending endpoint and commit.
/// @note Callables are synchronous borrowed views. They retain all original
/// evaluation/diagnostic/commit policy; no accepted history is owned here.
template <typename Evaluate, typename Commit>
Status RunReducedHhtStep(const ShellHhtIntegrator& integrator,
const ReducedHistory& history, double h,
const HhtParameters& parameters,
Evaluate&& evaluate, Commit&& commit) {
auto predictor = integrator.Predict(history, h, parameters);
auto pending = evaluate(integrator, std::move(predictor));
if (!pending.HasValue()) return pending.GetStatus();
return commit(std::move(pending.Value()));
}
} // namespace fesa::hht_reduced_internal
#endif // FESA_ANALYSIS_HHT_REDUCED_INTEGRATOR_H_
+18 -60
View File
@@ -10,13 +10,18 @@
#include <string>
#include <utility>
#include "fesa/core/source_identity.h"
#include "fesa/solvers/linear/tangent_solver.h"
#include "math/dense_blas_internal.h"
#include "math/high_low_arithmetic.h"
#include "math/finite_values.h"
#include "math/compensated_sum.h"
namespace fesa::hht_reduced_internal {
namespace {
using RateSum = math_internal::CompensatedSum;
Status Failure(const std::string& message,
const std::string& code = "dynamic-invalid-state") {
return Status::Failure(FailureCategory::kSolver,
@@ -25,27 +30,9 @@ Status Failure(const std::string& message,
}
bool Finite(const Vector& values) {
for (std::size_t i = 0U; i < values.Size(); ++i)
if (!std::isfinite(values[i])) return false;
return true;
return math_internal::IsFinite(values);
}
class RateSum {
public:
void Add(const double term) {
const double next = sum_ + term;
correction_ += std::abs(sum_) >= std::abs(term)
? (sum_ - next) + term
: (term - next) + sum_;
sum_ = next;
}
double Value() const { return sum_ + correction_; }
private:
double sum_ = 0.0;
double correction_ = 0.0;
};
bool ExactlyEqual(const Vector& left, const Vector& right) {
if (left.Size() != right.Size()) return false;
for (std::size_t i = 0U; i < left.Size(); ++i)
@@ -65,13 +52,6 @@ bool SameBits(const double left, const double right) {
return std::memcmp(&left, &right, sizeof(double)) == 0;
}
/// @brief Compares semantic and original source identity without object keys.
bool SameSource(const SourceEntityId& left, const SourceEntityId& right) {
return left.instance_name == right.instance_name &&
left.source_label == right.source_label &&
left.source_label_text == right.source_label_text;
}
/// @brief Finds only the current complete directional response, otherwise miss.
const hht_internal::DirectionalShellConstraintData* MatchingConstraints(
const Domain& domain, const hht_internal::RuntimeShell& shell,
@@ -98,7 +78,7 @@ const hht_internal::DirectionalShellConstraintData* MatchingConstraints(
hht_internal::ReadTrialEvaluationContext()))
return nullptr;
const auto configuration =
nonlinear_mitc4_internal::ConfigurationSnapshot::Read(shell.internal);
nonlinear_mitc4_internal::ConfigurationSnapshot::Read(*shell.internal_kernel);
if (record.configuration.source.file != configuration.source.file ||
record.configuration.source.line != configuration.source.line ||
record.configuration.identity != configuration.identity)
@@ -116,7 +96,7 @@ const hht_internal::DirectionalShellConstraintData* MatchingConstraints(
for (std::size_t node = 0U; node < 4U; ++node) {
const auto global_node = shell.scatter[6U * node] / 6U;
if (global_node >= domain.Nodes().size() ||
!SameSource(record.node_source_ids[node],
!SourceIdentityEquals(record.node_source_ids[node],
domain.Nodes()[global_node].source_id))
return nullptr;
}
@@ -534,7 +514,7 @@ Result<ConstraintOperators> EvaluateConstraintOperators(
std::optional<NonlinearMitc4InternalEquilibrium> fresh;
if (saved == nullptr) {
auto internal =
EvaluateInternalResidualAndTangent(shell.internal, Trial(q), &v);
shell.internal->EvaluateTrial(Trial(q), &v);
if (!internal.HasValue())
return Result<ConstraintOperators>::Failure(internal.GetStatus());
fresh = std::move(internal.Value());
@@ -572,7 +552,7 @@ Result<ConstraintOperators> EvaluateConstraintOperators(
if (external_done[global_node]) continue;
external_done[global_node] = true;
const auto first = 6U * global_node + 3U;
const auto external = shell.internal.EvaluateExternalDrillingConstraint(
const auto external = shell.internal->EvaluateExternalDrillingConstraint(
local_node, Vector3{q[6U * local_node + 3U],
q[6U * local_node + 4U],
q[6U * local_node + 5U]},
@@ -587,7 +567,7 @@ Result<ConstraintOperators> EvaluateConstraintOperators(
force_acceleration[first + 2U]});
if (!external.HasValue())
return Result<ConstraintOperators>::Failure(external.GetStatus());
const auto rate_term = shell.internal.EvaluateExternalDrillingConstraint(
const auto rate_term = shell.internal->EvaluateExternalDrillingConstraint(
local_node, Vector3{q[6U * local_node + 3U],
q[6U * local_node + 4U],
q[6U * local_node + 5U]},
@@ -596,7 +576,7 @@ Result<ConstraintOperators> EvaluateConstraintOperators(
if (!rate_term.HasValue())
return Result<ConstraintOperators>::Failure(rate_term.GetStatus());
const auto acceleration_term =
shell.internal.EvaluateExternalDrillingConstraint(
shell.internal->EvaluateExternalDrillingConstraint(
local_node, Vector3{q[6U * local_node + 3U],
q[6U * local_node + 4U],
q[6U * local_node + 5U]},
@@ -1321,21 +1301,10 @@ Result<double> CheckGraphRegularity(
if (!sparse.HasValue()) return Result<double>::Failure(sparse.GetStatus());
Status status = solver.FactorizeFresh(sparse.Value());
if (!status.IsOk()) return Result<double>::Failure(status);
Vector inverse_sums{size};
for (std::size_t column = 0U; column < size; ++column) {
Vector rhs{size};
rhs[column] = 1.0;
Vector solution{size};
status = solver.SolveCorrection(rhs, solution);
if (!status.IsOk()) return Result<double>::Failure(status);
status = hht_internal::CheckLinearSolve(sparse.Value(), rhs, solution);
if (!status.IsOk()) return Result<double>::Failure(status);
for (std::size_t row = 0U; row < size; ++row)
inverse_sums[row] += std::abs(solution[row]);
}
double inverse_norm = 0.0;
for (std::size_t row = 0U; row < size; ++row)
inverse_norm = std::max(inverse_norm, inverse_sums[row]);
status = hht_internal::AccumulateCheckedCanonicalNorm(
sparse.Value(), solver, inverse_norm);
if (!status.IsOk()) return Result<double>::Failure(status);
const double condition = 1.0 / norm / inverse_norm;
if (!std::isfinite(norm) || !std::isfinite(inverse_norm) ||
!std::isfinite(condition) || !(condition >= std::ldexp(1.0, -32))) {
@@ -1409,20 +1378,9 @@ Result<double> CheckSelectorTransversality(
if (!sparse.HasValue()) return Result<double>::Failure(sparse.GetStatus());
auto status = solver.FactorizeFresh(sparse.Value());
if (!status.IsOk()) return Result<double>::Failure(status);
Vector inverse_rows{rows.size()};
for (std::size_t column = 0U; column < rows.size(); ++column) {
Vector rhs{rows.size()};
rhs[column] = 1.0;
Vector solution{rows.size()};
status = solver.SolveCorrection(rhs, solution);
if (!status.IsOk()) return Result<double>::Failure(status);
status = hht_internal::CheckLinearSolve(sparse.Value(), rhs, solution);
if (!status.IsOk()) return Result<double>::Failure(status);
for (std::size_t row = 0U; row < rows.size(); ++row)
inverse_rows[row] += std::abs(solution[row]);
}
for (std::size_t row = 0U; row < rows.size(); ++row)
inverse_norm = (std::max)(inverse_norm, inverse_rows[row]);
status = hht_internal::AccumulateCheckedCanonicalNorm(
sparse.Value(), solver, inverse_norm);
if (!status.IsOk()) return Result<double>::Failure(status);
}
const double condition = 1.0 / norm / inverse_norm;
if (!std::isfinite(condition) || condition < std::ldexp(1.0, -32))
+520 -344
View File
@@ -1,4 +1,7 @@
#include "analysis/hht_reduced_procedure.h"
#include "analysis/hht_reduced_integrator.h"
#include "equilibrium_algorithm.h"
#include <algorithm>
#include <cmath>
@@ -296,12 +299,83 @@ Status CheckReducedUpdates(const Domain& domain,
return Status::Ok();
}
struct ReducedHistory {
Vector p{0U};
Vector velocity{0U};
Vector acceleration{0U};
Vector algorithmic_acceleration{0U};
Vector z{0U};
// Private nonvirtual primitive: keep velocity assignment before acceleration
// evaluation, including per-index position retries. No accepted state changes.
void EvaluateReducedShellRate(double predictor_velocity,
double algorithmic_acceleration,
double old_acceleration, double h,
const HhtParameters& parameters,
double* velocity, double* acceleration) {
*velocity = predictor_velocity + parameters.gamma * h * algorithmic_acceleration;
*acceleration = (algorithmic_acceleration + parameters.alpha * old_acceleration) /
(1.0 + parameters.alpha);
}
class ReducedCoordinateShellHhtIntegrator final : public ShellHhtIntegrator {
public:
ReducedShellPredictor
Predict(const ReducedHistory& history, double h,
const HhtParameters& parameters) const override {
Vector pp{history.p.Size()}, vpp{history.p.Size()};
for (std::size_t i = 0U; i < history.p.Size(); ++i) {
pp[i] =
history.p[i] + h * history.velocity[i] +
h * h * (0.5 - parameters.beta) * history.algorithmic_acceleration[i];
vpp[i] = history.velocity[i] + h * (1.0 - parameters.gamma) *
history.algorithmic_acceleration[i];
}
Vector coordinate_low{history.p.Size()};
Vector increment_trace{history.p.Size()};
Vector increment_trace_compensation{history.p.Size()};
Vector increment_trace_absolute_sum{history.p.Size()};
for (std::size_t i = 0U; i < history.p.Size(); ++i) {
const double velocity_term = h * history.velocity[i];
const double old_acceleration_term =
h * h * (0.5 - parameters.beta) * history.algorithmic_acceleration[i];
const double first_sum = history.p[i] + velocity_term;
coordinate_low[i] =
AdditionLowPart(history.p[i], velocity_term, first_sum) +
AdditionLowPart(first_sum, old_acceleration_term, pp[i]);
AddIncrementTraceTerm(velocity_term, &increment_trace[i],
&increment_trace_compensation[i]);
AddIncrementTraceTerm(old_acceleration_term, &increment_trace[i],
&increment_trace_compensation[i]);
increment_trace_absolute_sum[i] =
std::abs(velocity_term) + std::abs(old_acceleration_term);
}
return {std::move(pp),
std::move(vpp),
std::move(coordinate_low),
std::move(increment_trace),
std::move(increment_trace_compensation),
std::move(increment_trace_absolute_sum)};
}
void EvaluateRates(const ReducedShellRateRequest& request,
Vector& trial_v, Vector& trial_a) const override {
const auto& history = request.history;
const auto& trial_algorithmic = request.trial_algorithmic;
const auto& vpp = request.vpp;
const auto& parameters = request.parameters;
const double h = request.h;
for (std::size_t i = 0U; i < history.p.Size(); ++i) {
if (request.copy_translation_rates &&
PhysicalCoordinateChannel(request.chart, i) == 0U) {
trial_v[i] = request.retained_velocity[i];
trial_a[i] = request.retained_acceleration[i];
} else {
EvaluateReducedShellRate(
vpp[i], trial_algorithmic[i], history.acceleration[i], h, parameters,
&trial_v[i], &trial_a[i]);
}
}
}
Result<ReconstructedRates>
ReconstructPhysicalRates(const FixedChart& chart, const EmbeddingRoot& root,
const Vector& velocity, const Vector& acceleration,
TangentSolver& solver) const override {
return hht_reduced_internal::ReconstructPhysicalRates(chart, root, velocity,
acceleration, solver);
}
};
struct Trial {
@@ -320,6 +394,17 @@ Result<DynamicExecutionResult> ExecuteReducedHht(
const Domain& domain, NewtonIterationLogWriter& log,
const ParallelFor& parallel_for, LinearSolver& positive_solver,
TangentSolver& tangent_solver) {
const ReducedCoordinateShellHhtIntegrator integrator;
NewtonStepAlgorithm algorithm;
return ExecuteReducedHht(domain, log, parallel_for, positive_solver,
tangent_solver, integrator, algorithm);
}
Result<DynamicExecutionResult> ExecuteReducedHht(
const Domain& domain, NewtonIterationLogWriter& log,
const ParallelFor& parallel_for, LinearSolver& positive_solver,
TangentSolver& tangent_solver, const ShellHhtIntegrator& integrator,
EquilibriumAlgorithm& algorithm) {
if (domain.Dynamic() == nullptr)
return Result<DynamicExecutionResult>::Failure(Status::Failure(
FailureCategory::kModel, {{Severity::kError, "invalid-dynamic-step", {},
@@ -433,7 +518,7 @@ Result<DynamicExecutionResult> ExecuteReducedHht(
tangent_solver);
if (!root0.HasValue()) return Result<DynamicExecutionResult>::Failure(
WithProcedureContext(domain, root0.GetStatus(), "inner-position", 0U, 0.0));
auto rates0 = ReconstructPhysicalRates(chart.Value(), root0.Value(), history.velocity,
auto rates0 = integrator.ReconstructPhysicalRates(chart.Value(), root0.Value(), history.velocity,
history.acceleration, tangent_solver);
if (!rates0.HasValue()) return Result<DynamicExecutionResult>::Failure(
WithProcedureContext(domain, rates0.GetStatus(), "inner-rates", 0U, 0.0));
@@ -604,343 +689,434 @@ Result<DynamicExecutionResult> ExecuteReducedHht(
status = log.Write(record); if (status.IsOk()) status = log.Flush();
if (!status.IsOk()) return Result<DynamicExecutionResult>::Failure(status);
for (std::uint32_t increment = 1U; increment <= domain.Dynamic()->increment_count; ++increment) {
struct ReducedStepCandidate {
DynamicAcceptedFrameState frame;
ReducedHistory history;
};
for (std::uint32_t increment = 1U;
increment <= domain.Dynamic()->increment_count; ++increment) {
const auto& old = state.CommittedState();
Vector pp{history.p.Size()}, vpp{history.p.Size()};
for (std::size_t i = 0U; i < history.p.Size(); ++i) {
pp[i] = history.p[i] + h * history.velocity[i] + h * h * (0.5 - parameters.beta) * history.algorithmic_acceleration[i];
vpp[i] = history.velocity[i] + h * (1.0 - parameters.gamma) * history.algorithmic_acceleration[i];
}
DynamicAcceptedFrameState trial_frame = old;
trial_frame.is_initial = false; trial_frame.frame_index = increment;
trial_frame.increment_index = increment; trial_frame.time = static_cast<double>(increment) * h;
trial_frame.time_integration_formulation = DynamicTimeIntegrationFormulation::kReducedCoordinateHht;
Vector trial_algorithmic{history.p.Size()};
Vector trial_p = pp;
Vector coordinate_low{history.p.Size()};
Vector chosen_start_p = pp;
Vector increment_trace{history.p.Size()};
Vector increment_trace_compensation{history.p.Size()};
Vector increment_trace_absolute_sum{history.p.Size()};
for (std::size_t i = 0U; i < history.p.Size(); ++i) {
const double velocity_term = h * history.velocity[i];
const double old_acceleration_term =
h * h * (0.5 - parameters.beta) * history.algorithmic_acceleration[i];
const double first_sum = history.p[i] + velocity_term;
coordinate_low[i] = AdditionLowPart(history.p[i], velocity_term, first_sum) +
AdditionLowPart(first_sum, old_acceleration_term, pp[i]);
AddIncrementTraceTerm(velocity_term, &increment_trace[i],
&increment_trace_compensation[i]);
AddIncrementTraceTerm(old_acceleration_term, &increment_trace[i],
&increment_trace_compensation[i]);
increment_trace_absolute_sum[i] =
std::abs(velocity_term) + std::abs(old_acceleration_term);
}
Vector seed = history.z;
record = {}; record.increment_index = increment; record.time = trial_frame.time; record.correction_pass = true;
bool translation_private_algorithmic_pass = true;
bool copy_translation_rates = false;
Vector retained_velocity{history.p.Size()};
Vector retained_acceleration{history.p.Size()};
bool accepted = false;
for (std::uint32_t iteration = 0U;
iteration <= kMaximumOuterCorrections; ++iteration) {
record.iteration_index = iteration; record.stage = "evaluation"; record.outcome = "candidate";
const auto fail = [&](const Status& failure,
const char* stage = "outer") -> Result<DynamicExecutionResult> {
const Status contextual = WithProcedureContext(
domain, failure, stage, increment, trial_frame.time);
record.stage = stage; record.outcome = "failed";
record.failure_code = contextual.Diagnostics().empty()
? "dynamic-solver-failure"
: contextual.Diagnostics().front().code;
const Status written = log.Write(record);
return Result<DynamicExecutionResult>::Failure(
written.IsOk() ? contextual : written);
};
Vector trial_v{history.p.Size()}, trial_a{history.p.Size()};
for (std::size_t i = 0U; i < trial_p.Size(); ++i) {
if (copy_translation_rates && PhysicalCoordinateChannel(chart.Value(), i) == 0U) {
trial_v[i] = retained_velocity[i];
trial_a[i] = retained_acceleration[i];
} else {
trial_v[i] = vpp[i] + parameters.gamma * h * trial_algorithmic[i];
trial_a[i] = (trial_algorithmic[i] + parameters.alpha * history.acceleration[i]) / (1.0 + parameters.alpha);
}
}
const auto evaluate_trial = [&](const Vector& physical_p,
const Vector& physical_v,
const Vector& physical_a,
const Vector& root_seed) -> Result<Trial> {
auto root = SolveEmbeddingRoot(chart.Value(), physical_p, root_seed,
[&](const Vector& q) { return position_operators(trial_frame, q); },
tangent_solver);
if (!root.HasValue()) return Result<Trial>::Failure(
WithProcedureContext(domain, root.GetStatus(), "inner-position",
increment, trial_frame.time));
auto rates = ReconstructPhysicalRates(chart.Value(), root.Value(), physical_v,
physical_a, tangent_solver);
if (!rates.HasValue()) return Result<Trial>::Failure(
WithProcedureContext(domain, rates.GetStatus(), "inner-rates",
increment, trial_frame.time));
auto candidate = trial_frame;
candidate.generalized_coordinates = root.Value().q;
candidate.velocity = rates.Value().velocity;
candidate.acceleration = rates.Value().acceleration;
candidate.algorithmic_acceleration = Vector(dofs.FullDofCount());
for (std::size_t i = 0U; i < candidate.algorithmic_acceleration.Size(); ++i)
candidate.algorithmic_acceleration[i] =
(1.0 + parameters.alpha) * candidate.acceleration[i] -
parameters.alpha * old.acceleration[i];
candidate.iteration_count = iteration;
auto assembly = EvaluateAssembly(domain, dofs, shells.Value(), candidate,
loads.Value()[1U], h, parameters,
parallel_for, true);
if (!assembly.HasValue()) return Result<Trial>::Failure(
WithProcedureContext(domain, assembly.GetStatus(), "outer",
increment, trial_frame.time));
assembly.Value().frame.time_integration_formulation =
DynamicTimeIntegrationFormulation::kReducedCoordinateHht;
return Result<Trial>::Success(
{std::move(assembly.Value()), std::move(root.Value()), physical_p,
physical_v, physical_a, trial_algorithmic,
std::move(rates.Value().z_velocity), std::move(rates.Value().z_acceleration)});
};
position_geometry_failure = false;
auto trial = evaluate_trial(trial_p, trial_v, trial_a, seed);
if (!trial.HasValue() && iteration == 0U && position_geometry_failure) {
for (std::size_t i = 0U; i < trial_p.Size(); ++i) {
trial_algorithmic[i] =
(history.p[i] - pp[i]) / (parameters.beta * h * h);
trial_p[i] = history.p[i];
chosen_start_p[i] = history.p[i];
coordinate_low[i] = 0.0;
increment_trace[i] = 0.0;
increment_trace_compensation[i] = 0.0;
increment_trace_absolute_sum[i] = 0.0;
trial_v[i] = vpp[i] + parameters.gamma * h * trial_algorithmic[i];
trial_a[i] = (trial_algorithmic[i] +
parameters.alpha * history.acceleration[i]) /
(1.0 + parameters.alpha);
}
trial = evaluate_trial(trial_p, trial_v, trial_a, seed);
}
if (!trial.HasValue()) return fail(trial.GetStatus());
if (iteration == 0U && !ExactlyEqual(trial_p, history.p)) {
Vector old_algorithmic{trial_p.Size()};
Vector old_velocity{trial_p.Size()};
Vector old_acceleration{trial_p.Size()};
for (std::size_t i = 0U; i < trial_p.Size(); ++i) {
old_algorithmic[i] = (history.p[i] - pp[i]) / (parameters.beta * h * h);
old_velocity[i] = vpp[i] + parameters.gamma * h * old_algorithmic[i];
old_acceleration[i] = (old_algorithmic[i] + parameters.alpha * history.acceleration[i]) /
(1.0 + parameters.alpha);
}
auto old_trial = evaluate_trial(history.p, old_velocity, old_acceleration, seed);
if (!old_trial.HasValue()) return fail(old_trial.GetStatus());
const auto reduced_residual = [&](const Trial& value) {
double norm = 0.0;
for (const auto full : dofs.FreeDofs()) {
const double scale = full % 6U < 3U ? length : 1.0;
norm = (std::max)(norm,
scale * std::abs(value.assembly.frame.residual[full]));
}
if (!std::isfinite(norm)) return std::numeric_limits<double>::infinity();
return norm;
};
const double old_residual = reduced_residual(old_trial.Value());
const double predictor_residual = reduced_residual(trial.Value());
if (!std::isfinite(old_residual) || !std::isfinite(predictor_residual))
return fail(Failure(domain, "dynamic-invalid-state",
"The scaled initial reduced residual is nonfinite."));
if (old_residual < predictor_residual) {
trial = std::move(old_trial);
trial_p = history.p;
chosen_start_p = history.p;
coordinate_low = Vector(history.p.Size());
increment_trace = Vector(history.p.Size());
increment_trace_compensation = Vector(history.p.Size());
increment_trace_absolute_sum = Vector(history.p.Size());
trial_v = old_velocity;
trial_a = old_acceleration;
trial_algorithmic = old_algorithmic;
}
}
auto candidate = trial.Value().assembly.frame;
auto operators = EvaluateConstraintOperators(domain, dofs, shells.Value(),
trial.Value().assembly, chart.Value(), zero, zero);
if (!operators.HasValue()) return fail(operators.GetStatus());
ResidualRecord(dofs, candidate, &record);
if (record.residual_pass && record.correction_pass) {
status = CompleteConstraintEvidence(domain, dofs, trial.Value().assembly, length, null_nodes,
positive_solver, tangent_solver, &candidate);
if (!status.IsOk()) return fail(status, "accepted");
status = CompleteEquilibrium(domain, dofs, trial.Value().assembly, &candidate);
if (!status.IsOk()) return fail(status, "accepted");
graph_regularity = CheckGraphRegularity(trial.Value().assembly,
chart.Value(), operators.Value(),
tangent_solver);
if (!graph_regularity.HasValue())
return fail(graph_regularity.GetStatus(), "accepted");
status = CheckReducedUpdates(domain, old, candidate, pp, vpp, trial_p, trial_v,
trial_a, trial.Value().root.z,
trial.Value().algebraic_velocity,
trial.Value().algebraic_acceleration,
trial_algorithmic, history.p, history.velocity,
history.acceleration,
history.algorithmic_acceleration,
increment_trace,
increment_trace_compensation,
increment_trace_absolute_sum,
chart.Value(), chosen_start_p, coordinate_low,
parameters, h);
if (!status.IsOk()) return fail(status, "accepted");
candidate.external_work = 0.0;
for (std::size_t i = 0U; i < dofs.FullDofCount(); ++i)
candidate.external_work += candidate.external_force[i] * candidate.generalized_coordinates[i];
candidate.energy_defect = candidate.kinetic_energy + candidate.physical_strain_energy +
candidate.drilling_energy - initial_energy - candidate.external_work;
status = state.CommitAcceptedFrame(candidate);
if (!status.IsOk()) return fail(status, "accepted");
history = {trial_p, trial_v, trial_a, trial_algorithmic, trial.Value().root.z};
record.stage = "accepted"; record.outcome = "accepted";
status = log.Write(record); if (status.IsOk()) status = log.Flush();
if (!status.IsOk()) return Result<DynamicExecutionResult>::Failure(status);
accepted = true; break;
}
if (!CanApplyOuterCorrection(iteration)) {
double full_hidden = 0.0;
double analytic_hidden = 0.0;
double inertia_hidden = 0.0;
double consistency = 0.0;
for (std::size_t row = 0U; row < chart.Value().null_nodes.size(); ++row) {
const auto node = chart.Value().null_nodes[row];
const auto& null = trial.Value().assembly.rotations[node].drill_null;
double full = 0.0;
double inertia = 0.0;
for (std::size_t component = 0U; component < 3U; ++component) {
const auto full_dof = 6U * node + 3U + component;
full += null[component] * candidate.residual[full_dof];
inertia += null[component] * candidate.inertia_force[full_dof];
}
const double analytic = operators.Value().rows[row].value;
full_hidden = (std::max)(full_hidden, std::abs(full));
analytic_hidden = (std::max)(analytic_hidden, std::abs(analytic));
inertia_hidden = (std::max)(inertia_hidden, std::abs(inertia));
consistency = (std::max)(consistency, std::abs(full - analytic - inertia));
}
std::ostringstream message;
const auto maximum = [](const Vector& values) {
double result = 0.0;
for (std::size_t i = 0U; i < values.Size(); ++i)
result = (std::max)(result, std::abs(values[i]));
return result;
};
Vector position_correction{trial_algorithmic.Size()};
Vector velocity_correction{trial_algorithmic.Size()};
for (std::size_t i = 0U; i < trial_algorithmic.Size(); ++i) {
position_correction[i] = parameters.beta * h * h * trial_algorithmic[i];
velocity_correction[i] = parameters.gamma * h * trial_algorithmic[i];
}
message << "HHT exhausted 100 corrections without satisfying every endpoint check; "
<< "stage=outer, outer_iteration=" << iteration
<< ", graph_iteration=" << trial.Value().root.graph_iteration_count
<< ", graph_residual=" << trial.Value().root.graph_residual_infinity
<< ", graph_relative=" << trial.Value().root.graph_residual_relative
<< ", graph_correction=" << trial.Value().root.graph_correction_infinity
<< ", max_NtE=" << full_hidden
<< ", max_analytic_psi=" << analytic_hidden
<< ", max_NtInertia=" << inertia_hidden
<< ", max_NtE_minus_psi_minus_NtInertia=" << consistency
<< ", max_pp=" << maximum(pp)
<< ", max_beta_h2_trial_Ar=" << maximum(position_correction)
<< ", max_trial_p=" << maximum(trial_p)
<< ", max_vp_predictor=" << maximum(vpp)
<< ", max_gamma_h_trial_Ar=" << maximum(velocity_correction)
<< ", max_trial_v=" << maximum(trial_v)
<< ", max_physical_v=" << maximum(trial.Value().velocity) << ".";
return fail(Failure(domain, "dynamic-nonconvergence", message.str()));
}
status = log.Write(record);
if (!status.IsOk()) return Result<DynamicExecutionResult>::Failure(status);
const double velocity_rate = parameters.gamma / (parameters.beta * h);
const double acceleration_rate = 1.0 / ((1.0 + parameters.alpha) * parameters.beta * h * h);
auto system = AssembleAugmentedReducedSystem(trial.Value().assembly, chart.Value(), operators.Value(), velocity_rate, acceleration_rate, length, h);
if (!system.HasValue()) return fail(system.GetStatus());
const bool angular_only = ShouldRefineAngular(
chart.Value(), candidate.free_residual_metrics,
{record.coordinate_correction[0U], record.velocity_correction[0U],
record.acceleration_correction[0U], record.algorithmic_correction[0U]},
translation_private_algorithmic_pass);
auto selected = SelectAugmentedSolve(system.Value(), chart.Value(), angular_only);
if (!selected.HasValue()) return fail(selected.GetStatus());
const auto linear_failure = [&](const Status& failure) {
return fail(failure, angular_only ? "angular-newton-linear-solve"
: "full-newton-linear-solve");
};
Vector selected_solution{selected.Value().rhs.Size()};
if (selected_solution.Size() > 0U) {
status = tangent_solver.FactorizeFresh(selected.Value().tangent);
if (!status.IsOk()) return linear_failure(status);
status = tangent_solver.SolveCorrection(selected.Value().rhs, selected_solution);
if (!status.IsOk()) return linear_failure(status);
status = CheckLinearSolve(selected.Value().tangent, selected.Value().rhs, selected_solution);
if (!status.IsOk()) return linear_failure(status);
}
auto solution = EmbedAugmentedSolution(selected.Value(), selected_solution,
system.Value().rhs.Size());
if (!solution.HasValue()) return fail(solution.GetStatus());
auto correction = MapAugmentedCorrection(chart.Value(), system.Value(), solution.Value(), velocity_rate, acceleration_rate);
if (!correction.HasValue()) return fail(correction.GetStatus());
Vector full_algorithmic{dofs.FullDofCount()};
Vector reduced_algorithmic{correction.Value().p.Size()};
Vector reduced_physical{correction.Value().p.Size()};
for (std::size_t i = 0U; i < full_algorithmic.Size(); ++i)
full_algorithmic[i] =
(1.0 + parameters.alpha) * correction.Value().acceleration[i];
for (std::size_t i = 0U; i < reduced_algorithmic.Size(); ++i) {
reduced_algorithmic[i] = correction.Value().p[i] /
(parameters.beta * h * h);
reduced_physical[i] = acceleration_rate * correction.Value().p[i];
}
CorrectionRecord(correction.Value().coordinate, correction.Value().velocity,
correction.Value().acceleration, full_algorithmic,
reduced_algorithmic, reduced_physical, chart.Value(), dofs, length, h,
parameters.alpha,
&record);
translation_private_algorithmic_pass = true;
for (std::size_t i = 0U; i < chart.Value().p_dofs.size(); ++i)
if (PhysicalCoordinateChannel(chart.Value(), i) == 0U)
translation_private_algorithmic_pass =
translation_private_algorithmic_pass &&
PrivateCorrectionEqual(chart.Value(), i, full_algorithmic,
reduced_algorithmic, reduced_physical, parameters.alpha);
copy_translation_rates = angular_only;
if (angular_only) {
retained_velocity = trial_v;
retained_acceleration = trial_a;
}
for (std::size_t i = 0U; i < trial_algorithmic.Size(); ++i) {
if (angular_only && PhysicalCoordinateChannel(chart.Value(), i) == 0U) continue;
trial_algorithmic[i] += reduced_algorithmic[i];
}
for (std::size_t i = 0U; i < trial_p.Size(); ++i) {
if (angular_only && PhysicalCoordinateChannel(chart.Value(), i) == 0U) continue;
AdvanceCoordinate(correction.Value().p[i], &trial_p[i], &coordinate_low[i]);
}
for (std::size_t i = 0U; i < increment_trace.Size(); ++i) {
if (angular_only && PhysicalCoordinateChannel(chart.Value(), i) == 0U) continue;
AddIncrementTraceTerm(correction.Value().p[i], &increment_trace[i],
&increment_trace_compensation[i]);
increment_trace_absolute_sum[i] += std::abs(correction.Value().p[i]);
}
seed = trial.Value().root.z;
for (std::size_t i = 0U; i < seed.Size(); ++i)
seed[i] += correction.Value().z[i];
}
if (!accepted) return Result<DynamicExecutionResult>::Failure(
Failure(domain, "dynamic-nonconvergence", "Dynamic increment was not accepted."));
const auto report_failure = [&](const Status& failure, const char* stage) {
const Status contextual = WithProcedureContext(
domain, failure, stage, increment, record.time);
record.stage = stage;
record.outcome = "failed";
record.failure_code = contextual.Diagnostics().empty()
? "dynamic-solver-failure"
: contextual.Diagnostics().front().code;
const Status written = log.Write(record);
return written.IsOk() ? contextual : written;
};
status = RunReducedHhtStep(
integrator, history, h, parameters,
[&](const ShellHhtIntegrator& step_integrator,
ReducedShellPredictor predictor) -> Result<ReducedStepCandidate> {
Vector pp = std::move(predictor.pp);
Vector vpp = std::move(predictor.vpp);
DynamicAcceptedFrameState trial_frame = old;
trial_frame.is_initial = false;
trial_frame.frame_index = increment;
trial_frame.increment_index = increment;
trial_frame.time = static_cast<double>(increment) * h;
trial_frame.time_integration_formulation =
DynamicTimeIntegrationFormulation::kReducedCoordinateHht;
Vector trial_algorithmic{history.p.Size()};
Vector trial_p = pp;
Vector coordinate_low = std::move(predictor.coordinate_low);
Vector chosen_start_p = pp;
Vector increment_trace = std::move(predictor.increment_trace);
Vector increment_trace_compensation =
std::move(predictor.increment_trace_compensation);
Vector increment_trace_absolute_sum =
std::move(predictor.increment_trace_absolute_sum);
Vector seed = history.z;
record = {};
record.increment_index = increment;
record.time = trial_frame.time;
record.correction_pass = true;
bool translation_private_algorithmic_pass = true;
bool copy_translation_rates = false;
Vector retained_velocity{history.p.Size()};
Vector retained_acceleration{history.p.Size()};
using StepResult = Result<std::optional<ReducedStepCandidate>>;
const auto write_iteration =
[&](const DynamicNewtonIterationLogRecord& value) {
return log.Write(value);
};
ReducedShellHhtStep<ReducedStepCandidate> iteration_step(
[&](std::uint32_t iteration) -> StepResult {
record.iteration_index = iteration;
record.stage = "evaluation";
record.outcome = "candidate";
const auto fail = [&](const Status& failure,
const char* stage = "outer") -> StepResult {
return StepResult::Failure(report_failure(failure, stage));
};
ReducedShellTrialRates reduced_rates{
Vector{history.p.Size()}, Vector{history.p.Size()}};
step_integrator.EvaluateRates(
{chart.Value(), history, trial_algorithmic, vpp,
copy_translation_rates, retained_velocity, retained_acceleration,
h, parameters}, reduced_rates.velocity,
reduced_rates.acceleration);
Vector trial_v = std::move(reduced_rates.velocity);
Vector trial_a = std::move(reduced_rates.acceleration);
const auto evaluate_trial =
[&](const Vector& physical_p, const Vector& physical_v,
const Vector& physical_a,
const Vector& root_seed) -> Result<Trial> {
auto root = SolveEmbeddingRoot(
chart.Value(), physical_p, root_seed,
[&](const Vector& q) {
return position_operators(trial_frame, q);
},
tangent_solver);
if (!root.HasValue())
return Result<Trial>::Failure(WithProcedureContext(
domain, root.GetStatus(), "inner-position", increment,
trial_frame.time));
auto rates = step_integrator.ReconstructPhysicalRates(
chart.Value(), root.Value(), physical_v, physical_a,
tangent_solver);
if (!rates.HasValue())
return Result<Trial>::Failure(
WithProcedureContext(domain, rates.GetStatus(), "inner-rates",
increment, trial_frame.time));
auto candidate = trial_frame;
candidate.generalized_coordinates = root.Value().q;
candidate.velocity = rates.Value().velocity;
candidate.acceleration = rates.Value().acceleration;
candidate.algorithmic_acceleration = Vector(dofs.FullDofCount());
for (std::size_t i = 0U;
i < candidate.algorithmic_acceleration.Size(); ++i)
candidate.algorithmic_acceleration[i] =
(1.0 + parameters.alpha) * candidate.acceleration[i] -
parameters.alpha * old.acceleration[i];
candidate.iteration_count = iteration;
auto assembly = EvaluateAssembly(domain, dofs, shells.Value(),
candidate, loads.Value()[1U], h,
parameters, parallel_for, true);
if (!assembly.HasValue())
return Result<Trial>::Failure(
WithProcedureContext(domain, assembly.GetStatus(), "outer",
increment, trial_frame.time));
assembly.Value().frame.time_integration_formulation =
DynamicTimeIntegrationFormulation::kReducedCoordinateHht;
return Result<Trial>::Success(
{std::move(assembly.Value()), std::move(root.Value()),
physical_p, physical_v, physical_a, trial_algorithmic,
std::move(rates.Value().z_velocity),
std::move(rates.Value().z_acceleration)});
};
position_geometry_failure = false;
auto trial = evaluate_trial(trial_p, trial_v, trial_a, seed);
if (!trial.HasValue() && iteration == 0U &&
position_geometry_failure) {
for (std::size_t i = 0U; i < trial_p.Size(); ++i) {
trial_algorithmic[i] =
(history.p[i] - pp[i]) / (parameters.beta * h * h);
trial_p[i] = history.p[i];
chosen_start_p[i] = history.p[i];
coordinate_low[i] = 0.0;
increment_trace[i] = 0.0;
increment_trace_compensation[i] = 0.0;
increment_trace_absolute_sum[i] = 0.0;
}
step_integrator.EvaluateRates(
{chart.Value(), history, trial_algorithmic, vpp, false,
retained_velocity, retained_acceleration, h, parameters},
trial_v, trial_a);
trial = evaluate_trial(trial_p, trial_v, trial_a, seed);
}
if (!trial.HasValue())
return fail(trial.GetStatus());
if (iteration == 0U && !ExactlyEqual(trial_p, history.p)) {
Vector old_algorithmic{trial_p.Size()};
Vector old_velocity{trial_p.Size()};
Vector old_acceleration{trial_p.Size()};
for (std::size_t i = 0U; i < trial_p.Size(); ++i) {
old_algorithmic[i] =
(history.p[i] - pp[i]) / (parameters.beta * h * h);
}
step_integrator.EvaluateRates(
{chart.Value(), history, old_algorithmic, vpp, false,
retained_velocity, retained_acceleration, h, parameters},
old_velocity, old_acceleration);
auto old_trial =
evaluate_trial(history.p, old_velocity, old_acceleration, seed);
if (!old_trial.HasValue())
return fail(old_trial.GetStatus());
const auto reduced_residual = [&](const Trial& value) {
double norm = 0.0;
for (const auto full : dofs.FreeDofs()) {
const double scale = full % 6U < 3U ? length : 1.0;
norm = (std::max)(norm,
scale *
std::abs(
value.assembly.frame.residual[full]));
}
if (!std::isfinite(norm))
return std::numeric_limits<double>::infinity();
return norm;
};
const double old_residual = reduced_residual(old_trial.Value());
const double predictor_residual = reduced_residual(trial.Value());
if (!std::isfinite(old_residual) ||
!std::isfinite(predictor_residual))
return fail(
Failure(domain, "dynamic-invalid-state",
"The scaled initial reduced residual is nonfinite."));
if (old_residual < predictor_residual) {
trial = std::move(old_trial);
trial_p = history.p;
chosen_start_p = history.p;
coordinate_low = Vector(history.p.Size());
increment_trace = Vector(history.p.Size());
increment_trace_compensation = Vector(history.p.Size());
increment_trace_absolute_sum = Vector(history.p.Size());
trial_v = old_velocity;
trial_a = old_acceleration;
trial_algorithmic = old_algorithmic;
}
}
auto candidate = trial.Value().assembly.frame;
auto operators = EvaluateConstraintOperators(
domain, dofs, shells.Value(), trial.Value().assembly,
chart.Value(), zero, zero);
if (!operators.HasValue())
return fail(operators.GetStatus());
ResidualRecord(dofs, candidate, &record);
if (record.residual_pass && record.correction_pass) {
status = CompleteConstraintEvidence(
domain, dofs, trial.Value().assembly, length, null_nodes,
positive_solver, tangent_solver, &candidate);
if (!status.IsOk())
return fail(status, "accepted");
status = CompleteEquilibrium(domain, dofs, trial.Value().assembly,
&candidate);
if (!status.IsOk())
return fail(status, "accepted");
graph_regularity =
CheckGraphRegularity(trial.Value().assembly, chart.Value(),
operators.Value(), tangent_solver);
if (!graph_regularity.HasValue())
return fail(graph_regularity.GetStatus(), "accepted");
status = CheckReducedUpdates(
domain, old, candidate, pp, vpp, trial_p, trial_v, trial_a,
trial.Value().root.z, trial.Value().algebraic_velocity,
trial.Value().algebraic_acceleration, trial_algorithmic,
history.p, history.velocity, history.acceleration,
history.algorithmic_acceleration, increment_trace,
increment_trace_compensation, increment_trace_absolute_sum,
chart.Value(), chosen_start_p, coordinate_low, parameters, h);
if (!status.IsOk())
return fail(status, "accepted");
candidate.external_work = 0.0;
for (std::size_t i = 0U; i < dofs.FullDofCount(); ++i)
candidate.external_work += candidate.external_force[i] *
candidate.generalized_coordinates[i];
candidate.energy_defect = candidate.kinetic_energy +
candidate.physical_strain_energy +
candidate.drilling_energy -
initial_energy - candidate.external_work;
return StepResult::Success(ReducedStepCandidate{
std::move(candidate),
{trial_p, trial_v, trial_a, trial_algorithmic,
trial.Value().root.z}});
}
if (!CanApplyOuterCorrection(iteration)) {
double full_hidden = 0.0;
double analytic_hidden = 0.0;
double inertia_hidden = 0.0;
double consistency = 0.0;
for (std::size_t row = 0U; row < chart.Value().null_nodes.size();
++row) {
const auto node = chart.Value().null_nodes[row];
const auto& null =
trial.Value().assembly.rotations[node].drill_null;
double full = 0.0;
double inertia = 0.0;
for (std::size_t component = 0U; component < 3U; ++component) {
const auto full_dof = 6U * node + 3U + component;
full += null[component] * candidate.residual[full_dof];
inertia += null[component] * candidate.inertia_force[full_dof];
}
const double analytic = operators.Value().rows[row].value;
full_hidden = (std::max)(full_hidden, std::abs(full));
analytic_hidden = (std::max)(analytic_hidden, std::abs(analytic));
inertia_hidden = (std::max)(inertia_hidden, std::abs(inertia));
consistency =
(std::max)(consistency, std::abs(full - analytic - inertia));
}
std::ostringstream message;
const auto maximum = [](const Vector& values) {
double result = 0.0;
for (std::size_t i = 0U; i < values.Size(); ++i)
result = (std::max)(result, std::abs(values[i]));
return result;
};
Vector position_correction{trial_algorithmic.Size()};
Vector velocity_correction{trial_algorithmic.Size()};
for (std::size_t i = 0U; i < trial_algorithmic.Size(); ++i) {
position_correction[i] =
parameters.beta * h * h * trial_algorithmic[i];
velocity_correction[i] =
parameters.gamma * h * trial_algorithmic[i];
}
message << "HHT exhausted 100 corrections without satisfying every "
"endpoint check; "
<< "stage=outer, outer_iteration=" << iteration
<< ", graph_iteration="
<< trial.Value().root.graph_iteration_count
<< ", graph_residual="
<< trial.Value().root.graph_residual_infinity
<< ", graph_relative="
<< trial.Value().root.graph_residual_relative
<< ", graph_correction="
<< trial.Value().root.graph_correction_infinity
<< ", max_NtE=" << full_hidden
<< ", max_analytic_psi=" << analytic_hidden
<< ", max_NtInertia=" << inertia_hidden
<< ", max_NtE_minus_psi_minus_NtInertia=" << consistency
<< ", max_pp=" << maximum(pp)
<< ", max_beta_h2_trial_Ar=" << maximum(position_correction)
<< ", max_trial_p=" << maximum(trial_p)
<< ", max_vp_predictor=" << maximum(vpp)
<< ", max_gamma_h_trial_Ar=" << maximum(velocity_correction)
<< ", max_trial_v=" << maximum(trial_v)
<< ", max_physical_v=" << maximum(trial.Value().velocity)
<< ".";
return fail(
Failure(domain, "dynamic-nonconvergence", message.str()));
}
status = write_iteration(record);
if (!status.IsOk())
return StepResult::Failure(status);
const double velocity_rate = parameters.gamma / (parameters.beta * h);
const double acceleration_rate =
1.0 / ((1.0 + parameters.alpha) * parameters.beta * h * h);
auto system = AssembleAugmentedReducedSystem(
trial.Value().assembly, chart.Value(), operators.Value(),
velocity_rate, acceleration_rate, length, h);
if (!system.HasValue())
return fail(system.GetStatus());
const bool angular_only = ShouldRefineAngular(
chart.Value(), candidate.free_residual_metrics,
{record.coordinate_correction[0U], record.velocity_correction[0U],
record.acceleration_correction[0U],
record.algorithmic_correction[0U]},
translation_private_algorithmic_pass);
auto selected =
SelectAugmentedSolve(system.Value(), chart.Value(), angular_only);
if (!selected.HasValue())
return fail(selected.GetStatus());
const auto linear_failure = [&](const Status& failure) {
return fail(failure, angular_only ? "angular-newton-linear-solve"
: "full-newton-linear-solve");
};
Vector selected_solution{selected.Value().rhs.Size()};
if (selected_solution.Size() > 0U) {
status = tangent_solver.FactorizeFresh(selected.Value().tangent);
if (!status.IsOk())
return linear_failure(status);
status = tangent_solver.SolveCorrection(selected.Value().rhs,
selected_solution);
if (!status.IsOk())
return linear_failure(status);
status = CheckLinearSolve(selected.Value().tangent,
selected.Value().rhs, selected_solution);
if (!status.IsOk())
return linear_failure(status);
}
auto solution = EmbedAugmentedSolution(
selected.Value(), selected_solution, system.Value().rhs.Size());
if (!solution.HasValue())
return fail(solution.GetStatus());
auto correction = MapAugmentedCorrection(
chart.Value(), system.Value(), solution.Value(), velocity_rate,
acceleration_rate);
if (!correction.HasValue())
return fail(correction.GetStatus());
Vector full_algorithmic{dofs.FullDofCount()};
Vector reduced_algorithmic{correction.Value().p.Size()};
Vector reduced_physical{correction.Value().p.Size()};
for (std::size_t i = 0U; i < full_algorithmic.Size(); ++i)
full_algorithmic[i] =
(1.0 + parameters.alpha) * correction.Value().acceleration[i];
for (std::size_t i = 0U; i < reduced_algorithmic.Size(); ++i) {
reduced_algorithmic[i] =
correction.Value().p[i] / (parameters.beta * h * h);
reduced_physical[i] = acceleration_rate * correction.Value().p[i];
}
CorrectionRecord(correction.Value().coordinate,
correction.Value().velocity,
correction.Value().acceleration, full_algorithmic,
reduced_algorithmic, reduced_physical, chart.Value(),
dofs, length, h, parameters.alpha, &record);
translation_private_algorithmic_pass = true;
for (std::size_t i = 0U; i < chart.Value().p_dofs.size(); ++i)
if (PhysicalCoordinateChannel(chart.Value(), i) == 0U)
translation_private_algorithmic_pass =
translation_private_algorithmic_pass &&
PrivateCorrectionEqual(chart.Value(), i, full_algorithmic,
reduced_algorithmic, reduced_physical,
parameters.alpha);
copy_translation_rates = angular_only;
if (angular_only) {
retained_velocity = trial_v;
retained_acceleration = trial_a;
}
for (std::size_t i = 0U; i < trial_algorithmic.Size(); ++i) {
if (angular_only &&
PhysicalCoordinateChannel(chart.Value(), i) == 0U)
continue;
trial_algorithmic[i] += reduced_algorithmic[i];
}
for (std::size_t i = 0U; i < trial_p.Size(); ++i) {
if (angular_only &&
PhysicalCoordinateChannel(chart.Value(), i) == 0U)
continue;
AdvanceCoordinate(correction.Value().p[i], &trial_p[i],
&coordinate_low[i]);
}
for (std::size_t i = 0U; i < increment_trace.Size(); ++i) {
if (angular_only &&
PhysicalCoordinateChannel(chart.Value(), i) == 0U)
continue;
AddIncrementTraceTerm(correction.Value().p[i], &increment_trace[i],
&increment_trace_compensation[i]);
increment_trace_absolute_sum[i] +=
std::abs(correction.Value().p[i]);
}
seed = trial.Value().root.z;
for (std::size_t i = 0U; i < seed.Size(); ++i)
seed[i] += correction.Value().z[i];
return StepResult::Success(std::nullopt);
},
kMaximumOuterCorrections);
const Status solved = iteration_step.Solve(algorithm);
if (!solved.IsOk())
return Result<ReducedStepCandidate>::Failure(solved);
if (!iteration_step.HasCandidate()) return Result<ReducedStepCandidate>::Failure(
Failure(domain, "dynamic-nonconvergence", "Dynamic increment was not accepted."));
return Result<ReducedStepCandidate>::Success(
iteration_step.TakeCandidate());
},
[&](ReducedStepCandidate&& accepted_candidate) -> Status {
status = state.CommitAcceptedFrame(accepted_candidate.frame);
if (!status.IsOk())
return report_failure(status, "accepted");
history = std::move(accepted_candidate.history);
record.stage = "accepted";
record.outcome = "accepted";
status = log.Write(record);
if (status.IsOk())
status = log.Flush();
return status;
});
if (!status.IsOk()) return Result<DynamicExecutionResult>::Failure(status);
}
return Result<DynamicExecutionResult>::Success({std::move(state), domain.Warnings()});
return Result<DynamicExecutionResult>::Success(
{std::move(state), domain.Warnings()});
}
} // namespace fesa
} // namespace fesa
+13 -1
View File
@@ -3,16 +3,20 @@
#include <cstdint>
#include "fesa/analysis/prepared_analysis.h"
#include "fesa/analysis/dynamic_execution_result.h"
#include "fesa/core/status.h"
namespace fesa {
namespace hht_reduced_internal {
class ShellHhtIntegrator;
constexpr std::uint32_t kMaximumOuterCorrections = 100U;
constexpr bool CanApplyOuterCorrection(const std::uint32_t completed) {
return completed < kMaximumOuterCorrections;
}
} // namespace hht_reduced_internal
class Domain;
class EquilibriumAlgorithm;
class LinearSolver;
class NewtonIterationLogWriter;
class ParallelFor;
@@ -22,6 +26,14 @@ Result<DynamicExecutionResult> ExecuteReducedHht(
const Domain& domain, NewtonIterationLogWriter& log,
const ParallelFor& parallel_for, LinearSolver& positive_solver,
TangentSolver& tangent_solver);
/// @brief Borrows caller iteration policy for the complete production core.
Result<DynamicExecutionResult> ExecuteReducedHht(
const Domain& domain, NewtonIterationLogWriter& log,
const ParallelFor& parallel_for, LinearSolver& positive_solver,
TangentSolver& tangent_solver,
const hht_reduced_internal::ShellHhtIntegrator& integrator,
EquilibriumAlgorithm& algorithm);
} // namespace fesa
#endif // FESA_ANALYSIS_HHT_REDUCED_PROCEDURE_H_
@@ -20,6 +20,7 @@
#include "fesa/elements/mitc4_shell.h"
#include "fesa/fem/dof_manager.h"
#include "fesa/math/sparse_matrix.h"
#include "fesa/solvers/eigen/mkl_feast_eigen_solver.h"
namespace fesa {
namespace {
@@ -84,7 +85,8 @@ double MaximumEdgeLength(const Domain& domain) {
return maximum;
}
Result<EigenAnalysisState> ExecuteCandidate(const Domain& domain) {
Result<EigenAnalysisState> ExecuteCandidate(const Domain& domain,
const EigenSolver& solver) {
const FrequencyStepDefinition* frequency = domain.Frequency();
if (frequency == nullptr || domain.Steps().Size() != 1U) {
return Result<EigenAnalysisState>::Failure(
@@ -101,6 +103,7 @@ Result<EigenAnalysisState> ExecuteCandidate(const Domain& domain) {
return Result<EigenAnalysisState>::Failure(model.GetStatus());
const ElementFactory factory;
std::vector<std::unique_ptr<Element>> owned;
std::vector<std::unique_ptr<ShellReferenceElement>> references;
ElementView view;
owned.reserve(model.Value().ActiveElements().size());
view.reserve(model.Value().ActiveElements().size());
@@ -108,13 +111,15 @@ Result<EigenAnalysisState> ExecuteCandidate(const Domain& domain) {
auto element = factory.Create(domain.Elements()[index], domain);
if (!element.HasValue())
return Result<EigenAnalysisState>::Failure(element.GetStatus());
if (dynamic_cast<const Mitc4Shell*>(element.Value().get()) == nullptr) {
auto reference = factory.BindShellReference(*element.Value());
if (reference == nullptr) {
return Result<EigenAnalysisState>::Failure(Failure(
"unsupported-frequency-element", std::to_string(index),
"The frequency procedure supports MITC4 shell elements only."));
}
view.emplace_back(*element.Value());
owned.push_back(std::move(element.Value()));
references.push_back(std::move(reference));
}
DofManager dofs;
const Status dof_status = dofs.Build(model.Value(), view);
@@ -158,7 +163,7 @@ Result<EigenAnalysisState> ExecuteCandidate(const Domain& domain) {
std::to_string(definition.MaterialIndex()),
"Every active shell material requires a positive density."));
}
const auto* element = dynamic_cast<const Mitc4Shell*>(owned[shell].get());
const auto& element = references[shell];
auto mass = element->Mass(density[definition.MaterialIndex()]);
auto shell_stiffness = element->Stiffness();
if (!mass.HasValue())
@@ -207,7 +212,7 @@ Result<EigenAnalysisState> ExecuteCandidate(const Domain& domain) {
(material->YoungsModulus() / density[material_id]) /
length / length);
}
auto backend = MklFeastEigenSolver{}.Solve(
auto backend = solver.Solve(
reduction.Value().whitened_operator, request, zero_scale);
if (!backend.HasValue())
return Result<EigenAnalysisState>::Failure(backend.GetStatus());
@@ -403,7 +408,8 @@ Result<EigenAnalysisState> ExecuteCandidate(const Domain& domain) {
Result<EigenAnalysisState> LinearFrequencyProcedureExecutor::Execute(
const Domain& domain) const {
try {
auto result = ExecuteCandidate(domain);
const MklFeastEigenSolver default_solver;
auto result = ExecuteCandidate(domain, solver_ ? *solver_ : default_solver);
if (!result.HasValue()) {
auto diagnostics = result.GetStatus().Diagnostics();
for (auto& diagnostic : diagnostics) {
@@ -142,7 +142,7 @@ Result<LinearStaticExecutionResult> LinearStaticProcedureExecutor::Execute(
}
state.ExternalForce() = std::move(nodal_external.Value());
Result<Vector> rhs = LoadAssembler::EffectiveFreeRhs(
Result<Vector> rhs = EssentialConstraintPolicy{}.EffectiveFreeRhs(
state.ExternalForce(), partitions.Value().k_fc, dofs.PrescribedValues(),
dofs);
if (!rhs.HasValue()) {
+10 -57
View File
@@ -1,6 +1,5 @@
#include "fesa/analysis/nonlinear_analysis_state.h"
#include <algorithm>
#include <array>
#include <cmath>
#include <cstddef>
@@ -9,11 +8,18 @@
#include <utility>
#include <vector>
#include "fesa/core/source_identity.h"
#include "fesa/elements/element_definition.h"
#include "math/finite_values.h"
#include "results/shell_result_recovery.h"
#include "analysis/shell_state_inventory.h"
namespace fesa {
namespace {
using analysis_internal::ShellElementOrder;
using math_internal::IsFinite;
constexpr std::size_t kDofsPerNode = 6U;
constexpr std::size_t kShellLocationsPerElement = 4U;
constexpr std::uint32_t kMaximumNewtonIterations = 100U;
@@ -27,59 +33,6 @@ Status StateFailure(const FailureCategory category, const std::string& code,
message}});
}
/// @brief Compares every preserved source-identity component.
bool SourceIdsMatch(const SourceEntityId& left, const SourceEntityId& right) {
return left.instance_name == right.instance_name &&
left.source_label == right.source_label &&
left.source_label_text == right.source_label_text;
}
template <std::size_t Size>
/// @brief Tests one fixed-size numerical array for finite values.
bool IsFinite(const std::array<double, Size>& values) {
return std::all_of(values.begin(), values.end(),
[](const double value) { return std::isfinite(value); });
}
/// @brief Tests each component of a stored trial vector for finiteness.
bool IsFinite(const Vector& values) {
for (std::size_t index = 0U; index < values.Size(); ++index) {
if (!std::isfinite(values[index])) {
return false;
}
}
return true;
}
/// @brief Tests all physical quantities attached to one shell recovery row.
bool IsFinite(const ShellResultRow& row) {
if (!IsFinite(row.natural_coordinates) || !IsFinite(row.generalized_strain) ||
!IsFinite(row.section_resultant)) {
return false;
}
for (const auto& axis : row.local_frame) {
if (!IsFinite(axis)) {
return false;
}
}
return std::all_of(row.stress.begin(), row.stress.end(),
[](const ShellSectionStressRow& stress) {
return std::isfinite(stress.zeta) &&
IsFinite(stress.components);
});
}
/// @brief Returns shell Domain indices in deterministic unified element order.
std::vector<EntityIndex> ShellElementOrder(const Domain& domain) {
std::vector<EntityIndex> order;
for (std::size_t index = 0U; index < domain.Elements().Size(); ++index) {
if (domain.Elements()[index].Kind() == ElementDefinitionKind::kMitc4Shell) {
order.push_back(static_cast<EntityIndex>(index));
}
}
return order;
}
/// @brief Validates one accepted frame before any committed-state mutation.
Status ValidateAcceptedFrame(const AcceptedFrameState& frame,
const NonlinearTrialState& committed,
@@ -117,7 +70,7 @@ Status ValidateAcceptedFrame(const AcceptedFrameState& frame,
for (std::size_t node = 0U; node < node_count; ++node) {
if (frame.nodal_internal_order[node] !=
committed.nodal_internal_order[node] ||
!SourceIdsMatch(frame.nodal_source_ids[node],
!SourceIdentityEquals(frame.nodal_source_ids[node],
committed.nodal_source_ids[node]) ||
!IsFinite(frame.nodal_triad[node][0U]) ||
!IsFinite(frame.nodal_triad[node][1U]) ||
@@ -179,7 +132,7 @@ Status ValidateAcceptedFrame(const AcceptedFrameState& frame,
if (row.element != shell_order[element] ||
row.location != expected_locations[point] ||
row.natural_coordinates != expected_coordinates[point] ||
!IsFinite(row)) {
!results_internal::IsFiniteShellRow(row)) {
return StateFailure(FailureCategory::kSolver, "invalid-accepted-state", {},
"Shell result rows do not preserve stable identity.");
}
@@ -241,7 +194,7 @@ Result<NonlinearAnalysisState> NonlinearAnalysisState::Create(
for (std::size_t node = 0U; node < node_count; ++node) {
const auto& entry = baseline.node_baselines[node];
if (entry.node_index != static_cast<EntityIndex>(node) ||
!SourceIdsMatch(entry.source_id, domain.Nodes()[node].source_id) ||
!SourceIdentityEquals(entry.source_id, domain.Nodes()[node].source_id) ||
!IsFinite(entry.triad[0U]) || !IsFinite(entry.triad[1U]) ||
!IsFinite(entry.triad[2U]) || !IsFinite(entry.director)) {
return Result<NonlinearAnalysisState>::Failure(StateFailure(
+13 -46
View File
@@ -12,10 +12,17 @@
#include "fesa/analysis/analysis_model.h"
#include "fesa/assembly/load_assembler.h"
#include "fesa/elements/finite_rotation.h"
#include "math/vector3_operations.h"
#include "elements/shell_kernel_values.h"
#include "math/dense_blas_internal.h"
namespace fesa {
namespace {
using shell_kernel_internal::FrameMatrix;
using math_internal::Multiply;
using math_internal::Skew;
constexpr std::size_t kDofsPerNode = 6U;
constexpr std::size_t kTranslationDofCount = 3U;
constexpr double kMomentAdmissibilityThreshold = 1.0e-12;
@@ -67,27 +74,6 @@ Result<Vector3> RotationVectorForNode(const Vector& trial,
return Result<Vector3>::Success(rotation);
}
/// @brief Builds a proper initial frame from its semantic nodal record.
Matrix InitialFrame(const ShellNodeInitialFrame& frame) {
Matrix result{3U, 3U};
for (std::size_t row = 0U; row < kTranslationDofCount; ++row) {
result(row, 0U) = frame.tangent_a[row];
result(row, 1U) = frame.tangent_b[row];
result(row, 2U) = frame.director[row];
}
return result;
}
/// @brief Multiplies a 3-by-3 matrix by a Vector3.
Vector3 Multiply(const Matrix& matrix, const Vector3& vector) {
return {matrix(0U, 0U) * vector.X() + matrix(0U, 1U) * vector.Y() +
matrix(0U, 2U) * vector.Z(),
matrix(1U, 0U) * vector.X() + matrix(1U, 1U) * vector.Y() +
matrix(1U, 2U) * vector.Z(),
matrix(2U, 0U) * vector.X() + matrix(2U, 1U) * vector.Y() +
matrix(2U, 2U) * vector.Z()};
}
/// @brief Multiplies a 3-by-3 matrix transpose by a Vector3.
Vector3 MultiplyTranspose(const Matrix& matrix, const Vector3& vector) {
return {matrix(0U, 0U) * vector.X() + matrix(1U, 0U) * vector.Y() +
@@ -98,37 +84,18 @@ Vector3 MultiplyTranspose(const Matrix& matrix, const Vector3& vector) {
matrix(2U, 2U) * vector.Z()};
}
/// @brief Forms the three-dimensional skew matrix of a global vector.
Matrix Skew(const Vector3& vector) {
Matrix result{3U, 3U};
result(0U, 1U) = -vector.Z();
result(0U, 2U) = vector.Y();
result(1U, 0U) = vector.Z();
result(1U, 2U) = -vector.X();
result(2U, 0U) = -vector.Y();
result(2U, 1U) = vector.X();
return result;
}
/// @brief Scales a small dense matrix without changing its shape.
Matrix Scale(const Matrix& matrix, const double scale) {
Matrix result{matrix.Rows(), matrix.Columns()};
for (std::size_t row = 0U; row < matrix.Rows(); ++row) {
for (std::size_t column = 0U; column < matrix.Columns(); ++column) {
result(row, column) = scale * matrix(row, column);
}
}
Matrix result{matrix};
result.Scale(scale);
return result;
}
/// @brief Adds matching small dense matrices.
Matrix Add(const Matrix& left, const Matrix& right) {
Matrix result{left.Rows(), left.Columns()};
for (std::size_t row = 0U; row < left.Rows(); ++row) {
for (std::size_t column = 0U; column < left.Columns(); ++column) {
result(row, column) = left(row, column) + right(row, column);
}
}
Matrix result{left};
dense_blas_internal::AxpyValues(1.0, right.Data(),
left.Rows() * left.Columns(), result.Data());
return result;
}
@@ -472,7 +439,7 @@ Result<GlobalFixedLoadContribution> AssembleGlobalFixedLoadContribution(
return Result<GlobalFixedLoadContribution>::Failure(phi.GetStatus());
}
auto rotation =
MakeFiniteRotationState(phi.Value(), InitialFrame(*frames[node]));
MakeFiniteRotationState(phi.Value(), FrameMatrix(*frames[node]));
if (!rotation.HasValue()) {
return Result<GlobalFixedLoadContribution>::Failure(rotation.GetStatus());
}
+16
View File
@@ -0,0 +1,16 @@
#ifndef FESA_ANALYSIS_NONLINEAR_STATIC_CORE_H_
#define FESA_ANALYSIS_NONLINEAR_STATIC_CORE_H_
#include "fesa/analysis/nonlinear_static_execution_result.h"
namespace fesa {
class EquilibriumAlgorithm;
class NewtonIterationLogWriter;
class ParallelFor;
class TangentSolver;
/// @brief Runs the production core with caller-owned iteration policy.
/// @note All adapters outlive this synchronous call; candidate/history stay local.
Result<NonlinearStaticExecutionResult> ExecuteNonlinearStaticCore(
const Domain& domain, NewtonIterationLogWriter& log,
const ParallelFor& parallel_for, TangentSolver& tangent_solver,
EquilibriumAlgorithm& algorithm);
} // namespace fesa
#endif // FESA_ANALYSIS_NONLINEAR_STATIC_CORE_H_
+275 -245
View File
@@ -1,11 +1,15 @@
#include "fesa/analysis/nonlinear_static_procedure.h"
#include "equilibrium_algorithm.h"
#include "analysis/nonlinear_static_core.h"
#include <algorithm>
#include <array>
#include <cmath>
#include <cstddef>
#include <cstdint>
#include <limits>
#include <memory>
#include <optional>
#include <string>
#include <utility>
@@ -17,6 +21,7 @@
#include "fesa/assembly/parallel_for.h"
#include "fesa/assembly/surface_pressure_assembler.h"
#include "fesa/constraints/essential_constraint_policy.h"
#include "fesa/elements/element_capabilities.h"
#include "fesa/elements/finite_rotation.h"
#include "fesa/elements/mitc4_shell.h"
#include "fesa/elements/nonlinear_mitc4_shell.h"
@@ -25,9 +30,16 @@
#include "fesa/properties/shell_section.h"
#include "fesa/solvers/linear/tangent_solver.h"
#include "assembly/ordered_contribution_internal.h"
#include "math/finite_values.h"
#include "elements/shell_kernel_values.h"
namespace fesa {
namespace {
using shell_kernel_internal::FrameMatrix;
using math_internal::IsFinite;
constexpr std::size_t kDofsPerNode = 6U;
constexpr std::size_t kTranslationsPerNode = 3U;
constexpr std::size_t kShellDofCount = 24U;
@@ -36,7 +48,8 @@ constexpr std::uint32_t kMaximumNewtonIterations = 100U;
struct RuntimeShell {
EntityIndex element_index;
std::array<std::size_t, kShellDofCount> scatter;
NonlinearMitc4Shell element;
std::unique_ptr<NonlinearMitc4Shell> kernel;
std::unique_ptr<ShellTrialElement> element;
};
struct InternalAssembly {
@@ -124,16 +137,6 @@ Status RecordFailure(NewtonIterationLogWriter& log,
return log_status.IsOk() ? failure : log_status;
}
Matrix InitialFrame(const ShellNodeInitialFrame& frame) {
Matrix result{3U, 3U};
for (std::size_t row = 0U; row < kTranslationsPerNode; ++row) {
result(row, 0U) = frame.tangent_a[row];
result(row, 1U) = frame.tangent_b[row];
result(row, 2U) = frame.director[row];
}
return result;
}
Result<DofManager> BuildDofManager(const Domain& domain) {
Result<AnalysisModel> model = AnalysisModel::Create(domain);
if (!model.HasValue()) {
@@ -239,9 +242,20 @@ Result<std::vector<RuntimeShell>> BuildRuntimeShells(const Domain& domain,
return Result<std::vector<RuntimeShell>>::Failure(shell.GetStatus());
}
const EntityIndex element_index = static_cast<EntityIndex>(index);
auto kernel =
std::make_unique<NonlinearMitc4Shell>(std::move(shell.Value()));
auto element = std::make_unique<Mitc4TrialAdapter>(
ElementDofLayout{definition->SourceId(), definition->NodeIndices(),
{DofComponent::kUx, DofComponent::kUy, DofComponent::kUz,
DofComponent::kUrx, DofComponent::kUry, DofComponent::kUrz}},
*kernel);
auto mapped = dofs.ElementScatter(element->DofLayout());
if (!mapped.HasValue())
return Result<std::vector<RuntimeShell>>::Failure(mapped.GetStatus());
std::array<std::size_t, kShellDofCount> scatter{};
std::copy(mapped.Value().begin(), mapped.Value().end(), scatter.begin());
shells.push_back(
{element_index, dofs.ShellElementScatter(element_index),
std::move(shell.Value())});
{element_index, scatter, std::move(kernel), std::move(element)});
}
return Result<std::vector<RuntimeShell>>::Success(std::move(shells));
}
@@ -269,7 +283,7 @@ Status PopulateNodalKinematics(const Domain& domain,
trial->generalized_coordinates[first + 4U],
trial->generalized_coordinates[first + 5U]};
Result<FiniteRotationState> rotation = MakeFiniteRotationState(
phi, InitialFrame(domain.ShellNodeInitialFrames()[node]));
phi, FrameMatrix(domain.ShellNodeInitialFrames()[node]));
if (!rotation.HasValue()) {
return rotation.GetStatus();
}
@@ -304,26 +318,6 @@ NonlinearMitc4TrialDofs ExtractElementTrial(
return trial;
}
bool IsFinite(const Vector& values) {
for (std::size_t index = 0U; index < values.Size(); ++index) {
if (!std::isfinite(values[index])) {
return false;
}
}
return true;
}
bool IsFinite(const Matrix& values) {
for (std::size_t row = 0U; row < values.Rows(); ++row) {
for (std::size_t column = 0U; column < values.Columns(); ++column) {
if (!std::isfinite(values(row, column))) {
return false;
}
}
}
return true;
}
Result<InternalAssembly> AssembleInternal(
const std::vector<RuntimeShell>& shells, const DofManager& dofs,
const Vector& coordinates, const ParallelFor& parallel_for) {
@@ -332,9 +326,8 @@ Result<InternalAssembly> AssembleInternal(
std::vector<std::optional<Status>> failures(shells.size());
parallel_for.Execute(shells.size(), [&](const std::size_t index) {
Result<NonlinearMitc4InternalEquilibrium> result =
EvaluateInternalResidualAndTangent(shells[index].element,
ExtractElementTrial(shells[index],
coordinates));
shells[index].element->EvaluateTrial(
ExtractElementTrial(shells[index], coordinates));
if (!result.HasValue()) {
failures[index] = result.GetStatus();
return;
@@ -363,12 +356,9 @@ Result<InternalAssembly> AssembleInternal(
}
for (std::size_t row = 0U; row < kShellDofCount; ++row) {
force[shells[index].scatter[row]] += local[index]->internal_residual[row];
for (std::size_t column = 0U; column < kShellDofCount; ++column) {
contributions.push_back(
{shells[index].scatter[row], shells[index].scatter[column],
local[index]->internal_tangent(row, column), index,
row * kShellDofCount + column});
}
assembly_internal::AppendLocalMatrixRow(
local[index]->internal_tangent, shells[index].scatter, row, index,
contributions);
}
for (const NonlinearMitc4RecoveryPoint& point :
local[index]->recovery.gauss_points) {
@@ -573,19 +563,6 @@ Vector CurrentIncrementComponents(const Vector& current, const Vector& start,
return SelectFreeComponents(difference, dofs, rotations);
}
Vector3 SpatialMoment(const Matrix& transform,
const std::array<double, 3>& generalized) {
return {transform(0U, 0U) * generalized[0U] +
transform(0U, 1U) * generalized[1U] +
transform(0U, 2U) * generalized[2U],
transform(1U, 0U) * generalized[0U] +
transform(1U, 1U) * generalized[1U] +
transform(1U, 2U) * generalized[2U],
transform(2U, 0U) * generalized[0U] +
transform(2U, 1U) * generalized[1U] +
transform(2U, 2U) * generalized[2U]};
}
/// @brief Computes accepted-state applied-plus-reaction balance evidence.
Result<std::array<double, 6>> GlobalEquilibrium(
const Domain& domain, const DofManager& dofs,
@@ -634,7 +611,6 @@ Result<std::array<double, 6>> GlobalEquilibrium(
constrained[first] ? trial.residual[first] : 0.0,
constrained[first + 1U] ? trial.residual[first + 1U] : 0.0,
constrained[first + 2U] ? trial.residual[first + 2U] : 0.0};
const Matrix transform = InverseTransposeLeftJacobianSO3(phi);
const std::array<double, 3> applied_generalized{
trial.external_force[first + 3U], trial.external_force[first + 4U],
trial.external_force[first + 5U]};
@@ -642,9 +618,16 @@ Result<std::array<double, 6>> GlobalEquilibrium(
constrained[first + 3U] ? trial.residual[first + 3U] : 0.0,
constrained[first + 4U] ? trial.residual[first + 4U] : 0.0,
constrained[first + 5U] ? trial.residual[first + 5U] : 0.0};
const Vector3 applied_moment = SpatialMoment(transform, applied_generalized);
const Vector3 reaction_moment =
SpatialMoment(transform, reaction_generalized);
const auto applied = SpatialMomentSO3(phi, Vector3(applied_generalized));
const auto reaction = SpatialMomentSO3(phi, Vector3(reaction_generalized));
if (!applied.HasValue() || !reaction.HasValue())
return Result<std::array<double, 6>>::Failure(ProcedureFailure(
FailureCategory::kSolver, "nonfinite-newton-data",
{domain.SourcePath(), 0U}, "NONLINEAR_STATIC_PROCEDURE",
domain.SourceContentIdentity(),
"Nonlinear global equilibrium balance is nonfinite."));
const Vector3 applied_moment = applied.Value();
const Vector3 reaction_moment = reaction.Value();
applied_force_resultant = applied_force_resultant + applied_force;
applied_moment_resultant =
applied_moment_resultant + position.Cross(applied_force) + applied_moment;
@@ -694,11 +677,11 @@ Status ApplyIncrementConstraints(const StepDefinition& step,
return Status::Ok();
}
AcceptedFrameState AcceptedFrame(const NonlinearAnalysisState& state,
AcceptedFrameState AcceptedFrame(const std::uint32_t frame_index,
const NonlinearTrialState& trial,
const LoadFactorIncrement& increment,
const std::uint32_t iteration) {
return {static_cast<std::uint32_t>(state.AcceptedFrames().size()),
return {frame_index,
increment.increment_index,
increment.load_factor,
iteration,
@@ -728,6 +711,15 @@ NonlinearStaticProcedureExecutor::NonlinearStaticProcedureExecutor(
Result<NonlinearStaticExecutionResult>
NonlinearStaticProcedureExecutor::Execute(
const Domain& domain, NewtonIterationLogWriter& log) const {
NewtonStepAlgorithm algorithm;
return ExecuteNonlinearStaticCore(domain, log, parallel_for_, tangent_solver_,
algorithm);
}
Result<NonlinearStaticExecutionResult> ExecuteNonlinearStaticCore(
const Domain& domain, NewtonIterationLogWriter& log,
const ParallelFor& parallel_for, TangentSolver& tangent_solver,
EquilibriumAlgorithm& algorithm) {
if (domain.Steps().Size() != 1U || !domain.Steps()[0U].IsNonlinearStatic()) {
return Result<NonlinearStaticExecutionResult>::Failure(ProcedureFailure(
FailureCategory::kInput, "invalid-nonlinear-static-data",
@@ -782,199 +774,237 @@ NonlinearStaticProcedureExecutor::Execute(
return Result<NonlinearStaticExecutionResult>::Failure(constraint_status);
}
for (std::uint32_t iteration = 1U; iteration <= kMaximumNewtonIterations;
++iteration) {
NewtonIterationLogRecord record =
NewLogRecord(step, increment, iteration);
trial.Value().increment_index = increment.increment_index;
trial.Value().load_factor = increment.load_factor;
trial.Value().is_factor_zero_baseline = false;
struct ShellStepCandidate {
AcceptedFrameState frame;
NewtonIterationLogRecord record;
};
const auto next_frame_index =
static_cast<std::uint32_t>(state.Value().AcceptedFrames().size());
const auto report_failure = [&](NewtonIterationLogRecord* record,
const Status& failure,
NewtonIterationStage stage) {
return RecordFailure(log, record, failure, stage);
};
const auto write_iteration = [&](const NewtonIterationLogRecord& record) {
return WriteAndFlush(log, record);
};
ShellFullNewtonStep<ShellStepCandidate> iteration_step(
[&](std::uint32_t iteration)
-> Result<std::optional<ShellStepCandidate>> {
NewtonIterationLogRecord record =
NewLogRecord(step, increment, iteration);
trial.Value().increment_index = increment.increment_index;
trial.Value().load_factor = increment.load_factor;
trial.Value().is_factor_zero_baseline = false;
Status kinematics = PopulateNodalKinematics(domain, &trial.Value());
if (!kinematics.IsOk()) {
return Result<NonlinearStaticExecutionResult>::Failure(
RecordFailure(log, &record, kinematics,
NewtonIterationStage::kKinematicsEvaluated));
}
record.last_completed_stage = NewtonIterationStage::kKinematicsEvaluated;
Status kinematics = PopulateNodalKinematics(domain, &trial.Value());
if (!kinematics.IsOk()) {
return Result<std::optional<ShellStepCandidate>>::Failure(
report_failure(&record, kinematics,
NewtonIterationStage::kKinematicsEvaluated));
}
record.last_completed_stage =
NewtonIterationStage::kKinematicsEvaluated;
Result<InternalAssembly> internal = AssembleInternal(
shells.Value(), dofs.Value(), trial.Value().generalized_coordinates,
parallel_for_);
if (!internal.HasValue()) {
return Result<NonlinearStaticExecutionResult>::Failure(RecordFailure(
log, &record, internal.GetStatus(),
NewtonIterationStage::kInternalAssembled));
}
record.last_completed_stage = NewtonIterationStage::kInternalAssembled;
Result<InternalAssembly> internal = AssembleInternal(
shells.Value(), dofs.Value(),
trial.Value().generalized_coordinates, parallel_for);
if (!internal.HasValue()) {
return Result<std::optional<ShellStepCandidate>>::Failure(
report_failure(&record, internal.GetStatus(),
NewtonIterationStage::kInternalAssembled));
}
record.last_completed_stage =
NewtonIterationStage::kInternalAssembled;
Result<GlobalFixedLoadContribution> fixed =
AssembleGlobalFixedLoadContribution(
domain, dofs.Value(), trial.Value().generalized_coordinates,
increment.load_factor);
if (!fixed.HasValue()) {
return Result<NonlinearStaticExecutionResult>::Failure(
RecordFailure(log, &record, fixed.GetStatus(),
NewtonIterationStage::kExternalAssembled));
}
Result<SurfacePressureAssemblyCandidate> pressure =
AssembleSurfacePressure(
analysis_model.Value(), dofs.Value(),
trial.Value().generalized_coordinates, increment.load_factor,
SurfacePressureConfiguration::kCurrent, parallel_for_);
if (!pressure.HasValue()) {
return Result<NonlinearStaticExecutionResult>::Failure(
RecordFailure(log, &record, pressure.GetStatus(),
NewtonIterationStage::kExternalAssembled));
}
Result<GlobalFixedLoadContribution> external =
CombineExternalContributions(fixed.Value(), pressure.Value(),
dofs.Value());
if (!external.HasValue()) {
return Result<NonlinearStaticExecutionResult>::Failure(
RecordFailure(log, &record, external.GetStatus(),
NewtonIterationStage::kExternalAssembled));
}
record.last_completed_stage = NewtonIterationStage::kExternalAssembled;
Result<GlobalFixedLoadContribution> fixed =
AssembleGlobalFixedLoadContribution(
domain, dofs.Value(), trial.Value().generalized_coordinates,
increment.load_factor);
if (!fixed.HasValue()) {
return Result<std::optional<ShellStepCandidate>>::Failure(
report_failure(&record, fixed.GetStatus(),
NewtonIterationStage::kExternalAssembled));
}
Result<SurfacePressureAssemblyCandidate> pressure =
AssembleSurfacePressure(
analysis_model.Value(), dofs.Value(),
trial.Value().generalized_coordinates, increment.load_factor,
SurfacePressureConfiguration::kCurrent, parallel_for);
if (!pressure.HasValue()) {
return Result<std::optional<ShellStepCandidate>>::Failure(
report_failure(&record, pressure.GetStatus(),
NewtonIterationStage::kExternalAssembled));
}
Result<GlobalFixedLoadContribution> external =
CombineExternalContributions(fixed.Value(), pressure.Value(),
dofs.Value());
if (!external.HasValue()) {
return Result<std::optional<ShellStepCandidate>>::Failure(
report_failure(&record, external.GetStatus(),
NewtonIterationStage::kExternalAssembled));
}
record.last_completed_stage =
NewtonIterationStage::kExternalAssembled;
Result<Vector> residual =
Difference(internal.Value().force,
external.Value().generalized_external_force);
if (!residual.HasValue()) {
return Result<NonlinearStaticExecutionResult>::Failure(RecordFailure(
log, &record, residual.GetStatus(),
NewtonIterationStage::kExternalAssembled));
}
Result<SparseMatrix> tangent =
Difference(internal.Value().tangent,
external.Value().generalized_external_tangent,
dofs.Value());
if (!tangent.HasValue()) {
return Result<NonlinearStaticExecutionResult>::Failure(RecordFailure(
log, &record, tangent.GetStatus(),
NewtonIterationStage::kExternalAssembled));
}
trial.Value().internal_force = std::move(internal.Value().force);
trial.Value().external_force =
std::move(external.Value().generalized_external_force);
trial.Value().residual = std::move(residual.Value());
trial.Value().reaction = trial.Value().residual;
trial.Value().shell_results = std::move(internal.Value().shell_rows);
trial.Value().physical_strain_energy =
internal.Value().physical_strain_energy;
Result<std::array<double, 6>> global_equilibrium =
GlobalEquilibrium(domain, dofs.Value(), trial.Value());
if (!global_equilibrium.HasValue()) {
return Result<NonlinearStaticExecutionResult>::Failure(RecordFailure(
log, &record, global_equilibrium.GetStatus(),
NewtonIterationStage::kExternalAssembled));
}
trial.Value().global_equilibrium = global_equilibrium.Value();
Result<Vector> residual =
Difference(internal.Value().force,
external.Value().generalized_external_force);
if (!residual.HasValue()) {
return Result<std::optional<ShellStepCandidate>>::Failure(
report_failure(&record, residual.GetStatus(),
NewtonIterationStage::kExternalAssembled));
}
Result<SparseMatrix> tangent = Difference(
internal.Value().tangent,
external.Value().generalized_external_tangent, dofs.Value());
if (!tangent.HasValue()) {
return Result<std::optional<ShellStepCandidate>>::Failure(
report_failure(&record, tangent.GetStatus(),
NewtonIterationStage::kExternalAssembled));
}
trial.Value().internal_force = std::move(internal.Value().force);
trial.Value().external_force =
std::move(external.Value().generalized_external_force);
trial.Value().residual = std::move(residual.Value());
trial.Value().reaction = trial.Value().residual;
trial.Value().shell_results = std::move(internal.Value().shell_rows);
trial.Value().physical_strain_energy =
internal.Value().physical_strain_energy;
Result<std::array<double, 6>> global_equilibrium =
GlobalEquilibrium(domain, dofs.Value(), trial.Value());
if (!global_equilibrium.HasValue()) {
return Result<std::optional<ShellStepCandidate>>::Failure(
report_failure(&record, global_equilibrium.GetStatus(),
NewtonIterationStage::kExternalAssembled));
}
trial.Value().global_equilibrium = global_equilibrium.Value();
Result<PartitionedStiffness> partition =
constraints.Partition(tangent.Value(), dofs.Value());
if (!partition.HasValue()) {
return Result<NonlinearStaticExecutionResult>::Failure(RecordFailure(
log, &record, partition.GetStatus(),
NewtonIterationStage::kExternalAssembled));
}
const Status factorization = tangent_solver_.FactorizeFresh(
partition.Value().k_ff);
if (!factorization.IsOk()) {
return Result<NonlinearStaticExecutionResult>::Failure(RecordFailure(
log, &record, factorization,
NewtonIterationStage::kSolvedCorrection));
}
Result<PartitionedStiffness> partition =
constraints.Partition(tangent.Value(), dofs.Value());
if (!partition.HasValue()) {
return Result<std::optional<ShellStepCandidate>>::Failure(
report_failure(&record, partition.GetStatus(),
NewtonIterationStage::kExternalAssembled));
}
const Status factorization =
tangent_solver.FactorizeFresh(partition.Value().k_ff);
if (!factorization.IsOk()) {
return Result<std::optional<ShellStepCandidate>>::Failure(
report_failure(&record, factorization,
NewtonIterationStage::kSolvedCorrection));
}
Vector correction_rhs = constraints.GatherFree(trial.Value().residual,
dofs.Value());
for (std::size_t index = 0U; index < correction_rhs.Size(); ++index) {
correction_rhs[index] = -correction_rhs[index];
}
Vector correction{dofs.Value().FreeDofCount()};
const Status solve =
tangent_solver_.SolveCorrection(correction_rhs, correction);
if (!solve.IsOk()) {
return Result<NonlinearStaticExecutionResult>::Failure(RecordFailure(
log, &record, solve, NewtonIterationStage::kSolvedCorrection));
}
record.last_completed_stage = NewtonIterationStage::kSolvedCorrection;
Vector correction_rhs =
constraints.GatherFree(trial.Value().residual, dofs.Value());
for (std::size_t index = 0U; index < correction_rhs.Size(); ++index) {
correction_rhs[index] = -correction_rhs[index];
}
Vector correction{dofs.Value().FreeDofCount()};
const Status solve =
tangent_solver.SolveCorrection(correction_rhs, correction);
if (!solve.IsOk()) {
return Result<std::optional<ShellStepCandidate>>::Failure(
report_failure(&record, solve,
NewtonIterationStage::kSolvedCorrection));
}
record.last_completed_stage = NewtonIterationStage::kSolvedCorrection;
const NewtonConvergenceResult convergence = EvaluateNewtonConvergence({
{"residual_force", SelectFreeComponents(trial.Value().residual,
dofs.Value(), false),
SelectFreeComponents(trial.Value().internal_force, dofs.Value(),
false),
SelectFreeComponents(trial.Value().external_force, dofs.Value(),
false)},
{"residual_moment", SelectFreeComponents(trial.Value().residual,
dofs.Value(), true),
SelectFreeComponents(trial.Value().internal_force, dofs.Value(),
true),
SelectFreeComponents(trial.Value().external_force, dofs.Value(),
true)},
{"correction_translation",
SelectCorrectionComponents(correction, dofs.Value(), false),
CurrentIncrementComponents(trial.Value().generalized_coordinates,
increment_start, dofs.Value(), false)},
{"correction_rotation",
SelectCorrectionComponents(correction, dofs.Value(), true),
CurrentIncrementComponents(trial.Value().generalized_coordinates,
increment_start, dofs.Value(), true)}});
record.force_residual = convergence.force_residual;
record.moment_residual = convergence.moment_residual;
record.translation_correction = convergence.translation_correction;
record.rotation_correction = convergence.rotation_correction;
record.combined_passed = convergence.combined_passed;
record.last_completed_stage = NewtonIterationStage::kConvergenceEvaluated;
const NewtonConvergenceResult convergence = EvaluateNewtonConvergence(
{{"residual_force",
SelectFreeComponents(trial.Value().residual, dofs.Value(),
false),
SelectFreeComponents(trial.Value().internal_force, dofs.Value(),
false),
SelectFreeComponents(trial.Value().external_force, dofs.Value(),
false)},
{"residual_moment",
SelectFreeComponents(trial.Value().residual, dofs.Value(),
true),
SelectFreeComponents(trial.Value().internal_force, dofs.Value(),
true),
SelectFreeComponents(trial.Value().external_force, dofs.Value(),
true)},
{"correction_translation",
SelectCorrectionComponents(correction, dofs.Value(), false),
CurrentIncrementComponents(
trial.Value().generalized_coordinates, increment_start,
dofs.Value(), false)},
{"correction_rotation",
SelectCorrectionComponents(correction, dofs.Value(), true),
CurrentIncrementComponents(
trial.Value().generalized_coordinates, increment_start,
dofs.Value(), true)}});
record.force_residual = convergence.force_residual;
record.moment_residual = convergence.moment_residual;
record.translation_correction = convergence.translation_correction;
record.rotation_correction = convergence.rotation_correction;
record.combined_passed = convergence.combined_passed;
record.last_completed_stage =
NewtonIterationStage::kConvergenceEvaluated;
if (convergence.combined_passed) {
const Status committed = state.Value().CommitAcceptedFrame(
AcceptedFrame(state.Value(), trial.Value(), increment, iteration));
if (!committed.IsOk()) {
return Result<NonlinearStaticExecutionResult>::Failure(RecordFailure(
log, &record, committed, NewtonIterationStage::kAccepted));
}
record.last_completed_stage = NewtonIterationStage::kAccepted;
record.transition = "accepted";
record.outcome = "accepted";
const Status log_status = WriteAndFlush(log, record);
if (!log_status.IsOk()) {
return Result<NonlinearStaticExecutionResult>::Failure(log_status);
}
break;
}
if (convergence.combined_passed) {
return Result<std::optional<ShellStepCandidate>>::Success(
ShellStepCandidate{AcceptedFrame(next_frame_index,
trial.Value(), increment,
iteration),
record});
}
if (iteration == kMaximumNewtonIterations) {
const Status failure = ProcedureFailure(
FailureCategory::kSolver, "nonlinear-nonconvergence",
step.Location(), "STATIC", step.Name(),
"The nonlinear procedure did not converge within 100 iterations.");
return Result<NonlinearStaticExecutionResult>::Failure(RecordFailure(
log, &record, failure,
NewtonIterationStage::kConvergenceEvaluated));
}
if (iteration == kMaximumNewtonIterations) {
const Status failure = ProcedureFailure(
FailureCategory::kSolver, "nonlinear-nonconvergence",
step.Location(), "STATIC", step.Name(),
"The nonlinear procedure did not converge within 100 "
"iterations.");
return Result<std::optional<ShellStepCandidate>>::Failure(
report_failure(&record, failure,
NewtonIterationStage::kConvergenceEvaluated));
}
for (std::size_t equation = 0U; equation < dofs.Value().FreeDofs().size();
++equation) {
const std::size_t full_dof = dofs.Value().FreeDofs()[equation];
trial.Value().generalized_coordinates[full_dof] += correction[equation];
if (!std::isfinite(trial.Value().generalized_coordinates[full_dof])) {
const Status failure = ProcedureFailure(
FailureCategory::kSolver, "nonfinite-newton-data", {},
"NONLINEAR_STATIC_PROCEDURE", std::to_string(full_dof),
"A nonlinear free-coordinate correction is nonfinite.");
return Result<NonlinearStaticExecutionResult>::Failure(RecordFailure(
log, &record, failure, NewtonIterationStage::kSolvedCorrection));
}
}
record.transition = "continue";
record.outcome = "not-converged";
const Status log_status = WriteAndFlush(log, record);
if (!log_status.IsOk()) {
return Result<NonlinearStaticExecutionResult>::Failure(log_status);
}
for (std::size_t equation = 0U;
equation < dofs.Value().FreeDofs().size(); ++equation) {
const std::size_t full_dof = dofs.Value().FreeDofs()[equation];
trial.Value().generalized_coordinates[full_dof] +=
correction[equation];
if (!std::isfinite(
trial.Value().generalized_coordinates[full_dof])) {
const Status failure = ProcedureFailure(
FailureCategory::kSolver, "nonfinite-newton-data", {},
"NONLINEAR_STATIC_PROCEDURE", std::to_string(full_dof),
"A nonlinear free-coordinate correction is nonfinite.");
return Result<std::optional<ShellStepCandidate>>::Failure(
report_failure(&record, failure,
NewtonIterationStage::kSolvedCorrection));
}
}
record.transition = "continue";
record.outcome = "not-converged";
const Status log_status = write_iteration(record);
if (!log_status.IsOk()) {
return Result<std::optional<ShellStepCandidate>>::Failure(
log_status);
}
return Result<std::optional<ShellStepCandidate>>::Success(
std::nullopt);
},
kMaximumNewtonIterations);
const Status solved = iteration_step.Solve(algorithm);
if (!solved.IsOk())
return Result<NonlinearStaticExecutionResult>::Failure(solved);
auto candidate = iteration_step.TakeCandidate();
auto& record = candidate.record;
const Status committed = state.Value().CommitAcceptedFrame(candidate.frame);
if (!committed.IsOk()) {
return Result<NonlinearStaticExecutionResult>::Failure(RecordFailure(
log, &record, committed, NewtonIterationStage::kAccepted));
}
record.last_completed_stage = NewtonIterationStage::kAccepted;
record.transition = "accepted";
record.outcome = "accepted";
const Status log_status = WriteAndFlush(log, record);
if (!log_status.IsOk())
return Result<NonlinearStaticExecutionResult>::Failure(log_status);
}
std::vector<Diagnostic> diagnostics = domain.Warnings();
@@ -983,4 +1013,4 @@ NonlinearStaticProcedureExecutor::Execute(
{std::move(state.Value()), std::move(diagnostics)});
}
} // namespace fesa
} // namespace fesa
+241 -193
View File
@@ -1,7 +1,10 @@
#include "fesa/analysis/prepared_analysis.h"
#include <memory>
#include <utility>
#include "analysis_procedures.h"
#include "fesa/analysis/analysis_model.h"
#include "fesa/analysis/hht_dynamic_procedure.h"
#include "fesa/analysis/linear_frequency_procedure.h"
@@ -9,15 +12,13 @@
#include "fesa/analysis/newton_iteration_log.h"
#include "fesa/analysis/nonlinear_static_procedure.h"
#include "fesa/analysis/solid_frequency_procedure.h"
#include "fesa/analysis/solid_hht_dynamic_procedure.h"
#include "fesa/analysis/solid_static_procedure.h"
#include "fesa/analysis/transient_heat_procedure.h"
#include "fesa/app/output_paths.h"
#include "fesa/io/abaqus/domain_mapper.h"
#include "fesa/io/abaqus/input_reader.h"
#include "fesa/results/dynamic_results_writer.h"
#include "fesa/results/eigen_results_writer.h"
#include "fesa/results/nonlinear_results_writer.h"
#include "fesa/results/results_writer.h"
#include "fesa/results/solid_results_writer.h"
#include "fesa/results/thermal_results_writer.h"
#include "fesa/results/prepared_results_writer.h"
namespace fesa {
@@ -39,6 +40,185 @@ Result<PreparedAnalysisInput> PrepareAnalysisInput(
return Result<PreparedAnalysisInput>::Success(std::move(prepared));
}
namespace {
enum class ProcedureKind {
kLinearStatic,
kNonlinearStatic,
kLinearFrequency,
kHhtDynamic,
kSolidStatic,
kSolidFrequency,
kSolidHhtDynamic,
kTransientHeat
};
// Keep procedure precedence and step-count diagnostics common to both APIs.
Result<ProcedureKind> SelectProcedure(const Domain& domain) {
if (domain.ThermalStep() != nullptr)
return Result<ProcedureKind>::Success(ProcedureKind::kTransientHeat);
if (domain.Steps().Size() != 1U) {
return Result<ProcedureKind>::Failure(Status::Failure(
FailureCategory::kInput,
{{Severity::kError,
"invalid-analysis-step-count",
{},
"*STEP",
{},
"Prepared analysis requires exactly one analysis step."}}));
}
if (domain.IsSolid()) {
if (domain.Dynamic() != nullptr)
return Result<ProcedureKind>::Success(ProcedureKind::kSolidHhtDynamic);
if (domain.Steps()[0U].IsFrequency())
return Result<ProcedureKind>::Success(ProcedureKind::kSolidFrequency);
return Result<ProcedureKind>::Success(ProcedureKind::kSolidStatic);
}
if (domain.Dynamic() != nullptr)
return Result<ProcedureKind>::Success(ProcedureKind::kHhtDynamic);
if (domain.Steps()[0U].IsFrequency())
return Result<ProcedureKind>::Success(ProcedureKind::kLinearFrequency);
if (domain.Steps()[0U].IsNonlinearStatic())
return Result<ProcedureKind>::Success(ProcedureKind::kNonlinearStatic);
return Result<ProcedureKind>::Success(ProcedureKind::kLinearStatic);
}
template <typename Payload>
Result<PreparedAnalysisOutcome> Completed(Result<Payload> result) {
if (!result.HasValue())
return Result<PreparedAnalysisOutcome>::Failure(result.GetStatus());
return Result<PreparedAnalysisOutcome>::Success(
PreparedAnalysisOutcome{std::move(result.Value())});
}
Result<PreparedAnalysisOutcome>
MissingExecutor(const char* code, const char* keyword, const char* message) {
return Result<PreparedAnalysisOutcome>::Failure(
Status::Failure(FailureCategory::kSolver,
{{Severity::kError, code, {}, keyword, {}, message}}));
}
} // namespace
Result<PreparedAnalysisOutcome>
LinearStaticProcedure::Execute(const Domain& domain) {
return Completed(executor_.Execute(domain));
}
Result<PreparedAnalysisOutcome>
NonlinearStaticProcedure::Execute(const Domain& domain) {
return Completed(executor_.Execute(domain, log_));
}
Result<PreparedAnalysisOutcome>
LinearFrequencyProcedure::Execute(const Domain& domain) {
return Completed(executor_.Execute(domain));
}
Result<PreparedAnalysisOutcome>
HhtDynamicProcedure::Execute(const Domain& domain) {
return Completed(executor_.Execute(domain, log_));
}
Result<PreparedAnalysisOutcome>
SolidStaticProcedure::Execute(const Domain& domain) {
SolidStaticState state;
const Status execution = executor_.ExecuteInto(domain, state);
for (const auto& record : state.IterationRecords()) {
const Status written = log_.Write(record, domain.Steps()[0U].Name());
if (!written.IsOk())
return Result<PreparedAnalysisOutcome>::Failure(written);
}
if (!execution.IsOk())
return Result<PreparedAnalysisOutcome>::Failure(execution);
return Result<PreparedAnalysisOutcome>::Success(PreparedAnalysisOutcome{
SolidStaticExecutionResult{std::move(state), domain.Warnings()}});
}
Result<PreparedAnalysisOutcome>
SolidFrequencyProcedure::Execute(const Domain& domain) {
return Completed(executor_.Execute(domain));
}
Result<PreparedAnalysisOutcome>
SolidHhtDynamicProcedure::Execute(const Domain& domain) {
auto model = AnalysisModel::Create(domain);
if (!model.HasValue())
return Result<PreparedAnalysisOutcome>::Failure(model.GetStatus());
auto dofs = DofManager::Create(model.Value());
if (!dofs.HasValue())
return Result<PreparedAnalysisOutcome>::Failure(dofs.GetStatus());
auto state = SolidDynamicAnalysisState::Create(domain, dofs.Value());
if (!state.HasValue())
return Result<PreparedAnalysisOutcome>::Failure(state.GetStatus());
const Status execution = executor_.ExecuteInto(domain, state.Value());
for (const auto& record : state.Value().IterationRecords()) {
const Status written = log_.Write(record);
if (!written.IsOk())
return Result<PreparedAnalysisOutcome>::Failure(written);
}
if (!execution.IsOk())
return Result<PreparedAnalysisOutcome>::Failure(execution);
return Result<PreparedAnalysisOutcome>::Success(
PreparedAnalysisOutcome{SolidDynamicExecutionResult{
std::move(dofs.Value()), std::move(state.Value())}});
}
Result<PreparedAnalysisOutcome>
TransientHeatProcedure::Execute(const Domain& domain) {
return Completed(executor_.Execute(domain));
}
Result<std::unique_ptr<AnalysisProcedure>> CreateAnalysisProcedure(
const Domain& domain, const ParallelFor& parallel_for,
LinearSolver& linear_solver, TangentSolver& tangent_solver,
const EigenSolver& eigen_solver, NewtonIterationLogWriter& log) {
const auto kind = SelectProcedure(domain);
if (!kind.HasValue())
return Result<std::unique_ptr<AnalysisProcedure>>::Failure(
kind.GetStatus());
std::unique_ptr<AnalysisProcedure> procedure;
switch (kind.Value()) {
case ProcedureKind::kLinearStatic:
procedure =
std::make_unique<LinearStaticProcedure>(parallel_for, linear_solver);
break;
case ProcedureKind::kNonlinearStatic:
procedure = std::make_unique<NonlinearStaticProcedure>(parallel_for,
tangent_solver, log);
break;
case ProcedureKind::kLinearFrequency:
procedure = std::make_unique<LinearFrequencyProcedure>(eigen_solver);
break;
case ProcedureKind::kHhtDynamic:
procedure = std::make_unique<HhtDynamicProcedure>(
parallel_for, linear_solver, tangent_solver, log);
break;
case ProcedureKind::kSolidStatic:
procedure = std::make_unique<SolidStaticProcedure>(
parallel_for, linear_solver, tangent_solver, log);
break;
case ProcedureKind::kSolidFrequency:
procedure = std::make_unique<SolidFrequencyProcedure>(eigen_solver);
break;
case ProcedureKind::kSolidHhtDynamic:
procedure = std::make_unique<SolidHhtDynamicProcedure>(
parallel_for, linear_solver, tangent_solver, log);
break;
case ProcedureKind::kTransientHeat:
procedure =
std::make_unique<TransientHeatProcedure>(parallel_for, linear_solver);
break;
}
return Result<std::unique_ptr<AnalysisProcedure>>::Success(
std::move(procedure));
}
Result<PreparedAnalysisOutcome>
ExecutePreparedAnalysis(const Domain& domain, AnalysisProcedure& procedure) {
return procedure.Execute(domain);
}
Result<PreparedAnalysisOutcome> ExecutePreparedAnalysis(
const Domain& domain, LinearStaticProcedureExecutor& linear_executor,
NonlinearStaticProcedureExecutor& nonlinear_executor,
@@ -49,133 +229,59 @@ Result<PreparedAnalysisOutcome> ExecutePreparedAnalysis(
const SolidStaticProcedureExecutor* solid_static_executor,
const SolidFrequencyProcedureExecutor* solid_frequency_executor,
const SolidHhtDynamicProcedureExecutor* solid_dynamic_executor) {
if (domain.ThermalStep() != nullptr) {
if (thermal_executor == nullptr) {
return Result<PreparedAnalysisOutcome>::Failure(
Status::Failure(FailureCategory::kSolver,
{{Severity::kError,
"missing-thermal-executor",
{},
"HEAT TRANSFER",
{},
"Thermal execution adapter is required."}}));
}
auto thermal = thermal_executor->Execute(domain);
if (!thermal.HasValue()) {
return Result<PreparedAnalysisOutcome>::Failure(thermal.GetStatus());
}
return Result<PreparedAnalysisOutcome>::Success(
PreparedAnalysisOutcome{std::move(thermal.Value())});
}
if (domain.Steps().Size() != 1U) {
return Result<PreparedAnalysisOutcome>::Failure(Status::Failure(
FailureCategory::kInput,
{{Severity::kError,
"invalid-analysis-step-count",
{},
"*STEP",
{},
"Prepared analysis requires exactly one analysis step."}}));
}
if (domain.IsSolid()) {
const auto missing = []() {
return Result<PreparedAnalysisOutcome>::Failure(Status::Failure(
FailureCategory::kSolver,
{{Severity::kError,
"missing-solid-executor",
{},
"",
{},
"The selected Solid execution adapter is required."}}));
};
if (domain.Dynamic() != nullptr) {
if (solid_dynamic_executor == nullptr) return missing();
auto model = AnalysisModel::Create(domain);
if (!model.HasValue())
return Result<PreparedAnalysisOutcome>::Failure(model.GetStatus());
auto dofs = DofManager::Create(model.Value());
if (!dofs.HasValue())
return Result<PreparedAnalysisOutcome>::Failure(dofs.GetStatus());
auto state = SolidDynamicAnalysisState::Create(domain, dofs.Value());
if (!state.HasValue())
return Result<PreparedAnalysisOutcome>::Failure(state.GetStatus());
const Status execution =
solid_dynamic_executor->ExecuteInto(domain, state.Value());
for (const auto& record : state.Value().IterationRecords()) {
const Status written = log.Write(record);
if (!written.IsOk())
return Result<PreparedAnalysisOutcome>::Failure(written);
}
if (!execution.IsOk())
return Result<PreparedAnalysisOutcome>::Failure(execution);
return Result<PreparedAnalysisOutcome>::Success(
PreparedAnalysisOutcome{SolidDynamicExecutionResult{
std::move(dofs.Value()), std::move(state.Value())}});
}
if (domain.Steps()[0U].IsFrequency()) {
if (solid_frequency_executor == nullptr) return missing();
auto result = solid_frequency_executor->Execute(domain);
if (!result.HasValue())
return Result<PreparedAnalysisOutcome>::Failure(result.GetStatus());
return Result<PreparedAnalysisOutcome>::Success(
PreparedAnalysisOutcome{std::move(result.Value())});
}
if (solid_static_executor == nullptr) return missing();
SolidStaticState state;
const Status execution = solid_static_executor->ExecuteInto(domain, state);
for (const auto& record : state.IterationRecords()) {
const Status written = log.Write(record, domain.Steps()[0U].Name());
if (!written.IsOk())
return Result<PreparedAnalysisOutcome>::Failure(written);
}
if (!execution.IsOk())
return Result<PreparedAnalysisOutcome>::Failure(execution);
return Result<PreparedAnalysisOutcome>::Success(PreparedAnalysisOutcome{
SolidStaticExecutionResult{std::move(state), domain.Warnings()}});
}
if (domain.Dynamic() != nullptr) {
const auto kind = SelectProcedure(domain);
if (!kind.HasValue())
return Result<PreparedAnalysisOutcome>::Failure(kind.GetStatus());
std::unique_ptr<AnalysisProcedure> procedure;
switch (kind.Value()) {
case ProcedureKind::kLinearStatic:
procedure = std::make_unique<LinearStaticProcedure>(linear_executor);
break;
case ProcedureKind::kNonlinearStatic:
procedure =
std::make_unique<NonlinearStaticProcedure>(nonlinear_executor, log);
break;
case ProcedureKind::kLinearFrequency:
procedure = std::make_unique<LinearFrequencyProcedure>(frequency_executor);
break;
case ProcedureKind::kHhtDynamic:
if (dynamic_executor == nullptr)
return Result<PreparedAnalysisOutcome>::Failure(
Status::Failure(FailureCategory::kSolver,
{{Severity::kError,
"missing-dynamic-executor",
{},
"DYNAMIC",
{},
"Dynamic execution adapter is required."}}));
auto dynamic = dynamic_executor->Execute(domain, log);
if (!dynamic.HasValue())
return Result<PreparedAnalysisOutcome>::Failure(dynamic.GetStatus());
return Result<PreparedAnalysisOutcome>::Success(
PreparedAnalysisOutcome{std::move(dynamic.Value())});
return MissingExecutor("missing-dynamic-executor", "DYNAMIC",
"Dynamic execution adapter is required.");
procedure = std::make_unique<HhtDynamicProcedure>(*dynamic_executor, log);
break;
case ProcedureKind::kSolidStatic:
if (solid_static_executor == nullptr)
return MissingExecutor(
"missing-solid-executor", "",
"The selected Solid execution adapter is required.");
procedure =
std::make_unique<SolidStaticProcedure>(*solid_static_executor, log);
break;
case ProcedureKind::kSolidFrequency:
if (solid_frequency_executor == nullptr)
return MissingExecutor(
"missing-solid-executor", "",
"The selected Solid execution adapter is required.");
procedure =
std::make_unique<SolidFrequencyProcedure>(*solid_frequency_executor);
break;
case ProcedureKind::kSolidHhtDynamic:
if (solid_dynamic_executor == nullptr)
return MissingExecutor(
"missing-solid-executor", "",
"The selected Solid execution adapter is required.");
procedure = std::make_unique<SolidHhtDynamicProcedure>(
*solid_dynamic_executor, log);
break;
case ProcedureKind::kTransientHeat:
if (thermal_executor == nullptr)
return MissingExecutor("missing-thermal-executor", "HEAT TRANSFER",
"Thermal execution adapter is required.");
procedure = std::make_unique<TransientHeatProcedure>(*thermal_executor);
break;
}
if (domain.Steps()[0U].IsFrequency()) {
auto eigen = frequency_executor.Execute(domain);
if (!eigen.HasValue()) {
return Result<PreparedAnalysisOutcome>::Failure(eigen.GetStatus());
}
return Result<PreparedAnalysisOutcome>::Success(
PreparedAnalysisOutcome{std::move(eigen.Value())});
}
if (domain.Steps()[0U].IsNonlinearStatic()) {
Result<NonlinearStaticExecutionResult> nonlinear =
nonlinear_executor.Execute(domain, log);
if (!nonlinear.HasValue()) {
return Result<PreparedAnalysisOutcome>::Failure(nonlinear.GetStatus());
}
return Result<PreparedAnalysisOutcome>::Success(
PreparedAnalysisOutcome{std::move(nonlinear.Value())});
}
Result<LinearStaticExecutionResult> linear = linear_executor.Execute(domain);
if (!linear.HasValue()) {
return Result<PreparedAnalysisOutcome>::Failure(linear.GetStatus());
}
return Result<PreparedAnalysisOutcome>::Success(
PreparedAnalysisOutcome{std::move(linear.Value())});
return ExecutePreparedAnalysis(domain, *procedure);
}
Status FinalizePreparedAnalysisResult(
@@ -185,68 +291,10 @@ Status FinalizePreparedAnalysisResult(
NonlinearResultsWriter& nonlinear_writer, EigenResultsWriter& eigen_writer,
DynamicResultsWriter* dynamic_writer, ThermalResultsWriter* thermal_writer,
SolidResultsWriter* solid_writer) {
if (std::holds_alternative<SolidStaticExecutionResult>(outcome) ||
std::holds_alternative<SolidFrequencyState>(outcome) ||
std::holds_alternative<SolidDynamicExecutionResult>(outcome)) {
if (solid_writer == nullptr)
return Status::Failure(FailureCategory::kOutput,
{{Severity::kError,
"missing-solid-writer",
{},
"",
{},
"Solid result writer is required."}});
if (const auto* result = std::get_if<SolidStaticExecutionResult>(&outcome))
return solid_writer->Write(paths.results_path, domain, result->state,
diagnostics);
if (const auto* result = std::get_if<SolidFrequencyState>(&outcome))
return solid_writer->Write(paths.results_path, domain, *result,
diagnostics);
return solid_writer->Write(
paths.results_path, domain,
std::get<SolidDynamicExecutionResult>(outcome).state, diagnostics);
}
if (std::holds_alternative<ThermalExecutionResult>(outcome)) {
if (thermal_writer == nullptr) {
return Status::Failure(FailureCategory::kOutput,
{{Severity::kError,
"missing-thermal-writer",
{},
"HEAT TRANSFER",
{},
"Thermal result writer is required."}});
}
return thermal_writer->Write(
paths.results_path, domain,
std::get<ThermalExecutionResult>(outcome).state, diagnostics);
}
if (std::holds_alternative<DynamicExecutionResult>(outcome)) {
if (dynamic_writer == nullptr)
return Status::Failure(FailureCategory::kOutput,
{{Severity::kError,
"missing-dynamic-writer",
{},
"DYNAMIC",
{},
"Dynamic result writer is required."}});
return dynamic_writer->Write(
paths.results_path, domain,
std::get<DynamicExecutionResult>(outcome).state, diagnostics);
}
if (std::holds_alternative<EigenAnalysisState>(outcome)) {
return eigen_writer.Write(paths.results_path, domain,
std::get<EigenAnalysisState>(outcome),
diagnostics);
}
if (std::holds_alternative<LinearStaticExecutionResult>(outcome)) {
return linear_writer.Write(
paths.results_path, domain,
std::get<LinearStaticExecutionResult>(outcome).state, diagnostics);
}
return nonlinear_writer.Write(
paths.results_path, domain,
std::get<NonlinearStaticExecutionResult>(outcome).state, diagnostics);
return WritePreparedAnalysisResult(
paths.results_path, domain, outcome, diagnostics, linear_writer,
nonlinear_writer, eigen_writer, dynamic_writer, thermal_writer,
solid_writer);
}
} // namespace fesa
+25
View File
@@ -0,0 +1,25 @@
#ifndef FESA_ANALYSIS_SHELL_STATE_INVENTORY_H_
#define FESA_ANALYSIS_SHELL_STATE_INVENTORY_H_
#include <cstddef>
#include <vector>
#include "fesa/elements/element_definition.h"
#include "fesa/model/domain.h"
namespace fesa::analysis_internal {
/// @brief Collects unified MITC4 element indices in stable Domain order.
inline std::vector<EntityIndex> ShellElementOrder(const Domain& domain) {
std::vector<EntityIndex> order;
for (std::size_t index = 0U; index < domain.Elements().Size(); ++index) {
if (domain.Elements()[index].Kind() == ElementDefinitionKind::kMitc4Shell) {
order.push_back(static_cast<EntityIndex>(index));
}
}
return order;
}
} // namespace fesa::analysis_internal
#endif // FESA_ANALYSIS_SHELL_STATE_INVENTORY_H_
@@ -1,13 +1,15 @@
#include "fesa/analysis/solid_dynamic_analysis_state.h"
#include <algorithm>
#include <cmath>
#include <stdexcept>
#include <string>
#include "fesa/core/source_identity.h"
#include "fesa/elements/element_factory.h"
#include "fesa/elements/hex8_element.h"
#include "fesa/model/domain.h"
#include "math/high_low_arithmetic.h"
#include "math/finite_values.h"
namespace fesa {
namespace {
@@ -24,8 +26,7 @@ Status Failure(const std::string& message) {
/// @brief Checks a physical record without replacing nonfinite quantities.
template <std::size_t N>
bool Finite(const std::array<double, N>& values) {
return std::all_of(values.begin(), values.end(),
[](double x) { return std::isfinite(x); });
return math_internal::IsFinite(values);
}
bool ValidPair(double high, double low) {
const math_internal::HighLow value(high, low);
@@ -44,12 +45,6 @@ bool ValidPair(const std::array<double, N>& high,
if (!ValidPair(high[i], low[i])) return false;
return true;
}
/// @brief Preserves canonical and raw source identity independently of indices.
bool SameId(const SourceEntityId& a, const SourceEntityId& b) {
return a.instance_name == b.instance_name &&
a.source_label == b.source_label &&
a.source_label_text == b.source_label_text;
}
} // namespace
Result<SolidDynamicAnalysisState> SolidDynamicAnalysisState::Create(
@@ -83,7 +78,7 @@ Result<SolidDynamicAnalysisState> SolidDynamicAnalysisState::Create(
auto runtime = factory.Create(domain.Elements()[e], domain);
if (!runtime.HasValue())
return Result<SolidDynamicAnalysisState>::Failure(runtime.GetStatus());
const auto* solid = dynamic_cast<const Hex8Element*>(runtime.Value().get());
auto solid = factory.BindSolidTrial(*runtime.Value());
if (solid == nullptr)
return Result<SolidDynamicAnalysisState>::Failure(Failure(
"Dynamic Solid inventory contains a different element family."));
@@ -117,7 +112,7 @@ Status SolidDynamicAnalysisState::CommitAcceptedFrame(
}
for (std::size_t n = 0; n < source_ids_.size(); ++n)
if (f.nodal_internal_order[n] != n ||
!SameId(f.nodal_source_ids[n], source_ids_[n]))
!SourceIdentityEquals(f.nodal_source_ids[n], source_ids_[n]))
return Failure("Solid nodal source identity differs from its inventory.");
const std::array<const Vector*, 10> high{
&f.displacement, &f.velocity,
@@ -18,6 +18,7 @@
#include "fesa/elements/hex8_element.h"
#include "fesa/fem/dof_manager.h"
#include "fesa/math/sparse_matrix.h"
#include "fesa/solvers/eigen/mkl_feast_eigen_solver.h"
#include "fesa/math/vector.h"
#include "fesa/model/domain.h"
@@ -50,7 +51,8 @@ double Ratio(double numerator, double denominator) {
return numerator / denominator;
}
/// @brief Builds an immutable candidate; all returns precede public commit.
Result<SolidFrequencyState> ExecuteCandidate(const Domain& domain) {
Result<SolidFrequencyState> ExecuteCandidate(const Domain& domain,
const EigenSolver& solver) {
const auto* frequency = domain.Frequency();
if (!domain.IsSolid() || frequency == nullptr ||
frequency->requested_mode_count <= 0)
@@ -76,6 +78,7 @@ Result<SolidFrequencyState> ExecuteCandidate(const Domain& domain) {
}
ElementFactory factory;
std::vector<std::unique_ptr<Element>> owned;
std::vector<std::unique_ptr<SolidTrialElement>> trials;
ElementView view;
for (const auto index : model.Value().ActiveElements()) {
const auto& definition = domain.Elements()[index];
@@ -87,11 +90,13 @@ Result<SolidFrequencyState> ExecuteCandidate(const Domain& domain) {
auto element = factory.Create(definition, domain);
if (!element.HasValue())
return Result<SolidFrequencyState>::Failure(element.GetStatus());
if (dynamic_cast<const Hex8Element*>(element.Value().get()) == nullptr)
auto trial = factory.BindSolidTrial(*element.Value());
if (trial == nullptr)
return Result<SolidFrequencyState>::Failure(
Failure("unsupported-frequency-element", "Only C3D8 is supported."));
view.emplace_back(*element.Value());
owned.push_back(std::move(element.Value()));
trials.push_back(std::move(trial));
}
DofManager dofs;
const Status mapping = dofs.Build(model.Value(), view);
@@ -121,7 +126,7 @@ Result<SolidFrequencyState> ExecuteCandidate(const Domain& domain) {
std::vector<std::size_t> additions_by_dof(dofs.FullDofCount(), 0U);
constexpr double eps = std::numeric_limits<double>::epsilon();
for (std::size_t e = 0; e < owned.size(); ++e) {
const auto& hex = static_cast<const Hex8Element&>(*owned[e]);
const auto& hex = *trials[e];
auto mass = hex.ComputeMass();
if (!mass.HasValue())
return Result<SolidFrequencyState>::Failure(mass.GetStatus());
@@ -220,7 +225,7 @@ Result<SolidFrequencyState> ExecuteCandidate(const Domain& domain) {
return Result<SolidFrequencyState>::Failure(
Failure("invalid-solid-eigen-scale",
"A positive finite classification scale is required."));
auto backend = MklFeastEigenSolver{}.SolveGeneralized(k, m, requested,
auto backend = solver.SolveGeneralized(k, m, requested,
mass_lower, scale);
if (!backend.HasValue())
return Result<SolidFrequencyState>::Failure(backend.GetStatus());
@@ -269,11 +274,11 @@ Result<SolidFrequencyState> ExecuteCandidate(const Domain& domain) {
"Normalized modal vector is not finite."));
}
mv = m.Multiply(vector);
const Vector kv = k.Multiply(vector);
Vector kv = k.Multiply(vector);
const double lambda = state.backend.eigenvalues[c];
const double modal_mass = vector.Dot(mv);
const double energy = vector.Dot(kv);
Vector residual = kv;
Vector residual = std::move(kv);
residual.Axpy(-lambda, mv);
const double norm_phi = vector.Norm();
const double residual_norm = residual.Norm();
@@ -355,7 +360,8 @@ Result<SolidFrequencyState> ExecuteCandidate(const Domain& domain) {
Result<SolidFrequencyState> SolidFrequencyProcedureExecutor::Execute(
const Domain& domain) const {
try {
auto result = ExecuteCandidate(domain);
const MklFeastEigenSolver default_solver;
auto result = ExecuteCandidate(domain, solver_ ? *solver_ : default_solver);
if (result.HasValue()) return result;
auto diagnostics = result.GetStatus().Diagnostics();
for (auto& diagnostic : diagnostics)
+20
View File
@@ -0,0 +1,20 @@
#ifndef FESA_ANALYSIS_SOLID_HHT_CORE_H_
#define FESA_ANALYSIS_SOLID_HHT_CORE_H_
#include "fesa/core/status.h"
namespace fesa {
namespace detail { class SolidHhtIntegrator; }
class Domain;
class SolidDynamicAnalysisState;
class ParallelFor;
class LinearSolver;
class TangentSolver;
class EquilibriumAlgorithm;
/// @brief Runs the complete production core with borrowed iteration policy.
/// @note Adapters outlive this synchronous call; history remains state-owned.
Status ExecuteSolidHhtCore(
const Domain& domain, SolidDynamicAnalysisState& state, double alpha,
const ParallelFor& parallel_for, LinearSolver& mass_solver,
TangentSolver& tangent_solver, const detail::SolidHhtIntegrator& integrator,
EquilibriumAlgorithm& algorithm);
} // namespace fesa
#endif // FESA_ANALYSIS_SOLID_HHT_CORE_H_
+204 -164
View File
@@ -1,5 +1,9 @@
#include "fesa/analysis/solid_hht_dynamic_procedure.h"
#include "equilibrium_algorithm.h"
#include "analysis/solid_hht_core.h"
#include "solid_hht_integrator.h"
#include <algorithm>
#include <array>
#include <cmath>
@@ -85,7 +89,7 @@ Status CheckArithmeticEnvironment(const Domain& domain) {
/// @brief Owns immutable kernels and the stable full-space translation scatter.
struct RuntimeSolid {
std::unique_ptr<Element> owner;
const Hex8Element* solid;
std::unique_ptr<SolidTrialElement> solid;
std::vector<std::size_t> scatter;
};
/// @brief Creates checked kernels in declaration order, with no mutable element
@@ -98,7 +102,7 @@ Result<std::vector<RuntimeSolid>> BuildSolids(const Domain& domain,
auto owner = factory.Create(domain.Elements()[e], domain);
if (!owner.HasValue())
return Result<std::vector<RuntimeSolid>>::Failure(owner.GetStatus());
const auto* solid = dynamic_cast<const Hex8Element*>(owner.Value().get());
auto solid = factory.BindSolidTrial(*owner.Value());
if (solid == nullptr)
return Result<std::vector<RuntimeSolid>>::Failure(
Failure(domain, "solid-dynamic-element-family",
@@ -107,7 +111,7 @@ Result<std::vector<RuntimeSolid>> BuildSolids(const Domain& domain,
if (!scatter.HasValue())
return Result<std::vector<RuntimeSolid>>::Failure(scatter.GetStatus());
solids.push_back(
{std::move(owner.Value()), solid, std::move(scatter.Value())});
{std::move(owner.Value()), std::move(solid), std::move(scatter.Value())});
}
return Result<std::vector<RuntimeSolid>>::Success(std::move(solids));
}
@@ -239,7 +243,7 @@ Result<double> ReferenceLength(const Domain& domain) {
"Reference length is not finite positive."));
return Result<double>::Success(length);
}
/// @brief Forms M/(beta*h²)+(1+alpha)*Kt in the unchanged full sparse pattern.
/// @brief Forms M/(beta*h^2)+(1+alpha)*Kt in the unchanged full sparse pattern.
Result<SparseMatrix> EffectiveTangent(const SparseMatrix& m,
const PairedTangent& k,
const DofManager& dofs, double h,
@@ -549,24 +553,6 @@ bool CorrectionPass(const std::array<HighLow, 4>& metrics) {
Compare(x, HighLow(1e-10)) <= 0;
});
}
Status SetRates(const SolidDynamicAcceptedFrameState& old,
const detail::SolidHhtPairedPredictor& predictor, double h,
const HhtParameters& p,
SolidDynamicAcceptedFrameState* candidate) {
auto rates = detail::EvaluateSolidHhtHighLowRates(
candidate->displacement, candidate->low.displacement, predictor,
old.acceleration, old.low.acceleration, h, p);
if (!rates.HasValue()) return rates.GetStatus();
candidate->velocity = std::move(rates.Value().velocity);
candidate->low.velocity = std::move(rates.Value().velocity_low);
candidate->acceleration = std::move(rates.Value().acceleration);
candidate->low.acceleration = std::move(rates.Value().acceleration_low);
candidate->algorithmic_acceleration =
std::move(rates.Value().algorithmic_acceleration);
candidate->low.algorithmic_acceleration =
std::move(rates.Value().algorithmic_acceleration_low);
return Status::Ok();
}
} // namespace
SolidHhtDynamicProcedureExecutor::SolidHhtDynamicProcedureExecutor(
@@ -600,6 +586,17 @@ Status SolidHhtDynamicProcedureExecutor::ExecuteInto(
Status SolidHhtDynamicProcedureExecutor::ExecuteIntoForVerification(
const Domain& domain, SolidDynamicAnalysisState& state,
double alpha) const {
const detail::PairedSolidHhtIntegrator integrator;
NewtonStepAlgorithm algorithm;
return ExecuteSolidHhtCore(domain, state, alpha, parallel_for_, mass_solver_,
tangent_solver_, integrator, algorithm);
}
Status ExecuteSolidHhtCore(
const Domain& domain, SolidDynamicAnalysisState& state, double alpha,
const ParallelFor& parallel_for, LinearSolver& mass_solver,
TangentSolver& tangent_solver, const detail::SolidHhtIntegrator& integrator,
EquilibriumAlgorithm& algorithm) {
SolidDynamicIterationRecord record;
auto fail = [&](const Status& failure) {
record.stage = "failed";
@@ -630,7 +627,7 @@ Status SolidHhtDynamicProcedureExecutor::ExecuteIntoForVerification(
auto mass_partition = partition.Partition(mass.Value(), dofs);
if (!mass_partition.HasValue()) return fail(mass_partition.GetStatus());
if (dofs.FreeDofCount() != 0) {
const auto status = mass_solver_.Factorize(mass_partition.Value().k_ff);
const auto status = mass_solver.Factorize(mass_partition.Value().k_ff);
if (!status.IsOk()) return fail(status);
environment = CheckArithmeticEnvironment(domain);
if (!environment.IsOk()) return fail(environment);
@@ -655,7 +652,7 @@ Status SolidHhtDynamicProcedureExecutor::ExecuteIntoForVerification(
if (!force.HasValue()) return fail(force.GetStatus());
initial.external_force = std::move(force.Value());
auto tangent =
EvaluateSolids(domain, solids.Value(), dofs, parallel_for_, &initial);
EvaluateSolids(domain, solids.Value(), dofs, parallel_for, &initial);
if (!tangent.HasValue()) return fail(tangent.GetStatus());
SolidDynamicIterationEvidence evidence;
bool initial_converged = false;
@@ -682,7 +679,7 @@ Status SolidHhtDynamicProcedureExecutor::ExecuteIntoForVerification(
const auto i = dofs.FreeDofs()[j];
rhs[j] = Rounded(Negate(Pair(initial.residual, initial.low.residual, i)));
}
status = mass_solver_.Solve(rhs, a);
status = mass_solver.Solve(rhs, a);
if (!status.IsOk()) return fail(status);
environment = CheckArithmeticEnvironment(domain);
if (!environment.IsOk()) return fail(environment);
@@ -726,145 +723,188 @@ Status SolidHhtDynamicProcedureExecutor::ExecuteIntoForVerification(
record = {};
record.increment_index = increment;
record.time = static_cast<double>(increment) * h;
auto predictor = detail::PredictSolidHhtHighLow(old, h, p);
if (!predictor.HasValue()) return fail(predictor.GetStatus());
auto candidate = old;
candidate.frame_index = candidate.increment_index = increment;
candidate.iteration_count = 0;
candidate.time = record.time;
candidate.is_initial = false;
candidate.iterations.clear();
// Begin from certified accepted geometry, not an unchecked predictor.
force = LoadAssembler::AssembleFullNodalLoad(model.Value(), dofs,
candidate.time);
if (!force.HasValue()) return fail(force.GetStatus());
candidate.external_force = std::move(force.Value());
candidate.low.external_force = Vector(dofs.FullDofCount());
std::array<HighLow, 4> correction{}, fresh_correction{};
bool accepted = false;
for (std::uint32_t iteration = 0; iteration <= 100; ++iteration) {
record.stage = "evaluation";
record.outcome = "paired-candidate";
record.evidence = {};
record.evidence.iteration_index = iteration;
status = SetRates(old, predictor.Value(), h, p, &candidate);
if (!status.IsOk()) return fail(status);
tangent = EvaluateSolids(domain, solids.Value(), dofs, parallel_for_,
&candidate);
if (!tangent.HasValue()) return fail(tangent.GetStatus());
status = RecoverEndpoint(domain, dofs, mass.Value(), &old, initial_energy,
&candidate, &record.evidence);
if (!status.IsOk()) return fail(status);
auto MA = detail::MultiplySolidHhtMassHighLow(
mass.Value(), candidate.algorithmic_acceleration,
candidate.low.algorithmic_acceleration);
if (!MA.HasValue()) return fail(MA.GetStatus());
Vector rhs(dofs.FreeDofCount());
HighLow identity_metric, original_norm, solved_norm, old_norm;
for (std::size_t j = 0; j < dofs.FreeDofCount(); ++j) {
const auto i = dofs.FreeDofs()[j];
detail::SolidHhtPairedForceIdentity identity;
if (!detail::EvaluateSolidHhtHighLowForceIdentity(
mass.Value(), i, Pair(MA.Value().high, MA.Value().low, i),
candidate, old, p.alpha, &identity))
return fail(
Failure(domain, "solid-dynamic-nonfinite-residual",
"Paired weighted force arithmetic is unsupported."));
rhs[j] = Rounded(Negate(identity.solved));
identity_metric = Maximum(identity_metric, identity.metric);
original_norm = Maximum(original_norm, Abs(identity.original));
solved_norm = Maximum(solved_norm, Abs(identity.solved));
old_norm =
Maximum(old_norm, Abs(Pair(old.residual, old.low.residual, i)));
if (i == record.evidence.worst_full_dof)
record.evidence.worst_weighted_terms = {
Rounded(identity.original), Rounded(identity.solved),
Rounded(Pair(old.residual, old.low.residual, i)),
Rounded(identity.scale)};
}
if (!Valid(identity_metric) || !Valid(original_norm) ||
!Valid(solved_norm) || !Valid(old_norm) ||
Compare(identity_metric, HighLow(1e-12)) > 0)
return fail(Failure(domain,
"solid-dynamic-weighted-algebra-inconsistency",
"Paired original and solved forces disagree beyond "
"their uncancelled scale."));
record.evidence.weighted_equivalence_metric = Rounded(identity_metric);
record.evidence.weighted_residual_numerator = Rounded(original_norm);
record.evidence.recombined_residual_numerator = Rounded(solved_norm);
record.evidence.old_residual_numerator = Rounded(old_norm);
for (std::size_t c = 0; c < 4; ++c) {
if (!Valid(correction[c]) || !Valid(fresh_correction[c]))
return fail(detail::SolidHhtPairedKinematicsFailure());
record.evidence.correction_metrics[c] = Rounded(correction[c]);
}
record.evidence.correction_pass =
CorrectionPass(correction) && CorrectionPass(fresh_correction);
const auto update = CheckUpdates(old, candidate, p, h);
if (!Valid(update))
return fail(detail::SolidHhtPairedKinematicsFailure());
record.evidence.update_metric = Rounded(update);
record.evidence.update_pass = Compare(update, HighLow(1e-12)) <= 0;
candidate.iterations.push_back(record.evidence);
candidate.iteration_count = iteration;
if (record.evidence.residual_pass && record.evidence.correction_pass &&
record.evidence.update_pass) {
status = state.CommitAcceptedFrame(candidate);
if (!status.IsOk()) return fail(status);
record.stage = "accepted";
record.outcome = "increment";
state.RecordIteration(record);
accepted = true;
break;
}
state.RecordIteration(record);
if (iteration == 100)
return fail(Failure(
domain, "solid-dynamic-nonconvergence",
"Paired HHT endpoint did not converge in100 Newton solves."));
auto effective =
EffectiveTangent(mass.Value(), tangent.Value(), dofs, h, p);
if (!effective.HasValue()) return fail(effective.GetStatus());
auto reduced = partition.Partition(effective.Value(), dofs);
if (!reduced.HasValue()) return fail(reduced.GetStatus());
Vector du(dofs.FreeDofCount());
if (dofs.FreeDofCount() != 0) {
status = tangent_solver_.FactorizeFresh(reduced.Value().k_ff);
if (!status.IsOk()) return fail(status);
environment = CheckArithmeticEnvironment(domain);
if (!environment.IsOk()) return fail(environment);
status = tangent_solver_.SolveCorrection(rhs, du);
if (!status.IsOk()) return fail(status);
environment = CheckArithmeticEnvironment(domain);
if (!environment.IsOk()) return fail(environment);
}
if (du.Size() != dofs.FreeDofCount())
return fail(Failure(domain, "solid-dynamic-invalid-backend-result",
"Tangent solve returned incompatible correction."));
for (std::size_t j = 0; j < du.Size(); ++j)
if (!std::isfinite(du[j]))
return fail(Failure(domain, "solid-dynamic-invalid-backend-result",
"Tangent solve returned nonfinite correction."));
fresh_correction = FreshCorrectionMetrics(du, length.Value(), p);
const auto before = candidate;
for (std::size_t j = 0; j < du.Size(); ++j) {
const auto i = dofs.FreeDofs()[j];
const auto value =
Add(Pair(candidate.displacement, candidate.low.displacement, i),
HighLow(du[j] == 0 ? 0.0 : du[j]));
if (!Valid(value))
return fail(detail::SolidHhtPairedKinematicsFailure());
Store(value, &candidate.displacement, &candidate.low.displacement, i);
}
status = SetRates(old, predictor.Value(), h, p, &candidate);
if (!status.IsOk()) return fail(status);
correction =
ObservedCorrectionMetrics(before, candidate, h, length.Value());
}
if (!accepted)
return fail(Failure(domain, "solid-dynamic-nonconvergence",
"No complete paired endpoint was accepted."));
status = detail::RunSolidHhtStep(
integrator, old, h, p, fail,
[&](const detail::SolidHhtIntegrator& step_integrator,
const detail::SolidHhtPairedPredictor& predictor)
-> Result<SolidDynamicAcceptedFrameState> {
auto candidate = old;
candidate.frame_index = candidate.increment_index = increment;
candidate.iteration_count = 0;
candidate.time = record.time;
candidate.is_initial = false;
candidate.iterations.clear();
// Begin from certified accepted geometry, not an unchecked predictor.
force = LoadAssembler::AssembleFullNodalLoad(model.Value(), dofs,
candidate.time);
if (!force.HasValue())
return Result<SolidDynamicAcceptedFrameState>::Failure(fail(force.GetStatus()));
candidate.external_force = std::move(force.Value());
candidate.low.external_force = Vector(dofs.FullDofCount());
std::array<HighLow, 4> correction{}, fresh_correction{};
using StepResult = Result<std::optional<SolidDynamicAcceptedFrameState>>;
const auto report_failure = fail;
const auto record_iteration = [&](const auto& value) {
state.RecordIteration(value);
};
SolidHhtStep<SolidDynamicAcceptedFrameState> iteration_step(
[&](std::uint32_t iteration) -> StepResult {
const auto fail = [&](const Status& failure) {
return StepResult::Failure(report_failure(failure));
};
record.stage = "evaluation";
record.outcome = "paired-candidate";
record.evidence = {};
record.evidence.iteration_index = iteration;
status =
detail::SetSolidHhtRates(step_integrator, old, predictor, h, p, &candidate);
if (!status.IsOk())
return fail(status);
tangent = EvaluateSolids(domain, solids.Value(), dofs, parallel_for,
&candidate);
if (!tangent.HasValue())
return fail(tangent.GetStatus());
status =
RecoverEndpoint(domain, dofs, mass.Value(), &old, initial_energy,
&candidate, &record.evidence);
if (!status.IsOk())
return fail(status);
auto MA = detail::MultiplySolidHhtMassHighLow(
mass.Value(), candidate.algorithmic_acceleration,
candidate.low.algorithmic_acceleration);
if (!MA.HasValue())
return fail(MA.GetStatus());
Vector rhs(dofs.FreeDofCount());
HighLow identity_metric, original_norm, solved_norm, old_norm;
for (std::size_t j = 0; j < dofs.FreeDofCount(); ++j) {
const auto i = dofs.FreeDofs()[j];
detail::SolidHhtPairedForceIdentity identity;
if (!detail::EvaluateSolidHhtHighLowForceIdentity(
mass.Value(), i, Pair(MA.Value().high, MA.Value().low, i),
candidate, old, p.alpha, &identity))
return fail(
Failure(domain, "solid-dynamic-nonfinite-residual",
"Paired weighted force arithmetic is unsupported."));
rhs[j] = Rounded(Negate(identity.solved));
identity_metric = Maximum(identity_metric, identity.metric);
original_norm = Maximum(original_norm, Abs(identity.original));
solved_norm = Maximum(solved_norm, Abs(identity.solved));
old_norm =
Maximum(old_norm, Abs(Pair(old.residual, old.low.residual, i)));
if (i == record.evidence.worst_full_dof)
record.evidence.worst_weighted_terms = {
Rounded(identity.original), Rounded(identity.solved),
Rounded(Pair(old.residual, old.low.residual, i)),
Rounded(identity.scale)};
}
if (!Valid(identity_metric) || !Valid(original_norm) ||
!Valid(solved_norm) || !Valid(old_norm) ||
Compare(identity_metric, HighLow(1e-12)) > 0)
return fail(
Failure(domain, "solid-dynamic-weighted-algebra-inconsistency",
"Paired original and solved forces disagree beyond "
"their uncancelled scale."));
record.evidence.weighted_equivalence_metric =
Rounded(identity_metric);
record.evidence.weighted_residual_numerator = Rounded(original_norm);
record.evidence.recombined_residual_numerator = Rounded(solved_norm);
record.evidence.old_residual_numerator = Rounded(old_norm);
for (std::size_t c = 0; c < 4; ++c) {
if (!Valid(correction[c]) || !Valid(fresh_correction[c]))
return fail(detail::SolidHhtPairedKinematicsFailure());
record.evidence.correction_metrics[c] = Rounded(correction[c]);
}
record.evidence.correction_pass =
CorrectionPass(correction) && CorrectionPass(fresh_correction);
const auto update = CheckUpdates(old, candidate, p, h);
if (!Valid(update))
return fail(detail::SolidHhtPairedKinematicsFailure());
record.evidence.update_metric = Rounded(update);
record.evidence.update_pass = Compare(update, HighLow(1e-12)) <= 0;
candidate.iterations.push_back(record.evidence);
candidate.iteration_count = iteration;
if (record.evidence.residual_pass &&
record.evidence.correction_pass && record.evidence.update_pass) {
return StepResult::Success(candidate);
}
record_iteration(record);
if (iteration == 100)
return fail(Failure(
domain, "solid-dynamic-nonconvergence",
"Paired HHT endpoint did not converge in100 Newton solves."));
auto effective =
EffectiveTangent(mass.Value(), tangent.Value(), dofs, h, p);
if (!effective.HasValue())
return fail(effective.GetStatus());
auto reduced = partition.Partition(effective.Value(), dofs);
if (!reduced.HasValue())
return fail(reduced.GetStatus());
Vector du(dofs.FreeDofCount());
if (dofs.FreeDofCount() != 0) {
status = tangent_solver.FactorizeFresh(reduced.Value().k_ff);
if (!status.IsOk())
return fail(status);
environment = CheckArithmeticEnvironment(domain);
if (!environment.IsOk())
return fail(environment);
status = tangent_solver.SolveCorrection(rhs, du);
if (!status.IsOk())
return fail(status);
environment = CheckArithmeticEnvironment(domain);
if (!environment.IsOk())
return fail(environment);
}
if (du.Size() != dofs.FreeDofCount())
return fail(
Failure(domain, "solid-dynamic-invalid-backend-result",
"Tangent solve returned incompatible correction."));
for (std::size_t j = 0; j < du.Size(); ++j)
if (!std::isfinite(du[j]))
return fail(
Failure(domain, "solid-dynamic-invalid-backend-result",
"Tangent solve returned nonfinite correction."));
fresh_correction = FreshCorrectionMetrics(du, length.Value(), p);
const auto before = candidate;
for (std::size_t j = 0; j < du.Size(); ++j) {
const auto i = dofs.FreeDofs()[j];
const auto value =
Add(Pair(candidate.displacement, candidate.low.displacement, i),
HighLow(du[j] == 0 ? 0.0 : du[j]));
if (!Valid(value))
return fail(detail::SolidHhtPairedKinematicsFailure());
Store(value, &candidate.displacement, &candidate.low.displacement,
i);
}
status =
detail::SetSolidHhtRates(step_integrator, old, predictor, h, p, &candidate);
if (!status.IsOk())
return fail(status);
correction =
ObservedCorrectionMetrics(before, candidate, h, length.Value());
return StepResult::Success(std::nullopt);
},
100U);
const Status solved = iteration_step.Solve(algorithm);
if (!solved.IsOk())
return Result<SolidDynamicAcceptedFrameState>::Failure(solved);
if (!iteration_step.HasCandidate())
return Result<SolidDynamicAcceptedFrameState>::Failure(
fail(Failure(domain, "solid-dynamic-nonconvergence",
"No complete paired endpoint was accepted.")));
return Result<SolidDynamicAcceptedFrameState>::Success(
iteration_step.TakeCandidate());
},
[&](SolidDynamicAcceptedFrameState&& accepted_candidate) -> Status {
status = state.CommitAcceptedFrame(accepted_candidate);
if (!status.IsOk())
return fail(status);
record.stage = "accepted";
record.outcome = "increment";
state.RecordIteration(record);
return Status::Ok();
});
if (!status.IsOk()) return status;
}
return Status::Ok();
}
} // namespace fesa
} // namespace fesa
+75
View File
@@ -0,0 +1,75 @@
#ifndef FESA_ANALYSIS_SOLID_HHT_INTEGRATOR_H_
#define FESA_ANALYSIS_SOLID_HHT_INTEGRATOR_H_
#include <utility>
#include "analysis/solid_hht_high_low.h"
namespace fesa::detail {
/// @brief Predicts and evaluates complete paired mechanical HHT kinematics.
class SolidHhtIntegrator {
public:
virtual ~SolidHhtIntegrator() = default;
virtual Result<SolidHhtPairedPredictor> Predict(
const SolidDynamicAcceptedFrameState& old, double h,
const HhtParameters& parameters) const = 0;
virtual Result<SolidHhtPairedRates> EvaluateRates(
const Vector& high, const Vector& low,
const SolidHhtPairedPredictor& predictor, const Vector& old_acceleration,
const Vector& old_acceleration_low, double h,
const HhtParameters& parameters) const = 0;
};
/// @brief Retains every high/low physical and auxiliary acceleration channel.
class PairedSolidHhtIntegrator final : public SolidHhtIntegrator {
public:
Result<SolidHhtPairedPredictor> Predict(
const SolidDynamicAcceptedFrameState& old, double h,
const HhtParameters& parameters) const override {
return PredictSolidHhtHighLow(old, h, parameters);
}
Result<SolidHhtPairedRates> EvaluateRates(
const Vector& high, const Vector& low,
const SolidHhtPairedPredictor& predictor, const Vector& old_acceleration,
const Vector& old_acceleration_low, double h,
const HhtParameters& parameters) const override {
return EvaluateSolidHhtHighLowRates(high, low, predictor, old_acceleration,
old_acceleration_low, h, parameters);
}
};
inline Status SetSolidHhtRates(const SolidHhtIntegrator& integrator,
const SolidDynamicAcceptedFrameState& old,
const SolidHhtPairedPredictor& predictor, double h,
const HhtParameters& p,
SolidDynamicAcceptedFrameState* candidate) {
auto rates = integrator.EvaluateRates(
candidate->displacement, candidate->low.displacement, predictor,
old.acceleration, old.low.acceleration, h, p);
if (!rates.HasValue()) return rates.GetStatus();
candidate->velocity = std::move(rates.Value().velocity);
candidate->low.velocity = std::move(rates.Value().velocity_low);
candidate->acceleration = std::move(rates.Value().acceleration);
candidate->low.acceleration = std::move(rates.Value().acceleration_low);
candidate->algorithmic_acceleration =
std::move(rates.Value().algorithmic_acceleration);
candidate->low.algorithmic_acceleration =
std::move(rates.Value().algorithmic_acceleration_low);
return Status::Ok();
}
/// @brief Preserves predictor failure recording, endpoint evaluation and commit.
/// @note Borrowed callbacks do not escape; only commit may update history.
template <typename Failure, typename Evaluate, typename Commit>
Status RunSolidHhtStep(const SolidHhtIntegrator& integrator,
const SolidDynamicAcceptedFrameState& old, double h,
const HhtParameters& parameters, Failure&& failure,
Evaluate&& evaluate, Commit&& commit) {
auto predictor = integrator.Predict(old, h, parameters);
if (!predictor.HasValue()) return failure(predictor.GetStatus());
auto pending = evaluate(integrator, predictor.Value());
if (!pending.HasValue()) return pending.GetStatus();
return commit(std::move(pending.Value()));
}
} // namespace fesa::detail
#endif // FESA_ANALYSIS_SOLID_HHT_INTEGRATOR_H_
+27
View File
@@ -0,0 +1,27 @@
#ifndef FESA_ANALYSIS_SOLID_STATIC_CORE_H_
#define FESA_ANALYSIS_SOLID_STATIC_CORE_H_
#include "fesa/core/status.h"
namespace fesa {
class Domain;
class SolidStaticState;
class ParallelFor;
class LinearSolver;
class TangentSolver;
class EquilibriumAlgorithm;
/// @brief Borrows the production static core's execution/solve/iteration policy.
/// @note Every adapter must outlive ExecuteInto; state owns accepted history.
class SolidStaticCore {
public:
SolidStaticCore(const ParallelFor& parallel_for, LinearSolver& linear_solver,
TangentSolver& tangent_solver, EquilibriumAlgorithm& algorithm)
: parallel_for_(parallel_for), linear_solver_(linear_solver),
tangent_solver_(tangent_solver), algorithm_(algorithm) {}
Status ExecuteInto(const Domain& domain, SolidStaticState& state) const;
private:
const ParallelFor& parallel_for_;
LinearSolver& linear_solver_;
TangentSolver& tangent_solver_;
EquilibriumAlgorithm& algorithm_;
};
} // namespace fesa
#endif // FESA_ANALYSIS_SOLID_STATIC_CORE_H_
+138 -90
View File
@@ -1,5 +1,8 @@
#include "fesa/analysis/solid_static_procedure.h"
#include "equilibrium_algorithm.h"
#include "analysis/solid_static_core.h"
#include <algorithm>
#include <array>
#include <cmath>
@@ -21,6 +24,8 @@
#include "fesa/solvers/linear/linear_solver.h"
#include "fesa/solvers/linear/tangent_solver.h"
#include "assembly/ordered_contribution_internal.h"
namespace fesa {
namespace {
constexpr std::uint32_t kMaximumIterations = 100U;
@@ -28,7 +33,7 @@ constexpr double kRoundoffBudget =
512.0 * std::numeric_limits<double>::epsilon();
struct RuntimeSolid {
const Hex8Element* element;
std::unique_ptr<SolidTrialElement> element;
std::vector<std::size_t> scatter;
};
struct SolidAssembly {
@@ -131,9 +136,8 @@ Result<SolidAssembly> Assemble(const std::vector<RuntimeSolid>& solids,
const auto& response = responses[e]->Value();
for (std::size_t i = 0U; i < 24U; ++i) {
force[solids[e].scatter[i]] += response.internal_force[i];
for (std::size_t j = 0U; j < 24U; ++j)
contributions.push_back({solids[e].scatter[i], solids[e].scatter[j],
response.tangent(i, j), e, 24U * i + j});
assembly_internal::AppendLocalMatrixRow(
response.tangent, solids[e].scatter, i, e, contributions);
}
energy += response.strain_energy;
points.insert(points.end(), response.points.begin(), response.points.end());
@@ -283,6 +287,13 @@ SolidStaticProcedureExecutor::SolidStaticProcedureExecutor(
Status SolidStaticProcedureExecutor::ExecuteInto(
const Domain& domain, SolidStaticState& state) const {
NewtonStepAlgorithm algorithm;
return SolidStaticCore(parallel_for_, linear_solver_, tangent_solver_, algorithm)
.ExecuteInto(domain, state);
}
Status SolidStaticCore::ExecuteInto(
const Domain& domain, SolidStaticState& state) const {
if (state.initialized_)
return Failure(domain, "solid-static-observer-not-fresh",
"Execution requires a fresh static observer.");
@@ -309,14 +320,14 @@ Status SolidStaticProcedureExecutor::ExecuteInto(
state = std::move(baseline.Value());
std::vector<RuntimeSolid> solids;
for (const auto& element : elements) {
const auto* hex = dynamic_cast<const Hex8Element*>(element.get());
auto hex = ElementFactory{}.BindSolidTrial(*element);
if (hex == nullptr)
return Failure(domain, "invalid-solid-static-element",
"Solid STATIC requires full-integration Hex8 kernels.",
FailureCategory::kModel);
auto scatter = dofs.ElementScatter(hex->DofLayout());
if (!scatter.HasValue()) return scatter.GetStatus();
solids.push_back({hex, std::move(scatter.Value())});
solids.push_back({std::move(hex), std::move(scatter.Value())});
}
auto stiffness = SparseAssembler::Assemble(view, dofs, parallel_for_);
if (!stiffness.HasValue()) return stiffness.GetStatus();
@@ -336,7 +347,7 @@ Status SolidStaticProcedureExecutor::ExecuteInto(
// Preserve the required factorization-before-load-assembly lifecycle.
auto load = LoadAssembler::AssembleFullNodalLoad(model.Value(), dofs);
if (!load.HasValue()) return load.GetStatus();
auto rhs = LoadAssembler::EffectiveFreeRhs(
auto rhs = constraints.EffectiveFreeRhs(
load.Value(), partition.Value().k_fc, dofs.PrescribedValues(), dofs);
if (!rhs.HasValue()) return rhs.GetStatus();
Vector free{dofs.FreeDofCount()};
@@ -369,91 +380,128 @@ Status SolidStaticProcedureExecutor::ExecuteInto(
for (std::size_t c = 0U; c < dofs.ConstrainedDofCount(); ++c)
trial[dofs.ConstrainedDofs()[c]] =
increment.load_factor * dofs.PrescribedValues()[c];
for (std::uint32_t iteration = 1U; iteration <= kMaximumIterations;
++iteration) {
struct SolidStepCandidate {
SolidStaticFrame frame;
SolidStaticIterationRecord record;
record.increment_index = increment.increment_index;
record.load_factor = increment.load_factor;
record.iteration = iteration;
record.stage = "entered";
const auto fail = [&](const Status& failure) {
record.diagnostic_code = failure.Diagnostics().empty()
? "solid-static-failure"
: failure.Diagnostics()[0].code;
record.failure_reason = failure.Diagnostics().empty()
? "Static trial failed."
: failure.Diagnostics()[0].message;
state.iterations_.push_back(record);
return failure;
};
record.stage = "trial-recovery";
auto assembly = Assemble(solids, dofs, trial, true, parallel_for_);
if (!assembly.HasValue()) return fail(assembly.GetStatus());
SolidStaticFrame frame = state.committed_;
frame.balance = {};
frame.global_equilibrium = {};
frame.frame_index =
static_cast<std::uint32_t>(state.accepted_frames_.size());
frame.increment_index = increment.increment_index;
frame.load_factor = increment.load_factor;
frame.iteration_count = iteration;
frame.is_factor_one = increment.is_factor_one;
frame.displacement = trial;
frame.internal_force = std::move(assembly.Value().force);
frame.external_force = base_load.Value();
frame.external_force.Scale(increment.load_factor);
frame.solid_results = std::move(assembly.Value().points);
frame.physical_strain_energy = assembly.Value().energy;
auto status = CompleteFrame(domain, dofs, true, stiffness.Value(), frame);
if (!status.IsOk()) return fail(status);
record.stage = "tangent-partition";
auto partition = constraints.Partition(assembly.Value().tangent, dofs);
if (!partition.HasValue()) return fail(partition.GetStatus());
Vector correction{dofs.FreeDofCount()};
// Empty free is vacuous; prescribed geometry/recovery above still runs.
if (dofs.FreeDofCount() != 0U) {
record.stage = "tangent-factorization";
status = tangent_solver_.FactorizeFresh(partition.Value().k_ff);
if (!status.IsOk()) return fail(status);
auto rhs = constraints.GatherFree(frame.residual, dofs);
rhs.Scale(-1.0);
record.stage = "correction-solve";
status = tangent_solver_.SolveCorrection(rhs, correction);
if (!status.IsOk()) return fail(status);
}
Vector current_increment{dofs.FreeDofCount()};
for (std::size_t f = 0U; f < dofs.FreeDofCount(); ++f) {
const auto full = dofs.FreeDofs()[f];
current_increment[f] = trial[full] - start[full];
}
record.stage = "convergence";
auto convergence = EvaluateSolidStaticConvergence(
constraints.GatherFree(frame.residual, dofs),
constraints.GatherFree(frame.internal_force, dofs),
constraints.GatherFree(frame.external_force, dofs), correction,
current_increment);
if (!convergence.HasValue()) return fail(convergence.GetStatus());
record.force_residual = convergence.Value().force_residual;
record.translation_correction =
convergence.Value().translation_correction;
record.convergence_evaluated = true;
if (record.force_residual.passed &&
record.translation_correction.passed) {
record.stage = "commit";
status = state.CommitAcceptedFrame(frame);
if (!status.IsOk()) return fail(status);
record.accepted = true;
state.iterations_.push_back(record);
break;
}
if (iteration == kMaximumIterations)
return fail(Failure(domain, "solid-static-nonconvergence",
"Solid STATIC exceeded 100 Newton iterations."));
for (std::size_t f = 0U; f < dofs.FreeDofCount(); ++f)
trial[dofs.FreeDofs()[f]] += correction[f];
record.stage = "continue";
state.iterations_.push_back(record);
};
const auto observe_iteration =
[&](const SolidStaticIterationRecord& record) {
state.iterations_.push_back(record);
};
SolidFullNewtonStep<SolidStepCandidate> iteration_step(
[&](std::uint32_t iteration)
-> Result<std::optional<SolidStepCandidate>> {
SolidStaticIterationRecord record;
record.increment_index = increment.increment_index;
record.load_factor = increment.load_factor;
record.iteration = iteration;
record.stage = "entered";
const auto fail = [&](const Status& failure) {
record.diagnostic_code = failure.Diagnostics().empty()
? "solid-static-failure"
: failure.Diagnostics()[0].code;
record.failure_reason = failure.Diagnostics().empty()
? "Static trial failed."
: failure.Diagnostics()[0].message;
observe_iteration(record);
return Result<std::optional<SolidStepCandidate>>::Failure(failure);
};
record.stage = "trial-recovery";
auto assembly = Assemble(solids, dofs, trial, true, parallel_for_);
if (!assembly.HasValue())
return fail(assembly.GetStatus());
SolidStaticFrame frame = state.committed_;
frame.balance = {};
frame.global_equilibrium = {};
frame.frame_index =
static_cast<std::uint32_t>(state.accepted_frames_.size());
frame.increment_index = increment.increment_index;
frame.load_factor = increment.load_factor;
frame.iteration_count = iteration;
frame.is_factor_one = increment.is_factor_one;
frame.displacement = trial;
frame.internal_force = std::move(assembly.Value().force);
frame.external_force = base_load.Value();
frame.external_force.Scale(increment.load_factor);
frame.solid_results = std::move(assembly.Value().points);
frame.physical_strain_energy = assembly.Value().energy;
auto status =
CompleteFrame(domain, dofs, true, stiffness.Value(), frame);
if (!status.IsOk())
return fail(status);
record.stage = "tangent-partition";
auto partition =
constraints.Partition(assembly.Value().tangent, dofs);
if (!partition.HasValue())
return fail(partition.GetStatus());
Vector correction{dofs.FreeDofCount()};
// Empty free is vacuous; prescribed geometry/recovery above still
// runs.
if (dofs.FreeDofCount() != 0U) {
record.stage = "tangent-factorization";
status = tangent_solver_.FactorizeFresh(partition.Value().k_ff);
if (!status.IsOk())
return fail(status);
auto rhs = constraints.GatherFree(frame.residual, dofs);
rhs.Scale(-1.0);
record.stage = "correction-solve";
status = tangent_solver_.SolveCorrection(rhs, correction);
if (!status.IsOk())
return fail(status);
}
Vector current_increment{dofs.FreeDofCount()};
for (std::size_t f = 0U; f < dofs.FreeDofCount(); ++f) {
const auto full = dofs.FreeDofs()[f];
current_increment[f] = trial[full] - start[full];
}
record.stage = "convergence";
auto convergence = EvaluateSolidStaticConvergence(
constraints.GatherFree(frame.residual, dofs),
constraints.GatherFree(frame.internal_force, dofs),
constraints.GatherFree(frame.external_force, dofs), correction,
current_increment);
if (!convergence.HasValue())
return fail(convergence.GetStatus());
record.force_residual = convergence.Value().force_residual;
record.translation_correction =
convergence.Value().translation_correction;
record.convergence_evaluated = true;
if (record.force_residual.passed &&
record.translation_correction.passed) {
record.stage = "commit";
return Result<std::optional<SolidStepCandidate>>::Success(
SolidStepCandidate{std::move(frame), record});
}
if (iteration == kMaximumIterations)
return fail(
Failure(domain, "solid-static-nonconvergence",
"Solid STATIC exceeded 100 Newton iterations."));
for (std::size_t f = 0U; f < dofs.FreeDofCount(); ++f)
trial[dofs.FreeDofs()[f]] += correction[f];
record.stage = "continue";
observe_iteration(record);
return Result<std::optional<SolidStepCandidate>>::Success(
std::nullopt);
},
kMaximumIterations);
const Status solved = iteration_step.Solve(algorithm_);
if (!solved.IsOk())
return solved;
auto candidate = iteration_step.TakeCandidate();
auto& record = candidate.record;
const Status committed = state.CommitAcceptedFrame(candidate.frame);
if (!committed.IsOk()) {
record.diagnostic_code = committed.Diagnostics().empty()
? "solid-static-failure"
: committed.Diagnostics()[0].code;
record.failure_reason = committed.Diagnostics().empty()
? "Static trial failed."
: committed.Diagnostics()[0].message;
observe_iteration(record);
return committed;
}
record.accepted = true;
observe_iteration(record);
}
return Status::Ok();
}
+4 -5
View File
@@ -37,10 +37,9 @@ double InfinityNorm(const Vector& values) {
return norm;
}
/// @brief Compares immutable semantic and raw source identities.
bool SameIdentity(const SourceEntityId& a, const SourceEntityId& b) {
return a.instance_name == b.instance_name &&
a.source_label == b.source_label &&
a.source_label_text == b.source_label_text;
bool SameIdentity(const SourceEntityId& a,
const SourceEntityId& b) {
return fesa::SourceIdentityEquals(a, b);
}
/// @brief Builds one raw criterion with the approved branch and physical unit.
NewtonChannelDecision Decision(const char* name, double numerator,
@@ -119,7 +118,7 @@ Result<SolidStaticState> SolidStaticState::Create(const Domain& domain,
auto element = ElementFactory{}.Create(domain.Elements()[e], domain);
if (!element.HasValue())
return Result<SolidStaticState>::Failure(element.GetStatus());
const auto* hex = dynamic_cast<const Hex8Element*>(element.Value().get());
auto hex = ElementFactory{}.BindSolidTrial(*element.Value());
if (hex == nullptr)
return Result<SolidStaticState>::Failure(StateFailure(
"invalid-solid-static-state", "Static state requires Hex8 kernels."));
+6 -5
View File
@@ -8,6 +8,9 @@
#include <utility>
#include <vector>
#include "fesa/assembly/thermal_assembler.h"
#include "math/finite_values.h"
namespace fesa {
namespace {
@@ -23,9 +26,7 @@ Status Failure(const std::string& message) {
/// @brief Checks every stored scalar without imposing a temperature sign bound.
bool Finite(const Vector& vector) {
for (std::size_t i = 0; i < vector.Size(); ++i)
if (!std::isfinite(vector[i])) return false;
return true;
return math_internal::IsFinite(vector);
}
/// @brief Compares prescribed row identities without conflating node and row
@@ -64,8 +65,8 @@ Result<ThermalAnalysisState> ThermalAnalysisState::Create(
for (const auto node : state.node_order_)
state.node_source_ids_.push_back(domain.Nodes()[node].source_id);
for (std::size_t index = 0; index < state.element_order_.size(); ++index) {
state.element_source_ids_.push_back(assembly.Kernels()[index].SourceId());
const auto frame = assembly.Kernels()[index].Recover({});
state.element_source_ids_.push_back(assembly.Elements()[index]->DofLayout().source_id);
const auto frame = assembly.Elements()[index]->Recover({});
if (!frame.HasValue())
return Result<ThermalAnalysisState>::Failure(frame.GetStatus());
state.local_frames_.push_back(frame.Value().frames);
+28 -16
View File
@@ -7,8 +7,10 @@
#include <string>
#include <utility>
#include "fesa/assembly/thermal_assembler.h"
#include "thermal_transient_bounds.h"
#include "math/dense_blas_internal.h"
#include "math/finite_values.h"
namespace fesa::thermal_transient_internal {
namespace {
@@ -35,9 +37,7 @@ Status Failure(const std::string& message) {
}
/// @brief Checks every scalar while preserving signed temperatures and powers.
bool Finite(const Vector& vector) {
for (std::size_t i = 0; i < vector.Size(); ++i)
if (!std::isfinite(vector[i])) return false;
return true;
return math_internal::IsFinite(vector);
}
/// @brief Checks aligned full operators and their coefficient-error storage.
bool SamePattern(const SparseMatrix& a, const SparseMatrix& b) {
@@ -500,6 +500,16 @@ Status RunThermalHistory(const ThermalAssembly& assembly,
const DofManager& dofs, ThermalAnalysisState& state,
LinearSolver& solver, const ThermalRecovery& recovery,
std::vector<ThermalStepEvidence>* evidence) {
const BackwardEulerHeatIntegrator integrator;
return RunThermalHistory(assembly, dofs, state, solver, integrator, recovery,
evidence);
}
Status RunThermalHistory(const ThermalAssembly& assembly,
const DofManager& dofs, ThermalAnalysisState& state,
LinearSolver& solver, const HeatStepIntegrator& integrator,
const ThermalRecovery& recovery,
std::vector<ThermalStepEvidence>* evidence) {
if (!dofs.ValidateInvariants().IsOk() ||
dofs.Profile() != DofProfile::kThermal ||
dofs.FullDofCount() != state.CommittedState().temperature.Size() ||
@@ -517,19 +527,21 @@ Status RunThermalHistory(const ThermalAssembly& assembly,
i <= count; ++i) {
const auto frame = state.TimeGrid().Frame(static_cast<std::uint32_t>(i));
if (!frame.HasValue()) return frame.GetStatus();
auto candidate = SolveThermalIncrement(system, partition,
state.CommittedState().temperature,
frame.Value().delta, solver, cache);
if (!candidate.HasValue()) return candidate.GetStatus();
auto flux = recovery(candidate.Value().temperature);
if (!flux.HasValue()) return flux.GetStatus();
auto completed = state.MakeCompletedFrame(
static_cast<std::uint32_t>(i), std::move(candidate.Value().temperature),
std::move(candidate.Value().reaction), std::move(flux.Value()));
if (!completed.HasValue()) return completed.GetStatus();
const auto committed = state.CommitAcceptedFrame(completed.Value());
if (!committed.IsOk()) return committed;
if (evidence) evidence->push_back(candidate.Value().evidence);
const Status step_status = RunThermalStep(
integrator, system, partition, state.CommittedState().temperature,
frame.Value().delta, solver, cache, recovery,
[&](ThermalStepCandidate&& candidate,
std::vector<ThermalElementFlux>&& flux) -> Status {
auto completed = state.MakeCompletedFrame(
static_cast<std::uint32_t>(i), std::move(candidate.temperature),
std::move(candidate.reaction), std::move(flux));
if (!completed.HasValue()) return completed.GetStatus();
const auto committed = state.CommitAcceptedFrame(completed.Value());
if (!committed.IsOk()) return committed;
if (evidence) evidence->push_back(candidate.evidence);
return Status::Ok();
});
if (!step_status.IsOk()) return step_status;
}
return Status::Ok();
}
@@ -4,8 +4,10 @@
#include <functional>
#include <optional>
#include <vector>
#include <utility>
#include "fesa/analysis/thermal_analysis_state.h"
#include "fesa/math/sparse_matrix.h"
#include "fesa/solvers/linear/linear_solver.h"
namespace fesa::thermal_transient_internal {
@@ -84,11 +86,52 @@ Result<ThermalStepCandidate> SolveThermalIncrement(
const Vector& old_temperature, double delta, LinearSolver& solver,
ThermalFactorization& factorization);
/// @brief Solves one thermal increment without committing accepted history.
class HeatStepIntegrator {
public:
virtual ~HeatStepIntegrator() = default;
virtual Result<ThermalStepCandidate>
SolveIncrement(const ThermalSystem &system, const ThermalPartition &partition,
const Vector &old_temperature, double delta,
LinearSolver &solver,
ThermalFactorization &factorization) const = 0;
};
/// @brief Preserves backward Euler actual-delta and factorization reuse policy.
class BackwardEulerHeatIntegrator final : public HeatStepIntegrator {
public:
Result<ThermalStepCandidate>
SolveIncrement(const ThermalSystem &system, const ThermalPartition &partition,
const Vector &old_temperature, double delta,
LinearSolver &solver,
ThermalFactorization &factorization) const override {
return SolveThermalIncrement(system, partition, old_temperature, delta,
solver, factorization);
}
};
/// @brief Provides an internal recovery seam for the actual history commit
/// path.
using ThermalRecovery = std::function<Result<std::vector<ThermalElementFlux>>(
const Vector& temperature)>;
/// @brief Runs the actual solve/recovery/commit sequence without owning history.
/// @note Commit receives a validated unpublished candidate only after recovery.
template <typename Commit>
Status RunThermalStep(const HeatStepIntegrator& integrator,
const ThermalSystem& system,
const ThermalPartition& partition,
const Vector& old_temperature, double delta,
LinearSolver& solver, ThermalFactorization& factorization,
const ThermalRecovery& recovery, Commit&& commit) {
auto candidate = integrator.SolveIncrement(system, partition, old_temperature,
delta, solver, factorization);
if (!candidate.HasValue()) return candidate.GetStatus();
auto flux = recovery(candidate.Value().temperature);
if (!flux.HasValue()) return flux.GetStatus();
return commit(std::move(candidate.Value()), std::move(flux.Value()));
}
/// @brief Advances an existing raw/accepted history, preserving it on every
/// failure.
/// @note Recovery runs after algebra validation and before atomic commit.
@@ -101,5 +144,13 @@ Status RunThermalHistory(const ThermalAssembly& assembly,
LinearSolver& solver, const ThermalRecovery& recovery,
std::vector<ThermalStepEvidence>* evidence = nullptr);
/// @brief Borrows a caller-selected integrator for the complete history core.
/// @note Integrator and other referenced adapters outlive the synchronous call.
Status RunThermalHistory(const ThermalAssembly& assembly,
const DofManager& dofs, ThermalAnalysisState& state,
LinearSolver& solver, const HeatStepIntegrator& integrator,
const ThermalRecovery& recovery,
std::vector<ThermalStepEvidence>* evidence = nullptr);
} // namespace fesa::thermal_transient_internal
#endif // FESA_ANALYSIS_THERMAL_TRANSIENT_ALGEBRA_H_
@@ -1,6 +1,7 @@
#include "fesa/analysis/transient_heat_procedure.h"
#include <utility>
#include "fesa/assembly/thermal_assembler.h"
#include "thermal_transient_algebra.h"
+11 -17
View File
@@ -12,6 +12,9 @@
#include "fesa/analysis/nonlinear_static_procedure.h"
#include "fesa/analysis/prepared_analysis.h"
#include "fesa/analysis/solid_frequency_procedure.h"
#include "fesa/analysis/solid_hht_dynamic_procedure.h"
#include "fesa/analysis/solid_static_procedure.h"
#include "fesa/analysis/transient_heat_procedure.h"
#include "fesa/app/output_paths.h"
#include "fesa/assembly/parallel_for.h"
#include "fesa/core/diagnostic.h"
@@ -21,6 +24,7 @@
#include "fesa/io/hdf5/nonlinear_hdf5_results_writer.h"
#include "fesa/io/hdf5/solid_hdf5_results_writer.h"
#include "fesa/io/hdf5/thermal_hdf5_results_writer.h"
#include "fesa/solvers/eigen/mkl_feast_eigen_solver.h"
#include "fesa/solvers/linear/mkl_pardiso_solver.h"
#include "fesa/solvers/linear/mkl_pardiso_tangent_solver.h"
@@ -84,6 +88,7 @@ int FesaApplication::Run(const std::vector<std::string>& arguments) {
}
TbbParallelFor parallel_for;
const MklFeastEigenSolver eigen_solver;
MklPardisoSolver linear_solver;
MklPardisoTangentSolver tangent_solver;
Hdf5ResultsWriter linear_writer;
@@ -105,23 +110,12 @@ int FesaApplication::Run(const std::vector<std::string>& arguments) {
if (!prepared.HasValue()) {
status = prepared.GetStatus();
} else {
LinearStaticProcedureExecutor linear_executor(parallel_for, linear_solver);
NonlinearStaticProcedureExecutor nonlinear_executor(parallel_for,
tangent_solver);
const LinearFrequencyProcedureExecutor frequency_executor;
const HhtDynamicProcedureExecutor dynamic_executor(
parallel_for, linear_solver, tangent_solver);
const TransientHeatProcedureExecutor thermal_executor(parallel_for,
linear_solver);
const SolidStaticProcedureExecutor solid_static(parallel_for, linear_solver,
tangent_solver);
const SolidFrequencyProcedureExecutor solid_frequency;
const SolidHhtDynamicProcedureExecutor solid_dynamic(
parallel_for, linear_solver, tangent_solver);
Result<PreparedAnalysisOutcome> outcome = ExecutePreparedAnalysis(
prepared.Value().domain, linear_executor, nonlinear_executor,
frequency_executor, log, &dynamic_executor, &thermal_executor,
&solid_static, &solid_frequency, &solid_dynamic);
auto procedure = CreateAnalysisProcedure(
prepared.Value().domain, parallel_for, linear_solver, tangent_solver,
eigen_solver, log);
Result<PreparedAnalysisOutcome> outcome = procedure.HasValue()
? ExecutePreparedAnalysis(prepared.Value().domain, *procedure.Value())
: Result<PreparedAnalysisOutcome>::Failure(procedure.GetStatus());
if (!outcome.HasValue()) {
status = outcome.GetStatus();
} else {
+6 -113
View File
@@ -11,7 +11,8 @@
#include "fesa/constraints/essential_constraint_policy.h"
#include "fesa/loads/load.h"
#include "fesa/model/source_target_resolver.h"
#include "math/dense_blas_internal.h"
#include "assembly/ordered_contribution_internal.h"
namespace fesa {
namespace {
@@ -27,20 +28,6 @@ Status LoadFailure(const std::string& code, const SourceLocation& location,
{{Severity::kError, code, location, keyword, identity, message}});
}
Status ValidateFiniteVector(const Vector& values,
const SourceLocation& location,
const std::string& identity) {
for (std::size_t index = 0U; index < values.Size(); ++index) {
if (!std::isfinite(values[index])) {
return LoadFailure("nonfinite-load-value", location, "LOAD_ASSEMBLER",
identity + ":" + std::to_string(index),
"Load and prescribed displacement vectors must "
"contain finite values.");
}
}
return Status::Ok();
}
Status ValidateShellMoments(const Domain& domain, const Vector& full_load) {
if (domain.ShellElements().Empty()) {
return Status::Ok();
@@ -224,14 +211,13 @@ Result<Vector> LoadAssembler::AssembleFullNodalLoad(const AnalysisModel& model,
for (const auto& contributions : contributions_by_load) {
for (const auto& contribution : contributions) {
const std::size_t full_dof = contribution.full_dof_index;
const double accumulated = full_load[full_dof] + contribution.value;
if (!std::isfinite(accumulated)) {
if (!assembly_internal::AccumulateFiniteLoad(
full_load, full_dof, contribution.value)) {
return Result<Vector>::Failure(LoadFailure(
"nonfinite-load-accumulation", model.Step().Location(),
"LOAD_ASSEMBLER", std::to_string(full_dof),
"Source-order load accumulation produced a nonfinite value."));
}
full_load[full_dof] = accumulated;
}
}
const Status shell_moment_status = ValidateShellMoments(domain, full_load);
@@ -245,101 +231,8 @@ Result<Vector> LoadAssembler::EffectiveFreeRhs(const Vector& full_load,
const SparseMatrix& kfc,
const Vector& prescribed_values,
const DofManager& dofs) {
const SourceLocation location{{}, 0U};
if (dofs.FullDofCount() != full_load.Size()) {
return Result<Vector>::Failure(
LoadFailure("invalid-load-dimensions", location, "LOAD_ASSEMBLER",
std::to_string(dofs.FullDofCount()),
"The DofManager full dimension must match the full load."));
}
const Status dof_status = dofs.ValidateInvariants();
if (!dof_status.IsOk()) {
return Result<Vector>::Failure(dof_status);
}
if (kfc.Rows() != dofs.FreeDofCount() ||
kfc.Columns() != dofs.ConstrainedDofCount() ||
prescribed_values.Size() != dofs.ConstrainedDofCount()) {
return Result<Vector>::Failure(LoadFailure(
"invalid-load-dimensions", location, "LOAD_ASSEMBLER",
std::to_string(kfc.Rows()) + "x" + std::to_string(kfc.Columns()),
"Kfc rows/columns and prescribed values must match free/constrained "
"order."));
}
const Status matrix_status = kfc.Validate();
if (!matrix_status.IsOk()) {
return Result<Vector>::Failure(matrix_status);
}
const Status load_status =
ValidateFiniteVector(full_load, location, "full-load");
if (!load_status.IsOk()) {
return Result<Vector>::Failure(load_status);
}
const Status prescribed_status =
ValidateFiniteVector(prescribed_values, location, "prescribed-values");
if (!prescribed_status.IsOk()) {
return Result<Vector>::Failure(prescribed_status);
}
Vector entry_coefficients{kfc.Values().size()};
Vector entry_prescribed{kfc.Values().size()};
for (std::size_t position = 0U; position < kfc.Values().size(); ++position) {
entry_coefficients[position] = kfc.Values()[position];
entry_prescribed[position] =
prescribed_values[kfc.ColumnIndices()[position]];
}
Vector entry_products{0U};
try {
entry_products = dense_blas_internal::MultiplyDiagonal(entry_coefficients,
entry_prescribed);
} catch (const std::length_error&) {
return Result<Vector>::Failure(LoadFailure(
"load-backend-dimension-overflow", location, "LOAD_ASSEMBLER", "Kfc",
"Prescribed displacement products exceed the backend integer range."));
} catch (const std::invalid_argument&) {
return Result<Vector>::Failure(LoadFailure(
"invalid-load-product", location, "LOAD_ASSEMBLER", "Kfc",
"Prescribed displacement product inputs must be finite and compatible."));
}
// MKL computes independent entries; ordered CSR reduction below retains
// cancellation and the first product/row-sum overflow diagnostic.
Vector correction{kfc.Rows()};
for (std::size_t row = 0U; row < kfc.Rows(); ++row) {
double sum = 0.0;
for (std::size_t position = kfc.RowOffsets()[row];
position < kfc.RowOffsets()[row + 1U]; ++position) {
const double product = entry_products[position];
if (!std::isfinite(product)) {
return Result<Vector>::Failure(LoadFailure(
"nonfinite-load-accumulation", location, "LOAD_ASSEMBLER",
std::to_string(row),
"Kfc times prescribed displacement produced a nonfinite product."));
}
sum += product;
if (!std::isfinite(sum)) {
return Result<Vector>::Failure(LoadFailure(
"nonfinite-load-accumulation", location, "LOAD_ASSEMBLER",
std::to_string(row),
"Kfc times prescribed displacement produced a nonfinite row sum."));
}
}
correction[row] = sum;
}
Vector rhs = EssentialConstraintPolicy{}.GatherFree(full_load, dofs);
// The constrained vector is already in DofManager order, so this is the
// approved elimination equation rhs = Ff - Kfc*dc without reordering dc.
for (std::size_t row = 0U; row < rhs.Size(); ++row) {
const double value = rhs[row] - correction[row];
if (!std::isfinite(value)) {
return Result<Vector>::Failure(
LoadFailure("nonfinite-load-accumulation", location, "LOAD_ASSEMBLER",
std::to_string(row),
"Effective RHS subtraction produced a nonfinite value."));
}
rhs[row] = value;
}
return Result<Vector>::Success(std::move(rhs));
return EssentialConstraintPolicy{}.EffectiveFreeRhs(
full_load, kfc, prescribed_values, dofs);
}
} // namespace fesa
@@ -0,0 +1,58 @@
#ifndef FESA_ASSEMBLY_ORDERED_CONTRIBUTION_INTERNAL_H_
#define FESA_ASSEMBLY_ORDERED_CONTRIBUTION_INTERNAL_H_
#include <cmath>
#include <cstddef>
#include <vector>
#include "fesa/math/matrix.h"
#include "fesa/math/sparse_matrix.h"
#include "fesa/math/vector.h"
namespace fesa::assembly_internal {
/// @brief Appends one validated local entry without changing its stable order.
/// @note Caller owns shape/scatter validation and diagnostic selection. Retains
/// every structural zero; never writes shared global storage from a worker.
template <class Scatter>
void AppendLocalMatrixEntry(const Scatter& scatter, std::size_t row,
std::size_t column, double value,
std::size_t source_order,
std::vector<CooContribution>& contributions) {
contributions.push_back({scatter[row], scatter[column], value, source_order,
row * scatter.size() + column});
}
/// @brief Appends a validated matrix row in the original local-column order.
template <class Scatter>
void AppendLocalMatrixRow(const Matrix& values, const Scatter& scatter,
std::size_t row, std::size_t source_order,
std::vector<CooContribution>& contributions) {
for (std::size_t column = 0U; column < scatter.size(); ++column) {
AppendLocalMatrixEntry(scatter, row, column, values(row, column),
source_order, contributions);
}
}
/// @brief Appends a validated matrix in fixed local row/column order.
template <class Scatter>
void AppendLocalMatrix(const Matrix& values, const Scatter& scatter,
std::size_t source_order,
std::vector<CooContribution>& contributions) {
for (std::size_t row = 0U; row < scatter.size(); ++row) {
AppendLocalMatrixRow(values, scatter, row, source_order, contributions);
}
}
/// @brief Adds one full-space load entry, committing only a finite candidate.
/// @note Caller validates the index and keeps source-order error provenance.
inline bool AccumulateFiniteLoad(Vector& full, std::size_t index, double value) {
const double accumulated = full[index] + value;
if (!std::isfinite(value) || !std::isfinite(accumulated)) return false;
full[index] = accumulated;
return true;
}
} // namespace fesa::assembly_internal
#endif // FESA_ASSEMBLY_ORDERED_CONTRIBUTION_INTERNAL_H_

Some files were not shown because too many files have changed in this diff Show More