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 # local document backups and initialization state
.dryforge/ .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) cmake_minimum_required(VERSION 3.25)
project(FESA VERSION 0.1.0 LANGUAGES CXX) project(FESA VERSION 0.1.0 LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF) 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) include(cmake/FesaDependencies.cmake)
find_package(Doxygen QUIET) find_package(Doxygen QUIET)
if(Doxygen_FOUND) if(Doxygen_FOUND)
add_custom_target(fesa_docs add_custom_target(fesa_docs
@@ -19,6 +20,5 @@ if(Doxygen_FOUND)
endif() endif()
enable_testing() enable_testing()
add_subdirectory(src/fesa) add_subdirectory(src/fesa)
add_subdirectory(tests) add_subdirectory(tests)
+21 -7
View File
@@ -3,11 +3,11 @@
"configurePresets": [ "configurePresets": [
{ {
"name": "windows-msvc-ninja-debug", "name": "windows-msvc-ninja-debug",
"displayName": "Windows MSVC x64 Debug (Ninja)",
"generator": "Ninja", "generator": "Ninja",
"binaryDir": "${sourceDir}/build/windows-msvc-ninja-debug", "binaryDir": "${sourceDir}/.build/cmake-debug",
"cacheVariables": { "cacheVariables": {
"CMAKE_BUILD_TYPE": "Debug", "CMAKE_BUILD_TYPE": "Debug",
"CMAKE_EXPORT_COMPILE_COMMANDS": "ON",
"FESA_GTEST_SOURCE_DIR": "$env{FESA_GTEST_SOURCE_DIR}", "FESA_GTEST_SOURCE_DIR": "$env{FESA_GTEST_SOURCE_DIR}",
"MKL_DIR": "$env{MKL_DIR}", "MKL_DIR": "$env{MKL_DIR}",
"TBB_DIR": "$env{TBB_DIR}", "TBB_DIR": "$env{TBB_DIR}",
@@ -16,11 +16,15 @@
}, },
{ {
"name": "windows-msvc-ninja-release", "name": "windows-msvc-ninja-release",
"displayName": "Windows MSVC x64 Release with symbols (Ninja)", "generator": "Ninja",
"inherits": "windows-msvc-ninja-debug", "binaryDir": "${sourceDir}/.build/cmake-release",
"binaryDir": "${sourceDir}/build/windows-msvc-ninja-release",
"cacheVariables": { "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": [ "testPresets": [
{ {
"name": "windows-msvc-ninja-debug", "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 <vector>
#include "fesa/core/status.h" #include "fesa/core/status.h"
#include "fesa/fem/dof_manager.h"
#include "fesa/math/vector.h" #include "fesa/math/vector.h"
#include "fesa/results/result_records.h" #include "fesa/results/result_records.h"
namespace fesa { namespace fesa {
class DofManager;
/// @brief Owns mutable quantities required by the V0 linear-static frame. /// @brief Owns mutable quantities required by the V0 linear-static frame.
class AnalysisState { class AnalysisState {
public: 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 <vector>
#include "fesa/math/matrix.h" #include "fesa/math/matrix.h"
#include "fesa/solvers/eigen/mkl_feast_eigen_solver.h" #include "fesa/solvers/eigen/eigen_solver.h"
namespace fesa { namespace fesa {
@@ -1,10 +1,14 @@
#ifndef FESA_ANALYSIS_HHT_DYNAMIC_PROCEDURE_H_ #ifndef FESA_ANALYSIS_HHT_DYNAMIC_PROCEDURE_H_
#define 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 { namespace fesa {
class Domain;
class LinearSolver; class LinearSolver;
class NewtonIterationLogWriter;
class ParallelFor; class ParallelFor;
class TangentSolver; class TangentSolver;
@@ -6,13 +6,23 @@
namespace fesa { namespace fesa {
class Domain; class Domain;
class EigenSolver;
/// @brief Executes the supported linear MITC4 frequency procedure. /// @brief Executes the supported linear MITC4 frequency procedure.
class LinearFrequencyProcedureExecutor final { class LinearFrequencyProcedureExecutor final {
public: 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. /// @brief Builds, solves, and validates one frequency modal candidate.
/// @return Requested physical modes plus all-c validation evidence. /// @return Requested physical modes plus all-c validation evidence.
Result<EigenAnalysisState> Execute(const Domain& domain) const; Result<EigenAnalysisState> Execute(const Domain& domain) const;
private:
const EigenSolver* solver_{nullptr};
}; };
} // namespace fesa } // 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_ #ifndef FESA_ANALYSIS_LINEAR_STATIC_PROCEDURE_H_
#define 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 { namespace fesa {
class Domain;
class LinearSolver; class LinearSolver;
class ParallelFor; 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_ #ifndef FESA_ANALYSIS_NONLINEAR_STATIC_PROCEDURE_H_
#define 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 { namespace fesa {
class Domain;
class NewtonIterationLogWriter; class NewtonIterationLogWriter;
class ParallelFor; class ParallelFor;
class TangentSolver; class TangentSolver;
+19 -32
View File
@@ -2,23 +2,21 @@
#define FESA_ANALYSIS_PREPARED_ANALYSIS_H_ #define FESA_ANALYSIS_PREPARED_ANALYSIS_H_
#include <filesystem> #include <filesystem>
#include <variant> #include <memory>
#include <vector> #include <vector>
#include "fesa/analysis/analysis_state.h" #include "fesa/analysis/analysis_procedure.h"
#include "fesa/analysis/dynamic_analysis_state.h" #include "fesa/analysis/prepared_analysis_outcome.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/core/diagnostic.h" #include "fesa/core/diagnostic.h"
#include "fesa/core/status.h" #include "fesa/core/status.h"
#include "fesa/model/domain.h" #include "fesa/model/domain.h"
namespace fesa { namespace fesa {
class ParallelFor;
class LinearSolver;
class TangentSolver;
class EigenSolver;
class LinearStaticProcedureExecutor; class LinearStaticProcedureExecutor;
class LinearFrequencyProcedureExecutor; class LinearFrequencyProcedureExecutor;
class EigenResultsWriter; class EigenResultsWriter;
@@ -31,6 +29,9 @@ class DynamicResultsWriter;
class ThermalResultsWriter; class ThermalResultsWriter;
class SolidResultsWriter; class SolidResultsWriter;
class SolidFrequencyProcedureExecutor; class SolidFrequencyProcedureExecutor;
class SolidStaticProcedureExecutor;
class SolidHhtDynamicProcedureExecutor;
class TransientHeatProcedureExecutor;
struct DerivedOutputPaths; struct DerivedOutputPaths;
/// @brief Owns one parsed and semantically mapped analysis input. /// @brief Owns one parsed and semantically mapped analysis input.
@@ -46,30 +47,16 @@ struct PreparedAnalysisInput {
Result<PreparedAnalysisInput> PrepareAnalysisInput( Result<PreparedAnalysisInput> PrepareAnalysisInput(
const std::filesystem::path& input_path); const std::filesystem::path& input_path);
/// @brief Stores a completed linear-static calculation before result writing. /// @brief Creates exactly one selected family with caller-owned adapters.
struct LinearStaticExecutionResult { /// @note Domain and adapters remain alive and unmoved throughout execution.
AnalysisState state; Result<std::unique_ptr<AnalysisProcedure>> CreateAnalysisProcedure(
std::vector<Diagnostic> diagnostics; 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 /// @brief Executes a caller-selected procedure through the common interface.
/// writing. Result<PreparedAnalysisOutcome>
struct NonlinearStaticExecutionResult { ExecutePreparedAnalysis(const Domain &domain, AnalysisProcedure &procedure);
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 Selects the approved procedure from a prepared Domain. /// @brief Selects the approved procedure from a prepared Domain.
/// @pre The executors and log outlive this call. /// @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 { namespace fesa {
class Domain; class Domain;
class EigenSolver;
/// @brief Executes unloaded reference-state C3D8 generalized frequency /// @brief Executes unloaded reference-state C3D8 generalized frequency
/// analysis. /// analysis.
class SolidFrequencyProcedureExecutor final { class SolidFrequencyProcedureExecutor final {
public: 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 /// @brief Returns lowest requested modes after validating every returned
/// pair. /// pair.
/// @note Domain is immutable and must remain alive during execution. /// @note Domain is immutable and must remain alive during execution.
Result<SolidFrequencyState> Execute(const Domain& domain) const; Result<SolidFrequencyState> Execute(const Domain& domain) const;
private:
const EigenSolver* solver_{nullptr};
}; };
} // namespace fesa } // namespace fesa
#endif // FESA_ANALYSIS_SOLID_FREQUENCY_PROCEDURE_H_ #endif // FESA_ANALYSIS_SOLID_FREQUENCY_PROCEDURE_H_
@@ -8,7 +8,7 @@
#include <vector> #include <vector>
#include "fesa/math/matrix.h" #include "fesa/math/matrix.h"
#include "fesa/solvers/eigen/mkl_feast_eigen_solver.h" #include "fesa/solvers/eigen/eigen_solver.h"
namespace fesa { namespace fesa {
/// @brief Identifies a source-ordered physical Solid eigenmode. /// @brief Identifies a source-ordered physical Solid eigenmode.
@@ -1,19 +1,13 @@
#ifndef FESA_ANALYSIS_SOLID_HHT_DYNAMIC_PROCEDURE_H_ #ifndef FESA_ANALYSIS_SOLID_HHT_DYNAMIC_PROCEDURE_H_
#define 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 { namespace fesa {
class LinearSolver; class LinearSolver;
class ParallelFor; class ParallelFor;
class TangentSolver; 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 /// @brief Integrates linear or TL Solid mechanics with constant consistent
/// mass. /// mass.
class SolidHhtDynamicProcedureExecutor { 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_ #ifndef FESA_ANALYSIS_SOLID_STATIC_PROCEDURE_H_
#define 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 { namespace fesa {
class LinearSolver; class LinearSolver;
class ParallelFor; class ParallelFor;
class TangentSolver; 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. /// @brief Executes linear K0 or fixed-factor TL StVK static analysis.
class SolidStaticProcedureExecutor { class SolidStaticProcedureExecutor {
public: public:
+1 -1
View File
@@ -99,7 +99,7 @@ class SolidStaticState {
const noexcept; const noexcept;
private: private:
friend class SolidStaticProcedureExecutor; friend class SolidStaticCore;
bool initialized_{false}; bool initialized_{false};
bool nonlinear_{false}; bool nonlinear_{false};
std::vector<SolidIntegrationPointRow> reference_points_; std::vector<SolidIntegrationPointRow> reference_points_;
@@ -6,11 +6,15 @@
#include <vector> #include <vector>
#include "fesa/analysis/thermal_time_grid.h" #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" #include "fesa/results/thermal_result_records.h"
namespace fesa { namespace fesa {
class AnalysisModel;
class DofManager;
class ThermalAssembly;
/// @brief Owns the immutable thermal result inventory and accepted history. /// @brief Owns the immutable thermal result inventory and accepted history.
/// @note No Domain/model/assembly references survive Create; state may outlive /// @note No Domain/model/assembly references survive Create; state may outlive
/// them. Numerical equilibrium and energy checks belong to the procedure before /// 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_ #ifndef FESA_ANALYSIS_TRANSIENT_HEAT_PROCEDURE_H_
#define FESA_ANALYSIS_TRANSIENT_HEAT_PROCEDURE_H_ #define FESA_ANALYSIS_TRANSIENT_HEAT_PROCEDURE_H_
#include <vector> #include "fesa/analysis/thermal_execution_result.h"
#include "fesa/analysis/thermal_analysis_state.h"
#include "fesa/core/status.h" #include "fesa/core/status.h"
namespace fesa { namespace fesa {
class Domain;
class LinearSolver; class LinearSolver;
class ParallelFor; 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 /// @brief Executes fixed-increment backward Euler with checked thermal
/// recovery. /// recovery.
class TransientHeatProcedureExecutor final { class TransientHeatProcedureExecutor final {
+2 -1
View File
@@ -30,7 +30,8 @@ class LoadAssembler {
double step_time = 0.0); double step_time = 0.0);
/// @brief Forms Ff-Kfc*dc in stable free/constrained order. /// @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, static Result<Vector> EffectiveFreeRhs(const Vector& full_load,
const SparseMatrix& kfc, const SparseMatrix& kfc,
const Vector& prescribed_values, const Vector& prescribed_values,
@@ -2,10 +2,12 @@
#define FESA_ASSEMBLY_THERMAL_ASSEMBLER_H_ #define FESA_ASSEMBLY_THERMAL_ASSEMBLER_H_
#include <array> #include <array>
#include <memory>
#include <vector> #include <vector>
#include "fesa/analysis/analysis_model.h" #include "fesa/analysis/analysis_model.h"
#include "fesa/assembly/parallel_for.h" #include "fesa/assembly/parallel_for.h"
#include "fesa/elements/element_capabilities.h"
#include "fesa/elements/thermal_shell.h" #include "fesa/elements/thermal_shell.h"
#include "fesa/fem/dof_manager.h" #include "fesa/fem/dof_manager.h"
#include "fesa/math/sparse_matrix.h" #include "fesa/math/sparse_matrix.h"
@@ -50,6 +52,8 @@ class ThermalAssembly final {
const std::vector<EntityIndex>& ElementOrder() const noexcept; const std::vector<EntityIndex>& ElementOrder() const noexcept;
/// @brief Returns checked kernels in stable active element order. /// @brief Returns checked kernels in stable active element order.
const std::vector<ThermalShell>& Kernels() const noexcept; 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. /// @brief Returns node-major twenty-entry full DOF scatters in kernel order.
const std::vector<std::array<std::size_t, 20>>& Scatters() const noexcept; const std::vector<std::array<std::size_t, 20>>& Scatters() const noexcept;
/// @brief Checks source Domain identity and the complete supplied equation /// @brief Checks source Domain identity and the complete supplied equation
@@ -79,7 +83,10 @@ class ThermalAssembly final {
SparseMatrix capacity_error_; SparseMatrix capacity_error_;
Vector input_error_; Vector input_error_;
std::vector<EntityIndex> element_order_; 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<ThermalShell> kernels_;
std::vector<std::unique_ptr<ThermalElement>> elements_;
std::vector<std::array<std::size_t, 20>> scatters_; std::vector<std::array<std::size_t, 20>> scatters_;
}; };
@@ -24,6 +24,16 @@ class EssentialConstraintPolicy {
Result<PartitionedStiffness> Partition(const SparseMatrix& full_stiffness, Result<PartitionedStiffness> Partition(const SparseMatrix& full_stiffness,
const DofManager& dof_manager) const; 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. /// @brief Gathers a full vector in stable free-equation order.
Vector GatherFree(const Vector& full_values, Vector GatherFree(const Vector& full_values,
const DofManager& dof_manager) const; const DofManager& dof_manager) const;
@@ -5,7 +5,7 @@
#include "fesa/constraints/boundary_condition.h" #include "fesa/constraints/boundary_condition.h"
#include "fesa/core/diagnostic.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" #include "fesa/model/source_target_resolver.h"
namespace fesa { namespace fesa {
+6
View File
@@ -2,6 +2,7 @@
#define FESA_CORE_ASCII_H_ #define FESA_CORE_ASCII_H_
#include <cstdint> #include <cstdint>
#include <string>
#include <string_view> #include <string_view>
#include "fesa/core/status.h" #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. /// @return The lowercase ASCII byte, or the input byte when it is not A-Z.
char AsciiLower(char value) noexcept; 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. /// @brief Compares two byte strings with ASCII-only case folding.
/// @return True when the strings have equal length and equal ASCII-folded /// @return True when the strings have equal length and equal ASCII-folded
/// bytes. /// bytes.
+8
View File
@@ -24,6 +24,14 @@ struct SourceEntityId {
std::string source_label_text; 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 } // namespace fesa
#endif // FESA_CORE_SOURCE_IDENTITY_H_ #endif // FESA_CORE_SOURCE_IDENTITY_H_
+2 -2
View File
@@ -98,11 +98,11 @@ class Result {
struct FailureTag {}; struct FailureTag {};
/// @brief Constructs the successful value alternative. /// @brief Constructs the successful value alternative.
Result(SuccessTag, T value) Result(SuccessTag, T&& value)
: value_{std::move(value)}, status_{Status::Ok()} {} : value_{std::move(value)}, status_{Status::Ok()} {}
/// @brief Constructs the failed status alternative. /// @brief Constructs the failed status alternative.
Result(FailureTag, Status status) Result(FailureTag, Status&& status)
: value_{std::nullopt}, status_{std::move(status)} {} : value_{std::nullopt}, status_{std::move(status)} {}
std::optional<T> value_; std::optional<T> value_;
+1 -24
View File
@@ -1,13 +1,11 @@
#ifndef FESA_ELEMENTS_ELEMENT_H_ #ifndef FESA_ELEMENTS_ELEMENT_H_
#define FESA_ELEMENTS_ELEMENT_H_ #define FESA_ELEMENTS_ELEMENT_H_
#include <cstdint>
#include <functional>
#include <variant> #include <variant>
#include <vector> #include <vector>
#include "fesa/core/source_identity.h"
#include "fesa/core/status.h" #include "fesa/core/status.h"
#include "fesa/elements/element_dof_layout.h"
#include "fesa/math/matrix.h" #include "fesa/math/matrix.h"
#include "fesa/math/vector.h" #include "fesa/math/vector.h"
#include "fesa/results/result_records.h" #include "fesa/results/result_records.h"
@@ -15,23 +13,6 @@
namespace fesa { 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. /// @brief Carries one element-local stiffness in its declared DOF order.
struct ElementStiffnessContribution { struct ElementStiffnessContribution {
ElementDofLayout layout; ElementDofLayout layout;
@@ -78,10 +59,6 @@ class Element {
const Vector& element_displacement) const = 0; 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 } // namespace fesa
#endif // FESA_ELEMENTS_ELEMENT_H_ #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/core/status.h"
#include "fesa/elements/element.h" #include "fesa/elements/element.h"
#include "fesa/elements/element_capabilities.h"
namespace fesa { namespace fesa {
@@ -21,6 +22,14 @@ class ElementFactory {
Result<std::unique_ptr<Element>> Create(const ElementDefinition& definition, Result<std::unique_ptr<Element>> Create(const ElementDefinition& definition,
const Domain& domain) const; 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: private:
/// @brief Creates one checked B33 runtime candidate. /// @brief Creates one checked B33 runtime candidate.
Result<std::unique_ptr<Element>> CreateBeam( Result<std::unique_ptr<Element>> CreateBeam(
+1 -34
View File
@@ -4,12 +4,11 @@
#include <array> #include <array>
#include <cstddef> #include <cstddef>
#include <string> #include <string>
#include <string_view>
#include <vector> #include <vector>
#include "fesa/core/status.h" #include "fesa/core/status.h"
#include "fesa/elements/element.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/materials/isotropic_linear_elastic_material.h"
#include "fesa/math/matrix.h" #include "fesa/math/matrix.h"
#include "fesa/math/vector.h" #include "fesa/math/vector.h"
@@ -17,40 +16,8 @@
namespace fesa { namespace fesa {
class Domain;
struct Node; 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. /// @brief Stores constant line-load components in the beam local frame.
struct ConstantLocalLineLoad { struct ConstantLocalLineLoad {
double px; 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. /// @pre phi is finite and lies inside the approved principal chart.
Matrix LeftJacobianSO3(const Vector3& phi); Matrix LeftJacobianSO3(const Vector3& phi);
/// @brief Computes the inverse transpose of the SO(3) left Jacobian. /// @brief Solves J(phi)^T * moment = generalized for a spatial moment.
/// @param phi Fixed-global rotation coordinate in radians. /// @param phi Fixed-global principal-chart rotation coordinate in radians.
/// @return The 3-by-3 inverse-transpose left Jacobian. /// @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. /// @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. /// @brief Builds a checked finite-rotation state from its reference frame.
/// @param phi Fixed-global principal-chart rotation coordinate in radians. /// @param phi Fixed-global principal-chart rotation coordinate in radians.
+2 -48
View File
@@ -3,12 +3,11 @@
#include <array> #include <array>
#include <string> #include <string>
#include <string_view>
#include <vector> #include <vector>
#include "fesa/core/status.h" #include "fesa/core/status.h"
#include "fesa/elements/element.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/materials/isotropic_linear_elastic_material.h"
#include "fesa/math/matrix.h" #include "fesa/math/matrix.h"
#include "fesa/math/vector.h" #include "fesa/math/vector.h"
@@ -17,52 +16,8 @@
namespace fesa { namespace fesa {
class Domain;
struct Node; 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. /// @brief Stores bilinear shape values and natural-coordinate derivatives.
struct Mitc4ShapeFunctions { struct Mitc4ShapeFunctions {
std::array<double, 4> values; std::array<double, 4> values;
@@ -213,11 +168,10 @@ class Mitc4Shell final : public Element {
void BindRuntime(ElementDofLayout layout, EntityIndex element_index, void BindRuntime(ElementDofLayout layout, EntityIndex element_index,
SourceLocation location); SourceLocation location);
/// @brief Stores covariant, reciprocal, frame, and Jacobian data at one /// @brief Stores covariant, frame, and Jacobian data at one
/// point. /// point.
struct GeometryData { struct GeometryData {
std::array<Vector3, 3> covariant; std::array<Vector3, 3> covariant;
std::array<Vector3, 3> reciprocal;
Mitc4LocalFrame frame; Mitc4LocalFrame frame;
double jacobian; 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 <array>
#include "fesa/model/model_types.h" #include "fesa/core/source_identity.h"
#include "fesa/elements/thermal_dof_component.h"
namespace fesa { namespace fesa {
+3 -8
View File
@@ -6,25 +6,20 @@
#include <optional> #include <optional>
#include <vector> #include <vector>
#include "fesa/analysis/analysis_model.h"
#include "fesa/constraints/boundary_condition.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/elements/thermal_element_layout.h"
#include "fesa/math/sparse_pattern.h"
#include "fesa/math/vector.h" #include "fesa/math/vector.h"
namespace fesa { namespace fesa {
class AnalysisModel;
class DofManagerTestAccess; class DofManagerTestAccess;
/// @brief Distinguishes six-component, Solid translation, and thermal maps. /// @brief Distinguishes six-component, Solid translation, and thermal maps.
enum class DofProfile { kStructural, kThermal, kSolidStructural }; 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. /// @brief Owns full/free/constrained numbering, scatter maps, and CSR pattern.
class DofManager { class DofManager {
public: public:
+20
View File
@@ -32,6 +32,26 @@ class Matrix {
/// @brief Returns the column count. /// @brief Returns the column count.
std::size_t Columns() const noexcept; 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. /// @brief Returns a bounds-checked mutable entry.
/// @throws std::out_of_range if the index is outside the matrix. /// @throws std::out_of_range if the index is outside the matrix.
double& operator()(std::size_t row, std::size_t column); double& operator()(std::size_t row, std::size_t column);
+1 -2
View File
@@ -5,12 +5,11 @@
#include <vector> #include <vector>
#include "fesa/core/status.h" #include "fesa/core/status.h"
#include "fesa/math/sparse_pattern.h"
#include "fesa/math/vector.h" #include "fesa/math/vector.h"
namespace fesa { namespace fesa {
struct SparsePattern;
/// @brief Carries one deterministic element-local COO contribution. /// @brief Carries one deterministic element-local COO contribution.
struct CooContribution { struct CooContribution {
std::size_t row; 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/boundary_condition.h"
#include "fesa/constraints/prescribed_displacement.h" #include "fesa/constraints/prescribed_displacement.h"
#include "fesa/core/status.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/concentrated_nodal_load.h"
#include "fesa/loads/load.h" #include "fesa/loads/load.h"
#include "fesa/model/model_types.h" #include "fesa/model/model_types.h"
+3 -14
View File
@@ -13,12 +13,11 @@
#include "fesa/core/diagnostic.h" #include "fesa/core/diagnostic.h"
#include "fesa/core/source_identity.h" #include "fesa/core/source_identity.h"
#include "fesa/elements/ds4_thermal.h" #include "fesa/elements/euler_beam_3d_definition.h"
#include "fesa/elements/euler_beam_3d.h"
#include "fesa/elements/hex8_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_linear_elastic_material.h"
#include "fesa/materials/isotropic_thermal_material.h"
#include "fesa/properties/general_beam_section.h" #include "fesa/properties/general_beam_section.h"
#include "fesa/properties/shell_section.h" #include "fesa/properties/shell_section.h"
#include "fesa/properties/solid_section.h" #include "fesa/properties/solid_section.h"
@@ -302,16 +301,6 @@ struct ThermalInitialConditionDefinition {
SourceLocation location; 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. /// @brief Stores one prescribed thermal source DOF value.
struct ThermalBoundaryConditionDefinition { struct ThermalBoundaryConditionDefinition {
EntityIndex node_index; EntityIndex node_index;
@@ -38,6 +38,14 @@ class SourceTargetIndex {
/// @return An owning index that preserves Domain declaration order. /// @return An owning index that preserves Domain declaration order.
static SourceTargetIndex FromDomain(const Domain& domain); 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. /// @brief Returns owned entries without exposing mutable index state.
const std::vector<SourceTargetIndexEntry>& Entries() const noexcept; 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_ #ifndef FESA_SOLVERS_EIGEN_MKL_FEAST_EIGEN_SOLVER_H_
#define FESA_SOLVERS_EIGEN_MKL_FEAST_EIGEN_SOLVER_H_ #define FESA_SOLVERS_EIGEN_MKL_FEAST_EIGEN_SOLVER_H_
#include <cstddef> #include "fesa/solvers/eigen/eigen_solver.h"
#include <cstdint>
#include <string>
#include <vector>
#include "fesa/core/status.h"
#include "fesa/math/matrix.h"
namespace fesa { 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. /// @brief Adapts standard and symmetric-definite sparse pencils to FEAST.
class MklFeastEigenSolver final { class MklFeastEigenSolver final : public EigenSolver {
public: public:
/// @brief Computes all validated interval pairs for a requested lowest-mode /// @brief Computes all validated interval pairs for a requested lowest-mode
/// prefix. /// prefix.
@@ -74,8 +21,8 @@ class MklFeastEigenSolver final {
/// @note The implementation fixes oneMKL local thread count to one during /// @note The implementation fixes oneMKL local thread count to one during
/// every call. /// every call.
Result<FeastEigenResult> Solve(const SparseMatrix& whitened_operator, Result<FeastEigenResult> Solve(const SparseMatrix& whitened_operator,
std::size_t requested_modes, std::size_t requested_modes,
double zero_operator_scale) const; double zero_operator_scale) const override;
/// @brief Solves the original sparse symmetric definite K/M pencil. /// @brief Solves the original sparse symmetric definite K/M pencil.
/// @param mass_lower_bound Certified positive lower spectral bound of M, /// @param mass_lower_bound Certified positive lower spectral bound of M,
/// including floating-point assembly uncertainty; never a diagonal estimate. /// 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. /// classification_scale, while eigenvalues are returned in physical units.
Result<FeastEigenResult> SolveGeneralized(const SparseMatrix& stiffness, Result<FeastEigenResult> SolveGeneralized(const SparseMatrix& stiffness,
const SparseMatrix& mass, const SparseMatrix& mass,
std::size_t requested_modes, std::size_t requested_modes,
double mass_lower_bound, double mass_lower_bound,
double classification_scale) const; double classification_scale) const override;
private: private:
/// @brief Shares deterministic count/search controls without changing /// @brief Shares deterministic count/search controls without changing
/// pencils. /// pencils.
Result<FeastEigenResult> SolveImpl(const SparseMatrix& stiffness, Result<FeastEigenResult> SolveImpl(const SparseMatrix& stiffness,
const SparseMatrix* mass, const SparseMatrix* mass,
std::size_t requested_modes, std::size_t requested_modes,
double physical_scale, double physical_scale,
double mass_lower_bound) const; 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/ascii.cpp
core/diagnostic.cpp core/diagnostic.cpp
core/status.cpp core/status.cpp
elements/element_capabilities.cpp
elements/element_factory.cpp elements/element_factory.cpp
elements/hex8_element.cpp elements/hex8_element.cpp
elements/hex8_high_low.cpp elements/hex8_high_low.cpp
@@ -93,12 +94,14 @@ add_library(
results/analysis_state_commit.cpp results/analysis_state_commit.cpp
results/beam_result_recovery.cpp results/beam_result_recovery.cpp
results/global_equilibrium_recovery.cpp results/global_equilibrium_recovery.cpp
results/prepared_results_writer.cpp
results/recovery_candidate.cpp results/recovery_candidate.cpp
results/result_recovery.cpp results/result_recovery.cpp
results/shell_result_recovery.cpp results/shell_result_recovery.cpp
solvers/eigen/mkl_feast_eigen_solver.cpp solvers/eigen/mkl_feast_eigen_solver.cpp
solvers/linear/mkl_pardiso_solver.cpp solvers/linear/mkl_pardiso_solver.cpp
solvers/linear/mkl_pardiso_tangent_solver.cpp solvers/linear/mkl_pardiso_tangent_solver.cpp
solvers/linear/pardiso_session.cpp
) )
target_include_directories( 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 <string>
#include <utility> #include <utility>
#include "fesa/fem/dof_manager.h"
#include "math/finite_values.h"
#include "results/shell_result_recovery.h"
namespace fesa { namespace fesa {
namespace { namespace {
using math_internal::IsFinite;
constexpr std::size_t kShellLocationsPerElement = 4U; constexpr std::size_t kShellLocationsPerElement = 4U;
/// @brief Creates a structured failure without mutating existing state. /// @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}}); {{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 } // namespace
AnalysisState AnalysisState::Create(const DofManager& dofs, AnalysisState AnalysisState::Create(const DofManager& dofs,
@@ -170,7 +151,7 @@ Status AnalysisState::CommitShellResults(
"Shell stress rows require BOTTOM, MIDDLE, TOP identity."); "Shell stress rows require BOTTOM, MIDDLE, TOP identity.");
} }
} }
if (!IsFinite(row)) { if (!results_internal::IsFiniteShellRow(row)) {
return ShellCandidateFailure( return ShellCandidateFailure(
"nonfinite-shell-state-value", std::to_string(row.element), "nonfinite-shell-state-value", std::to_string(row.element),
"Shell result rows must contain only finite values."); "Shell result rows must contain only finite values.");
+9 -56
View File
@@ -1,18 +1,24 @@
#include "fesa/analysis/dynamic_analysis_state.h" #include "fesa/analysis/dynamic_analysis_state.h"
#include <algorithm>
#include <cmath> #include <cmath>
#include <limits> #include <limits>
#include <stdexcept> #include <stdexcept>
#include <string> #include <string>
#include <utility> #include <utility>
#include "fesa/core/source_identity.h"
#include "fesa/elements/element_definition.h" #include "fesa/elements/element_definition.h"
#include "fesa/elements/finite_rotation.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 fesa {
namespace { namespace {
using analysis_internal::ShellElementOrder;
using math_internal::IsFinite;
constexpr std::size_t kDofsPerNode = 6U; constexpr std::size_t kDofsPerNode = 6U;
constexpr std::size_t kAngularDofsPerNode = 3U; constexpr std::size_t kAngularDofsPerNode = 3U;
constexpr std::size_t kShellLocationsPerElement = 4U; constexpr std::size_t kShellLocationsPerElement = 4U;
@@ -28,59 +34,6 @@ Status Failure(const FailureCategory category, const std::string& message) {
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. /// @brief Requires complete ordered Gauss and section-position rows.
Status ValidateShellRows(const DynamicAcceptedFrameState& frame, Status ValidateShellRows(const DynamicAcceptedFrameState& frame,
const std::vector<EntityIndex>& shell_order) { const std::vector<EntityIndex>& shell_order) {
@@ -110,7 +63,7 @@ Status ValidateShellRows(const DynamicAcceptedFrameState& frame,
frame.shell_results[element * kShellLocationsPerElement + point]; frame.shell_results[element * kShellLocationsPerElement + point];
if (row.element != shell_order[element] || if (row.element != shell_order[element] ||
row.location != kLocations[point] || row.location != kLocations[point] ||
row.natural_coordinates != natural[point] || !IsFinite(row)) { row.natural_coordinates != natural[point] || !results_internal::IsFiniteShellRow(row)) {
return Failure(FailureCategory::kSolver, return Failure(FailureCategory::kSolver,
"Shell recovery rows have invalid identity or values."); "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) { for (std::size_t node = 0U; node < node_count; ++node) {
if (frame.nodal_internal_order[node] != 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][0U]) ||
!IsFinite(frame.nodal_triad[node][1U]) || !IsFinite(frame.nodal_triad[node][1U]) ||
!IsFinite(frame.nodal_triad[node][2U]) || !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, Failure("eigen-mass-conditioning", node,
"A positive-space mass pivot is not resolvable.")); "A positive-space mass pivot is not resolvable."));
} }
Matrix lower(positive, positive); const auto factor = dense_blas_internal::DenseFactor::Cholesky(normalized_mp);
Matrix inverse_lower(positive, positive); if (!factor.HasValue()) {
if (!dense_blas_internal::CholeskyLowerAndInverse(normalized_mp, lower,
inverse_lower)) {
return Result<NodalLift>::Failure( return Result<NodalLift>::Failure(
Failure("eigen-mass-conditioning", node, Failure("eigen-mass-conditioning", node,
"A positive-space mass pivot is not resolvable.")); "A positive-space mass pivot is not resolvable."));
} }
const Matrix lower = std::move(factor.Value().Lower().Value());
Matrix transposed_lower(positive, positive); 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 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); transposed_lower(i, j) = lower(j, i);
scaled_inverse_transpose(i, j) = inverse_lower(j, i) / root_scale; const auto condition = factor.Value().ReciprocalConditionInfinity();
}
const auto inverse_mass = dense_blas_internal::MultiplyTransposeLeft(
inverse_lower, inverse_lower);
const auto rebuilt_mass = lower.Multiply(transposed_lower); const auto rebuilt_mass = lower.Multiply(transposed_lower);
double norm_mp = 0.0;
double norm_inverse = 0.0;
double reconstruction = 0.0; double reconstruction = 0.0;
double scaled_mass_norm = 0.0; double scaled_mass_norm = 0.0;
double rebuilt_mass_norm = 0.0; double rebuilt_mass_norm = 0.0;
for (std::size_t i = 0; i < positive; ++i) { 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) { for (std::size_t j = 0; j < positive; ++j) {
const double inverse = inverse_mass(i, j);
const double rebuilt = rebuilt_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); reconstruction = std::hypot(reconstruction, rebuilt - mp(i, j) / scale);
scaled_mass_norm = std::hypot(scaled_mass_norm, 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); 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 (!condition.HasValue() || !(condition.Value() >= kConditionLimit) ||
if (!(result.record.rcond_inf >= kConditionLimit) ||
!(Ratio(reconstruction, scaled_mass_norm + rebuilt_mass_norm) <= !(Ratio(reconstruction, scaled_mass_norm + rebuilt_mass_norm) <=
kAlgebraLimit)) { kAlgebraLimit)) {
return Result<NodalLift>::Failure(Failure( return Result<NodalLift>::Failure(Failure(
"eigen-mass-conditioning", node, "eigen-mass-conditioning", node,
"Nodal mass fails rcond_inf >= 2^-32 or Cholesky reconstruction.")); "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 c = 0; c < 6U; ++c) {
for (std::size_t j = 0; j < positive; ++j) { for (std::size_t j = 0; j < positive; ++j) {
if (!std::isfinite(result.scaled_lift(c, 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/linear_solver.h"
#include "fesa/solvers/linear/tangent_solver.h" #include "fesa/solvers/linear/tangent_solver.h"
#include "math/dense_blas_internal.h" #include "math/dense_blas_internal.h"
#include "math/compensated_sum.h"
namespace fesa { namespace fesa {
namespace hht_internal { namespace hht_internal {
namespace { namespace {
using math_internal::AddCompensated;
/// @brief Attaches a fail-closed dynamic diagnostic to the source model. /// @brief Attaches a fail-closed dynamic diagnostic to the source model.
Status Failure(const Domain& domain, const std::string& code, Status Failure(const Domain& domain, const std::string& code,
const std::string& message) { const std::string& message) {
@@ -148,48 +151,23 @@ Status CheckLocalMass(const Domain& domain, const SparseMatrix& matrix,
return Failure(domain, "dynamic-positive-mass", return Failure(domain, "dynamic-positive-mass",
"A nonempty physical mass block has no positive scale."); "A nonempty physical mass block has no positive scale.");
} }
double norm = 0.0; for (std::size_t i = 0; i < size; ++i)
for (std::size_t i = 0; i < size; ++i) { for (std::size_t j = 0; j < size; ++j) block(i, j) /= scale;
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;
try { try {
if (!dense_blas_internal::CholeskyLowerAndInverse( const auto factor = dense_blas_internal::DenseFactor::Cholesky(block);
block, lower, inverse_lower, &factor_failure)) { if (!factor.HasValue())
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.");
return Failure( return Failure(
domain, "dynamic-positive-mass", domain, "dynamic-positive-mass",
"Physical mass requires strictly positive Cholesky pivots."); "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) { } catch (const std::exception& error) {
return Failure(domain, "dynamic-positive-mass", error.what()); 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(); return Status::Ok();
} }
@@ -468,15 +446,6 @@ Result<SparseMatrix> ConstraintGradient(const DynamicAssembly& assembly,
std::move(entries)); 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. /// @brief Stores W-scaled regularity with exact inverse-action infinity norms.
struct Regularity { struct Regularity {
SparseMatrix matrix; SparseMatrix matrix;
@@ -553,23 +522,10 @@ Result<Regularity> CheckRegularity(const Domain& domain,
sum += std::abs(matrix.Value().Values()[index]); sum += std::abs(matrix.Value().Values()[index]);
norm = std::max(norm, sum); 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; double inverse_norm = 0.0;
for (std::size_t row = 0; row < nodes.size(); ++row) const Status accumulated =
inverse_norm = std::max(inverse_norm, inverse_sums[row]); AccumulateCheckedCanonicalNorm(matrix.Value(), solver, inverse_norm);
if (!accumulated.IsOk()) return Result<Regularity>::Failure(accumulated);
const double condition = 1.0 / norm / inverse_norm; const double condition = 1.0 / norm / inverse_norm;
if (!std::isfinite(norm) || !std::isfinite(inverse_norm) || if (!std::isfinite(norm) || !std::isfinite(inverse_norm) ||
!std::isfinite(condition) || !(condition >= std::ldexp(1.0, -32))) { !std::isfinite(condition) || !(condition >= std::ldexp(1.0, -32))) {
@@ -662,6 +618,26 @@ Status HiddenEvidence(const Domain& domain, const DynamicAssembly& assembly,
} // namespace } // 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, double OrderedNullMetric(const SparseMatrix& metric, const std::size_t node,
const Vector3& direction) { const Vector3& direction) {
Vector left{9U}; Vector left{9U};
+28 -11
View File
@@ -151,9 +151,24 @@ Result<std::vector<RuntimeShell>> BuildRuntimeShells(const Domain& domain,
*material, density); *material, density);
if (!inertia.HasValue()) if (!inertia.HasValue())
return Result<std::vector<RuntimeShell>>::Failure(inertia.GetStatus()); return Result<std::vector<RuntimeShell>>::Failure(inertia.GetStatus());
shells.push_back({static_cast<EntityIndex>(index), auto internal_kernel =
dofs.ShellElementScatter(static_cast<EntityIndex>(index)), std::make_unique<NonlinearMitc4Shell>(std::move(internal.Value()));
std::move(internal.Value()), std::move(inertia.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)); return Result<std::vector<RuntimeShell>>::Success(std::move(shells));
} }
@@ -431,8 +446,8 @@ Result<DynamicAssembly> EvaluateAssembly(
} }
const auto before = retain_constraints ? ReadTrialEvaluationContext() const auto before = retain_constraints ? ReadTrialEvaluationContext()
: TrialEvaluationContext{}; : TrialEvaluationContext{};
auto f = EvaluateInternalResidualAndTangent( auto f = shells[e].internal->EvaluateTrial(
shells[e].internal, trial, directional_third ? &v : nullptr); trial, directional_third ? &v : nullptr);
if (!f.HasValue()) { if (!f.HasValue()) {
failures[e] = f.GetStatus(); failures[e] = f.GetStatus();
return; return;
@@ -441,7 +456,7 @@ Result<DynamicAssembly> EvaluateAssembly(
const auto after = ReadTrialEvaluationContext(); const auto after = ReadTrialEvaluationContext();
if (ReusableTrialContext(before, after)) evaluation_contexts[e] = after; 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()) { if (!m.HasValue()) {
failures[e] = m.GetStatus(); failures[e] = m.GetStatus();
return; return;
@@ -499,7 +514,7 @@ Result<DynamicAssembly> EvaluateAssembly(
external_done[node] = true; external_done[node] = true;
const auto first = 6U * node + 3U; const auto first = 6U * node + 3U;
const auto external = const auto external =
shells[e].internal.EvaluateExternalDrillingConstraint( shells[e].internal->EvaluateExternalDrillingConstraint(
local, Triple(frame.generalized_coordinates, first), local, Triple(frame.generalized_coordinates, first),
Triple(frame.velocity, first), Triple(frame.velocity, first),
Triple(frame.external_force, first), {}, {}); Triple(frame.external_force, first), {}, {});
@@ -660,7 +675,7 @@ Result<DynamicAssembly> EvaluateAssembly(
record.scatter = shells[e].scatter; record.scatter = shells[e].scatter;
record.configuration = record.configuration =
nonlinear_mitc4_internal::ConfigurationSnapshot::Read( nonlinear_mitc4_internal::ConfigurationSnapshot::Read(
shells[e].internal); *shells[e].internal_kernel);
record.evaluation_context = evaluation_contexts[e]; record.evaluation_context = evaluation_contexts[e];
for (std::size_t i = 0U; i < 24U; ++i) { for (std::size_t i = 0U; i < 24U; ++i) {
record.q[i] = frame.generalized_coordinates[shells[e].scatter[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 auto first = 6U * node;
const Vector3 arm = Vector3(domain.Nodes()[node].coordinates) + const Vector3 arm = Vector3(domain.Nodes()[node].coordinates) +
Triple(frame->generalized_coordinates, first); Triple(frame->generalized_coordinates, first);
const Matrix transform = InverseTransposeLeftJacobianSO3( const Vector3 phi = Triple(frame->generalized_coordinates, first + 3U);
Triple(frame->generalized_coordinates, first + 3U));
for (const Vector* values : {&frame->external_force, &frame->reaction}) { for (const Vector* values : {&frame->external_force, &frame->reaction}) {
const Vector3 force = Triple(*values, first); 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_force = total_force + force;
total_moment = total_moment + arm.Cross(force) + moment; total_moment = total_moment + arm.Cross(force) + moment;
force_scale += force.Norm(); force_scale += force.Norm();
+12 -2
View File
@@ -4,11 +4,13 @@
#include <array> #include <array>
#include <cstdint> #include <cstdint>
#include <filesystem> #include <filesystem>
#include <memory>
#include <optional> #include <optional>
#include <vector> #include <vector>
#include "fesa/analysis/dynamic_analysis_state.h" #include "fesa/analysis/dynamic_analysis_state.h"
#include "fesa/analysis/hht_time_integration.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_inertia.h"
#include "fesa/elements/nonlinear_mitc4_shell.h" #include "fesa/elements/nonlinear_mitc4_shell.h"
#include "fesa/math/sparse_matrix.h" #include "fesa/math/sparse_matrix.h"
@@ -25,8 +27,10 @@ namespace hht_internal {
struct RuntimeShell { struct RuntimeShell {
EntityIndex element_index; EntityIndex element_index;
std::array<std::size_t, 24> scatter; std::array<std::size_t, 24> scatter;
NonlinearMitc4Shell internal; std::unique_ptr<NonlinearMitc4Shell> internal_kernel;
NonlinearMitc4Inertia inertia; 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. /// @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, Status CheckLinearSolve(const SparseMatrix& matrix, const Vector& rhs,
const Vector& solution); 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. /// @brief Finds analytic admissible node null directions without rank cuts.
std::vector<std::size_t> NullNodes( std::vector<std::size_t> NullNodes(
const DofManager& dofs, 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 <string>
#include <utility> #include <utility>
#include "fesa/core/source_identity.h"
#include "fesa/solvers/linear/tangent_solver.h" #include "fesa/solvers/linear/tangent_solver.h"
#include "math/dense_blas_internal.h" #include "math/dense_blas_internal.h"
#include "math/high_low_arithmetic.h" #include "math/high_low_arithmetic.h"
#include "math/finite_values.h"
#include "math/compensated_sum.h"
namespace fesa::hht_reduced_internal { namespace fesa::hht_reduced_internal {
namespace { namespace {
using RateSum = math_internal::CompensatedSum;
Status Failure(const std::string& message, Status Failure(const std::string& message,
const std::string& code = "dynamic-invalid-state") { const std::string& code = "dynamic-invalid-state") {
return Status::Failure(FailureCategory::kSolver, return Status::Failure(FailureCategory::kSolver,
@@ -25,27 +30,9 @@ Status Failure(const std::string& message,
} }
bool Finite(const Vector& values) { bool Finite(const Vector& values) {
for (std::size_t i = 0U; i < values.Size(); ++i) return math_internal::IsFinite(values);
if (!std::isfinite(values[i])) return false;
return true;
} }
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) { bool ExactlyEqual(const Vector& left, const Vector& right) {
if (left.Size() != right.Size()) return false; if (left.Size() != right.Size()) return false;
for (std::size_t i = 0U; i < left.Size(); ++i) 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; 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. /// @brief Finds only the current complete directional response, otherwise miss.
const hht_internal::DirectionalShellConstraintData* MatchingConstraints( const hht_internal::DirectionalShellConstraintData* MatchingConstraints(
const Domain& domain, const hht_internal::RuntimeShell& shell, const Domain& domain, const hht_internal::RuntimeShell& shell,
@@ -98,7 +78,7 @@ const hht_internal::DirectionalShellConstraintData* MatchingConstraints(
hht_internal::ReadTrialEvaluationContext())) hht_internal::ReadTrialEvaluationContext()))
return nullptr; return nullptr;
const auto configuration = 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 || if (record.configuration.source.file != configuration.source.file ||
record.configuration.source.line != configuration.source.line || record.configuration.source.line != configuration.source.line ||
record.configuration.identity != configuration.identity) record.configuration.identity != configuration.identity)
@@ -116,7 +96,7 @@ const hht_internal::DirectionalShellConstraintData* MatchingConstraints(
for (std::size_t node = 0U; node < 4U; ++node) { for (std::size_t node = 0U; node < 4U; ++node) {
const auto global_node = shell.scatter[6U * node] / 6U; const auto global_node = shell.scatter[6U * node] / 6U;
if (global_node >= domain.Nodes().size() || if (global_node >= domain.Nodes().size() ||
!SameSource(record.node_source_ids[node], !SourceIdentityEquals(record.node_source_ids[node],
domain.Nodes()[global_node].source_id)) domain.Nodes()[global_node].source_id))
return nullptr; return nullptr;
} }
@@ -534,7 +514,7 @@ Result<ConstraintOperators> EvaluateConstraintOperators(
std::optional<NonlinearMitc4InternalEquilibrium> fresh; std::optional<NonlinearMitc4InternalEquilibrium> fresh;
if (saved == nullptr) { if (saved == nullptr) {
auto internal = auto internal =
EvaluateInternalResidualAndTangent(shell.internal, Trial(q), &v); shell.internal->EvaluateTrial(Trial(q), &v);
if (!internal.HasValue()) if (!internal.HasValue())
return Result<ConstraintOperators>::Failure(internal.GetStatus()); return Result<ConstraintOperators>::Failure(internal.GetStatus());
fresh = std::move(internal.Value()); fresh = std::move(internal.Value());
@@ -572,7 +552,7 @@ Result<ConstraintOperators> EvaluateConstraintOperators(
if (external_done[global_node]) continue; if (external_done[global_node]) continue;
external_done[global_node] = true; external_done[global_node] = true;
const auto first = 6U * global_node + 3U; 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], local_node, Vector3{q[6U * local_node + 3U],
q[6U * local_node + 4U], q[6U * local_node + 4U],
q[6U * local_node + 5U]}, q[6U * local_node + 5U]},
@@ -587,7 +567,7 @@ Result<ConstraintOperators> EvaluateConstraintOperators(
force_acceleration[first + 2U]}); force_acceleration[first + 2U]});
if (!external.HasValue()) if (!external.HasValue())
return Result<ConstraintOperators>::Failure(external.GetStatus()); 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], local_node, Vector3{q[6U * local_node + 3U],
q[6U * local_node + 4U], q[6U * local_node + 4U],
q[6U * local_node + 5U]}, q[6U * local_node + 5U]},
@@ -596,7 +576,7 @@ Result<ConstraintOperators> EvaluateConstraintOperators(
if (!rate_term.HasValue()) if (!rate_term.HasValue())
return Result<ConstraintOperators>::Failure(rate_term.GetStatus()); return Result<ConstraintOperators>::Failure(rate_term.GetStatus());
const auto acceleration_term = const auto acceleration_term =
shell.internal.EvaluateExternalDrillingConstraint( shell.internal->EvaluateExternalDrillingConstraint(
local_node, Vector3{q[6U * local_node + 3U], local_node, Vector3{q[6U * local_node + 3U],
q[6U * local_node + 4U], q[6U * local_node + 4U],
q[6U * local_node + 5U]}, q[6U * local_node + 5U]},
@@ -1321,21 +1301,10 @@ Result<double> CheckGraphRegularity(
if (!sparse.HasValue()) return Result<double>::Failure(sparse.GetStatus()); if (!sparse.HasValue()) return Result<double>::Failure(sparse.GetStatus());
Status status = solver.FactorizeFresh(sparse.Value()); Status status = solver.FactorizeFresh(sparse.Value());
if (!status.IsOk()) return Result<double>::Failure(status); 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; double inverse_norm = 0.0;
for (std::size_t row = 0U; row < size; ++row) status = hht_internal::AccumulateCheckedCanonicalNorm(
inverse_norm = std::max(inverse_norm, inverse_sums[row]); sparse.Value(), solver, inverse_norm);
if (!status.IsOk()) return Result<double>::Failure(status);
const double condition = 1.0 / norm / inverse_norm; const double condition = 1.0 / norm / inverse_norm;
if (!std::isfinite(norm) || !std::isfinite(inverse_norm) || if (!std::isfinite(norm) || !std::isfinite(inverse_norm) ||
!std::isfinite(condition) || !(condition >= std::ldexp(1.0, -32))) { !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()); if (!sparse.HasValue()) return Result<double>::Failure(sparse.GetStatus());
auto status = solver.FactorizeFresh(sparse.Value()); auto status = solver.FactorizeFresh(sparse.Value());
if (!status.IsOk()) return Result<double>::Failure(status); if (!status.IsOk()) return Result<double>::Failure(status);
Vector inverse_rows{rows.size()}; status = hht_internal::AccumulateCheckedCanonicalNorm(
for (std::size_t column = 0U; column < rows.size(); ++column) { sparse.Value(), solver, inverse_norm);
Vector rhs{rows.size()}; if (!status.IsOk()) return Result<double>::Failure(status);
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]);
} }
const double condition = 1.0 / norm / inverse_norm; const double condition = 1.0 / norm / inverse_norm;
if (!std::isfinite(condition) || condition < std::ldexp(1.0, -32)) 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_procedure.h"
#include "analysis/hht_reduced_integrator.h"
#include "equilibrium_algorithm.h"
#include <algorithm> #include <algorithm>
#include <cmath> #include <cmath>
@@ -296,12 +299,83 @@ Status CheckReducedUpdates(const Domain& domain,
return Status::Ok(); return Status::Ok();
} }
struct ReducedHistory { // Private nonvirtual primitive: keep velocity assignment before acceleration
Vector p{0U}; // evaluation, including per-index position retries. No accepted state changes.
Vector velocity{0U}; void EvaluateReducedShellRate(double predictor_velocity,
Vector acceleration{0U}; double algorithmic_acceleration,
Vector algorithmic_acceleration{0U}; double old_acceleration, double h,
Vector z{0U}; 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 { struct Trial {
@@ -320,6 +394,17 @@ Result<DynamicExecutionResult> ExecuteReducedHht(
const Domain& domain, NewtonIterationLogWriter& log, const Domain& domain, NewtonIterationLogWriter& log,
const ParallelFor& parallel_for, LinearSolver& positive_solver, const ParallelFor& parallel_for, LinearSolver& positive_solver,
TangentSolver& tangent_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) if (domain.Dynamic() == nullptr)
return Result<DynamicExecutionResult>::Failure(Status::Failure( return Result<DynamicExecutionResult>::Failure(Status::Failure(
FailureCategory::kModel, {{Severity::kError, "invalid-dynamic-step", {}, FailureCategory::kModel, {{Severity::kError, "invalid-dynamic-step", {},
@@ -433,7 +518,7 @@ Result<DynamicExecutionResult> ExecuteReducedHht(
tangent_solver); tangent_solver);
if (!root0.HasValue()) return Result<DynamicExecutionResult>::Failure( if (!root0.HasValue()) return Result<DynamicExecutionResult>::Failure(
WithProcedureContext(domain, root0.GetStatus(), "inner-position", 0U, 0.0)); 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); history.acceleration, tangent_solver);
if (!rates0.HasValue()) return Result<DynamicExecutionResult>::Failure( if (!rates0.HasValue()) return Result<DynamicExecutionResult>::Failure(
WithProcedureContext(domain, rates0.GetStatus(), "inner-rates", 0U, 0.0)); 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(); status = log.Write(record); if (status.IsOk()) status = log.Flush();
if (!status.IsOk()) return Result<DynamicExecutionResult>::Failure(status); 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(); const auto& old = state.CommittedState();
Vector pp{history.p.Size()}, vpp{history.p.Size()}; const auto report_failure = [&](const Status& failure, const char* stage) {
for (std::size_t i = 0U; i < history.p.Size(); ++i) { const Status contextual = WithProcedureContext(
pp[i] = history.p[i] + h * history.velocity[i] + h * h * (0.5 - parameters.beta) * history.algorithmic_acceleration[i]; domain, failure, stage, increment, record.time);
vpp[i] = history.velocity[i] + h * (1.0 - parameters.gamma) * history.algorithmic_acceleration[i]; record.stage = stage;
} record.outcome = "failed";
DynamicAcceptedFrameState trial_frame = old; record.failure_code = contextual.Diagnostics().empty()
trial_frame.is_initial = false; trial_frame.frame_index = increment; ? "dynamic-solver-failure"
trial_frame.increment_index = increment; trial_frame.time = static_cast<double>(increment) * h; : contextual.Diagnostics().front().code;
trial_frame.time_integration_formulation = DynamicTimeIntegrationFormulation::kReducedCoordinateHht; const Status written = log.Write(record);
Vector trial_algorithmic{history.p.Size()}; return written.IsOk() ? contextual : written;
Vector trial_p = pp; };
Vector coordinate_low{history.p.Size()}; status = RunReducedHhtStep(
Vector chosen_start_p = pp; integrator, history, h, parameters,
Vector increment_trace{history.p.Size()}; [&](const ShellHhtIntegrator& step_integrator,
Vector increment_trace_compensation{history.p.Size()}; ReducedShellPredictor predictor) -> Result<ReducedStepCandidate> {
Vector increment_trace_absolute_sum{history.p.Size()}; Vector pp = std::move(predictor.pp);
for (std::size_t i = 0U; i < history.p.Size(); ++i) { Vector vpp = std::move(predictor.vpp);
const double velocity_term = h * history.velocity[i]; DynamicAcceptedFrameState trial_frame = old;
const double old_acceleration_term = trial_frame.is_initial = false;
h * h * (0.5 - parameters.beta) * history.algorithmic_acceleration[i]; trial_frame.frame_index = increment;
const double first_sum = history.p[i] + velocity_term; trial_frame.increment_index = increment;
coordinate_low[i] = AdditionLowPart(history.p[i], velocity_term, first_sum) + trial_frame.time = static_cast<double>(increment) * h;
AdditionLowPart(first_sum, old_acceleration_term, pp[i]); trial_frame.time_integration_formulation =
AddIncrementTraceTerm(velocity_term, &increment_trace[i], DynamicTimeIntegrationFormulation::kReducedCoordinateHht;
&increment_trace_compensation[i]); Vector trial_algorithmic{history.p.Size()};
AddIncrementTraceTerm(old_acceleration_term, &increment_trace[i], Vector trial_p = pp;
&increment_trace_compensation[i]); Vector coordinate_low = std::move(predictor.coordinate_low);
increment_trace_absolute_sum[i] = Vector chosen_start_p = pp;
std::abs(velocity_term) + std::abs(old_acceleration_term); Vector increment_trace = std::move(predictor.increment_trace);
} Vector increment_trace_compensation =
Vector seed = history.z; std::move(predictor.increment_trace_compensation);
record = {}; record.increment_index = increment; record.time = trial_frame.time; record.correction_pass = true; Vector increment_trace_absolute_sum =
bool translation_private_algorithmic_pass = true; std::move(predictor.increment_trace_absolute_sum);
bool copy_translation_rates = false; Vector seed = history.z;
Vector retained_velocity{history.p.Size()}; record = {};
Vector retained_acceleration{history.p.Size()}; record.increment_index = increment;
bool accepted = false; record.time = trial_frame.time;
for (std::uint32_t iteration = 0U; record.correction_pass = true;
iteration <= kMaximumOuterCorrections; ++iteration) { bool translation_private_algorithmic_pass = true;
record.iteration_index = iteration; record.stage = "evaluation"; record.outcome = "candidate"; bool copy_translation_rates = false;
const auto fail = [&](const Status& failure, Vector retained_velocity{history.p.Size()};
const char* stage = "outer") -> Result<DynamicExecutionResult> { Vector retained_acceleration{history.p.Size()};
const Status contextual = WithProcedureContext( using StepResult = Result<std::optional<ReducedStepCandidate>>;
domain, failure, stage, increment, trial_frame.time); const auto write_iteration =
record.stage = stage; record.outcome = "failed"; [&](const DynamicNewtonIterationLogRecord& value) {
record.failure_code = contextual.Diagnostics().empty() return log.Write(value);
? "dynamic-solver-failure" };
: contextual.Diagnostics().front().code; ReducedShellHhtStep<ReducedStepCandidate> iteration_step(
const Status written = log.Write(record); [&](std::uint32_t iteration) -> StepResult {
return Result<DynamicExecutionResult>::Failure( record.iteration_index = iteration;
written.IsOk() ? contextual : written); record.stage = "evaluation";
}; record.outcome = "candidate";
Vector trial_v{history.p.Size()}, trial_a{history.p.Size()}; const auto fail = [&](const Status& failure,
for (std::size_t i = 0U; i < trial_p.Size(); ++i) { const char* stage = "outer") -> StepResult {
if (copy_translation_rates && PhysicalCoordinateChannel(chart.Value(), i) == 0U) { return StepResult::Failure(report_failure(failure, stage));
trial_v[i] = retained_velocity[i]; };
trial_a[i] = retained_acceleration[i]; ReducedShellTrialRates reduced_rates{
} else { Vector{history.p.Size()}, Vector{history.p.Size()}};
trial_v[i] = vpp[i] + parameters.gamma * h * trial_algorithmic[i]; step_integrator.EvaluateRates(
trial_a[i] = (trial_algorithmic[i] + parameters.alpha * history.acceleration[i]) / (1.0 + parameters.alpha); {chart.Value(), history, trial_algorithmic, vpp,
} copy_translation_rates, retained_velocity, retained_acceleration,
} h, parameters}, reduced_rates.velocity,
const auto evaluate_trial = [&](const Vector& physical_p, reduced_rates.acceleration);
const Vector& physical_v, Vector trial_v = std::move(reduced_rates.velocity);
const Vector& physical_a, Vector trial_a = std::move(reduced_rates.acceleration);
const Vector& root_seed) -> Result<Trial> { const auto evaluate_trial =
auto root = SolveEmbeddingRoot(chart.Value(), physical_p, root_seed, [&](const Vector& physical_p, const Vector& physical_v,
[&](const Vector& q) { return position_operators(trial_frame, q); }, const Vector& physical_a,
tangent_solver); const Vector& root_seed) -> Result<Trial> {
if (!root.HasValue()) return Result<Trial>::Failure( auto root = SolveEmbeddingRoot(
WithProcedureContext(domain, root.GetStatus(), "inner-position", chart.Value(), physical_p, root_seed,
increment, trial_frame.time)); [&](const Vector& q) {
auto rates = ReconstructPhysicalRates(chart.Value(), root.Value(), physical_v, return position_operators(trial_frame, q);
physical_a, tangent_solver); },
if (!rates.HasValue()) return Result<Trial>::Failure( tangent_solver);
WithProcedureContext(domain, rates.GetStatus(), "inner-rates", if (!root.HasValue())
increment, trial_frame.time)); return Result<Trial>::Failure(WithProcedureContext(
auto candidate = trial_frame; domain, root.GetStatus(), "inner-position", increment,
candidate.generalized_coordinates = root.Value().q; trial_frame.time));
candidate.velocity = rates.Value().velocity; auto rates = step_integrator.ReconstructPhysicalRates(
candidate.acceleration = rates.Value().acceleration; chart.Value(), root.Value(), physical_v, physical_a,
candidate.algorithmic_acceleration = Vector(dofs.FullDofCount()); tangent_solver);
for (std::size_t i = 0U; i < candidate.algorithmic_acceleration.Size(); ++i) if (!rates.HasValue())
candidate.algorithmic_acceleration[i] = return Result<Trial>::Failure(
(1.0 + parameters.alpha) * candidate.acceleration[i] - WithProcedureContext(domain, rates.GetStatus(), "inner-rates",
parameters.alpha * old.acceleration[i]; increment, trial_frame.time));
candidate.iteration_count = iteration; auto candidate = trial_frame;
auto assembly = EvaluateAssembly(domain, dofs, shells.Value(), candidate, candidate.generalized_coordinates = root.Value().q;
loads.Value()[1U], h, parameters, candidate.velocity = rates.Value().velocity;
parallel_for, true); candidate.acceleration = rates.Value().acceleration;
if (!assembly.HasValue()) return Result<Trial>::Failure( candidate.algorithmic_acceleration = Vector(dofs.FullDofCount());
WithProcedureContext(domain, assembly.GetStatus(), "outer", for (std::size_t i = 0U;
increment, trial_frame.time)); i < candidate.algorithmic_acceleration.Size(); ++i)
assembly.Value().frame.time_integration_formulation = candidate.algorithmic_acceleration[i] =
DynamicTimeIntegrationFormulation::kReducedCoordinateHht; (1.0 + parameters.alpha) * candidate.acceleration[i] -
return Result<Trial>::Success( parameters.alpha * old.acceleration[i];
{std::move(assembly.Value()), std::move(root.Value()), physical_p, candidate.iteration_count = iteration;
physical_v, physical_a, trial_algorithmic, auto assembly = EvaluateAssembly(domain, dofs, shells.Value(),
std::move(rates.Value().z_velocity), std::move(rates.Value().z_acceleration)}); candidate, loads.Value()[1U], h,
}; parameters, parallel_for, true);
position_geometry_failure = false; if (!assembly.HasValue())
auto trial = evaluate_trial(trial_p, trial_v, trial_a, seed); return Result<Trial>::Failure(
if (!trial.HasValue() && iteration == 0U && position_geometry_failure) { WithProcedureContext(domain, assembly.GetStatus(), "outer",
for (std::size_t i = 0U; i < trial_p.Size(); ++i) { increment, trial_frame.time));
trial_algorithmic[i] = assembly.Value().frame.time_integration_formulation =
(history.p[i] - pp[i]) / (parameters.beta * h * h); DynamicTimeIntegrationFormulation::kReducedCoordinateHht;
trial_p[i] = history.p[i]; return Result<Trial>::Success(
chosen_start_p[i] = history.p[i]; {std::move(assembly.Value()), std::move(root.Value()),
coordinate_low[i] = 0.0; physical_p, physical_v, physical_a, trial_algorithmic,
increment_trace[i] = 0.0; std::move(rates.Value().z_velocity),
increment_trace_compensation[i] = 0.0; std::move(rates.Value().z_acceleration)});
increment_trace_absolute_sum[i] = 0.0; };
trial_v[i] = vpp[i] + parameters.gamma * h * trial_algorithmic[i]; position_geometry_failure = false;
trial_a[i] = (trial_algorithmic[i] + auto trial = evaluate_trial(trial_p, trial_v, trial_a, seed);
parameters.alpha * history.acceleration[i]) / if (!trial.HasValue() && iteration == 0U &&
(1.0 + parameters.alpha); position_geometry_failure) {
} for (std::size_t i = 0U; i < trial_p.Size(); ++i) {
trial = evaluate_trial(trial_p, trial_v, trial_a, seed); trial_algorithmic[i] =
} (history.p[i] - pp[i]) / (parameters.beta * h * h);
if (!trial.HasValue()) return fail(trial.GetStatus()); trial_p[i] = history.p[i];
if (iteration == 0U && !ExactlyEqual(trial_p, history.p)) { chosen_start_p[i] = history.p[i];
Vector old_algorithmic{trial_p.Size()}; coordinate_low[i] = 0.0;
Vector old_velocity{trial_p.Size()}; increment_trace[i] = 0.0;
Vector old_acceleration{trial_p.Size()}; increment_trace_compensation[i] = 0.0;
for (std::size_t i = 0U; i < trial_p.Size(); ++i) { increment_trace_absolute_sum[i] = 0.0;
old_algorithmic[i] = (history.p[i] - pp[i]) / (parameters.beta * h * h); }
old_velocity[i] = vpp[i] + parameters.gamma * h * old_algorithmic[i]; step_integrator.EvaluateRates(
old_acceleration[i] = (old_algorithmic[i] + parameters.alpha * history.acceleration[i]) / {chart.Value(), history, trial_algorithmic, vpp, false,
(1.0 + parameters.alpha); retained_velocity, retained_acceleration, h, parameters},
} trial_v, trial_a);
auto old_trial = evaluate_trial(history.p, old_velocity, old_acceleration, seed); trial = evaluate_trial(trial_p, trial_v, trial_a, seed);
if (!old_trial.HasValue()) return fail(old_trial.GetStatus()); }
const auto reduced_residual = [&](const Trial& value) { if (!trial.HasValue())
double norm = 0.0; return fail(trial.GetStatus());
for (const auto full : dofs.FreeDofs()) { if (iteration == 0U && !ExactlyEqual(trial_p, history.p)) {
const double scale = full % 6U < 3U ? length : 1.0; Vector old_algorithmic{trial_p.Size()};
norm = (std::max)(norm, Vector old_velocity{trial_p.Size()};
scale * std::abs(value.assembly.frame.residual[full])); Vector old_acceleration{trial_p.Size()};
} for (std::size_t i = 0U; i < trial_p.Size(); ++i) {
if (!std::isfinite(norm)) return std::numeric_limits<double>::infinity(); old_algorithmic[i] =
return norm; (history.p[i] - pp[i]) / (parameters.beta * h * h);
}; }
const double old_residual = reduced_residual(old_trial.Value()); step_integrator.EvaluateRates(
const double predictor_residual = reduced_residual(trial.Value()); {chart.Value(), history, old_algorithmic, vpp, false,
if (!std::isfinite(old_residual) || !std::isfinite(predictor_residual)) retained_velocity, retained_acceleration, h, parameters},
return fail(Failure(domain, "dynamic-invalid-state", old_velocity, old_acceleration);
"The scaled initial reduced residual is nonfinite.")); auto old_trial =
if (old_residual < predictor_residual) { evaluate_trial(history.p, old_velocity, old_acceleration, seed);
trial = std::move(old_trial); if (!old_trial.HasValue())
trial_p = history.p; return fail(old_trial.GetStatus());
chosen_start_p = history.p; const auto reduced_residual = [&](const Trial& value) {
coordinate_low = Vector(history.p.Size()); double norm = 0.0;
increment_trace = Vector(history.p.Size()); for (const auto full : dofs.FreeDofs()) {
increment_trace_compensation = Vector(history.p.Size()); const double scale = full % 6U < 3U ? length : 1.0;
increment_trace_absolute_sum = Vector(history.p.Size()); norm = (std::max)(norm,
trial_v = old_velocity; scale *
trial_a = old_acceleration; std::abs(
trial_algorithmic = old_algorithmic; value.assembly.frame.residual[full]));
} }
} if (!std::isfinite(norm))
auto candidate = trial.Value().assembly.frame; return std::numeric_limits<double>::infinity();
auto operators = EvaluateConstraintOperators(domain, dofs, shells.Value(), return norm;
trial.Value().assembly, chart.Value(), zero, zero); };
if (!operators.HasValue()) return fail(operators.GetStatus()); const double old_residual = reduced_residual(old_trial.Value());
ResidualRecord(dofs, candidate, &record); const double predictor_residual = reduced_residual(trial.Value());
if (record.residual_pass && record.correction_pass) { if (!std::isfinite(old_residual) ||
status = CompleteConstraintEvidence(domain, dofs, trial.Value().assembly, length, null_nodes, !std::isfinite(predictor_residual))
positive_solver, tangent_solver, &candidate); return fail(
if (!status.IsOk()) return fail(status, "accepted"); Failure(domain, "dynamic-invalid-state",
status = CompleteEquilibrium(domain, dofs, trial.Value().assembly, &candidate); "The scaled initial reduced residual is nonfinite."));
if (!status.IsOk()) return fail(status, "accepted"); if (old_residual < predictor_residual) {
graph_regularity = CheckGraphRegularity(trial.Value().assembly, trial = std::move(old_trial);
chart.Value(), operators.Value(), trial_p = history.p;
tangent_solver); chosen_start_p = history.p;
if (!graph_regularity.HasValue()) coordinate_low = Vector(history.p.Size());
return fail(graph_regularity.GetStatus(), "accepted"); increment_trace = Vector(history.p.Size());
status = CheckReducedUpdates(domain, old, candidate, pp, vpp, trial_p, trial_v, increment_trace_compensation = Vector(history.p.Size());
trial_a, trial.Value().root.z, increment_trace_absolute_sum = Vector(history.p.Size());
trial.Value().algebraic_velocity, trial_v = old_velocity;
trial.Value().algebraic_acceleration, trial_a = old_acceleration;
trial_algorithmic, history.p, history.velocity, trial_algorithmic = old_algorithmic;
history.acceleration, }
history.algorithmic_acceleration, }
increment_trace, auto candidate = trial.Value().assembly.frame;
increment_trace_compensation, auto operators = EvaluateConstraintOperators(
increment_trace_absolute_sum, domain, dofs, shells.Value(), trial.Value().assembly,
chart.Value(), chosen_start_p, coordinate_low, chart.Value(), zero, zero);
parameters, h); if (!operators.HasValue())
if (!status.IsOk()) return fail(status, "accepted"); return fail(operators.GetStatus());
candidate.external_work = 0.0; ResidualRecord(dofs, candidate, &record);
for (std::size_t i = 0U; i < dofs.FullDofCount(); ++i) if (record.residual_pass && record.correction_pass) {
candidate.external_work += candidate.external_force[i] * candidate.generalized_coordinates[i]; status = CompleteConstraintEvidence(
candidate.energy_defect = candidate.kinetic_energy + candidate.physical_strain_energy + domain, dofs, trial.Value().assembly, length, null_nodes,
candidate.drilling_energy - initial_energy - candidate.external_work; positive_solver, tangent_solver, &candidate);
status = state.CommitAcceptedFrame(candidate); if (!status.IsOk())
if (!status.IsOk()) return fail(status, "accepted"); return fail(status, "accepted");
history = {trial_p, trial_v, trial_a, trial_algorithmic, trial.Value().root.z}; status = CompleteEquilibrium(domain, dofs, trial.Value().assembly,
record.stage = "accepted"; record.outcome = "accepted"; &candidate);
status = log.Write(record); if (status.IsOk()) status = log.Flush(); if (!status.IsOk())
if (!status.IsOk()) return Result<DynamicExecutionResult>::Failure(status); return fail(status, "accepted");
accepted = true; break; graph_regularity =
} CheckGraphRegularity(trial.Value().assembly, chart.Value(),
if (!CanApplyOuterCorrection(iteration)) { operators.Value(), tangent_solver);
double full_hidden = 0.0; if (!graph_regularity.HasValue())
double analytic_hidden = 0.0; return fail(graph_regularity.GetStatus(), "accepted");
double inertia_hidden = 0.0; status = CheckReducedUpdates(
double consistency = 0.0; domain, old, candidate, pp, vpp, trial_p, trial_v, trial_a,
for (std::size_t row = 0U; row < chart.Value().null_nodes.size(); ++row) { trial.Value().root.z, trial.Value().algebraic_velocity,
const auto node = chart.Value().null_nodes[row]; trial.Value().algebraic_acceleration, trial_algorithmic,
const auto& null = trial.Value().assembly.rotations[node].drill_null; history.p, history.velocity, history.acceleration,
double full = 0.0; history.algorithmic_acceleration, increment_trace,
double inertia = 0.0; increment_trace_compensation, increment_trace_absolute_sum,
for (std::size_t component = 0U; component < 3U; ++component) { chart.Value(), chosen_start_p, coordinate_low, parameters, h);
const auto full_dof = 6U * node + 3U + component; if (!status.IsOk())
full += null[component] * candidate.residual[full_dof]; return fail(status, "accepted");
inertia += null[component] * candidate.inertia_force[full_dof]; candidate.external_work = 0.0;
} for (std::size_t i = 0U; i < dofs.FullDofCount(); ++i)
const double analytic = operators.Value().rows[row].value; candidate.external_work += candidate.external_force[i] *
full_hidden = (std::max)(full_hidden, std::abs(full)); candidate.generalized_coordinates[i];
analytic_hidden = (std::max)(analytic_hidden, std::abs(analytic)); candidate.energy_defect = candidate.kinetic_energy +
inertia_hidden = (std::max)(inertia_hidden, std::abs(inertia)); candidate.physical_strain_energy +
consistency = (std::max)(consistency, std::abs(full - analytic - inertia)); candidate.drilling_energy -
} initial_energy - candidate.external_work;
std::ostringstream message; return StepResult::Success(ReducedStepCandidate{
const auto maximum = [](const Vector& values) { std::move(candidate),
double result = 0.0; {trial_p, trial_v, trial_a, trial_algorithmic,
for (std::size_t i = 0U; i < values.Size(); ++i) trial.Value().root.z}});
result = (std::max)(result, std::abs(values[i])); }
return result; if (!CanApplyOuterCorrection(iteration)) {
}; double full_hidden = 0.0;
Vector position_correction{trial_algorithmic.Size()}; double analytic_hidden = 0.0;
Vector velocity_correction{trial_algorithmic.Size()}; double inertia_hidden = 0.0;
for (std::size_t i = 0U; i < trial_algorithmic.Size(); ++i) { double consistency = 0.0;
position_correction[i] = parameters.beta * h * h * trial_algorithmic[i]; for (std::size_t row = 0U; row < chart.Value().null_nodes.size();
velocity_correction[i] = parameters.gamma * h * trial_algorithmic[i]; ++row) {
} const auto node = chart.Value().null_nodes[row];
message << "HHT exhausted 100 corrections without satisfying every endpoint check; " const auto& null =
<< "stage=outer, outer_iteration=" << iteration trial.Value().assembly.rotations[node].drill_null;
<< ", graph_iteration=" << trial.Value().root.graph_iteration_count double full = 0.0;
<< ", graph_residual=" << trial.Value().root.graph_residual_infinity double inertia = 0.0;
<< ", graph_relative=" << trial.Value().root.graph_residual_relative for (std::size_t component = 0U; component < 3U; ++component) {
<< ", graph_correction=" << trial.Value().root.graph_correction_infinity const auto full_dof = 6U * node + 3U + component;
<< ", max_NtE=" << full_hidden full += null[component] * candidate.residual[full_dof];
<< ", max_analytic_psi=" << analytic_hidden inertia += null[component] * candidate.inertia_force[full_dof];
<< ", max_NtInertia=" << inertia_hidden }
<< ", max_NtE_minus_psi_minus_NtInertia=" << consistency const double analytic = operators.Value().rows[row].value;
<< ", max_pp=" << maximum(pp) full_hidden = (std::max)(full_hidden, std::abs(full));
<< ", max_beta_h2_trial_Ar=" << maximum(position_correction) analytic_hidden = (std::max)(analytic_hidden, std::abs(analytic));
<< ", max_trial_p=" << maximum(trial_p) inertia_hidden = (std::max)(inertia_hidden, std::abs(inertia));
<< ", max_vp_predictor=" << maximum(vpp) consistency =
<< ", max_gamma_h_trial_Ar=" << maximum(velocity_correction) (std::max)(consistency, std::abs(full - analytic - inertia));
<< ", max_trial_v=" << maximum(trial_v) }
<< ", max_physical_v=" << maximum(trial.Value().velocity) << "."; std::ostringstream message;
return fail(Failure(domain, "dynamic-nonconvergence", message.str())); const auto maximum = [](const Vector& values) {
} double result = 0.0;
status = log.Write(record); for (std::size_t i = 0U; i < values.Size(); ++i)
if (!status.IsOk()) return Result<DynamicExecutionResult>::Failure(status); result = (std::max)(result, std::abs(values[i]));
const double velocity_rate = parameters.gamma / (parameters.beta * h); return result;
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); Vector position_correction{trial_algorithmic.Size()};
if (!system.HasValue()) return fail(system.GetStatus()); Vector velocity_correction{trial_algorithmic.Size()};
const bool angular_only = ShouldRefineAngular( for (std::size_t i = 0U; i < trial_algorithmic.Size(); ++i) {
chart.Value(), candidate.free_residual_metrics, position_correction[i] =
{record.coordinate_correction[0U], record.velocity_correction[0U], parameters.beta * h * h * trial_algorithmic[i];
record.acceleration_correction[0U], record.algorithmic_correction[0U]}, velocity_correction[i] =
translation_private_algorithmic_pass); parameters.gamma * h * trial_algorithmic[i];
auto selected = SelectAugmentedSolve(system.Value(), chart.Value(), angular_only); }
if (!selected.HasValue()) return fail(selected.GetStatus()); message << "HHT exhausted 100 corrections without satisfying every "
const auto linear_failure = [&](const Status& failure) { "endpoint check; "
return fail(failure, angular_only ? "angular-newton-linear-solve" << "stage=outer, outer_iteration=" << iteration
: "full-newton-linear-solve"); << ", graph_iteration="
}; << trial.Value().root.graph_iteration_count
Vector selected_solution{selected.Value().rhs.Size()}; << ", graph_residual="
if (selected_solution.Size() > 0U) { << trial.Value().root.graph_residual_infinity
status = tangent_solver.FactorizeFresh(selected.Value().tangent); << ", graph_relative="
if (!status.IsOk()) return linear_failure(status); << trial.Value().root.graph_residual_relative
status = tangent_solver.SolveCorrection(selected.Value().rhs, selected_solution); << ", graph_correction="
if (!status.IsOk()) return linear_failure(status); << trial.Value().root.graph_correction_infinity
status = CheckLinearSolve(selected.Value().tangent, selected.Value().rhs, selected_solution); << ", max_NtE=" << full_hidden
if (!status.IsOk()) return linear_failure(status); << ", max_analytic_psi=" << analytic_hidden
} << ", max_NtInertia=" << inertia_hidden
auto solution = EmbedAugmentedSolution(selected.Value(), selected_solution, << ", max_NtE_minus_psi_minus_NtInertia=" << consistency
system.Value().rhs.Size()); << ", max_pp=" << maximum(pp)
if (!solution.HasValue()) return fail(solution.GetStatus()); << ", max_beta_h2_trial_Ar=" << maximum(position_correction)
auto correction = MapAugmentedCorrection(chart.Value(), system.Value(), solution.Value(), velocity_rate, acceleration_rate); << ", max_trial_p=" << maximum(trial_p)
if (!correction.HasValue()) return fail(correction.GetStatus()); << ", max_vp_predictor=" << maximum(vpp)
Vector full_algorithmic{dofs.FullDofCount()}; << ", max_gamma_h_trial_Ar=" << maximum(velocity_correction)
Vector reduced_algorithmic{correction.Value().p.Size()}; << ", max_trial_v=" << maximum(trial_v)
Vector reduced_physical{correction.Value().p.Size()}; << ", max_physical_v=" << maximum(trial.Value().velocity)
for (std::size_t i = 0U; i < full_algorithmic.Size(); ++i) << ".";
full_algorithmic[i] = return fail(
(1.0 + parameters.alpha) * correction.Value().acceleration[i]; Failure(domain, "dynamic-nonconvergence", message.str()));
for (std::size_t i = 0U; i < reduced_algorithmic.Size(); ++i) { }
reduced_algorithmic[i] = correction.Value().p[i] / status = write_iteration(record);
(parameters.beta * h * h); if (!status.IsOk())
reduced_physical[i] = acceleration_rate * correction.Value().p[i]; return StepResult::Failure(status);
} const double velocity_rate = parameters.gamma / (parameters.beta * h);
CorrectionRecord(correction.Value().coordinate, correction.Value().velocity, const double acceleration_rate =
correction.Value().acceleration, full_algorithmic, 1.0 / ((1.0 + parameters.alpha) * parameters.beta * h * h);
reduced_algorithmic, reduced_physical, chart.Value(), dofs, length, h, auto system = AssembleAugmentedReducedSystem(
parameters.alpha, trial.Value().assembly, chart.Value(), operators.Value(),
&record); velocity_rate, acceleration_rate, length, h);
translation_private_algorithmic_pass = true; if (!system.HasValue())
for (std::size_t i = 0U; i < chart.Value().p_dofs.size(); ++i) return fail(system.GetStatus());
if (PhysicalCoordinateChannel(chart.Value(), i) == 0U) const bool angular_only = ShouldRefineAngular(
translation_private_algorithmic_pass = chart.Value(), candidate.free_residual_metrics,
translation_private_algorithmic_pass && {record.coordinate_correction[0U], record.velocity_correction[0U],
PrivateCorrectionEqual(chart.Value(), i, full_algorithmic, record.acceleration_correction[0U],
reduced_algorithmic, reduced_physical, parameters.alpha); record.algorithmic_correction[0U]},
copy_translation_rates = angular_only; translation_private_algorithmic_pass);
if (angular_only) { auto selected =
retained_velocity = trial_v; SelectAugmentedSolve(system.Value(), chart.Value(), angular_only);
retained_acceleration = trial_a; if (!selected.HasValue())
} return fail(selected.GetStatus());
for (std::size_t i = 0U; i < trial_algorithmic.Size(); ++i) { const auto linear_failure = [&](const Status& failure) {
if (angular_only && PhysicalCoordinateChannel(chart.Value(), i) == 0U) continue; return fail(failure, angular_only ? "angular-newton-linear-solve"
trial_algorithmic[i] += reduced_algorithmic[i]; : "full-newton-linear-solve");
} };
for (std::size_t i = 0U; i < trial_p.Size(); ++i) { Vector selected_solution{selected.Value().rhs.Size()};
if (angular_only && PhysicalCoordinateChannel(chart.Value(), i) == 0U) continue; if (selected_solution.Size() > 0U) {
AdvanceCoordinate(correction.Value().p[i], &trial_p[i], &coordinate_low[i]); status = tangent_solver.FactorizeFresh(selected.Value().tangent);
} if (!status.IsOk())
for (std::size_t i = 0U; i < increment_trace.Size(); ++i) { return linear_failure(status);
if (angular_only && PhysicalCoordinateChannel(chart.Value(), i) == 0U) continue; status = tangent_solver.SolveCorrection(selected.Value().rhs,
AddIncrementTraceTerm(correction.Value().p[i], &increment_trace[i], selected_solution);
&increment_trace_compensation[i]); if (!status.IsOk())
increment_trace_absolute_sum[i] += std::abs(correction.Value().p[i]); return linear_failure(status);
} status = CheckLinearSolve(selected.Value().tangent,
seed = trial.Value().root.z; selected.Value().rhs, selected_solution);
for (std::size_t i = 0U; i < seed.Size(); ++i) if (!status.IsOk())
seed[i] += correction.Value().z[i]; return linear_failure(status);
} }
if (!accepted) return Result<DynamicExecutionResult>::Failure( auto solution = EmbedAugmentedSolution(
Failure(domain, "dynamic-nonconvergence", "Dynamic increment was not accepted.")); 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 <cstdint>
#include "fesa/analysis/prepared_analysis.h" #include "fesa/analysis/dynamic_execution_result.h"
#include "fesa/core/status.h"
namespace fesa { namespace fesa {
namespace hht_reduced_internal { namespace hht_reduced_internal {
class ShellHhtIntegrator;
constexpr std::uint32_t kMaximumOuterCorrections = 100U; constexpr std::uint32_t kMaximumOuterCorrections = 100U;
constexpr bool CanApplyOuterCorrection(const std::uint32_t completed) { constexpr bool CanApplyOuterCorrection(const std::uint32_t completed) {
return completed < kMaximumOuterCorrections; return completed < kMaximumOuterCorrections;
} }
} // namespace hht_reduced_internal } // namespace hht_reduced_internal
class Domain;
class EquilibriumAlgorithm;
class LinearSolver; class LinearSolver;
class NewtonIterationLogWriter; class NewtonIterationLogWriter;
class ParallelFor; class ParallelFor;
@@ -22,6 +26,14 @@ Result<DynamicExecutionResult> ExecuteReducedHht(
const Domain& domain, NewtonIterationLogWriter& log, const Domain& domain, NewtonIterationLogWriter& log,
const ParallelFor& parallel_for, LinearSolver& positive_solver, const ParallelFor& parallel_for, LinearSolver& positive_solver,
TangentSolver& tangent_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 } // namespace fesa
#endif // FESA_ANALYSIS_HHT_REDUCED_PROCEDURE_H_ #endif // FESA_ANALYSIS_HHT_REDUCED_PROCEDURE_H_
@@ -20,6 +20,7 @@
#include "fesa/elements/mitc4_shell.h" #include "fesa/elements/mitc4_shell.h"
#include "fesa/fem/dof_manager.h" #include "fesa/fem/dof_manager.h"
#include "fesa/math/sparse_matrix.h" #include "fesa/math/sparse_matrix.h"
#include "fesa/solvers/eigen/mkl_feast_eigen_solver.h"
namespace fesa { namespace fesa {
namespace { namespace {
@@ -84,7 +85,8 @@ double MaximumEdgeLength(const Domain& domain) {
return maximum; return maximum;
} }
Result<EigenAnalysisState> ExecuteCandidate(const Domain& domain) { Result<EigenAnalysisState> ExecuteCandidate(const Domain& domain,
const EigenSolver& solver) {
const FrequencyStepDefinition* frequency = domain.Frequency(); const FrequencyStepDefinition* frequency = domain.Frequency();
if (frequency == nullptr || domain.Steps().Size() != 1U) { if (frequency == nullptr || domain.Steps().Size() != 1U) {
return Result<EigenAnalysisState>::Failure( return Result<EigenAnalysisState>::Failure(
@@ -101,6 +103,7 @@ Result<EigenAnalysisState> ExecuteCandidate(const Domain& domain) {
return Result<EigenAnalysisState>::Failure(model.GetStatus()); return Result<EigenAnalysisState>::Failure(model.GetStatus());
const ElementFactory factory; const ElementFactory factory;
std::vector<std::unique_ptr<Element>> owned; std::vector<std::unique_ptr<Element>> owned;
std::vector<std::unique_ptr<ShellReferenceElement>> references;
ElementView view; ElementView view;
owned.reserve(model.Value().ActiveElements().size()); owned.reserve(model.Value().ActiveElements().size());
view.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); auto element = factory.Create(domain.Elements()[index], domain);
if (!element.HasValue()) if (!element.HasValue())
return Result<EigenAnalysisState>::Failure(element.GetStatus()); 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( return Result<EigenAnalysisState>::Failure(Failure(
"unsupported-frequency-element", std::to_string(index), "unsupported-frequency-element", std::to_string(index),
"The frequency procedure supports MITC4 shell elements only.")); "The frequency procedure supports MITC4 shell elements only."));
} }
view.emplace_back(*element.Value()); view.emplace_back(*element.Value());
owned.push_back(std::move(element.Value())); owned.push_back(std::move(element.Value()));
references.push_back(std::move(reference));
} }
DofManager dofs; DofManager dofs;
const Status dof_status = dofs.Build(model.Value(), view); const Status dof_status = dofs.Build(model.Value(), view);
@@ -158,7 +163,7 @@ Result<EigenAnalysisState> ExecuteCandidate(const Domain& domain) {
std::to_string(definition.MaterialIndex()), std::to_string(definition.MaterialIndex()),
"Every active shell material requires a positive density.")); "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 mass = element->Mass(density[definition.MaterialIndex()]);
auto shell_stiffness = element->Stiffness(); auto shell_stiffness = element->Stiffness();
if (!mass.HasValue()) if (!mass.HasValue())
@@ -207,7 +212,7 @@ Result<EigenAnalysisState> ExecuteCandidate(const Domain& domain) {
(material->YoungsModulus() / density[material_id]) / (material->YoungsModulus() / density[material_id]) /
length / length); length / length);
} }
auto backend = MklFeastEigenSolver{}.Solve( auto backend = solver.Solve(
reduction.Value().whitened_operator, request, zero_scale); reduction.Value().whitened_operator, request, zero_scale);
if (!backend.HasValue()) if (!backend.HasValue())
return Result<EigenAnalysisState>::Failure(backend.GetStatus()); return Result<EigenAnalysisState>::Failure(backend.GetStatus());
@@ -403,7 +408,8 @@ Result<EigenAnalysisState> ExecuteCandidate(const Domain& domain) {
Result<EigenAnalysisState> LinearFrequencyProcedureExecutor::Execute( Result<EigenAnalysisState> LinearFrequencyProcedureExecutor::Execute(
const Domain& domain) const { const Domain& domain) const {
try { try {
auto result = ExecuteCandidate(domain); const MklFeastEigenSolver default_solver;
auto result = ExecuteCandidate(domain, solver_ ? *solver_ : default_solver);
if (!result.HasValue()) { if (!result.HasValue()) {
auto diagnostics = result.GetStatus().Diagnostics(); auto diagnostics = result.GetStatus().Diagnostics();
for (auto& diagnostic : diagnostics) { for (auto& diagnostic : diagnostics) {
@@ -142,7 +142,7 @@ Result<LinearStaticExecutionResult> LinearStaticProcedureExecutor::Execute(
} }
state.ExternalForce() = std::move(nodal_external.Value()); 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(), state.ExternalForce(), partitions.Value().k_fc, dofs.PrescribedValues(),
dofs); dofs);
if (!rhs.HasValue()) { if (!rhs.HasValue()) {
+10 -57
View File
@@ -1,6 +1,5 @@
#include "fesa/analysis/nonlinear_analysis_state.h" #include "fesa/analysis/nonlinear_analysis_state.h"
#include <algorithm>
#include <array> #include <array>
#include <cmath> #include <cmath>
#include <cstddef> #include <cstddef>
@@ -9,11 +8,18 @@
#include <utility> #include <utility>
#include <vector> #include <vector>
#include "fesa/core/source_identity.h"
#include "fesa/elements/element_definition.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 fesa {
namespace { namespace {
using analysis_internal::ShellElementOrder;
using math_internal::IsFinite;
constexpr std::size_t kDofsPerNode = 6U; constexpr std::size_t kDofsPerNode = 6U;
constexpr std::size_t kShellLocationsPerElement = 4U; constexpr std::size_t kShellLocationsPerElement = 4U;
constexpr std::uint32_t kMaximumNewtonIterations = 100U; constexpr std::uint32_t kMaximumNewtonIterations = 100U;
@@ -27,59 +33,6 @@ Status StateFailure(const FailureCategory category, const std::string& code,
message}}); 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. /// @brief Validates one accepted frame before any committed-state mutation.
Status ValidateAcceptedFrame(const AcceptedFrameState& frame, Status ValidateAcceptedFrame(const AcceptedFrameState& frame,
const NonlinearTrialState& committed, const NonlinearTrialState& committed,
@@ -117,7 +70,7 @@ Status ValidateAcceptedFrame(const AcceptedFrameState& frame,
for (std::size_t node = 0U; node < node_count; ++node) { for (std::size_t node = 0U; node < node_count; ++node) {
if (frame.nodal_internal_order[node] != if (frame.nodal_internal_order[node] !=
committed.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]) || committed.nodal_source_ids[node]) ||
!IsFinite(frame.nodal_triad[node][0U]) || !IsFinite(frame.nodal_triad[node][0U]) ||
!IsFinite(frame.nodal_triad[node][1U]) || !IsFinite(frame.nodal_triad[node][1U]) ||
@@ -179,7 +132,7 @@ Status ValidateAcceptedFrame(const AcceptedFrameState& frame,
if (row.element != shell_order[element] || if (row.element != shell_order[element] ||
row.location != expected_locations[point] || row.location != expected_locations[point] ||
row.natural_coordinates != expected_coordinates[point] || row.natural_coordinates != expected_coordinates[point] ||
!IsFinite(row)) { !results_internal::IsFiniteShellRow(row)) {
return StateFailure(FailureCategory::kSolver, "invalid-accepted-state", {}, return StateFailure(FailureCategory::kSolver, "invalid-accepted-state", {},
"Shell result rows do not preserve stable identity."); "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) { for (std::size_t node = 0U; node < node_count; ++node) {
const auto& entry = baseline.node_baselines[node]; const auto& entry = baseline.node_baselines[node];
if (entry.node_index != static_cast<EntityIndex>(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[0U]) || !IsFinite(entry.triad[1U]) ||
!IsFinite(entry.triad[2U]) || !IsFinite(entry.director)) { !IsFinite(entry.triad[2U]) || !IsFinite(entry.director)) {
return Result<NonlinearAnalysisState>::Failure(StateFailure( return Result<NonlinearAnalysisState>::Failure(StateFailure(
+13 -46
View File
@@ -12,10 +12,17 @@
#include "fesa/analysis/analysis_model.h" #include "fesa/analysis/analysis_model.h"
#include "fesa/assembly/load_assembler.h" #include "fesa/assembly/load_assembler.h"
#include "fesa/elements/finite_rotation.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 fesa {
namespace { namespace {
using shell_kernel_internal::FrameMatrix;
using math_internal::Multiply;
using math_internal::Skew;
constexpr std::size_t kDofsPerNode = 6U; constexpr std::size_t kDofsPerNode = 6U;
constexpr std::size_t kTranslationDofCount = 3U; constexpr std::size_t kTranslationDofCount = 3U;
constexpr double kMomentAdmissibilityThreshold = 1.0e-12; constexpr double kMomentAdmissibilityThreshold = 1.0e-12;
@@ -67,27 +74,6 @@ Result<Vector3> RotationVectorForNode(const Vector& trial,
return Result<Vector3>::Success(rotation); 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. /// @brief Multiplies a 3-by-3 matrix transpose by a Vector3.
Vector3 MultiplyTranspose(const Matrix& matrix, const Vector3& vector) { Vector3 MultiplyTranspose(const Matrix& matrix, const Vector3& vector) {
return {matrix(0U, 0U) * vector.X() + matrix(1U, 0U) * vector.Y() + 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()}; 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. /// @brief Scales a small dense matrix without changing its shape.
Matrix Scale(const Matrix& matrix, const double scale) { Matrix Scale(const Matrix& matrix, const double scale) {
Matrix result{matrix.Rows(), matrix.Columns()}; Matrix result{matrix};
for (std::size_t row = 0U; row < matrix.Rows(); ++row) { result.Scale(scale);
for (std::size_t column = 0U; column < matrix.Columns(); ++column) {
result(row, column) = scale * matrix(row, column);
}
}
return result; return result;
} }
/// @brief Adds matching small dense matrices. /// @brief Adds matching small dense matrices.
Matrix Add(const Matrix& left, const Matrix& right) { Matrix Add(const Matrix& left, const Matrix& right) {
Matrix result{left.Rows(), left.Columns()}; Matrix result{left};
for (std::size_t row = 0U; row < left.Rows(); ++row) { dense_blas_internal::AxpyValues(1.0, right.Data(),
for (std::size_t column = 0U; column < left.Columns(); ++column) { left.Rows() * left.Columns(), result.Data());
result(row, column) = left(row, column) + right(row, column);
}
}
return result; return result;
} }
@@ -472,7 +439,7 @@ Result<GlobalFixedLoadContribution> AssembleGlobalFixedLoadContribution(
return Result<GlobalFixedLoadContribution>::Failure(phi.GetStatus()); return Result<GlobalFixedLoadContribution>::Failure(phi.GetStatus());
} }
auto rotation = auto rotation =
MakeFiniteRotationState(phi.Value(), InitialFrame(*frames[node])); MakeFiniteRotationState(phi.Value(), FrameMatrix(*frames[node]));
if (!rotation.HasValue()) { if (!rotation.HasValue()) {
return Result<GlobalFixedLoadContribution>::Failure(rotation.GetStatus()); 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 "fesa/analysis/nonlinear_static_procedure.h"
#include "equilibrium_algorithm.h"
#include "analysis/nonlinear_static_core.h"
#include <algorithm> #include <algorithm>
#include <array> #include <array>
#include <cmath> #include <cmath>
#include <cstddef> #include <cstddef>
#include <cstdint> #include <cstdint>
#include <limits> #include <limits>
#include <memory>
#include <optional> #include <optional>
#include <string> #include <string>
#include <utility> #include <utility>
@@ -17,6 +21,7 @@
#include "fesa/assembly/parallel_for.h" #include "fesa/assembly/parallel_for.h"
#include "fesa/assembly/surface_pressure_assembler.h" #include "fesa/assembly/surface_pressure_assembler.h"
#include "fesa/constraints/essential_constraint_policy.h" #include "fesa/constraints/essential_constraint_policy.h"
#include "fesa/elements/element_capabilities.h"
#include "fesa/elements/finite_rotation.h" #include "fesa/elements/finite_rotation.h"
#include "fesa/elements/mitc4_shell.h" #include "fesa/elements/mitc4_shell.h"
#include "fesa/elements/nonlinear_mitc4_shell.h" #include "fesa/elements/nonlinear_mitc4_shell.h"
@@ -25,9 +30,16 @@
#include "fesa/properties/shell_section.h" #include "fesa/properties/shell_section.h"
#include "fesa/solvers/linear/tangent_solver.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 fesa {
namespace { namespace {
using shell_kernel_internal::FrameMatrix;
using math_internal::IsFinite;
constexpr std::size_t kDofsPerNode = 6U; constexpr std::size_t kDofsPerNode = 6U;
constexpr std::size_t kTranslationsPerNode = 3U; constexpr std::size_t kTranslationsPerNode = 3U;
constexpr std::size_t kShellDofCount = 24U; constexpr std::size_t kShellDofCount = 24U;
@@ -36,7 +48,8 @@ constexpr std::uint32_t kMaximumNewtonIterations = 100U;
struct RuntimeShell { struct RuntimeShell {
EntityIndex element_index; EntityIndex element_index;
std::array<std::size_t, kShellDofCount> scatter; std::array<std::size_t, kShellDofCount> scatter;
NonlinearMitc4Shell element; std::unique_ptr<NonlinearMitc4Shell> kernel;
std::unique_ptr<ShellTrialElement> element;
}; };
struct InternalAssembly { struct InternalAssembly {
@@ -124,16 +137,6 @@ Status RecordFailure(NewtonIterationLogWriter& log,
return log_status.IsOk() ? failure : log_status; 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<DofManager> BuildDofManager(const Domain& domain) {
Result<AnalysisModel> model = AnalysisModel::Create(domain); Result<AnalysisModel> model = AnalysisModel::Create(domain);
if (!model.HasValue()) { if (!model.HasValue()) {
@@ -239,9 +242,20 @@ Result<std::vector<RuntimeShell>> BuildRuntimeShells(const Domain& domain,
return Result<std::vector<RuntimeShell>>::Failure(shell.GetStatus()); return Result<std::vector<RuntimeShell>>::Failure(shell.GetStatus());
} }
const EntityIndex element_index = static_cast<EntityIndex>(index); 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( shells.push_back(
{element_index, dofs.ShellElementScatter(element_index), {element_index, scatter, std::move(kernel), std::move(element)});
std::move(shell.Value())});
} }
return Result<std::vector<RuntimeShell>>::Success(std::move(shells)); 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 + 4U],
trial->generalized_coordinates[first + 5U]}; trial->generalized_coordinates[first + 5U]};
Result<FiniteRotationState> rotation = MakeFiniteRotationState( Result<FiniteRotationState> rotation = MakeFiniteRotationState(
phi, InitialFrame(domain.ShellNodeInitialFrames()[node])); phi, FrameMatrix(domain.ShellNodeInitialFrames()[node]));
if (!rotation.HasValue()) { if (!rotation.HasValue()) {
return rotation.GetStatus(); return rotation.GetStatus();
} }
@@ -304,26 +318,6 @@ NonlinearMitc4TrialDofs ExtractElementTrial(
return trial; 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( Result<InternalAssembly> AssembleInternal(
const std::vector<RuntimeShell>& shells, const DofManager& dofs, const std::vector<RuntimeShell>& shells, const DofManager& dofs,
const Vector& coordinates, const ParallelFor& parallel_for) { const Vector& coordinates, const ParallelFor& parallel_for) {
@@ -332,9 +326,8 @@ Result<InternalAssembly> AssembleInternal(
std::vector<std::optional<Status>> failures(shells.size()); std::vector<std::optional<Status>> failures(shells.size());
parallel_for.Execute(shells.size(), [&](const std::size_t index) { parallel_for.Execute(shells.size(), [&](const std::size_t index) {
Result<NonlinearMitc4InternalEquilibrium> result = Result<NonlinearMitc4InternalEquilibrium> result =
EvaluateInternalResidualAndTangent(shells[index].element, shells[index].element->EvaluateTrial(
ExtractElementTrial(shells[index], ExtractElementTrial(shells[index], coordinates));
coordinates));
if (!result.HasValue()) { if (!result.HasValue()) {
failures[index] = result.GetStatus(); failures[index] = result.GetStatus();
return; return;
@@ -363,12 +356,9 @@ Result<InternalAssembly> AssembleInternal(
} }
for (std::size_t row = 0U; row < kShellDofCount; ++row) { for (std::size_t row = 0U; row < kShellDofCount; ++row) {
force[shells[index].scatter[row]] += local[index]->internal_residual[row]; force[shells[index].scatter[row]] += local[index]->internal_residual[row];
for (std::size_t column = 0U; column < kShellDofCount; ++column) { assembly_internal::AppendLocalMatrixRow(
contributions.push_back( local[index]->internal_tangent, shells[index].scatter, row, index,
{shells[index].scatter[row], shells[index].scatter[column], contributions);
local[index]->internal_tangent(row, column), index,
row * kShellDofCount + column});
}
} }
for (const NonlinearMitc4RecoveryPoint& point : for (const NonlinearMitc4RecoveryPoint& point :
local[index]->recovery.gauss_points) { local[index]->recovery.gauss_points) {
@@ -573,19 +563,6 @@ Vector CurrentIncrementComponents(const Vector& current, const Vector& start,
return SelectFreeComponents(difference, dofs, rotations); 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. /// @brief Computes accepted-state applied-plus-reaction balance evidence.
Result<std::array<double, 6>> GlobalEquilibrium( Result<std::array<double, 6>> GlobalEquilibrium(
const Domain& domain, const DofManager& dofs, 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] ? trial.residual[first] : 0.0,
constrained[first + 1U] ? trial.residual[first + 1U] : 0.0, constrained[first + 1U] ? trial.residual[first + 1U] : 0.0,
constrained[first + 2U] ? trial.residual[first + 2U] : 0.0}; constrained[first + 2U] ? trial.residual[first + 2U] : 0.0};
const Matrix transform = InverseTransposeLeftJacobianSO3(phi);
const std::array<double, 3> applied_generalized{ const std::array<double, 3> applied_generalized{
trial.external_force[first + 3U], trial.external_force[first + 4U], trial.external_force[first + 3U], trial.external_force[first + 4U],
trial.external_force[first + 5U]}; 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 + 3U] ? trial.residual[first + 3U] : 0.0,
constrained[first + 4U] ? trial.residual[first + 4U] : 0.0, constrained[first + 4U] ? trial.residual[first + 4U] : 0.0,
constrained[first + 5U] ? trial.residual[first + 5U] : 0.0}; constrained[first + 5U] ? trial.residual[first + 5U] : 0.0};
const Vector3 applied_moment = SpatialMoment(transform, applied_generalized); const auto applied = SpatialMomentSO3(phi, Vector3(applied_generalized));
const Vector3 reaction_moment = const auto reaction = SpatialMomentSO3(phi, Vector3(reaction_generalized));
SpatialMoment(transform, 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_force_resultant = applied_force_resultant + applied_force;
applied_moment_resultant = applied_moment_resultant =
applied_moment_resultant + position.Cross(applied_force) + applied_moment; applied_moment_resultant + position.Cross(applied_force) + applied_moment;
@@ -694,11 +677,11 @@ Status ApplyIncrementConstraints(const StepDefinition& step,
return Status::Ok(); return Status::Ok();
} }
AcceptedFrameState AcceptedFrame(const NonlinearAnalysisState& state, AcceptedFrameState AcceptedFrame(const std::uint32_t frame_index,
const NonlinearTrialState& trial, const NonlinearTrialState& trial,
const LoadFactorIncrement& increment, const LoadFactorIncrement& increment,
const std::uint32_t iteration) { const std::uint32_t iteration) {
return {static_cast<std::uint32_t>(state.AcceptedFrames().size()), return {frame_index,
increment.increment_index, increment.increment_index,
increment.load_factor, increment.load_factor,
iteration, iteration,
@@ -728,6 +711,15 @@ NonlinearStaticProcedureExecutor::NonlinearStaticProcedureExecutor(
Result<NonlinearStaticExecutionResult> Result<NonlinearStaticExecutionResult>
NonlinearStaticProcedureExecutor::Execute( NonlinearStaticProcedureExecutor::Execute(
const Domain& domain, NewtonIterationLogWriter& log) const { 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()) { if (domain.Steps().Size() != 1U || !domain.Steps()[0U].IsNonlinearStatic()) {
return Result<NonlinearStaticExecutionResult>::Failure(ProcedureFailure( return Result<NonlinearStaticExecutionResult>::Failure(ProcedureFailure(
FailureCategory::kInput, "invalid-nonlinear-static-data", FailureCategory::kInput, "invalid-nonlinear-static-data",
@@ -782,199 +774,237 @@ NonlinearStaticProcedureExecutor::Execute(
return Result<NonlinearStaticExecutionResult>::Failure(constraint_status); return Result<NonlinearStaticExecutionResult>::Failure(constraint_status);
} }
for (std::uint32_t iteration = 1U; iteration <= kMaximumNewtonIterations; struct ShellStepCandidate {
++iteration) { AcceptedFrameState frame;
NewtonIterationLogRecord record = NewtonIterationLogRecord record;
NewLogRecord(step, increment, iteration); };
trial.Value().increment_index = increment.increment_index; const auto next_frame_index =
trial.Value().load_factor = increment.load_factor; static_cast<std::uint32_t>(state.Value().AcceptedFrames().size());
trial.Value().is_factor_zero_baseline = false; 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()); Status kinematics = PopulateNodalKinematics(domain, &trial.Value());
if (!kinematics.IsOk()) { if (!kinematics.IsOk()) {
return Result<NonlinearStaticExecutionResult>::Failure( return Result<std::optional<ShellStepCandidate>>::Failure(
RecordFailure(log, &record, kinematics, report_failure(&record, kinematics,
NewtonIterationStage::kKinematicsEvaluated)); NewtonIterationStage::kKinematicsEvaluated));
} }
record.last_completed_stage = NewtonIterationStage::kKinematicsEvaluated; record.last_completed_stage =
NewtonIterationStage::kKinematicsEvaluated;
Result<InternalAssembly> internal = AssembleInternal( Result<InternalAssembly> internal = AssembleInternal(
shells.Value(), dofs.Value(), trial.Value().generalized_coordinates, shells.Value(), dofs.Value(),
parallel_for_); trial.Value().generalized_coordinates, parallel_for);
if (!internal.HasValue()) { if (!internal.HasValue()) {
return Result<NonlinearStaticExecutionResult>::Failure(RecordFailure( return Result<std::optional<ShellStepCandidate>>::Failure(
log, &record, internal.GetStatus(), report_failure(&record, internal.GetStatus(),
NewtonIterationStage::kInternalAssembled)); NewtonIterationStage::kInternalAssembled));
} }
record.last_completed_stage = NewtonIterationStage::kInternalAssembled; record.last_completed_stage =
NewtonIterationStage::kInternalAssembled;
Result<GlobalFixedLoadContribution> fixed = Result<GlobalFixedLoadContribution> fixed =
AssembleGlobalFixedLoadContribution( AssembleGlobalFixedLoadContribution(
domain, dofs.Value(), trial.Value().generalized_coordinates, domain, dofs.Value(), trial.Value().generalized_coordinates,
increment.load_factor); increment.load_factor);
if (!fixed.HasValue()) { if (!fixed.HasValue()) {
return Result<NonlinearStaticExecutionResult>::Failure( return Result<std::optional<ShellStepCandidate>>::Failure(
RecordFailure(log, &record, fixed.GetStatus(), report_failure(&record, fixed.GetStatus(),
NewtonIterationStage::kExternalAssembled)); NewtonIterationStage::kExternalAssembled));
} }
Result<SurfacePressureAssemblyCandidate> pressure = Result<SurfacePressureAssemblyCandidate> pressure =
AssembleSurfacePressure( AssembleSurfacePressure(
analysis_model.Value(), dofs.Value(), analysis_model.Value(), dofs.Value(),
trial.Value().generalized_coordinates, increment.load_factor, trial.Value().generalized_coordinates, increment.load_factor,
SurfacePressureConfiguration::kCurrent, parallel_for_); SurfacePressureConfiguration::kCurrent, parallel_for);
if (!pressure.HasValue()) { if (!pressure.HasValue()) {
return Result<NonlinearStaticExecutionResult>::Failure( return Result<std::optional<ShellStepCandidate>>::Failure(
RecordFailure(log, &record, pressure.GetStatus(), report_failure(&record, pressure.GetStatus(),
NewtonIterationStage::kExternalAssembled)); NewtonIterationStage::kExternalAssembled));
} }
Result<GlobalFixedLoadContribution> external = Result<GlobalFixedLoadContribution> external =
CombineExternalContributions(fixed.Value(), pressure.Value(), CombineExternalContributions(fixed.Value(), pressure.Value(),
dofs.Value()); dofs.Value());
if (!external.HasValue()) { if (!external.HasValue()) {
return Result<NonlinearStaticExecutionResult>::Failure( return Result<std::optional<ShellStepCandidate>>::Failure(
RecordFailure(log, &record, external.GetStatus(), report_failure(&record, external.GetStatus(),
NewtonIterationStage::kExternalAssembled)); NewtonIterationStage::kExternalAssembled));
} }
record.last_completed_stage = NewtonIterationStage::kExternalAssembled; record.last_completed_stage =
NewtonIterationStage::kExternalAssembled;
Result<Vector> residual = Result<Vector> residual =
Difference(internal.Value().force, Difference(internal.Value().force,
external.Value().generalized_external_force); external.Value().generalized_external_force);
if (!residual.HasValue()) { if (!residual.HasValue()) {
return Result<NonlinearStaticExecutionResult>::Failure(RecordFailure( return Result<std::optional<ShellStepCandidate>>::Failure(
log, &record, residual.GetStatus(), report_failure(&record, residual.GetStatus(),
NewtonIterationStage::kExternalAssembled)); NewtonIterationStage::kExternalAssembled));
} }
Result<SparseMatrix> tangent = Result<SparseMatrix> tangent = Difference(
Difference(internal.Value().tangent, internal.Value().tangent,
external.Value().generalized_external_tangent, external.Value().generalized_external_tangent, dofs.Value());
dofs.Value()); if (!tangent.HasValue()) {
if (!tangent.HasValue()) { return Result<std::optional<ShellStepCandidate>>::Failure(
return Result<NonlinearStaticExecutionResult>::Failure(RecordFailure( report_failure(&record, tangent.GetStatus(),
log, &record, tangent.GetStatus(), NewtonIterationStage::kExternalAssembled));
NewtonIterationStage::kExternalAssembled)); }
} trial.Value().internal_force = std::move(internal.Value().force);
trial.Value().internal_force = std::move(internal.Value().force); trial.Value().external_force =
trial.Value().external_force = std::move(external.Value().generalized_external_force);
std::move(external.Value().generalized_external_force); trial.Value().residual = std::move(residual.Value());
trial.Value().residual = std::move(residual.Value()); trial.Value().reaction = trial.Value().residual;
trial.Value().reaction = trial.Value().residual; trial.Value().shell_results = std::move(internal.Value().shell_rows);
trial.Value().shell_results = std::move(internal.Value().shell_rows); trial.Value().physical_strain_energy =
trial.Value().physical_strain_energy = internal.Value().physical_strain_energy;
internal.Value().physical_strain_energy; Result<std::array<double, 6>> global_equilibrium =
Result<std::array<double, 6>> global_equilibrium = GlobalEquilibrium(domain, dofs.Value(), trial.Value());
GlobalEquilibrium(domain, dofs.Value(), trial.Value()); if (!global_equilibrium.HasValue()) {
if (!global_equilibrium.HasValue()) { return Result<std::optional<ShellStepCandidate>>::Failure(
return Result<NonlinearStaticExecutionResult>::Failure(RecordFailure( report_failure(&record, global_equilibrium.GetStatus(),
log, &record, global_equilibrium.GetStatus(), NewtonIterationStage::kExternalAssembled));
NewtonIterationStage::kExternalAssembled)); }
} trial.Value().global_equilibrium = global_equilibrium.Value();
trial.Value().global_equilibrium = global_equilibrium.Value();
Result<PartitionedStiffness> partition = Result<PartitionedStiffness> partition =
constraints.Partition(tangent.Value(), dofs.Value()); constraints.Partition(tangent.Value(), dofs.Value());
if (!partition.HasValue()) { if (!partition.HasValue()) {
return Result<NonlinearStaticExecutionResult>::Failure(RecordFailure( return Result<std::optional<ShellStepCandidate>>::Failure(
log, &record, partition.GetStatus(), report_failure(&record, partition.GetStatus(),
NewtonIterationStage::kExternalAssembled)); NewtonIterationStage::kExternalAssembled));
} }
const Status factorization = tangent_solver_.FactorizeFresh( const Status factorization =
partition.Value().k_ff); tangent_solver.FactorizeFresh(partition.Value().k_ff);
if (!factorization.IsOk()) { if (!factorization.IsOk()) {
return Result<NonlinearStaticExecutionResult>::Failure(RecordFailure( return Result<std::optional<ShellStepCandidate>>::Failure(
log, &record, factorization, report_failure(&record, factorization,
NewtonIterationStage::kSolvedCorrection)); NewtonIterationStage::kSolvedCorrection));
} }
Vector correction_rhs = constraints.GatherFree(trial.Value().residual, Vector correction_rhs =
dofs.Value()); constraints.GatherFree(trial.Value().residual, dofs.Value());
for (std::size_t index = 0U; index < correction_rhs.Size(); ++index) { for (std::size_t index = 0U; index < correction_rhs.Size(); ++index) {
correction_rhs[index] = -correction_rhs[index]; correction_rhs[index] = -correction_rhs[index];
} }
Vector correction{dofs.Value().FreeDofCount()}; Vector correction{dofs.Value().FreeDofCount()};
const Status solve = const Status solve =
tangent_solver_.SolveCorrection(correction_rhs, correction); tangent_solver.SolveCorrection(correction_rhs, correction);
if (!solve.IsOk()) { if (!solve.IsOk()) {
return Result<NonlinearStaticExecutionResult>::Failure(RecordFailure( return Result<std::optional<ShellStepCandidate>>::Failure(
log, &record, solve, NewtonIterationStage::kSolvedCorrection)); report_failure(&record, solve,
} NewtonIterationStage::kSolvedCorrection));
record.last_completed_stage = NewtonIterationStage::kSolvedCorrection; }
record.last_completed_stage = NewtonIterationStage::kSolvedCorrection;
const NewtonConvergenceResult convergence = EvaluateNewtonConvergence({ const NewtonConvergenceResult convergence = EvaluateNewtonConvergence(
{"residual_force", SelectFreeComponents(trial.Value().residual, {{"residual_force",
dofs.Value(), false), SelectFreeComponents(trial.Value().residual, dofs.Value(),
SelectFreeComponents(trial.Value().internal_force, dofs.Value(), false),
false), SelectFreeComponents(trial.Value().internal_force, dofs.Value(),
SelectFreeComponents(trial.Value().external_force, dofs.Value(), false),
false)}, SelectFreeComponents(trial.Value().external_force, dofs.Value(),
{"residual_moment", SelectFreeComponents(trial.Value().residual, false)},
dofs.Value(), true), {"residual_moment",
SelectFreeComponents(trial.Value().internal_force, dofs.Value(), SelectFreeComponents(trial.Value().residual, dofs.Value(),
true), true),
SelectFreeComponents(trial.Value().external_force, dofs.Value(), SelectFreeComponents(trial.Value().internal_force, dofs.Value(),
true)}, true),
{"correction_translation", SelectFreeComponents(trial.Value().external_force, dofs.Value(),
SelectCorrectionComponents(correction, dofs.Value(), false), true)},
CurrentIncrementComponents(trial.Value().generalized_coordinates, {"correction_translation",
increment_start, dofs.Value(), false)}, SelectCorrectionComponents(correction, dofs.Value(), false),
{"correction_rotation", CurrentIncrementComponents(
SelectCorrectionComponents(correction, dofs.Value(), true), trial.Value().generalized_coordinates, increment_start,
CurrentIncrementComponents(trial.Value().generalized_coordinates, dofs.Value(), false)},
increment_start, dofs.Value(), true)}}); {"correction_rotation",
record.force_residual = convergence.force_residual; SelectCorrectionComponents(correction, dofs.Value(), true),
record.moment_residual = convergence.moment_residual; CurrentIncrementComponents(
record.translation_correction = convergence.translation_correction; trial.Value().generalized_coordinates, increment_start,
record.rotation_correction = convergence.rotation_correction; dofs.Value(), true)}});
record.combined_passed = convergence.combined_passed; record.force_residual = convergence.force_residual;
record.last_completed_stage = NewtonIterationStage::kConvergenceEvaluated; 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) { if (convergence.combined_passed) {
const Status committed = state.Value().CommitAcceptedFrame( return Result<std::optional<ShellStepCandidate>>::Success(
AcceptedFrame(state.Value(), trial.Value(), increment, iteration)); ShellStepCandidate{AcceptedFrame(next_frame_index,
if (!committed.IsOk()) { trial.Value(), increment,
return Result<NonlinearStaticExecutionResult>::Failure(RecordFailure( iteration),
log, &record, committed, NewtonIterationStage::kAccepted)); record});
} }
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 (iteration == kMaximumNewtonIterations) { if (iteration == kMaximumNewtonIterations) {
const Status failure = ProcedureFailure( const Status failure = ProcedureFailure(
FailureCategory::kSolver, "nonlinear-nonconvergence", FailureCategory::kSolver, "nonlinear-nonconvergence",
step.Location(), "STATIC", step.Name(), step.Location(), "STATIC", step.Name(),
"The nonlinear procedure did not converge within 100 iterations."); "The nonlinear procedure did not converge within 100 "
return Result<NonlinearStaticExecutionResult>::Failure(RecordFailure( "iterations.");
log, &record, failure, return Result<std::optional<ShellStepCandidate>>::Failure(
NewtonIterationStage::kConvergenceEvaluated)); report_failure(&record, failure,
} NewtonIterationStage::kConvergenceEvaluated));
}
for (std::size_t equation = 0U; equation < dofs.Value().FreeDofs().size(); for (std::size_t equation = 0U;
++equation) { equation < dofs.Value().FreeDofs().size(); ++equation) {
const std::size_t full_dof = dofs.Value().FreeDofs()[equation]; const std::size_t full_dof = dofs.Value().FreeDofs()[equation];
trial.Value().generalized_coordinates[full_dof] += correction[equation]; trial.Value().generalized_coordinates[full_dof] +=
if (!std::isfinite(trial.Value().generalized_coordinates[full_dof])) { correction[equation];
const Status failure = ProcedureFailure( if (!std::isfinite(
FailureCategory::kSolver, "nonfinite-newton-data", {}, trial.Value().generalized_coordinates[full_dof])) {
"NONLINEAR_STATIC_PROCEDURE", std::to_string(full_dof), const Status failure = ProcedureFailure(
"A nonlinear free-coordinate correction is nonfinite."); FailureCategory::kSolver, "nonfinite-newton-data", {},
return Result<NonlinearStaticExecutionResult>::Failure(RecordFailure( "NONLINEAR_STATIC_PROCEDURE", std::to_string(full_dof),
log, &record, failure, NewtonIterationStage::kSolvedCorrection)); "A nonlinear free-coordinate correction is nonfinite.");
} return Result<std::optional<ShellStepCandidate>>::Failure(
} report_failure(&record, failure,
record.transition = "continue"; NewtonIterationStage::kSolvedCorrection));
record.outcome = "not-converged"; }
const Status log_status = WriteAndFlush(log, record); }
if (!log_status.IsOk()) { record.transition = "continue";
return Result<NonlinearStaticExecutionResult>::Failure(log_status); 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(); std::vector<Diagnostic> diagnostics = domain.Warnings();
@@ -983,4 +1013,4 @@ NonlinearStaticProcedureExecutor::Execute(
{std::move(state.Value()), std::move(diagnostics)}); {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 "fesa/analysis/prepared_analysis.h"
#include <memory>
#include <utility> #include <utility>
#include "analysis_procedures.h"
#include "fesa/analysis/analysis_model.h" #include "fesa/analysis/analysis_model.h"
#include "fesa/analysis/hht_dynamic_procedure.h" #include "fesa/analysis/hht_dynamic_procedure.h"
#include "fesa/analysis/linear_frequency_procedure.h" #include "fesa/analysis/linear_frequency_procedure.h"
@@ -9,15 +12,13 @@
#include "fesa/analysis/newton_iteration_log.h" #include "fesa/analysis/newton_iteration_log.h"
#include "fesa/analysis/nonlinear_static_procedure.h" #include "fesa/analysis/nonlinear_static_procedure.h"
#include "fesa/analysis/solid_frequency_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/app/output_paths.h"
#include "fesa/io/abaqus/domain_mapper.h" #include "fesa/io/abaqus/domain_mapper.h"
#include "fesa/io/abaqus/input_reader.h" #include "fesa/io/abaqus/input_reader.h"
#include "fesa/results/dynamic_results_writer.h" #include "fesa/results/prepared_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"
namespace fesa { namespace fesa {
@@ -39,6 +40,185 @@ Result<PreparedAnalysisInput> PrepareAnalysisInput(
return Result<PreparedAnalysisInput>::Success(std::move(prepared)); 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( Result<PreparedAnalysisOutcome> ExecutePreparedAnalysis(
const Domain& domain, LinearStaticProcedureExecutor& linear_executor, const Domain& domain, LinearStaticProcedureExecutor& linear_executor,
NonlinearStaticProcedureExecutor& nonlinear_executor, NonlinearStaticProcedureExecutor& nonlinear_executor,
@@ -49,133 +229,59 @@ Result<PreparedAnalysisOutcome> ExecutePreparedAnalysis(
const SolidStaticProcedureExecutor* solid_static_executor, const SolidStaticProcedureExecutor* solid_static_executor,
const SolidFrequencyProcedureExecutor* solid_frequency_executor, const SolidFrequencyProcedureExecutor* solid_frequency_executor,
const SolidHhtDynamicProcedureExecutor* solid_dynamic_executor) { const SolidHhtDynamicProcedureExecutor* solid_dynamic_executor) {
if (domain.ThermalStep() != nullptr) { const auto kind = SelectProcedure(domain);
if (thermal_executor == nullptr) { if (!kind.HasValue())
return Result<PreparedAnalysisOutcome>::Failure( return Result<PreparedAnalysisOutcome>::Failure(kind.GetStatus());
Status::Failure(FailureCategory::kSolver, std::unique_ptr<AnalysisProcedure> procedure;
{{Severity::kError, switch (kind.Value()) {
"missing-thermal-executor", case ProcedureKind::kLinearStatic:
{}, procedure = std::make_unique<LinearStaticProcedure>(linear_executor);
"HEAT TRANSFER", break;
{}, case ProcedureKind::kNonlinearStatic:
"Thermal execution adapter is required."}})); procedure =
} std::make_unique<NonlinearStaticProcedure>(nonlinear_executor, log);
auto thermal = thermal_executor->Execute(domain); break;
if (!thermal.HasValue()) { case ProcedureKind::kLinearFrequency:
return Result<PreparedAnalysisOutcome>::Failure(thermal.GetStatus()); procedure = std::make_unique<LinearFrequencyProcedure>(frequency_executor);
} break;
return Result<PreparedAnalysisOutcome>::Success( case ProcedureKind::kHhtDynamic:
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) {
if (dynamic_executor == nullptr) if (dynamic_executor == nullptr)
return Result<PreparedAnalysisOutcome>::Failure( return MissingExecutor("missing-dynamic-executor", "DYNAMIC",
Status::Failure(FailureCategory::kSolver, "Dynamic execution adapter is required.");
{{Severity::kError, procedure = std::make_unique<HhtDynamicProcedure>(*dynamic_executor, log);
"missing-dynamic-executor", break;
{}, case ProcedureKind::kSolidStatic:
"DYNAMIC", if (solid_static_executor == nullptr)
{}, return MissingExecutor(
"Dynamic execution adapter is required."}})); "missing-solid-executor", "",
auto dynamic = dynamic_executor->Execute(domain, log); "The selected Solid execution adapter is required.");
if (!dynamic.HasValue()) procedure =
return Result<PreparedAnalysisOutcome>::Failure(dynamic.GetStatus()); std::make_unique<SolidStaticProcedure>(*solid_static_executor, log);
return Result<PreparedAnalysisOutcome>::Success( break;
PreparedAnalysisOutcome{std::move(dynamic.Value())}); 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()) { return ExecutePreparedAnalysis(domain, *procedure);
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())});
} }
Status FinalizePreparedAnalysisResult( Status FinalizePreparedAnalysisResult(
@@ -185,68 +291,10 @@ Status FinalizePreparedAnalysisResult(
NonlinearResultsWriter& nonlinear_writer, EigenResultsWriter& eigen_writer, NonlinearResultsWriter& nonlinear_writer, EigenResultsWriter& eigen_writer,
DynamicResultsWriter* dynamic_writer, ThermalResultsWriter* thermal_writer, DynamicResultsWriter* dynamic_writer, ThermalResultsWriter* thermal_writer,
SolidResultsWriter* solid_writer) { SolidResultsWriter* solid_writer) {
if (std::holds_alternative<SolidStaticExecutionResult>(outcome) || return WritePreparedAnalysisResult(
std::holds_alternative<SolidFrequencyState>(outcome) || paths.results_path, domain, outcome, diagnostics, linear_writer,
std::holds_alternative<SolidDynamicExecutionResult>(outcome)) { nonlinear_writer, eigen_writer, dynamic_writer, thermal_writer,
if (solid_writer == nullptr) solid_writer);
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);
} }
} // namespace fesa } // 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 "fesa/analysis/solid_dynamic_analysis_state.h"
#include <algorithm>
#include <cmath> #include <cmath>
#include <stdexcept> #include <stdexcept>
#include <string> #include <string>
#include "fesa/core/source_identity.h"
#include "fesa/elements/element_factory.h" #include "fesa/elements/element_factory.h"
#include "fesa/elements/hex8_element.h" #include "fesa/elements/hex8_element.h"
#include "fesa/model/domain.h"
#include "math/high_low_arithmetic.h" #include "math/high_low_arithmetic.h"
#include "math/finite_values.h"
namespace fesa { namespace fesa {
namespace { namespace {
@@ -24,8 +26,7 @@ Status Failure(const std::string& message) {
/// @brief Checks a physical record without replacing nonfinite quantities. /// @brief Checks a physical record without replacing nonfinite quantities.
template <std::size_t N> template <std::size_t N>
bool Finite(const std::array<double, N>& values) { bool Finite(const std::array<double, N>& values) {
return std::all_of(values.begin(), values.end(), return math_internal::IsFinite(values);
[](double x) { return std::isfinite(x); });
} }
bool ValidPair(double high, double low) { bool ValidPair(double high, double low) {
const math_internal::HighLow value(high, 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; if (!ValidPair(high[i], low[i])) return false;
return true; 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 } // namespace
Result<SolidDynamicAnalysisState> SolidDynamicAnalysisState::Create( Result<SolidDynamicAnalysisState> SolidDynamicAnalysisState::Create(
@@ -83,7 +78,7 @@ Result<SolidDynamicAnalysisState> SolidDynamicAnalysisState::Create(
auto runtime = factory.Create(domain.Elements()[e], domain); auto runtime = factory.Create(domain.Elements()[e], domain);
if (!runtime.HasValue()) if (!runtime.HasValue())
return Result<SolidDynamicAnalysisState>::Failure(runtime.GetStatus()); 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) if (solid == nullptr)
return Result<SolidDynamicAnalysisState>::Failure(Failure( return Result<SolidDynamicAnalysisState>::Failure(Failure(
"Dynamic Solid inventory contains a different element family.")); "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) for (std::size_t n = 0; n < source_ids_.size(); ++n)
if (f.nodal_internal_order[n] != 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."); return Failure("Solid nodal source identity differs from its inventory.");
const std::array<const Vector*, 10> high{ const std::array<const Vector*, 10> high{
&f.displacement, &f.velocity, &f.displacement, &f.velocity,
@@ -18,6 +18,7 @@
#include "fesa/elements/hex8_element.h" #include "fesa/elements/hex8_element.h"
#include "fesa/fem/dof_manager.h" #include "fesa/fem/dof_manager.h"
#include "fesa/math/sparse_matrix.h" #include "fesa/math/sparse_matrix.h"
#include "fesa/solvers/eigen/mkl_feast_eigen_solver.h"
#include "fesa/math/vector.h" #include "fesa/math/vector.h"
#include "fesa/model/domain.h" #include "fesa/model/domain.h"
@@ -50,7 +51,8 @@ double Ratio(double numerator, double denominator) {
return numerator / denominator; return numerator / denominator;
} }
/// @brief Builds an immutable candidate; all returns precede public commit. /// @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(); const auto* frequency = domain.Frequency();
if (!domain.IsSolid() || frequency == nullptr || if (!domain.IsSolid() || frequency == nullptr ||
frequency->requested_mode_count <= 0) frequency->requested_mode_count <= 0)
@@ -76,6 +78,7 @@ Result<SolidFrequencyState> ExecuteCandidate(const Domain& domain) {
} }
ElementFactory factory; ElementFactory factory;
std::vector<std::unique_ptr<Element>> owned; std::vector<std::unique_ptr<Element>> owned;
std::vector<std::unique_ptr<SolidTrialElement>> trials;
ElementView view; ElementView view;
for (const auto index : model.Value().ActiveElements()) { for (const auto index : model.Value().ActiveElements()) {
const auto& definition = domain.Elements()[index]; const auto& definition = domain.Elements()[index];
@@ -87,11 +90,13 @@ Result<SolidFrequencyState> ExecuteCandidate(const Domain& domain) {
auto element = factory.Create(definition, domain); auto element = factory.Create(definition, domain);
if (!element.HasValue()) if (!element.HasValue())
return Result<SolidFrequencyState>::Failure(element.GetStatus()); 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( return Result<SolidFrequencyState>::Failure(
Failure("unsupported-frequency-element", "Only C3D8 is supported.")); Failure("unsupported-frequency-element", "Only C3D8 is supported."));
view.emplace_back(*element.Value()); view.emplace_back(*element.Value());
owned.push_back(std::move(element.Value())); owned.push_back(std::move(element.Value()));
trials.push_back(std::move(trial));
} }
DofManager dofs; DofManager dofs;
const Status mapping = dofs.Build(model.Value(), view); 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); std::vector<std::size_t> additions_by_dof(dofs.FullDofCount(), 0U);
constexpr double eps = std::numeric_limits<double>::epsilon(); constexpr double eps = std::numeric_limits<double>::epsilon();
for (std::size_t e = 0; e < owned.size(); ++e) { 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(); auto mass = hex.ComputeMass();
if (!mass.HasValue()) if (!mass.HasValue())
return Result<SolidFrequencyState>::Failure(mass.GetStatus()); return Result<SolidFrequencyState>::Failure(mass.GetStatus());
@@ -220,7 +225,7 @@ Result<SolidFrequencyState> ExecuteCandidate(const Domain& domain) {
return Result<SolidFrequencyState>::Failure( return Result<SolidFrequencyState>::Failure(
Failure("invalid-solid-eigen-scale", Failure("invalid-solid-eigen-scale",
"A positive finite classification scale is required.")); "A positive finite classification scale is required."));
auto backend = MklFeastEigenSolver{}.SolveGeneralized(k, m, requested, auto backend = solver.SolveGeneralized(k, m, requested,
mass_lower, scale); mass_lower, scale);
if (!backend.HasValue()) if (!backend.HasValue())
return Result<SolidFrequencyState>::Failure(backend.GetStatus()); return Result<SolidFrequencyState>::Failure(backend.GetStatus());
@@ -269,11 +274,11 @@ Result<SolidFrequencyState> ExecuteCandidate(const Domain& domain) {
"Normalized modal vector is not finite.")); "Normalized modal vector is not finite."));
} }
mv = m.Multiply(vector); mv = m.Multiply(vector);
const Vector kv = k.Multiply(vector); Vector kv = k.Multiply(vector);
const double lambda = state.backend.eigenvalues[c]; const double lambda = state.backend.eigenvalues[c];
const double modal_mass = vector.Dot(mv); const double modal_mass = vector.Dot(mv);
const double energy = vector.Dot(kv); const double energy = vector.Dot(kv);
Vector residual = kv; Vector residual = std::move(kv);
residual.Axpy(-lambda, mv); residual.Axpy(-lambda, mv);
const double norm_phi = vector.Norm(); const double norm_phi = vector.Norm();
const double residual_norm = residual.Norm(); const double residual_norm = residual.Norm();
@@ -355,7 +360,8 @@ Result<SolidFrequencyState> ExecuteCandidate(const Domain& domain) {
Result<SolidFrequencyState> SolidFrequencyProcedureExecutor::Execute( Result<SolidFrequencyState> SolidFrequencyProcedureExecutor::Execute(
const Domain& domain) const { const Domain& domain) const {
try { try {
auto result = ExecuteCandidate(domain); const MklFeastEigenSolver default_solver;
auto result = ExecuteCandidate(domain, solver_ ? *solver_ : default_solver);
if (result.HasValue()) return result; if (result.HasValue()) return result;
auto diagnostics = result.GetStatus().Diagnostics(); auto diagnostics = result.GetStatus().Diagnostics();
for (auto& diagnostic : 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 "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 <algorithm>
#include <array> #include <array>
#include <cmath> #include <cmath>
@@ -85,7 +89,7 @@ Status CheckArithmeticEnvironment(const Domain& domain) {
/// @brief Owns immutable kernels and the stable full-space translation scatter. /// @brief Owns immutable kernels and the stable full-space translation scatter.
struct RuntimeSolid { struct RuntimeSolid {
std::unique_ptr<Element> owner; std::unique_ptr<Element> owner;
const Hex8Element* solid; std::unique_ptr<SolidTrialElement> solid;
std::vector<std::size_t> scatter; std::vector<std::size_t> scatter;
}; };
/// @brief Creates checked kernels in declaration order, with no mutable element /// @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); auto owner = factory.Create(domain.Elements()[e], domain);
if (!owner.HasValue()) if (!owner.HasValue())
return Result<std::vector<RuntimeSolid>>::Failure(owner.GetStatus()); 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) if (solid == nullptr)
return Result<std::vector<RuntimeSolid>>::Failure( return Result<std::vector<RuntimeSolid>>::Failure(
Failure(domain, "solid-dynamic-element-family", Failure(domain, "solid-dynamic-element-family",
@@ -107,7 +111,7 @@ Result<std::vector<RuntimeSolid>> BuildSolids(const Domain& domain,
if (!scatter.HasValue()) if (!scatter.HasValue())
return Result<std::vector<RuntimeSolid>>::Failure(scatter.GetStatus()); return Result<std::vector<RuntimeSolid>>::Failure(scatter.GetStatus());
solids.push_back( 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)); 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.")); "Reference length is not finite positive."));
return Result<double>::Success(length); 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, Result<SparseMatrix> EffectiveTangent(const SparseMatrix& m,
const PairedTangent& k, const PairedTangent& k,
const DofManager& dofs, double h, const DofManager& dofs, double h,
@@ -549,24 +553,6 @@ bool CorrectionPass(const std::array<HighLow, 4>& metrics) {
Compare(x, HighLow(1e-10)) <= 0; 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 } // namespace
SolidHhtDynamicProcedureExecutor::SolidHhtDynamicProcedureExecutor( SolidHhtDynamicProcedureExecutor::SolidHhtDynamicProcedureExecutor(
@@ -600,6 +586,17 @@ Status SolidHhtDynamicProcedureExecutor::ExecuteInto(
Status SolidHhtDynamicProcedureExecutor::ExecuteIntoForVerification( Status SolidHhtDynamicProcedureExecutor::ExecuteIntoForVerification(
const Domain& domain, SolidDynamicAnalysisState& state, const Domain& domain, SolidDynamicAnalysisState& state,
double alpha) const { 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; SolidDynamicIterationRecord record;
auto fail = [&](const Status& failure) { auto fail = [&](const Status& failure) {
record.stage = "failed"; record.stage = "failed";
@@ -630,7 +627,7 @@ Status SolidHhtDynamicProcedureExecutor::ExecuteIntoForVerification(
auto mass_partition = partition.Partition(mass.Value(), dofs); auto mass_partition = partition.Partition(mass.Value(), dofs);
if (!mass_partition.HasValue()) return fail(mass_partition.GetStatus()); if (!mass_partition.HasValue()) return fail(mass_partition.GetStatus());
if (dofs.FreeDofCount() != 0) { 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); if (!status.IsOk()) return fail(status);
environment = CheckArithmeticEnvironment(domain); environment = CheckArithmeticEnvironment(domain);
if (!environment.IsOk()) return fail(environment); if (!environment.IsOk()) return fail(environment);
@@ -655,7 +652,7 @@ Status SolidHhtDynamicProcedureExecutor::ExecuteIntoForVerification(
if (!force.HasValue()) return fail(force.GetStatus()); if (!force.HasValue()) return fail(force.GetStatus());
initial.external_force = std::move(force.Value()); initial.external_force = std::move(force.Value());
auto tangent = 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()); if (!tangent.HasValue()) return fail(tangent.GetStatus());
SolidDynamicIterationEvidence evidence; SolidDynamicIterationEvidence evidence;
bool initial_converged = false; bool initial_converged = false;
@@ -682,7 +679,7 @@ Status SolidHhtDynamicProcedureExecutor::ExecuteIntoForVerification(
const auto i = dofs.FreeDofs()[j]; const auto i = dofs.FreeDofs()[j];
rhs[j] = Rounded(Negate(Pair(initial.residual, initial.low.residual, i))); 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); if (!status.IsOk()) return fail(status);
environment = CheckArithmeticEnvironment(domain); environment = CheckArithmeticEnvironment(domain);
if (!environment.IsOk()) return fail(environment); if (!environment.IsOk()) return fail(environment);
@@ -726,145 +723,188 @@ Status SolidHhtDynamicProcedureExecutor::ExecuteIntoForVerification(
record = {}; record = {};
record.increment_index = increment; record.increment_index = increment;
record.time = static_cast<double>(increment) * h; record.time = static_cast<double>(increment) * h;
auto predictor = detail::PredictSolidHhtHighLow(old, h, p); status = detail::RunSolidHhtStep(
if (!predictor.HasValue()) return fail(predictor.GetStatus()); integrator, old, h, p, fail,
auto candidate = old; [&](const detail::SolidHhtIntegrator& step_integrator,
candidate.frame_index = candidate.increment_index = increment; const detail::SolidHhtPairedPredictor& predictor)
candidate.iteration_count = 0; -> Result<SolidDynamicAcceptedFrameState> {
candidate.time = record.time; auto candidate = old;
candidate.is_initial = false; candidate.frame_index = candidate.increment_index = increment;
candidate.iterations.clear(); candidate.iteration_count = 0;
// Begin from certified accepted geometry, not an unchecked predictor. candidate.time = record.time;
force = LoadAssembler::AssembleFullNodalLoad(model.Value(), dofs, candidate.is_initial = false;
candidate.time); candidate.iterations.clear();
if (!force.HasValue()) return fail(force.GetStatus()); // Begin from certified accepted geometry, not an unchecked predictor.
candidate.external_force = std::move(force.Value()); force = LoadAssembler::AssembleFullNodalLoad(model.Value(), dofs,
candidate.low.external_force = Vector(dofs.FullDofCount()); candidate.time);
std::array<HighLow, 4> correction{}, fresh_correction{}; if (!force.HasValue())
bool accepted = false; return Result<SolidDynamicAcceptedFrameState>::Failure(fail(force.GetStatus()));
for (std::uint32_t iteration = 0; iteration <= 100; ++iteration) { candidate.external_force = std::move(force.Value());
record.stage = "evaluation"; candidate.low.external_force = Vector(dofs.FullDofCount());
record.outcome = "paired-candidate"; std::array<HighLow, 4> correction{}, fresh_correction{};
record.evidence = {}; using StepResult = Result<std::optional<SolidDynamicAcceptedFrameState>>;
record.evidence.iteration_index = iteration; const auto report_failure = fail;
status = SetRates(old, predictor.Value(), h, p, &candidate); const auto record_iteration = [&](const auto& value) {
if (!status.IsOk()) return fail(status); state.RecordIteration(value);
tangent = EvaluateSolids(domain, solids.Value(), dofs, parallel_for_, };
&candidate); SolidHhtStep<SolidDynamicAcceptedFrameState> iteration_step(
if (!tangent.HasValue()) return fail(tangent.GetStatus()); [&](std::uint32_t iteration) -> StepResult {
status = RecoverEndpoint(domain, dofs, mass.Value(), &old, initial_energy, const auto fail = [&](const Status& failure) {
&candidate, &record.evidence); return StepResult::Failure(report_failure(failure));
if (!status.IsOk()) return fail(status); };
auto MA = detail::MultiplySolidHhtMassHighLow( record.stage = "evaluation";
mass.Value(), candidate.algorithmic_acceleration, record.outcome = "paired-candidate";
candidate.low.algorithmic_acceleration); record.evidence = {};
if (!MA.HasValue()) return fail(MA.GetStatus()); record.evidence.iteration_index = iteration;
Vector rhs(dofs.FreeDofCount()); status =
HighLow identity_metric, original_norm, solved_norm, old_norm; detail::SetSolidHhtRates(step_integrator, old, predictor, h, p, &candidate);
for (std::size_t j = 0; j < dofs.FreeDofCount(); ++j) { if (!status.IsOk())
const auto i = dofs.FreeDofs()[j]; return fail(status);
detail::SolidHhtPairedForceIdentity identity; tangent = EvaluateSolids(domain, solids.Value(), dofs, parallel_for,
if (!detail::EvaluateSolidHhtHighLowForceIdentity( &candidate);
mass.Value(), i, Pair(MA.Value().high, MA.Value().low, i), if (!tangent.HasValue())
candidate, old, p.alpha, &identity)) return fail(tangent.GetStatus());
return fail( status =
Failure(domain, "solid-dynamic-nonfinite-residual", RecoverEndpoint(domain, dofs, mass.Value(), &old, initial_energy,
"Paired weighted force arithmetic is unsupported.")); &candidate, &record.evidence);
rhs[j] = Rounded(Negate(identity.solved)); if (!status.IsOk())
identity_metric = Maximum(identity_metric, identity.metric); return fail(status);
original_norm = Maximum(original_norm, Abs(identity.original)); auto MA = detail::MultiplySolidHhtMassHighLow(
solved_norm = Maximum(solved_norm, Abs(identity.solved)); mass.Value(), candidate.algorithmic_acceleration,
old_norm = candidate.low.algorithmic_acceleration);
Maximum(old_norm, Abs(Pair(old.residual, old.low.residual, i))); if (!MA.HasValue())
if (i == record.evidence.worst_full_dof) return fail(MA.GetStatus());
record.evidence.worst_weighted_terms = { Vector rhs(dofs.FreeDofCount());
Rounded(identity.original), Rounded(identity.solved), HighLow identity_metric, original_norm, solved_norm, old_norm;
Rounded(Pair(old.residual, old.low.residual, i)), for (std::size_t j = 0; j < dofs.FreeDofCount(); ++j) {
Rounded(identity.scale)}; const auto i = dofs.FreeDofs()[j];
} detail::SolidHhtPairedForceIdentity identity;
if (!Valid(identity_metric) || !Valid(original_norm) || if (!detail::EvaluateSolidHhtHighLowForceIdentity(
!Valid(solved_norm) || !Valid(old_norm) || mass.Value(), i, Pair(MA.Value().high, MA.Value().low, i),
Compare(identity_metric, HighLow(1e-12)) > 0) candidate, old, p.alpha, &identity))
return fail(Failure(domain, return fail(
"solid-dynamic-weighted-algebra-inconsistency", Failure(domain, "solid-dynamic-nonfinite-residual",
"Paired original and solved forces disagree beyond " "Paired weighted force arithmetic is unsupported."));
"their uncancelled scale.")); rhs[j] = Rounded(Negate(identity.solved));
record.evidence.weighted_equivalence_metric = Rounded(identity_metric); identity_metric = Maximum(identity_metric, identity.metric);
record.evidence.weighted_residual_numerator = Rounded(original_norm); original_norm = Maximum(original_norm, Abs(identity.original));
record.evidence.recombined_residual_numerator = Rounded(solved_norm); solved_norm = Maximum(solved_norm, Abs(identity.solved));
record.evidence.old_residual_numerator = Rounded(old_norm); old_norm =
for (std::size_t c = 0; c < 4; ++c) { Maximum(old_norm, Abs(Pair(old.residual, old.low.residual, i)));
if (!Valid(correction[c]) || !Valid(fresh_correction[c])) if (i == record.evidence.worst_full_dof)
return fail(detail::SolidHhtPairedKinematicsFailure()); record.evidence.worst_weighted_terms = {
record.evidence.correction_metrics[c] = Rounded(correction[c]); Rounded(identity.original), Rounded(identity.solved),
} Rounded(Pair(old.residual, old.low.residual, i)),
record.evidence.correction_pass = Rounded(identity.scale)};
CorrectionPass(correction) && CorrectionPass(fresh_correction); }
const auto update = CheckUpdates(old, candidate, p, h); if (!Valid(identity_metric) || !Valid(original_norm) ||
if (!Valid(update)) !Valid(solved_norm) || !Valid(old_norm) ||
return fail(detail::SolidHhtPairedKinematicsFailure()); Compare(identity_metric, HighLow(1e-12)) > 0)
record.evidence.update_metric = Rounded(update); return fail(
record.evidence.update_pass = Compare(update, HighLow(1e-12)) <= 0; Failure(domain, "solid-dynamic-weighted-algebra-inconsistency",
candidate.iterations.push_back(record.evidence); "Paired original and solved forces disagree beyond "
candidate.iteration_count = iteration; "their uncancelled scale."));
if (record.evidence.residual_pass && record.evidence.correction_pass && record.evidence.weighted_equivalence_metric =
record.evidence.update_pass) { Rounded(identity_metric);
status = state.CommitAcceptedFrame(candidate); record.evidence.weighted_residual_numerator = Rounded(original_norm);
if (!status.IsOk()) return fail(status); record.evidence.recombined_residual_numerator = Rounded(solved_norm);
record.stage = "accepted"; record.evidence.old_residual_numerator = Rounded(old_norm);
record.outcome = "increment"; for (std::size_t c = 0; c < 4; ++c) {
state.RecordIteration(record); if (!Valid(correction[c]) || !Valid(fresh_correction[c]))
accepted = true; return fail(detail::SolidHhtPairedKinematicsFailure());
break; record.evidence.correction_metrics[c] = Rounded(correction[c]);
} }
state.RecordIteration(record); record.evidence.correction_pass =
if (iteration == 100) CorrectionPass(correction) && CorrectionPass(fresh_correction);
return fail(Failure( const auto update = CheckUpdates(old, candidate, p, h);
domain, "solid-dynamic-nonconvergence", if (!Valid(update))
"Paired HHT endpoint did not converge in100 Newton solves.")); return fail(detail::SolidHhtPairedKinematicsFailure());
auto effective = record.evidence.update_metric = Rounded(update);
EffectiveTangent(mass.Value(), tangent.Value(), dofs, h, p); record.evidence.update_pass = Compare(update, HighLow(1e-12)) <= 0;
if (!effective.HasValue()) return fail(effective.GetStatus()); candidate.iterations.push_back(record.evidence);
auto reduced = partition.Partition(effective.Value(), dofs); candidate.iteration_count = iteration;
if (!reduced.HasValue()) return fail(reduced.GetStatus()); if (record.evidence.residual_pass &&
Vector du(dofs.FreeDofCount()); record.evidence.correction_pass && record.evidence.update_pass) {
if (dofs.FreeDofCount() != 0) { return StepResult::Success(candidate);
status = tangent_solver_.FactorizeFresh(reduced.Value().k_ff); }
if (!status.IsOk()) return fail(status); record_iteration(record);
environment = CheckArithmeticEnvironment(domain); if (iteration == 100)
if (!environment.IsOk()) return fail(environment); return fail(Failure(
status = tangent_solver_.SolveCorrection(rhs, du); domain, "solid-dynamic-nonconvergence",
if (!status.IsOk()) return fail(status); "Paired HHT endpoint did not converge in100 Newton solves."));
environment = CheckArithmeticEnvironment(domain); auto effective =
if (!environment.IsOk()) return fail(environment); EffectiveTangent(mass.Value(), tangent.Value(), dofs, h, p);
} if (!effective.HasValue())
if (du.Size() != dofs.FreeDofCount()) return fail(effective.GetStatus());
return fail(Failure(domain, "solid-dynamic-invalid-backend-result", auto reduced = partition.Partition(effective.Value(), dofs);
"Tangent solve returned incompatible correction.")); if (!reduced.HasValue())
for (std::size_t j = 0; j < du.Size(); ++j) return fail(reduced.GetStatus());
if (!std::isfinite(du[j])) Vector du(dofs.FreeDofCount());
return fail(Failure(domain, "solid-dynamic-invalid-backend-result", if (dofs.FreeDofCount() != 0) {
"Tangent solve returned nonfinite correction.")); status = tangent_solver.FactorizeFresh(reduced.Value().k_ff);
fresh_correction = FreshCorrectionMetrics(du, length.Value(), p); if (!status.IsOk())
const auto before = candidate; return fail(status);
for (std::size_t j = 0; j < du.Size(); ++j) { environment = CheckArithmeticEnvironment(domain);
const auto i = dofs.FreeDofs()[j]; if (!environment.IsOk())
const auto value = return fail(environment);
Add(Pair(candidate.displacement, candidate.low.displacement, i), status = tangent_solver.SolveCorrection(rhs, du);
HighLow(du[j] == 0 ? 0.0 : du[j])); if (!status.IsOk())
if (!Valid(value)) return fail(status);
return fail(detail::SolidHhtPairedKinematicsFailure()); environment = CheckArithmeticEnvironment(domain);
Store(value, &candidate.displacement, &candidate.low.displacement, i); if (!environment.IsOk())
} return fail(environment);
status = SetRates(old, predictor.Value(), h, p, &candidate); }
if (!status.IsOk()) return fail(status); if (du.Size() != dofs.FreeDofCount())
correction = return fail(
ObservedCorrectionMetrics(before, candidate, h, length.Value()); Failure(domain, "solid-dynamic-invalid-backend-result",
} "Tangent solve returned incompatible correction."));
if (!accepted) for (std::size_t j = 0; j < du.Size(); ++j)
return fail(Failure(domain, "solid-dynamic-nonconvergence", if (!std::isfinite(du[j]))
"No complete paired endpoint was accepted.")); 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(); 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 "fesa/analysis/solid_static_procedure.h"
#include "equilibrium_algorithm.h"
#include "analysis/solid_static_core.h"
#include <algorithm> #include <algorithm>
#include <array> #include <array>
#include <cmath> #include <cmath>
@@ -21,6 +24,8 @@
#include "fesa/solvers/linear/linear_solver.h" #include "fesa/solvers/linear/linear_solver.h"
#include "fesa/solvers/linear/tangent_solver.h" #include "fesa/solvers/linear/tangent_solver.h"
#include "assembly/ordered_contribution_internal.h"
namespace fesa { namespace fesa {
namespace { namespace {
constexpr std::uint32_t kMaximumIterations = 100U; constexpr std::uint32_t kMaximumIterations = 100U;
@@ -28,7 +33,7 @@ constexpr double kRoundoffBudget =
512.0 * std::numeric_limits<double>::epsilon(); 512.0 * std::numeric_limits<double>::epsilon();
struct RuntimeSolid { struct RuntimeSolid {
const Hex8Element* element; std::unique_ptr<SolidTrialElement> element;
std::vector<std::size_t> scatter; std::vector<std::size_t> scatter;
}; };
struct SolidAssembly { struct SolidAssembly {
@@ -131,9 +136,8 @@ Result<SolidAssembly> Assemble(const std::vector<RuntimeSolid>& solids,
const auto& response = responses[e]->Value(); const auto& response = responses[e]->Value();
for (std::size_t i = 0U; i < 24U; ++i) { for (std::size_t i = 0U; i < 24U; ++i) {
force[solids[e].scatter[i]] += response.internal_force[i]; force[solids[e].scatter[i]] += response.internal_force[i];
for (std::size_t j = 0U; j < 24U; ++j) assembly_internal::AppendLocalMatrixRow(
contributions.push_back({solids[e].scatter[i], solids[e].scatter[j], response.tangent, solids[e].scatter, i, e, contributions);
response.tangent(i, j), e, 24U * i + j});
} }
energy += response.strain_energy; energy += response.strain_energy;
points.insert(points.end(), response.points.begin(), response.points.end()); points.insert(points.end(), response.points.begin(), response.points.end());
@@ -283,6 +287,13 @@ SolidStaticProcedureExecutor::SolidStaticProcedureExecutor(
Status SolidStaticProcedureExecutor::ExecuteInto( Status SolidStaticProcedureExecutor::ExecuteInto(
const Domain& domain, SolidStaticState& state) const { 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_) if (state.initialized_)
return Failure(domain, "solid-static-observer-not-fresh", return Failure(domain, "solid-static-observer-not-fresh",
"Execution requires a fresh static observer."); "Execution requires a fresh static observer.");
@@ -309,14 +320,14 @@ Status SolidStaticProcedureExecutor::ExecuteInto(
state = std::move(baseline.Value()); state = std::move(baseline.Value());
std::vector<RuntimeSolid> solids; std::vector<RuntimeSolid> solids;
for (const auto& element : elements) { for (const auto& element : elements) {
const auto* hex = dynamic_cast<const Hex8Element*>(element.get()); auto hex = ElementFactory{}.BindSolidTrial(*element);
if (hex == nullptr) if (hex == nullptr)
return Failure(domain, "invalid-solid-static-element", return Failure(domain, "invalid-solid-static-element",
"Solid STATIC requires full-integration Hex8 kernels.", "Solid STATIC requires full-integration Hex8 kernels.",
FailureCategory::kModel); FailureCategory::kModel);
auto scatter = dofs.ElementScatter(hex->DofLayout()); auto scatter = dofs.ElementScatter(hex->DofLayout());
if (!scatter.HasValue()) return scatter.GetStatus(); 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_); auto stiffness = SparseAssembler::Assemble(view, dofs, parallel_for_);
if (!stiffness.HasValue()) return stiffness.GetStatus(); if (!stiffness.HasValue()) return stiffness.GetStatus();
@@ -336,7 +347,7 @@ Status SolidStaticProcedureExecutor::ExecuteInto(
// Preserve the required factorization-before-load-assembly lifecycle. // Preserve the required factorization-before-load-assembly lifecycle.
auto load = LoadAssembler::AssembleFullNodalLoad(model.Value(), dofs); auto load = LoadAssembler::AssembleFullNodalLoad(model.Value(), dofs);
if (!load.HasValue()) return load.GetStatus(); if (!load.HasValue()) return load.GetStatus();
auto rhs = LoadAssembler::EffectiveFreeRhs( auto rhs = constraints.EffectiveFreeRhs(
load.Value(), partition.Value().k_fc, dofs.PrescribedValues(), dofs); load.Value(), partition.Value().k_fc, dofs.PrescribedValues(), dofs);
if (!rhs.HasValue()) return rhs.GetStatus(); if (!rhs.HasValue()) return rhs.GetStatus();
Vector free{dofs.FreeDofCount()}; Vector free{dofs.FreeDofCount()};
@@ -369,91 +380,128 @@ Status SolidStaticProcedureExecutor::ExecuteInto(
for (std::size_t c = 0U; c < dofs.ConstrainedDofCount(); ++c) for (std::size_t c = 0U; c < dofs.ConstrainedDofCount(); ++c)
trial[dofs.ConstrainedDofs()[c]] = trial[dofs.ConstrainedDofs()[c]] =
increment.load_factor * dofs.PrescribedValues()[c]; increment.load_factor * dofs.PrescribedValues()[c];
for (std::uint32_t iteration = 1U; iteration <= kMaximumIterations; struct SolidStepCandidate {
++iteration) { SolidStaticFrame frame;
SolidStaticIterationRecord record; SolidStaticIterationRecord record;
record.increment_index = increment.increment_index; };
record.load_factor = increment.load_factor; const auto observe_iteration =
record.iteration = iteration; [&](const SolidStaticIterationRecord& record) {
record.stage = "entered"; state.iterations_.push_back(record);
const auto fail = [&](const Status& failure) { };
record.diagnostic_code = failure.Diagnostics().empty() SolidFullNewtonStep<SolidStepCandidate> iteration_step(
? "solid-static-failure" [&](std::uint32_t iteration)
: failure.Diagnostics()[0].code; -> Result<std::optional<SolidStepCandidate>> {
record.failure_reason = failure.Diagnostics().empty() SolidStaticIterationRecord record;
? "Static trial failed." record.increment_index = increment.increment_index;
: failure.Diagnostics()[0].message; record.load_factor = increment.load_factor;
state.iterations_.push_back(record); record.iteration = iteration;
return failure; record.stage = "entered";
}; const auto fail = [&](const Status& failure) {
record.stage = "trial-recovery"; record.diagnostic_code = failure.Diagnostics().empty()
auto assembly = Assemble(solids, dofs, trial, true, parallel_for_); ? "solid-static-failure"
if (!assembly.HasValue()) return fail(assembly.GetStatus()); : failure.Diagnostics()[0].code;
SolidStaticFrame frame = state.committed_; record.failure_reason = failure.Diagnostics().empty()
frame.balance = {}; ? "Static trial failed."
frame.global_equilibrium = {}; : failure.Diagnostics()[0].message;
frame.frame_index = observe_iteration(record);
static_cast<std::uint32_t>(state.accepted_frames_.size()); return Result<std::optional<SolidStepCandidate>>::Failure(failure);
frame.increment_index = increment.increment_index; };
frame.load_factor = increment.load_factor; record.stage = "trial-recovery";
frame.iteration_count = iteration; auto assembly = Assemble(solids, dofs, trial, true, parallel_for_);
frame.is_factor_one = increment.is_factor_one; if (!assembly.HasValue())
frame.displacement = trial; return fail(assembly.GetStatus());
frame.internal_force = std::move(assembly.Value().force); SolidStaticFrame frame = state.committed_;
frame.external_force = base_load.Value(); frame.balance = {};
frame.external_force.Scale(increment.load_factor); frame.global_equilibrium = {};
frame.solid_results = std::move(assembly.Value().points); frame.frame_index =
frame.physical_strain_energy = assembly.Value().energy; static_cast<std::uint32_t>(state.accepted_frames_.size());
auto status = CompleteFrame(domain, dofs, true, stiffness.Value(), frame); frame.increment_index = increment.increment_index;
if (!status.IsOk()) return fail(status); frame.load_factor = increment.load_factor;
record.stage = "tangent-partition"; frame.iteration_count = iteration;
auto partition = constraints.Partition(assembly.Value().tangent, dofs); frame.is_factor_one = increment.is_factor_one;
if (!partition.HasValue()) return fail(partition.GetStatus()); frame.displacement = trial;
Vector correction{dofs.FreeDofCount()}; frame.internal_force = std::move(assembly.Value().force);
// Empty free is vacuous; prescribed geometry/recovery above still runs. frame.external_force = base_load.Value();
if (dofs.FreeDofCount() != 0U) { frame.external_force.Scale(increment.load_factor);
record.stage = "tangent-factorization"; frame.solid_results = std::move(assembly.Value().points);
status = tangent_solver_.FactorizeFresh(partition.Value().k_ff); frame.physical_strain_energy = assembly.Value().energy;
if (!status.IsOk()) return fail(status); auto status =
auto rhs = constraints.GatherFree(frame.residual, dofs); CompleteFrame(domain, dofs, true, stiffness.Value(), frame);
rhs.Scale(-1.0); if (!status.IsOk())
record.stage = "correction-solve"; return fail(status);
status = tangent_solver_.SolveCorrection(rhs, correction); record.stage = "tangent-partition";
if (!status.IsOk()) return fail(status); auto partition =
} constraints.Partition(assembly.Value().tangent, dofs);
Vector current_increment{dofs.FreeDofCount()}; if (!partition.HasValue())
for (std::size_t f = 0U; f < dofs.FreeDofCount(); ++f) { return fail(partition.GetStatus());
const auto full = dofs.FreeDofs()[f]; Vector correction{dofs.FreeDofCount()};
current_increment[f] = trial[full] - start[full]; // Empty free is vacuous; prescribed geometry/recovery above still
} // runs.
record.stage = "convergence"; if (dofs.FreeDofCount() != 0U) {
auto convergence = EvaluateSolidStaticConvergence( record.stage = "tangent-factorization";
constraints.GatherFree(frame.residual, dofs), status = tangent_solver_.FactorizeFresh(partition.Value().k_ff);
constraints.GatherFree(frame.internal_force, dofs), if (!status.IsOk())
constraints.GatherFree(frame.external_force, dofs), correction, return fail(status);
current_increment); auto rhs = constraints.GatherFree(frame.residual, dofs);
if (!convergence.HasValue()) return fail(convergence.GetStatus()); rhs.Scale(-1.0);
record.force_residual = convergence.Value().force_residual; record.stage = "correction-solve";
record.translation_correction = status = tangent_solver_.SolveCorrection(rhs, correction);
convergence.Value().translation_correction; if (!status.IsOk())
record.convergence_evaluated = true; return fail(status);
if (record.force_residual.passed && }
record.translation_correction.passed) { Vector current_increment{dofs.FreeDofCount()};
record.stage = "commit"; for (std::size_t f = 0U; f < dofs.FreeDofCount(); ++f) {
status = state.CommitAcceptedFrame(frame); const auto full = dofs.FreeDofs()[f];
if (!status.IsOk()) return fail(status); current_increment[f] = trial[full] - start[full];
record.accepted = true; }
state.iterations_.push_back(record); record.stage = "convergence";
break; auto convergence = EvaluateSolidStaticConvergence(
} constraints.GatherFree(frame.residual, dofs),
if (iteration == kMaximumIterations) constraints.GatherFree(frame.internal_force, dofs),
return fail(Failure(domain, "solid-static-nonconvergence", constraints.GatherFree(frame.external_force, dofs), correction,
"Solid STATIC exceeded 100 Newton iterations.")); current_increment);
for (std::size_t f = 0U; f < dofs.FreeDofCount(); ++f) if (!convergence.HasValue())
trial[dofs.FreeDofs()[f]] += correction[f]; return fail(convergence.GetStatus());
record.stage = "continue"; record.force_residual = convergence.Value().force_residual;
state.iterations_.push_back(record); 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(); return Status::Ok();
} }
+4 -5
View File
@@ -37,10 +37,9 @@ double InfinityNorm(const Vector& values) {
return norm; return norm;
} }
/// @brief Compares immutable semantic and raw source identities. /// @brief Compares immutable semantic and raw source identities.
bool SameIdentity(const SourceEntityId& a, const SourceEntityId& b) { bool SameIdentity(const SourceEntityId& a,
return a.instance_name == b.instance_name && const SourceEntityId& b) {
a.source_label == b.source_label && return fesa::SourceIdentityEquals(a, b);
a.source_label_text == b.source_label_text;
} }
/// @brief Builds one raw criterion with the approved branch and physical unit. /// @brief Builds one raw criterion with the approved branch and physical unit.
NewtonChannelDecision Decision(const char* name, double numerator, 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); auto element = ElementFactory{}.Create(domain.Elements()[e], domain);
if (!element.HasValue()) if (!element.HasValue())
return Result<SolidStaticState>::Failure(element.GetStatus()); 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) if (hex == nullptr)
return Result<SolidStaticState>::Failure(StateFailure( return Result<SolidStaticState>::Failure(StateFailure(
"invalid-solid-static-state", "Static state requires Hex8 kernels.")); "invalid-solid-static-state", "Static state requires Hex8 kernels."));
+6 -5
View File
@@ -8,6 +8,9 @@
#include <utility> #include <utility>
#include <vector> #include <vector>
#include "fesa/assembly/thermal_assembler.h"
#include "math/finite_values.h"
namespace fesa { namespace fesa {
namespace { namespace {
@@ -23,9 +26,7 @@ Status Failure(const std::string& message) {
/// @brief Checks every stored scalar without imposing a temperature sign bound. /// @brief Checks every stored scalar without imposing a temperature sign bound.
bool Finite(const Vector& vector) { bool Finite(const Vector& vector) {
for (std::size_t i = 0; i < vector.Size(); ++i) return math_internal::IsFinite(vector);
if (!std::isfinite(vector[i])) return false;
return true;
} }
/// @brief Compares prescribed row identities without conflating node and row /// @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_) for (const auto node : state.node_order_)
state.node_source_ids_.push_back(domain.Nodes()[node].source_id); state.node_source_ids_.push_back(domain.Nodes()[node].source_id);
for (std::size_t index = 0; index < state.element_order_.size(); ++index) { for (std::size_t index = 0; index < state.element_order_.size(); ++index) {
state.element_source_ids_.push_back(assembly.Kernels()[index].SourceId()); state.element_source_ids_.push_back(assembly.Elements()[index]->DofLayout().source_id);
const auto frame = assembly.Kernels()[index].Recover({}); const auto frame = assembly.Elements()[index]->Recover({});
if (!frame.HasValue()) if (!frame.HasValue())
return Result<ThermalAnalysisState>::Failure(frame.GetStatus()); return Result<ThermalAnalysisState>::Failure(frame.GetStatus());
state.local_frames_.push_back(frame.Value().frames); state.local_frames_.push_back(frame.Value().frames);
+28 -16
View File
@@ -7,8 +7,10 @@
#include <string> #include <string>
#include <utility> #include <utility>
#include "fesa/assembly/thermal_assembler.h"
#include "thermal_transient_bounds.h" #include "thermal_transient_bounds.h"
#include "math/dense_blas_internal.h" #include "math/dense_blas_internal.h"
#include "math/finite_values.h"
namespace fesa::thermal_transient_internal { namespace fesa::thermal_transient_internal {
namespace { namespace {
@@ -35,9 +37,7 @@ Status Failure(const std::string& message) {
} }
/// @brief Checks every scalar while preserving signed temperatures and powers. /// @brief Checks every scalar while preserving signed temperatures and powers.
bool Finite(const Vector& vector) { bool Finite(const Vector& vector) {
for (std::size_t i = 0; i < vector.Size(); ++i) return math_internal::IsFinite(vector);
if (!std::isfinite(vector[i])) return false;
return true;
} }
/// @brief Checks aligned full operators and their coefficient-error storage. /// @brief Checks aligned full operators and their coefficient-error storage.
bool SamePattern(const SparseMatrix& a, const SparseMatrix& b) { bool SamePattern(const SparseMatrix& a, const SparseMatrix& b) {
@@ -500,6 +500,16 @@ Status RunThermalHistory(const ThermalAssembly& assembly,
const DofManager& dofs, ThermalAnalysisState& state, const DofManager& dofs, ThermalAnalysisState& state,
LinearSolver& solver, const ThermalRecovery& recovery, LinearSolver& solver, const ThermalRecovery& recovery,
std::vector<ThermalStepEvidence>* evidence) { 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() || if (!dofs.ValidateInvariants().IsOk() ||
dofs.Profile() != DofProfile::kThermal || dofs.Profile() != DofProfile::kThermal ||
dofs.FullDofCount() != state.CommittedState().temperature.Size() || dofs.FullDofCount() != state.CommittedState().temperature.Size() ||
@@ -517,19 +527,21 @@ Status RunThermalHistory(const ThermalAssembly& assembly,
i <= count; ++i) { i <= count; ++i) {
const auto frame = state.TimeGrid().Frame(static_cast<std::uint32_t>(i)); const auto frame = state.TimeGrid().Frame(static_cast<std::uint32_t>(i));
if (!frame.HasValue()) return frame.GetStatus(); if (!frame.HasValue()) return frame.GetStatus();
auto candidate = SolveThermalIncrement(system, partition, const Status step_status = RunThermalStep(
state.CommittedState().temperature, integrator, system, partition, state.CommittedState().temperature,
frame.Value().delta, solver, cache); frame.Value().delta, solver, cache, recovery,
if (!candidate.HasValue()) return candidate.GetStatus(); [&](ThermalStepCandidate&& candidate,
auto flux = recovery(candidate.Value().temperature); std::vector<ThermalElementFlux>&& flux) -> Status {
if (!flux.HasValue()) return flux.GetStatus(); auto completed = state.MakeCompletedFrame(
auto completed = state.MakeCompletedFrame( static_cast<std::uint32_t>(i), std::move(candidate.temperature),
static_cast<std::uint32_t>(i), std::move(candidate.Value().temperature), std::move(candidate.reaction), std::move(flux));
std::move(candidate.Value().reaction), std::move(flux.Value())); if (!completed.HasValue()) return completed.GetStatus();
if (!completed.HasValue()) return completed.GetStatus(); const auto committed = state.CommitAcceptedFrame(completed.Value());
const auto committed = state.CommitAcceptedFrame(completed.Value()); if (!committed.IsOk()) return committed;
if (!committed.IsOk()) return committed; if (evidence) evidence->push_back(candidate.evidence);
if (evidence) evidence->push_back(candidate.Value().evidence); return Status::Ok();
});
if (!step_status.IsOk()) return step_status;
} }
return Status::Ok(); return Status::Ok();
} }
@@ -4,8 +4,10 @@
#include <functional> #include <functional>
#include <optional> #include <optional>
#include <vector> #include <vector>
#include <utility>
#include "fesa/analysis/thermal_analysis_state.h" #include "fesa/analysis/thermal_analysis_state.h"
#include "fesa/math/sparse_matrix.h"
#include "fesa/solvers/linear/linear_solver.h" #include "fesa/solvers/linear/linear_solver.h"
namespace fesa::thermal_transient_internal { namespace fesa::thermal_transient_internal {
@@ -84,11 +86,52 @@ Result<ThermalStepCandidate> SolveThermalIncrement(
const Vector& old_temperature, double delta, LinearSolver& solver, const Vector& old_temperature, double delta, LinearSolver& solver,
ThermalFactorization& factorization); 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 /// @brief Provides an internal recovery seam for the actual history commit
/// path. /// path.
using ThermalRecovery = std::function<Result<std::vector<ThermalElementFlux>>( using ThermalRecovery = std::function<Result<std::vector<ThermalElementFlux>>(
const Vector& temperature)>; 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 /// @brief Advances an existing raw/accepted history, preserving it on every
/// failure. /// failure.
/// @note Recovery runs after algebra validation and before atomic commit. /// @note Recovery runs after algebra validation and before atomic commit.
@@ -101,5 +144,13 @@ Status RunThermalHistory(const ThermalAssembly& assembly,
LinearSolver& solver, const ThermalRecovery& recovery, LinearSolver& solver, const ThermalRecovery& recovery,
std::vector<ThermalStepEvidence>* evidence = nullptr); 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 } // namespace fesa::thermal_transient_internal
#endif // FESA_ANALYSIS_THERMAL_TRANSIENT_ALGEBRA_H_ #endif // FESA_ANALYSIS_THERMAL_TRANSIENT_ALGEBRA_H_
@@ -1,6 +1,7 @@
#include "fesa/analysis/transient_heat_procedure.h" #include "fesa/analysis/transient_heat_procedure.h"
#include <utility> #include <utility>
#include "fesa/assembly/thermal_assembler.h"
#include "thermal_transient_algebra.h" #include "thermal_transient_algebra.h"
+11 -17
View File
@@ -12,6 +12,9 @@
#include "fesa/analysis/nonlinear_static_procedure.h" #include "fesa/analysis/nonlinear_static_procedure.h"
#include "fesa/analysis/prepared_analysis.h" #include "fesa/analysis/prepared_analysis.h"
#include "fesa/analysis/solid_frequency_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/app/output_paths.h"
#include "fesa/assembly/parallel_for.h" #include "fesa/assembly/parallel_for.h"
#include "fesa/core/diagnostic.h" #include "fesa/core/diagnostic.h"
@@ -21,6 +24,7 @@
#include "fesa/io/hdf5/nonlinear_hdf5_results_writer.h" #include "fesa/io/hdf5/nonlinear_hdf5_results_writer.h"
#include "fesa/io/hdf5/solid_hdf5_results_writer.h" #include "fesa/io/hdf5/solid_hdf5_results_writer.h"
#include "fesa/io/hdf5/thermal_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_solver.h"
#include "fesa/solvers/linear/mkl_pardiso_tangent_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; TbbParallelFor parallel_for;
const MklFeastEigenSolver eigen_solver;
MklPardisoSolver linear_solver; MklPardisoSolver linear_solver;
MklPardisoTangentSolver tangent_solver; MklPardisoTangentSolver tangent_solver;
Hdf5ResultsWriter linear_writer; Hdf5ResultsWriter linear_writer;
@@ -105,23 +110,12 @@ int FesaApplication::Run(const std::vector<std::string>& arguments) {
if (!prepared.HasValue()) { if (!prepared.HasValue()) {
status = prepared.GetStatus(); status = prepared.GetStatus();
} else { } else {
LinearStaticProcedureExecutor linear_executor(parallel_for, linear_solver); auto procedure = CreateAnalysisProcedure(
NonlinearStaticProcedureExecutor nonlinear_executor(parallel_for, prepared.Value().domain, parallel_for, linear_solver, tangent_solver,
tangent_solver); eigen_solver, log);
const LinearFrequencyProcedureExecutor frequency_executor; Result<PreparedAnalysisOutcome> outcome = procedure.HasValue()
const HhtDynamicProcedureExecutor dynamic_executor( ? ExecutePreparedAnalysis(prepared.Value().domain, *procedure.Value())
parallel_for, linear_solver, tangent_solver); : Result<PreparedAnalysisOutcome>::Failure(procedure.GetStatus());
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);
if (!outcome.HasValue()) { if (!outcome.HasValue()) {
status = outcome.GetStatus(); status = outcome.GetStatus();
} else { } else {
+6 -113
View File
@@ -11,7 +11,8 @@
#include "fesa/constraints/essential_constraint_policy.h" #include "fesa/constraints/essential_constraint_policy.h"
#include "fesa/loads/load.h" #include "fesa/loads/load.h"
#include "fesa/model/source_target_resolver.h" #include "fesa/model/source_target_resolver.h"
#include "math/dense_blas_internal.h"
#include "assembly/ordered_contribution_internal.h"
namespace fesa { namespace fesa {
namespace { namespace {
@@ -27,20 +28,6 @@ Status LoadFailure(const std::string& code, const SourceLocation& location,
{{Severity::kError, code, location, keyword, identity, message}}); {{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) { Status ValidateShellMoments(const Domain& domain, const Vector& full_load) {
if (domain.ShellElements().Empty()) { if (domain.ShellElements().Empty()) {
return Status::Ok(); return Status::Ok();
@@ -224,14 +211,13 @@ Result<Vector> LoadAssembler::AssembleFullNodalLoad(const AnalysisModel& model,
for (const auto& contributions : contributions_by_load) { for (const auto& contributions : contributions_by_load) {
for (const auto& contribution : contributions) { for (const auto& contribution : contributions) {
const std::size_t full_dof = contribution.full_dof_index; const std::size_t full_dof = contribution.full_dof_index;
const double accumulated = full_load[full_dof] + contribution.value; if (!assembly_internal::AccumulateFiniteLoad(
if (!std::isfinite(accumulated)) { full_load, full_dof, contribution.value)) {
return Result<Vector>::Failure(LoadFailure( return Result<Vector>::Failure(LoadFailure(
"nonfinite-load-accumulation", model.Step().Location(), "nonfinite-load-accumulation", model.Step().Location(),
"LOAD_ASSEMBLER", std::to_string(full_dof), "LOAD_ASSEMBLER", std::to_string(full_dof),
"Source-order load accumulation produced a nonfinite value.")); "Source-order load accumulation produced a nonfinite value."));
} }
full_load[full_dof] = accumulated;
} }
} }
const Status shell_moment_status = ValidateShellMoments(domain, full_load); 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 SparseMatrix& kfc,
const Vector& prescribed_values, const Vector& prescribed_values,
const DofManager& dofs) { const DofManager& dofs) {
const SourceLocation location{{}, 0U}; return EssentialConstraintPolicy{}.EffectiveFreeRhs(
if (dofs.FullDofCount() != full_load.Size()) { full_load, kfc, prescribed_values, dofs);
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));
} }
} // namespace fesa } // 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