feat: add eigen2 frequency pipeline and fail-closed validation
Implement MITC4 mass, constrained whitening, MKL FEAST, modal HDF5, and deterministic Value comparison. MSVC Debug builds; 394 of 395 tests pass. Original-model all-c residual and the documented S3 static contract conflict remain unresolved; this is an implementation checkpoint, not feature acceptance.
This commit is contained in:
@@ -0,0 +1,78 @@
|
||||
#ifndef FESA_ANALYSIS_EIGEN_ANALYSIS_STATE_H_
|
||||
#define FESA_ANALYSIS_EIGEN_ANALYSIS_STATE_H_
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <optional>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "fesa/math/matrix.h"
|
||||
#include "fesa/solvers/eigen/mkl_feast_eigen_solver.h"
|
||||
|
||||
namespace fesa {
|
||||
|
||||
/// @brief Identifies one requested mode without conflating equal eigenvalues.
|
||||
struct EigenMode {
|
||||
std::size_t mode_id;
|
||||
double eigenvalue;
|
||||
std::uint8_t classification;
|
||||
std::size_t cluster_id;
|
||||
std::optional<double> frequency;
|
||||
};
|
||||
|
||||
/// @brief Records the analytic mass split at one source-ordered node.
|
||||
struct EigenNodalReductionRecord {
|
||||
std::uint8_t free_count;
|
||||
std::uint8_t null_count;
|
||||
std::uint8_t positive_count;
|
||||
bool rcond_available;
|
||||
double rcond_inf;
|
||||
};
|
||||
|
||||
/// @brief Stores independently evaluated evidence for every computed pair.
|
||||
struct EigenpairCheck {
|
||||
std::size_t sorted_rank;
|
||||
std::size_t solver_column;
|
||||
double eigenvalue;
|
||||
double modal_mass;
|
||||
double rayleigh_quotient;
|
||||
double eta_r;
|
||||
double eta_w;
|
||||
double rho_b;
|
||||
double eta_norm;
|
||||
double eta_energy;
|
||||
bool backend_res_available;
|
||||
double backend_res;
|
||||
};
|
||||
|
||||
/// @brief Owns a validated modal candidate independently of static state.
|
||||
/// @note Vectors are requested-mode by full source DOF; they are not response
|
||||
/// displacements. Backend work vectors are released after all-c validation.
|
||||
struct EigenAnalysisState {
|
||||
explicit EigenAnalysisState(FeastEigenResult backend_result)
|
||||
: backend{std::move(backend_result)} {}
|
||||
|
||||
std::size_t full_dof_count{0U};
|
||||
std::size_t free_dof_count{0U};
|
||||
std::size_t mass_null_dimension{0U};
|
||||
std::size_t finite_dimension{0U};
|
||||
double characteristic_length{0.0};
|
||||
double eta_gram{0.0};
|
||||
double max_eta_orth{0.0};
|
||||
bool null_checks_applicable{false};
|
||||
double eta_mz{0.0};
|
||||
double eta_kz{0.0};
|
||||
double eta_pz{0.0};
|
||||
std::vector<std::uint8_t> constraint_mask;
|
||||
std::vector<double> prescribed_displacement;
|
||||
std::vector<EigenNodalReductionRecord> nodal_reduction;
|
||||
std::vector<EigenMode> modes;
|
||||
Matrix vectors{0U, 0U};
|
||||
std::vector<EigenpairCheck> eigenpair_checks;
|
||||
FeastEigenResult backend;
|
||||
};
|
||||
|
||||
} // namespace fesa
|
||||
|
||||
#endif // FESA_ANALYSIS_EIGEN_ANALYSIS_STATE_H_
|
||||
@@ -0,0 +1,20 @@
|
||||
#ifndef FESA_ANALYSIS_LINEAR_FREQUENCY_PROCEDURE_H_
|
||||
#define FESA_ANALYSIS_LINEAR_FREQUENCY_PROCEDURE_H_
|
||||
|
||||
#include "fesa/analysis/eigen_analysis_state.h"
|
||||
|
||||
namespace fesa {
|
||||
|
||||
class Domain;
|
||||
|
||||
/// @brief Executes the supported linear MITC4 frequency procedure.
|
||||
class LinearFrequencyProcedureExecutor final {
|
||||
public:
|
||||
/// @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;
|
||||
};
|
||||
|
||||
} // namespace fesa
|
||||
|
||||
#endif // FESA_ANALYSIS_LINEAR_FREQUENCY_PROCEDURE_H_
|
||||
@@ -6,6 +6,7 @@
|
||||
#include <vector>
|
||||
|
||||
#include "fesa/analysis/analysis_state.h"
|
||||
#include "fesa/analysis/eigen_analysis_state.h"
|
||||
#include "fesa/analysis/nonlinear_analysis_state.h"
|
||||
#include "fesa/core/diagnostic.h"
|
||||
#include "fesa/core/status.h"
|
||||
@@ -14,6 +15,8 @@
|
||||
namespace fesa {
|
||||
|
||||
class LinearStaticProcedureExecutor;
|
||||
class LinearFrequencyProcedureExecutor;
|
||||
class EigenResultsWriter;
|
||||
class NewtonIterationLogWriter;
|
||||
class NonlinearResultsWriter;
|
||||
class NonlinearStaticProcedureExecutor;
|
||||
@@ -47,7 +50,8 @@ struct NonlinearStaticExecutionResult {
|
||||
};
|
||||
|
||||
using PreparedAnalysisOutcome =
|
||||
std::variant<LinearStaticExecutionResult, NonlinearStaticExecutionResult>;
|
||||
std::variant<LinearStaticExecutionResult, NonlinearStaticExecutionResult,
|
||||
EigenAnalysisState>;
|
||||
|
||||
/// @brief Selects the approved procedure from a prepared Domain.
|
||||
/// @pre The executors and log outlive this call.
|
||||
@@ -55,6 +59,7 @@ using PreparedAnalysisOutcome =
|
||||
Result<PreparedAnalysisOutcome> ExecutePreparedAnalysis(
|
||||
const Domain& domain, LinearStaticProcedureExecutor& linear_executor,
|
||||
NonlinearStaticProcedureExecutor& nonlinear_executor,
|
||||
const LinearFrequencyProcedureExecutor& frequency_executor,
|
||||
NewtonIterationLogWriter& log);
|
||||
|
||||
/// @brief Writes an already computed outcome to its authoritative result file.
|
||||
@@ -64,7 +69,7 @@ Status FinalizePreparedAnalysisResult(
|
||||
const DerivedOutputPaths& paths, const Domain& domain,
|
||||
const PreparedAnalysisOutcome& outcome,
|
||||
const std::vector<Diagnostic>& diagnostics, ResultsWriter& linear_writer,
|
||||
NonlinearResultsWriter& nonlinear_writer);
|
||||
NonlinearResultsWriter& nonlinear_writer, EigenResultsWriter& eigen_writer);
|
||||
|
||||
} // namespace fesa
|
||||
|
||||
|
||||
@@ -98,6 +98,14 @@ struct Mitc4Stiffness {
|
||||
double drilling_stiffness;
|
||||
};
|
||||
|
||||
/// @brief Stores consistent scalar moments and their derived MITC4 masses.
|
||||
struct Mitc4Mass {
|
||||
std::array<Matrix, 3> consistent_moments;
|
||||
Matrix consistent_global24;
|
||||
Matrix lumped_global24;
|
||||
std::array<std::array<double, 3>, 4> nodal_moments;
|
||||
};
|
||||
|
||||
/// @brief Stores physical shell recovery at one midsurface Gauss location.
|
||||
struct Mitc4PhysicalRecoveryPoint {
|
||||
std::array<double, 2> natural_coordinates;
|
||||
@@ -187,6 +195,11 @@ class Mitc4Shell final : public Element {
|
||||
/// @return Finite stiffness matrices or a structured model failure.
|
||||
[[nodiscard]] Result<Mitc4Stiffness> Stiffness() const;
|
||||
|
||||
/// @brief Computes consistent and scalar-row-sum lumped MITC4 mass.
|
||||
/// @param density Constant material density in mass per volume.
|
||||
/// @return Finite mass matrices or a structured model failure.
|
||||
[[nodiscard]] Result<Mitc4Mass> Mass(double density) const;
|
||||
|
||||
/// @brief Recovers physical shell quantities without drilling results.
|
||||
/// @param global_element_displacement24 Global element DOFs in node order.
|
||||
/// @return Physical recovery rows or a structured model failure.
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
#ifndef FESA_IO_HDF5_EIGEN_HDF5_RESULTS_WRITER_H_
|
||||
#define FESA_IO_HDF5_EIGEN_HDF5_RESULTS_WRITER_H_
|
||||
|
||||
#include <filesystem>
|
||||
#include <vector>
|
||||
|
||||
#include "fesa/results/eigen_results_writer.h"
|
||||
|
||||
namespace fesa {
|
||||
|
||||
/// @brief Atomically writes validated linear-frequency HDF5 results.
|
||||
class EigenHdf5ResultsWriter final : public EigenResultsWriter {
|
||||
public:
|
||||
/// @brief Serializes an accepted modal state after closed-file self-check.
|
||||
/// @return An output failure when the candidate cannot be validated or
|
||||
/// finalized; an existing final file is then preserved.
|
||||
Status Write(const std::filesystem::path& output_path, const Domain& domain,
|
||||
const EigenAnalysisState& state,
|
||||
const std::vector<Diagnostic>& diagnostics) override;
|
||||
};
|
||||
|
||||
} // namespace fesa
|
||||
|
||||
#endif // FESA_IO_HDF5_EIGEN_HDF5_RESULTS_WRITER_H_
|
||||
@@ -4,6 +4,7 @@
|
||||
#include <cstddef>
|
||||
#include <filesystem>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
@@ -47,7 +48,7 @@ class DomainCollectionView {
|
||||
std::vector<const T*> entries_;
|
||||
};
|
||||
|
||||
/// @brief Owns one immutable static-step semantic definition.
|
||||
/// @brief Owns one immutable static or frequency-step semantic definition.
|
||||
/// @note Loads retain source order and are owned polymorphically by unique
|
||||
/// pointers.
|
||||
class StepDefinition {
|
||||
@@ -84,6 +85,12 @@ class StepDefinition {
|
||||
/// @brief Reports whether this step uses the nonlinear static procedure.
|
||||
bool IsNonlinearStatic() const noexcept;
|
||||
|
||||
/// @brief Reports whether this step defines the frequency procedure.
|
||||
bool IsFrequency() const noexcept;
|
||||
|
||||
/// @brief Returns frequency source semantics when this is a frequency step.
|
||||
const FrequencyStepDefinition* Frequency() const noexcept;
|
||||
|
||||
/// @brief Reports the source NLGEOM setting for this static step.
|
||||
bool UsesNonlinearGeometry() const noexcept;
|
||||
|
||||
@@ -108,6 +115,9 @@ class StepDefinition {
|
||||
/// @brief Converts one parsed static-step record to owned semantic objects.
|
||||
explicit StepDefinition(StaticStepDefinition definition);
|
||||
|
||||
/// @brief Converts one parsed frequency-step record to owned semantics.
|
||||
explicit StepDefinition(FrequencyStepDefinition definition);
|
||||
|
||||
std::string name_;
|
||||
std::vector<std::unique_ptr<BoundaryCondition>> boundary_conditions_;
|
||||
BoundaryConditionView boundary_conditions_view_;
|
||||
@@ -124,6 +134,7 @@ class StepDefinition {
|
||||
double minimum_increment_;
|
||||
double maximum_increment_;
|
||||
SourceLocation location_;
|
||||
std::optional<FrequencyStepDefinition> frequency_;
|
||||
};
|
||||
|
||||
/// @brief Owns the complete immutable semantic model definition.
|
||||
@@ -184,9 +195,15 @@ class Domain {
|
||||
/// @brief Returns assembly element surfaces in stable declaration order.
|
||||
const std::vector<ElementSurfaceDefinition>& ElementSurfaces() const noexcept;
|
||||
|
||||
/// @brief Returns static steps in stable declaration order.
|
||||
/// @brief Returns procedure steps in stable declaration order.
|
||||
const DomainCollectionView<StepDefinition>& Steps() const noexcept;
|
||||
|
||||
/// @brief Returns resolved density records in stable material-index order.
|
||||
const std::vector<MaterialDensityDefinition>& Densities() const noexcept;
|
||||
|
||||
/// @brief Returns the sole frequency source definition when present.
|
||||
const FrequencyStepDefinition* Frequency() const noexcept;
|
||||
|
||||
/// @brief Returns sorted nonfatal mapping diagnostics.
|
||||
const std::vector<Diagnostic>& Warnings() const noexcept;
|
||||
|
||||
|
||||
@@ -117,18 +117,15 @@ struct StaticStepDefinition {
|
||||
std::vector<PrescribedDisplacementDefinition> step_boundaries,
|
||||
std::vector<NodalLoad> step_loads,
|
||||
std::vector<SurfacePressureLoadDefinition> step_surface_pressure_loads,
|
||||
StaticProcedureKind step_procedure_kind,
|
||||
bool step_nlgeom,
|
||||
double step_initial_increment,
|
||||
double step_time_period,
|
||||
double step_minimum_increment,
|
||||
double step_maximum_increment,
|
||||
StaticProcedureKind step_procedure_kind, bool step_nlgeom,
|
||||
double step_initial_increment, double step_time_period,
|
||||
double step_minimum_increment, double step_maximum_increment,
|
||||
SourceLocation step_location)
|
||||
: name{std::move(step_name)},
|
||||
boundaries{std::move(step_boundaries)},
|
||||
loads{std::move(step_loads)},
|
||||
surface_pressure_loads{std::move(step_surface_pressure_loads)},
|
||||
procedure_kind{step_procedure_kind},
|
||||
: name{std::move(step_name)},
|
||||
boundaries{std::move(step_boundaries)},
|
||||
loads{std::move(step_loads)},
|
||||
surface_pressure_loads{std::move(step_surface_pressure_loads)},
|
||||
procedure_kind{step_procedure_kind},
|
||||
nlgeom{step_nlgeom},
|
||||
initial_increment{step_initial_increment},
|
||||
time_period{step_time_period},
|
||||
@@ -141,35 +138,30 @@ struct StaticStepDefinition {
|
||||
std::string step_name,
|
||||
std::vector<PrescribedDisplacementDefinition> step_boundaries,
|
||||
std::vector<NodalLoad> step_loads,
|
||||
StaticProcedureKind step_procedure_kind,
|
||||
bool step_nlgeom,
|
||||
double step_initial_increment,
|
||||
double step_time_period,
|
||||
double step_minimum_increment,
|
||||
double step_maximum_increment,
|
||||
StaticProcedureKind step_procedure_kind, bool step_nlgeom,
|
||||
double step_initial_increment, double step_time_period,
|
||||
double step_minimum_increment, double step_maximum_increment,
|
||||
SourceLocation step_location)
|
||||
: StaticStepDefinition(
|
||||
std::move(step_name), std::move(step_boundaries),
|
||||
std::move(step_loads), {}, step_procedure_kind, step_nlgeom,
|
||||
step_initial_increment, step_time_period, step_minimum_increment,
|
||||
step_maximum_increment, std::move(step_location)) {}
|
||||
: StaticStepDefinition(std::move(step_name), std::move(step_boundaries),
|
||||
std::move(step_loads), {}, step_procedure_kind,
|
||||
step_nlgeom, step_initial_increment,
|
||||
step_time_period, step_minimum_increment,
|
||||
step_maximum_increment, std::move(step_location)) {
|
||||
}
|
||||
|
||||
/// @brief Creates a legacy linear static step definition.
|
||||
StaticStepDefinition(
|
||||
std::string step_name,
|
||||
std::vector<PrescribedDisplacementDefinition> step_boundaries,
|
||||
std::vector<NodalLoad> step_loads,
|
||||
double step_initial_increment,
|
||||
double step_time_period,
|
||||
double step_minimum_increment,
|
||||
double step_maximum_increment,
|
||||
SourceLocation step_location)
|
||||
: StaticStepDefinition(
|
||||
std::move(step_name), std::move(step_boundaries),
|
||||
std::move(step_loads),
|
||||
StaticProcedureKind::kLinearSmallDisplacement, false,
|
||||
step_initial_increment, step_time_period, step_minimum_increment,
|
||||
step_maximum_increment, std::move(step_location)) {}
|
||||
std::vector<NodalLoad> step_loads, double step_initial_increment,
|
||||
double step_time_period, double step_minimum_increment,
|
||||
double step_maximum_increment, SourceLocation step_location)
|
||||
: StaticStepDefinition(std::move(step_name), std::move(step_boundaries),
|
||||
std::move(step_loads),
|
||||
StaticProcedureKind::kLinearSmallDisplacement,
|
||||
false, step_initial_increment, step_time_period,
|
||||
step_minimum_increment, step_maximum_increment,
|
||||
std::move(step_location)) {}
|
||||
|
||||
std::string name;
|
||||
std::vector<PrescribedDisplacementDefinition> boundaries;
|
||||
@@ -184,6 +176,38 @@ struct StaticStepDefinition {
|
||||
SourceLocation location;
|
||||
};
|
||||
|
||||
/// @brief Identifies the accepted source eigensolver selection.
|
||||
enum class FrequencyEigensolver { kLanczos };
|
||||
|
||||
/// @brief Identifies the accepted modal normalization selection.
|
||||
enum class FrequencyNormalization { kMass };
|
||||
|
||||
/// @brief Stores one resolved constant material mass density.
|
||||
struct MaterialDensityDefinition {
|
||||
EntityIndex material_index;
|
||||
double rho;
|
||||
SourceLocation location;
|
||||
};
|
||||
|
||||
/// @brief Stores the sole supported frequency-step source semantics.
|
||||
struct FrequencyStepDefinition {
|
||||
std::string source_step_name_raw;
|
||||
std::string source_step_name_canonical;
|
||||
SourceLocation step_location;
|
||||
SourceLocation frequency_location;
|
||||
SourceLocation data_location;
|
||||
std::int64_t requested_mode_count;
|
||||
bool nlgeom_explicit;
|
||||
bool perturbation_explicit;
|
||||
bool eigensolver_explicit;
|
||||
bool sim_explicit;
|
||||
bool acoustic_coupling_explicit;
|
||||
bool normalization_explicit;
|
||||
FrequencyEigensolver eigensolver;
|
||||
FrequencyNormalization normalization;
|
||||
std::vector<PrescribedDisplacementDefinition> boundaries;
|
||||
};
|
||||
|
||||
/// @brief Stores a stable resolved node-set membership list.
|
||||
struct NodeSet {
|
||||
std::string name;
|
||||
@@ -245,6 +269,8 @@ struct ModelDefinition {
|
||||
std::vector<InstanceDefinition> instances;
|
||||
std::vector<ElementSurfaceDefinition> element_surfaces;
|
||||
std::vector<StaticStepDefinition> steps;
|
||||
std::vector<MaterialDensityDefinition> densities;
|
||||
std::optional<FrequencyStepDefinition> frequency_step;
|
||||
std::vector<Diagnostic> warnings;
|
||||
};
|
||||
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
#ifndef FESA_RESULTS_EIGEN_RESULTS_WRITER_H_
|
||||
#define FESA_RESULTS_EIGEN_RESULTS_WRITER_H_
|
||||
|
||||
#include <filesystem>
|
||||
#include <vector>
|
||||
|
||||
#include "fesa/analysis/eigen_analysis_state.h"
|
||||
#include "fesa/core/diagnostic.h"
|
||||
#include "fesa/core/status.h"
|
||||
|
||||
namespace fesa {
|
||||
|
||||
class Domain;
|
||||
|
||||
/// @brief Isolates authoritative modal storage from procedure execution.
|
||||
class EigenResultsWriter {
|
||||
public:
|
||||
virtual ~EigenResultsWriter() = default;
|
||||
|
||||
/// @brief Validates and atomically publishes the complete modal candidate.
|
||||
/// @return Success only after closed-file validation and final replacement.
|
||||
virtual Status Write(const std::filesystem::path& output_path,
|
||||
const Domain& domain, const EigenAnalysisState& state,
|
||||
const std::vector<Diagnostic>& diagnostics) = 0;
|
||||
};
|
||||
|
||||
} // namespace fesa
|
||||
|
||||
#endif // FESA_RESULTS_EIGEN_RESULTS_WRITER_H_
|
||||
@@ -0,0 +1,83 @@
|
||||
#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"
|
||||
|
||||
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 a whitened real-symmetric eigenproblem with oneMKL FEAST.
|
||||
class MklFeastEigenSolver final {
|
||||
public:
|
||||
/// @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.
|
||||
/// @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;
|
||||
};
|
||||
|
||||
} // namespace fesa
|
||||
|
||||
#endif // FESA_SOLVERS_EIGEN_MKL_FEAST_EIGEN_SOLVER_H_
|
||||
Reference in New Issue
Block a user