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