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:
@@ -279,6 +279,27 @@ Result<Mitc4Stiffness> StiffnessFailure(const SourceLocation& location,
|
||||
"*ELEMENT", identity, std::move(message)}}));
|
||||
}
|
||||
|
||||
Result<Mitc4Mass> MassFailure(const SourceLocation& location,
|
||||
const std::string& identity,
|
||||
std::string message) {
|
||||
return Result<Mitc4Mass>::Failure(
|
||||
Status::Failure(FailureCategory::kModel,
|
||||
{{Severity::kError, "invalid-shell-mass", location,
|
||||
"*ELEMENT", identity, std::move(message)}}));
|
||||
}
|
||||
|
||||
/// @brief Returns S=-[director]_cross for theta cross director.
|
||||
Matrix DirectorSkew(const Vector3& director) {
|
||||
Matrix result{3U, 3U};
|
||||
result(0U, 1U) = director.Z();
|
||||
result(0U, 2U) = -director.Y();
|
||||
result(1U, 0U) = -director.Z();
|
||||
result(1U, 2U) = director.X();
|
||||
result(2U, 0U) = director.Y();
|
||||
result(2U, 1U) = -director.X();
|
||||
return result;
|
||||
}
|
||||
|
||||
Result<Mitc4PhysicalRecovery> RecoveryFailure(const SourceLocation& location,
|
||||
const std::string& identity,
|
||||
std::string message) {
|
||||
@@ -625,24 +646,29 @@ Result<Mitc4Stiffness> Mitc4Shell::Stiffness() const {
|
||||
}
|
||||
}
|
||||
|
||||
double drilling_reference = (std::numeric_limits<double>::max)();
|
||||
bool has_drilling_reference = false;
|
||||
double maximum_rotary_diagonal = 0.0;
|
||||
std::array<double, 8> rotary_diagonals{};
|
||||
std::size_t rotary_index = 0U;
|
||||
for (std::size_t node = 0U; node < kNodeCount; ++node) {
|
||||
const std::size_t offset = node * kPhysicalDofsPerNode;
|
||||
for (std::size_t rotation = 3U; rotation < 5U; ++rotation) {
|
||||
const double diagonal =
|
||||
physical_local(offset + rotation, offset + rotation);
|
||||
if (std::isfinite(diagonal) && diagonal > 0.0) {
|
||||
drilling_reference = (std::min)(drilling_reference, diagonal);
|
||||
has_drilling_reference = true;
|
||||
if (!std::isfinite(diagonal) || diagonal < 0.0) {
|
||||
return StiffnessFailure(
|
||||
source_location_, identity_,
|
||||
"MITC4 drilling stabilization requires finite nonnegative "
|
||||
"physical tangent-rotation diagonals.");
|
||||
}
|
||||
rotary_diagonals[rotary_index++] = diagonal;
|
||||
maximum_rotary_diagonal = (std::max)(maximum_rotary_diagonal, diagonal);
|
||||
}
|
||||
}
|
||||
if (!has_drilling_reference) {
|
||||
if (!(maximum_rotary_diagonal > 0.0)) {
|
||||
return StiffnessFailure(
|
||||
source_location_, identity_,
|
||||
"MITC4 drilling stabilization requires a finite positive physical "
|
||||
"tangent-rotation diagonal.");
|
||||
"MITC4 drilling stabilization requires a positive physical "
|
||||
"tangent-rotation diagonal inventory.");
|
||||
}
|
||||
if (!IsFinite(physical_local)) {
|
||||
return StiffnessFailure(
|
||||
@@ -650,7 +676,12 @@ Result<Mitc4Stiffness> Mitc4Shell::Stiffness() const {
|
||||
"MITC4 physical stiffness must contain only finite values.");
|
||||
}
|
||||
|
||||
const double drilling_stiffness = 1.0e-3 * drilling_reference;
|
||||
double normalized_diagonal_sum = 0.0;
|
||||
for (const double diagonal : rotary_diagonals) {
|
||||
normalized_diagonal_sum += diagonal / maximum_rotary_diagonal;
|
||||
}
|
||||
const double drilling_stiffness =
|
||||
1.0e-3 * maximum_rotary_diagonal * (normalized_diagonal_sum / 8.0);
|
||||
if (!std::isfinite(drilling_stiffness) || !(drilling_stiffness > 0.0)) {
|
||||
return StiffnessFailure(
|
||||
source_location_, identity_,
|
||||
@@ -685,6 +716,144 @@ Result<Mitc4Stiffness> Mitc4Shell::Stiffness() const {
|
||||
drilling_stiffness});
|
||||
}
|
||||
|
||||
Result<Mitc4Mass> Mitc4Shell::Mass(const double density) const {
|
||||
if (!std::isfinite(density) || !(density > 0.0)) {
|
||||
return MassFailure(source_location_, identity_,
|
||||
"MITC4 mass requires finite density > 0.");
|
||||
}
|
||||
|
||||
std::array<Matrix, 3> moments{Matrix{kNodeCount, kNodeCount},
|
||||
Matrix{kNodeCount, kNodeCount},
|
||||
Matrix{kNodeCount, kNodeCount}};
|
||||
constexpr double kGaussCoordinate = 0.77459666924148337704;
|
||||
constexpr std::array<double, 3> kGaussPoints{-kGaussCoordinate, 0.0,
|
||||
kGaussCoordinate};
|
||||
constexpr std::array<double, 3> kGaussWeights{5.0 / 9.0, 8.0 / 9.0,
|
||||
5.0 / 9.0};
|
||||
for (std::size_t xi_index = 0U; xi_index < kGaussPoints.size(); ++xi_index) {
|
||||
for (std::size_t eta_index = 0U; eta_index < kGaussPoints.size();
|
||||
++eta_index) {
|
||||
const Mitc4ShapeFunctions shape =
|
||||
ShapeFunctions(kGaussPoints[xi_index], kGaussPoints[eta_index]);
|
||||
for (std::size_t zeta_index = 0U; zeta_index < kGaussPoints.size();
|
||||
++zeta_index) {
|
||||
GeometryData geometry{};
|
||||
const double zeta = kGaussPoints[zeta_index];
|
||||
if (!EvaluateGeometry(kGaussPoints[xi_index], kGaussPoints[eta_index],
|
||||
zeta, geometry)) {
|
||||
return MassFailure(source_location_, identity_,
|
||||
"MITC4 mass quadrature geometry is invalid.");
|
||||
}
|
||||
const double z = 0.5 * thickness_ * zeta;
|
||||
const double weight =
|
||||
density * geometry.jacobian * kGaussWeights[xi_index] *
|
||||
kGaussWeights[eta_index] * kGaussWeights[zeta_index];
|
||||
if (!std::isfinite(z) || !std::isfinite(weight)) {
|
||||
return MassFailure(
|
||||
source_location_, identity_,
|
||||
"MITC4 mass quadrature contribution is nonfinite.");
|
||||
}
|
||||
const std::array<double, 3> z_powers{1.0, z, z * z};
|
||||
for (std::size_t row = 0U; row < kNodeCount; ++row) {
|
||||
for (std::size_t column = row; column < kNodeCount; ++column) {
|
||||
const double shape_product =
|
||||
shape.values[row] * shape.values[column];
|
||||
for (std::size_t moment = 0U; moment < moments.size(); ++moment) {
|
||||
const double contribution =
|
||||
weight * shape_product * z_powers[moment];
|
||||
moments[moment](row, column) += contribution;
|
||||
if (row != column) {
|
||||
moments[moment](column, row) += contribution;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for (const Matrix& moment : moments) {
|
||||
if (!IsFinite(moment)) {
|
||||
return MassFailure(source_location_, identity_,
|
||||
"MITC4 consistent moments must be finite.");
|
||||
}
|
||||
}
|
||||
|
||||
Matrix consistent{kGlobalDofCount, kGlobalDofCount};
|
||||
for (std::size_t row_node = 0U; row_node < kNodeCount; ++row_node) {
|
||||
const Matrix row_skew = DirectorSkew(directors_[row_node]);
|
||||
for (std::size_t column_node = 0U; column_node < kNodeCount;
|
||||
++column_node) {
|
||||
const Matrix column_skew = DirectorSkew(directors_[column_node]);
|
||||
for (std::size_t row = 0U; row < 3U; ++row) {
|
||||
for (std::size_t column = 0U; column < 3U; ++column) {
|
||||
const std::size_t global_row = kGlobalDofsPerNode * row_node + row;
|
||||
const std::size_t global_column =
|
||||
kGlobalDofsPerNode * column_node + column;
|
||||
consistent(global_row, global_column) =
|
||||
moments[0U](row_node, column_node) * (row == column ? 1.0 : 0.0);
|
||||
consistent(global_row, global_column + 3U) =
|
||||
moments[1U](row_node, column_node) * column_skew(row, column);
|
||||
consistent(global_row + 3U, global_column) =
|
||||
moments[1U](row_node, column_node) * row_skew(column, row);
|
||||
double rotary = 0.0;
|
||||
for (std::size_t axis = 0U; axis < 3U; ++axis) {
|
||||
rotary += row_skew(axis, row) * column_skew(axis, column);
|
||||
}
|
||||
consistent(global_row + 3U, global_column + 3U) =
|
||||
moments[2U](row_node, column_node) * rotary;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Matrix lumped{kGlobalDofCount, kGlobalDofCount};
|
||||
std::array<std::array<double, 3>, kNodeCount> nodal_moments{};
|
||||
for (std::size_t node = 0U; node < kNodeCount; ++node) {
|
||||
double mass = 0.0;
|
||||
double first_moment = 0.0;
|
||||
double second_moment = 0.0;
|
||||
for (std::size_t column = 0U; column < kNodeCount; ++column) {
|
||||
mass += moments[0U](node, column);
|
||||
first_moment += moments[1U](node, column);
|
||||
second_moment += moments[2U](node, column);
|
||||
}
|
||||
const double centered_inertia =
|
||||
second_moment - first_moment * (first_moment / mass);
|
||||
if (!std::isfinite(mass) || !std::isfinite(first_moment) ||
|
||||
!std::isfinite(second_moment) || !std::isfinite(centered_inertia) ||
|
||||
!(mass > 0.0) || !(second_moment > 0.0) || !(centered_inertia > 0.0)) {
|
||||
return MassFailure(
|
||||
source_location_, identity_,
|
||||
"MITC4 nodal mass moments must be finite and positive.");
|
||||
}
|
||||
nodal_moments[node] = {mass, first_moment, second_moment};
|
||||
const Matrix skew = DirectorSkew(directors_[node]);
|
||||
const std::size_t offset = kGlobalDofsPerNode * node;
|
||||
for (std::size_t row = 0U; row < 3U; ++row) {
|
||||
for (std::size_t column = 0U; column < 3U; ++column) {
|
||||
lumped(offset + row, offset + column) =
|
||||
mass * (row == column ? 1.0 : 0.0);
|
||||
lumped(offset + row, offset + column + 3U) =
|
||||
first_moment * skew(row, column);
|
||||
lumped(offset + row + 3U, offset + column) =
|
||||
first_moment * skew(column, row);
|
||||
double projector = row == column ? 1.0 : 0.0;
|
||||
projector -= directors_[node][row] * directors_[node][column];
|
||||
lumped(offset + row + 3U, offset + column + 3U) =
|
||||
second_moment * projector;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!IsFinite(consistent) || !IsFinite(lumped)) {
|
||||
return MassFailure(
|
||||
source_location_, identity_,
|
||||
"MITC4 transformed mass must contain only finite values.");
|
||||
}
|
||||
return Result<Mitc4Mass>::Success(
|
||||
Mitc4Mass{std::move(moments), std::move(consistent), std::move(lumped),
|
||||
std::move(nodal_moments)});
|
||||
}
|
||||
|
||||
Result<Mitc4PhysicalRecovery> Mitc4Shell::RecoverPhysical(
|
||||
const Vector& global_element_displacement24) const {
|
||||
if (global_element_displacement24.Size() != kGlobalDofCount) {
|
||||
|
||||
Reference in New Issue
Block a user