Integrate Refactor solver sources and dual MSVC CMake builds
This commit is contained in:
@@ -0,0 +1,115 @@
|
||||
#include "fesa/elements/element_capabilities.h"
|
||||
|
||||
#include <utility>
|
||||
|
||||
#include "fesa/elements/hex8_element.h"
|
||||
#include "fesa/elements/nonlinear_mitc4_inertia.h"
|
||||
#include "fesa/elements/thermal_shell.h"
|
||||
|
||||
namespace fesa {
|
||||
|
||||
Mitc4TrialAdapter::Mitc4TrialAdapter(ElementDofLayout layout,
|
||||
const NonlinearMitc4Shell& kernel)
|
||||
: layout_{std::move(layout)}, kernel_{kernel} {}
|
||||
const ElementDofLayout& Mitc4TrialAdapter::DofLayout() const noexcept {
|
||||
return layout_;
|
||||
}
|
||||
Result<NonlinearMitc4InternalEquilibrium> Mitc4TrialAdapter::EvaluateTrial(
|
||||
const NonlinearMitc4TrialDofs& trial, const Vector* direction) const {
|
||||
return EvaluateInternalResidualAndTangent(kernel_, trial, direction);
|
||||
}
|
||||
Result<NonlinearMitc4ExternalDrillingConstraint>
|
||||
Mitc4TrialAdapter::EvaluateExternalDrillingConstraint(
|
||||
std::size_t node, const Vector3& phi, const Vector3& phi_dot,
|
||||
const Vector3& force, const Vector3& force_rate,
|
||||
const Vector3& force_acceleration) const {
|
||||
return kernel_.EvaluateExternalDrillingConstraint(
|
||||
node, phi, phi_dot, force, force_rate, force_acceleration);
|
||||
}
|
||||
|
||||
Hex8TrialAdapter::Hex8TrialAdapter(const Hex8Element& kernel)
|
||||
: kernel_{kernel} {}
|
||||
const ElementDofLayout& Hex8TrialAdapter::DofLayout() const noexcept {
|
||||
return kernel_.DofLayout();
|
||||
}
|
||||
Result<ElementStiffnessContribution> Hex8TrialAdapter::ComputeStiffness() const {
|
||||
return kernel_.ComputeStiffness();
|
||||
}
|
||||
Result<ElementResultBundle> Hex8TrialAdapter::Recover(const Vector& u) const {
|
||||
return kernel_.Recover(u);
|
||||
}
|
||||
Result<Hex8Response> Hex8TrialAdapter::EvaluateLinear(const Vector& u) const {
|
||||
return kernel_.EvaluateLinear(u);
|
||||
}
|
||||
Result<Hex8Response> Hex8TrialAdapter::EvaluateNonlinear(const Vector& u) const {
|
||||
return kernel_.EvaluateNonlinear(u);
|
||||
}
|
||||
Result<Hex8HighLowResponse> Hex8TrialAdapter::EvaluateLinearHighLow(
|
||||
const Vector& high, const Vector& low) const {
|
||||
return kernel_.EvaluateLinearHighLow(high, low);
|
||||
}
|
||||
Result<Hex8HighLowResponse> Hex8TrialAdapter::EvaluateNonlinearHighLow(
|
||||
const Vector& high, const Vector& low) const {
|
||||
return kernel_.EvaluateNonlinearHighLow(high, low);
|
||||
}
|
||||
Result<ElementStiffnessContribution> Hex8TrialAdapter::ComputeMass() const {
|
||||
return kernel_.ComputeMass();
|
||||
}
|
||||
std::array<double, 8> Hex8TrialAdapter::ReferenceWeights() const noexcept {
|
||||
return kernel_.ReferenceWeights();
|
||||
}
|
||||
|
||||
Mitc4InertiaAdapter::Mitc4InertiaAdapter(
|
||||
ElementDofLayout layout, const NonlinearMitc4Inertia& kernel)
|
||||
: layout_{std::move(layout)}, kernel_{kernel} {}
|
||||
const ElementDofLayout& Mitc4InertiaAdapter::DofLayout() const noexcept {
|
||||
return layout_;
|
||||
}
|
||||
Result<NonlinearMitc4InertiaEvaluation> Mitc4InertiaAdapter::Evaluate(
|
||||
const NonlinearMitc4TrialDofs& trial, const Vector& velocity24,
|
||||
const Vector& acceleration24) const {
|
||||
return kernel_.Evaluate(trial, velocity24, acceleration24);
|
||||
}
|
||||
|
||||
Ds4ThermalAdapter::Ds4ThermalAdapter(const ThermalShell& kernel)
|
||||
: layout_{kernel.SourceId(), kernel.NodeIndices()}, kernel_{kernel} {}
|
||||
const ThermalElementDofLayout& Ds4ThermalAdapter::DofLayout() const noexcept {
|
||||
return layout_;
|
||||
}
|
||||
const Matrix& Ds4ThermalAdapter::Conductivity() const noexcept {
|
||||
return kernel_.Conductivity();
|
||||
}
|
||||
const Matrix& Ds4ThermalAdapter::Capacity() const noexcept {
|
||||
return kernel_.Capacity();
|
||||
}
|
||||
const Matrix& Ds4ThermalAdapter::ConductivityError() const noexcept {
|
||||
return kernel_.ConductivityError();
|
||||
}
|
||||
const Matrix& Ds4ThermalAdapter::CapacityError() const noexcept {
|
||||
return kernel_.CapacityError();
|
||||
}
|
||||
Result<ThermalShellRecovery> Ds4ThermalAdapter::Recover(
|
||||
const std::array<double, 20>& temperature) const {
|
||||
return kernel_.Recover(temperature);
|
||||
}
|
||||
|
||||
Mitc4ReferenceAdapter::Mitc4ReferenceAdapter(const Mitc4Shell& kernel)
|
||||
: kernel_{kernel} {}
|
||||
const ElementDofLayout& Mitc4ReferenceAdapter::DofLayout() const noexcept {
|
||||
return kernel_.DofLayout();
|
||||
}
|
||||
Result<ElementStiffnessContribution>
|
||||
Mitc4ReferenceAdapter::ComputeStiffness() const {
|
||||
return kernel_.ComputeStiffness();
|
||||
}
|
||||
Result<ElementResultBundle> Mitc4ReferenceAdapter::Recover(const Vector& u) const {
|
||||
return kernel_.Recover(u);
|
||||
}
|
||||
Result<Mitc4Stiffness> Mitc4ReferenceAdapter::Stiffness() const {
|
||||
return kernel_.Stiffness();
|
||||
}
|
||||
Result<Mitc4Mass> Mitc4ReferenceAdapter::Mass(double density) const {
|
||||
return kernel_.Mass(density);
|
||||
}
|
||||
|
||||
} // namespace fesa
|
||||
@@ -292,6 +292,18 @@ Result<std::unique_ptr<Element>> ElementFactory::CreateSolid(
|
||||
return Result<std::unique_ptr<Element>>::Success(std::move(result));
|
||||
}
|
||||
|
||||
std::unique_ptr<SolidTrialElement> ElementFactory::BindSolidTrial(
|
||||
const Element& kernel) const {
|
||||
const auto* solid = dynamic_cast<const Hex8Element*>(&kernel);
|
||||
return solid == nullptr ? nullptr : std::make_unique<Hex8TrialAdapter>(*solid);
|
||||
}
|
||||
|
||||
std::unique_ptr<ShellReferenceElement> ElementFactory::BindShellReference(
|
||||
const Element& kernel) const {
|
||||
const auto* shell = dynamic_cast<const Mitc4Shell*>(&kernel);
|
||||
return shell == nullptr ? nullptr : std::make_unique<Mitc4ReferenceAdapter>(*shell);
|
||||
}
|
||||
|
||||
Result<std::unique_ptr<Element>> ElementFactory::Create(
|
||||
const ElementDefinition& definition, const Domain& domain) const {
|
||||
switch (definition.Kind()) {
|
||||
|
||||
@@ -6,25 +6,21 @@
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
#include "math/dense_blas_internal.h"
|
||||
#include "math/finite_values.h"
|
||||
#include "math/vector3_operations.h"
|
||||
|
||||
namespace fesa {
|
||||
namespace {
|
||||
|
||||
using math_internal::Multiply;
|
||||
using math_internal::Skew;
|
||||
using math_internal::IsFinite;
|
||||
|
||||
constexpr std::size_t kRotationDimension = 3U;
|
||||
constexpr double kRotationStateTolerance = 1.0e-12;
|
||||
constexpr double kSmallAngleThreshold = 1.0e-4;
|
||||
|
||||
/// @brief Returns the SO(3) skew-symmetric matrix for a global vector.
|
||||
Matrix Skew(const Vector3& vector) {
|
||||
Matrix result{kRotationDimension, kRotationDimension};
|
||||
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 Returns the 3-by-3 identity matrix.
|
||||
Matrix Identity() {
|
||||
Matrix result{kRotationDimension, kRotationDimension};
|
||||
@@ -63,18 +59,6 @@ double Determinant(const Matrix& matrix) {
|
||||
matrix(1U, 1U) * matrix(2U, 0U));
|
||||
}
|
||||
|
||||
/// @brief Checks that all matrix entries are finite.
|
||||
bool IsFinite(const Matrix& matrix) {
|
||||
for (std::size_t row = 0U; row < matrix.Rows(); ++row) {
|
||||
for (std::size_t column = 0U; column < matrix.Columns(); ++column) {
|
||||
if (!std::isfinite(matrix(row, column))) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/// @brief Checks the exact 3-by-3 proper-orthogonal frame contract.
|
||||
bool IsProperOrthogonal(const Matrix& matrix) {
|
||||
if (matrix.Rows() != kRotationDimension ||
|
||||
@@ -113,17 +97,6 @@ bool IsPrincipalCoordinate(const Vector3& phi) {
|
||||
return std::isfinite(angle) && pi - angle > margin;
|
||||
}
|
||||
|
||||
/// @brief Multiplies a 3-by-3 frame by a global vector.
|
||||
Vector3 Multiply(const Matrix& matrix, const Vector3& vector) {
|
||||
return Vector3{
|
||||
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 Checks componentwise agreement without scale-dependent repair.
|
||||
bool IsNear(const Matrix& actual, const Matrix& expected) {
|
||||
if (actual.Rows() != expected.Rows() ||
|
||||
@@ -225,25 +198,15 @@ Matrix LeftJacobianSO3(const Vector3& phi) {
|
||||
quadratic_coefficient);
|
||||
}
|
||||
|
||||
Matrix InverseTransposeLeftJacobianSO3(const Vector3& phi) {
|
||||
const Matrix skew = Skew(phi);
|
||||
const Matrix skew_squared = skew.Multiply(skew);
|
||||
const double theta = phi.Norm();
|
||||
const double theta_squared = theta * theta;
|
||||
|
||||
double quadratic_coefficient = 1.0 / 12.0;
|
||||
if (theta >= kSmallAngleThreshold) {
|
||||
const double half_theta = 0.5 * theta;
|
||||
quadratic_coefficient =
|
||||
(1.0 - half_theta / std::tan(half_theta)) / theta_squared;
|
||||
} else {
|
||||
const double theta_fourth = theta_squared * theta_squared;
|
||||
const double theta_sixth = theta_fourth * theta_squared;
|
||||
quadratic_coefficient =
|
||||
1.0 / 12.0 + theta_squared / 720.0 + theta_fourth / 30240.0 +
|
||||
theta_sixth / 1209600.0;
|
||||
}
|
||||
return Polynomial(skew, skew_squared, 0.5, quadratic_coefficient);
|
||||
Result<Vector3> SpatialMomentSO3(const Vector3& phi, const Vector3& generalized) {
|
||||
const auto factor = dense_blas_internal::DenseFactor::Lu(LeftJacobianSO3(phi));
|
||||
if (!factor.HasValue()) return Result<Vector3>::Failure(factor.GetStatus());
|
||||
Matrix rhs{3U, 1U};
|
||||
for (std::size_t i = 0; i < 3U; ++i) rhs(i, 0U) = generalized[i];
|
||||
const auto solved = factor.Value().Solve(rhs, true);
|
||||
if (!solved.HasValue()) return Result<Vector3>::Failure(solved.GetStatus());
|
||||
return Result<Vector3>::Success(
|
||||
Vector3{solved.Value()(0, 0), solved.Value()(1, 0), solved.Value()(2, 0)});
|
||||
}
|
||||
|
||||
Result<FiniteRotationState> MakeFiniteRotationState(
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
#include "fesa/elements/hex8_element.h"
|
||||
|
||||
#include "fesa/math/vector3.h"
|
||||
|
||||
#include <cmath>
|
||||
#include <memory>
|
||||
#include <set>
|
||||
@@ -180,17 +182,46 @@ Result<Hex8Element> Hex8Element::Create(EntityIndex index,
|
||||
point.weight <= 0.0)
|
||||
return Result<Hex8Element>::Failure(candidate.Failure(
|
||||
"Reference quadrature Jacobian must be finite and positive."));
|
||||
const auto inverse = hex8_internal::Inverse(jacobian, point.weight);
|
||||
if (!Finite(inverse))
|
||||
const Vector3 c0{jacobian[0][0], jacobian[1][0], jacobian[2][0]};
|
||||
const Vector3 c1{jacobian[0][1], jacobian[1][1], jacobian[2][1]};
|
||||
const Vector3 c2{jacobian[0][2], jacobian[1][2], jacobian[2][2]};
|
||||
if (!c0.Cross(c1).IsFinite() || !c1.Cross(c2).IsFinite() ||
|
||||
!c2.Cross(c0).IsFinite())
|
||||
return Result<Hex8Element>::Failure(
|
||||
candidate.Failure("Reference Jacobian inverse is nonfinite."));
|
||||
Matrix coefficients{3U, 3U};
|
||||
Matrix natural_gradients{3U, 8U};
|
||||
for (std::size_t i = 0; i < 3U; ++i) {
|
||||
for (std::size_t d = 0; d < 3U; ++d)
|
||||
coefficients(i, d) = jacobian[i][d];
|
||||
for (std::size_t a = 0; a < 8U; ++a)
|
||||
natural_gradients(i, a) = derivatives[a][i];
|
||||
}
|
||||
const auto factor = dense_blas_internal::DenseFactor::Lu(coefficients);
|
||||
if (!factor.HasValue())
|
||||
return Result<Hex8Element>::Failure(
|
||||
candidate.Failure("Reference Jacobian inverse is nonfinite."));
|
||||
// The original geometry gate required every reciprocal action to be
|
||||
// finite, including entries not exposed by the smaller shape-gradient RHS.
|
||||
Matrix canonical{3U, 1U};
|
||||
for (std::size_t axis = 0; axis < 3U; ++axis) {
|
||||
if (axis != 0U) canonical(axis - 1U, 0U) = 0.0;
|
||||
canonical(axis, 0U) = 1.0;
|
||||
if (!factor.Value().Solve(canonical, true).HasValue())
|
||||
return Result<Hex8Element>::Failure(
|
||||
candidate.Failure("Reference Jacobian inverse is nonfinite."));
|
||||
}
|
||||
// J has Cartesian rows and natural columns: J^T grad_x N = grad_nat N.
|
||||
const auto gradients = factor.Value().Solve(natural_gradients, true);
|
||||
if (!gradients.HasValue())
|
||||
return Result<Hex8Element>::Failure(candidate.Failure(
|
||||
"Reference gradient or point coordinate is nonfinite."));
|
||||
point.position = coordinates[0];
|
||||
for (std::size_t a = 0; a < 8; ++a)
|
||||
for (std::size_t i = 0; i < 3; ++i) {
|
||||
point.position[i] +=
|
||||
point.shape[a] * (coordinates[a][i] - coordinates[0][i]);
|
||||
for (std::size_t d = 0; d < 3; ++d)
|
||||
point.gradients[a][i] += inverse[d][i] * derivatives[a][d];
|
||||
point.gradients[a][i] = gradients.Value()(i, a);
|
||||
if (!std::isfinite(point.gradients[a][i]) ||
|
||||
!std::isfinite(point.position[i]))
|
||||
return Result<Hex8Element>::Failure(candidate.Failure(
|
||||
|
||||
@@ -228,17 +228,4 @@ double Determinant(const Tensor& j) {
|
||||
j[0][1] * (j[1][0] * j[2][2] - j[1][2] * j[2][0]) +
|
||||
j[0][2] * (j[1][0] * j[2][1] - j[1][1] * j[2][0]);
|
||||
}
|
||||
Tensor Inverse(const Tensor& j, double determinant) {
|
||||
Tensor inverse{};
|
||||
for (std::size_t i = 0; i < 3; ++i)
|
||||
for (std::size_t d = 0; d < 3; ++d) {
|
||||
const auto r1 = (d + 1U) % 3U;
|
||||
const auto r2 = (d + 2U) % 3U;
|
||||
const auto c1 = (i + 1U) % 3U;
|
||||
const auto c2 = (i + 2U) % 3U;
|
||||
inverse[i][d] =
|
||||
(j[r1][c1] * j[r2][c2] - j[r1][c2] * j[r2][c1]) / determinant;
|
||||
}
|
||||
return inverse;
|
||||
}
|
||||
} // namespace fesa::hex8_internal
|
||||
|
||||
@@ -26,8 +26,6 @@ Tensor Jacobian(const Hex8Element::Coordinates& coordinates,
|
||||
const std::array<Triple, 8>& derivatives);
|
||||
/// @brief Computes the signed determinant; no absolute value or clamp.
|
||||
double Determinant(const Tensor& matrix);
|
||||
/// @brief Computes the inverse for an already validated positive determinant.
|
||||
Tensor Inverse(const Tensor& matrix, double determinant);
|
||||
} // namespace fesa::hex8_internal
|
||||
|
||||
#endif // FESA_ELEMENTS_HEX8_GEOMETRY_H_
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
#include "../math/high_low_arithmetic.h"
|
||||
#include "hex8_geometry.h"
|
||||
#include "hex8_linear_tangent_cache.h"
|
||||
#include "hex8_pair_arithmetic.h"
|
||||
#include "hex8_pair_simd.h"
|
||||
|
||||
namespace fesa {
|
||||
@@ -18,13 +17,13 @@ using Triple = std::array<HighLow, 3>;
|
||||
using Tensor = std::array<Triple, 3>;
|
||||
|
||||
HighLow operator+(HighLow a, HighLow b) {
|
||||
return hex8_internal::AddPair(a, b);
|
||||
return math_internal::Add(a, b);
|
||||
}
|
||||
HighLow operator-(HighLow a, HighLow b) {
|
||||
return math_internal::Subtract(a, b);
|
||||
}
|
||||
HighLow operator*(HighLow a, HighLow b) {
|
||||
return hex8_internal::MultiplyPair(a, b);
|
||||
return math_internal::Multiply(a, b);
|
||||
}
|
||||
HighLow operator/(HighLow a, HighLow b) {
|
||||
return math_internal::Divide(a, b);
|
||||
|
||||
@@ -1,40 +0,0 @@
|
||||
#ifndef FESA_SRC_FESA_ELEMENTS_HEX8_PAIR_ARITHMETIC_H_
|
||||
#define FESA_SRC_FESA_ELEMENTS_HEX8_PAIR_ARITHMETIC_H_
|
||||
|
||||
#include "../math/high_low_arithmetic.h"
|
||||
|
||||
namespace fesa::hex8_internal {
|
||||
/// @brief Preserves original zero-add normalization without redundant TwoSums.
|
||||
/// @note Valid nonzero terms retain the original addition association.
|
||||
inline math_internal::HighLow AddPair(math_internal::HighLow left,
|
||||
math_internal::HighLow right) {
|
||||
using namespace math_internal;
|
||||
if (!IsNormalized(left) || !IsNormalized(right)) return InvalidHighLow();
|
||||
if (left.high == 0 && left.low == 0)
|
||||
return {right.high, right.low == 0 ? 0.0 : right.low};
|
||||
if (right.high == 0 && right.low == 0)
|
||||
return {left.high, left.low == 0 ? 0.0 : left.low};
|
||||
return Add(left, right);
|
||||
}
|
||||
/// @brief Omits exact-zero cross products without changing nonzero association.
|
||||
/// @note A retained zero addition performs the original canonicalization and
|
||||
/// invalid-result check; its repeated application is idempotent.
|
||||
inline math_internal::HighLow MultiplyPair(math_internal::HighLow left,
|
||||
math_internal::HighLow right) {
|
||||
using namespace math_internal;
|
||||
if (!IsNormalized(left) || !IsNormalized(right)) return InvalidHighLow();
|
||||
if (left.low != 0 && right.low != 0) return Multiply(left, right);
|
||||
auto product = TwoProduct(left.high, right.high);
|
||||
if (right.low != 0) {
|
||||
product = AddPair(product, TwoProduct(left.high, right.low));
|
||||
} else if (left.low != 0) {
|
||||
// The original zero addition precedes the nonzero low-high term.
|
||||
product = AddPair(product, HighLow{});
|
||||
}
|
||||
if (left.low != 0)
|
||||
product = AddPair(product, TwoProduct(left.low, right.high));
|
||||
return AddPair(product, HighLow{});
|
||||
}
|
||||
} // namespace fesa::hex8_internal
|
||||
|
||||
#endif // FESA_SRC_FESA_ELEMENTS_HEX8_PAIR_ARITHMETIC_H_
|
||||
@@ -60,11 +60,11 @@ void EvaluateMaterialPacketScalar(const MaterialPacketInput& input,
|
||||
void EvaluateMaterialPacketAvx2(const MaterialPacketInput& input,
|
||||
PairPacket* output);
|
||||
/// @brief Baseline-ISA checked addition bridge in lane order 0..3.
|
||||
/// @note pair_shortcuts chooses the original local AddPair or general Add graph;
|
||||
/// @note Uses the shared checked high/low arithmetic owner;
|
||||
/// invalid pairs are preserved, with no FP-state mutation or normalization here.
|
||||
void ScalarPacketAdd(const PairPacket& left, const PairPacket& right,
|
||||
bool pair_shortcuts, PairPacket* output);
|
||||
/// @brief Baseline-ISA original checked MultiplyPair bridge in lane order 0..3.
|
||||
PairPacket* output);
|
||||
/// @brief Baseline-ISA shared checked multiplication bridge in lane order 0..3.
|
||||
/// @note Raw invalid outputs remain invalid; no component is rounded away.
|
||||
void ScalarPacketMultiply(const PairPacket& left, const PairPacket& right,
|
||||
PairPacket* output);
|
||||
|
||||
@@ -144,11 +144,11 @@ bool TwoSum(__m256d left, __m256d right, VectorPair* output) {
|
||||
return true;
|
||||
}
|
||||
/// @brief Routes exceptional packets to baseline checked Add in lane order.
|
||||
VectorPair ScalarAdd(VectorPair left, VectorPair right, bool pair_shortcuts) {
|
||||
VectorPair ScalarAdd(VectorPair left, VectorPair right) {
|
||||
const auto a = Store(left);
|
||||
const auto b = Store(right);
|
||||
PairPacket result;
|
||||
ScalarPacketAdd(a, b, pair_shortcuts, &result);
|
||||
ScalarPacketAdd(a, b, &result);
|
||||
return Load(result);
|
||||
}
|
||||
/// @brief Routes exceptional packets to baseline checked MultiplyPair.
|
||||
@@ -160,7 +160,7 @@ VectorPair ScalarMultiply(const PairPacket& left, const PairPacket& right) {
|
||||
/// @brief Retains normalized/zero branches and the checked per-lane Add graph.
|
||||
VectorPair Add(VectorPair left, VectorPair right, bool pair_shortcuts) {
|
||||
if (!NormalizedInputs(left, right))
|
||||
return ScalarAdd(left, right, pair_shortcuts);
|
||||
return ScalarAdd(left, right);
|
||||
const int left_zero = _mm256_movemask_pd(
|
||||
_mm256_and_pd(Zero(left.high), Zero(left.low)));
|
||||
const int right_zero = _mm256_movemask_pd(
|
||||
@@ -171,7 +171,7 @@ VectorPair Add(VectorPair left, VectorPair right, bool pair_shortcuts) {
|
||||
if (right_zero == 15)
|
||||
return {left.high, Select(Zero(left.low), _mm256_setzero_pd(), left.low)};
|
||||
if (left_zero != 0 || right_zero != 0)
|
||||
return ScalarAdd(left, right, true);
|
||||
return ScalarAdd(left, right);
|
||||
}
|
||||
VectorPair head;
|
||||
VectorPair tail;
|
||||
@@ -181,7 +181,7 @@ VectorPair Add(VectorPair left, VectorPair right, bool pair_shortcuts) {
|
||||
!TwoSum(left.low, right.low, &tail) ||
|
||||
!TwoSum(head.low, tail.high, &middle) ||
|
||||
!TwoSum(head.high, middle.high, &joined))
|
||||
return ScalarAdd(left, right, pair_shortcuts);
|
||||
return ScalarAdd(left, right);
|
||||
const auto remainder = _mm256_add_pd(
|
||||
_mm256_add_pd(joined.low, middle.low), tail.low);
|
||||
const auto valid = _mm256_and_pd(Finite(remainder), _mm256_and_pd(
|
||||
@@ -193,7 +193,7 @@ VectorPair Add(VectorPair left, VectorPair right, bool pair_shortcuts) {
|
||||
VectorPair result;
|
||||
if (!TwoSum(Select(valid, joined.high, _mm256_setzero_pd()),
|
||||
Select(valid, remainder, _mm256_setzero_pd()), &result))
|
||||
return ScalarAdd(left, right, pair_shortcuts);
|
||||
return ScalarAdd(left, right);
|
||||
return InvalidOutside(result, valid);
|
||||
}
|
||||
/// @brief Checks finite/zero/tail premises before products; neutralizes rejected
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#include "hex8_pair_simd.h"
|
||||
|
||||
#include "hex8_pair_arithmetic.h"
|
||||
#include "../math/high_low_arithmetic.h"
|
||||
|
||||
#if defined(FESA_HEX8_PAIR_AVX2) && defined(_MSC_VER) && defined(_M_X64)
|
||||
#include <float.h>
|
||||
@@ -16,11 +16,11 @@ namespace fesa::hex8_internal {
|
||||
#endif
|
||||
FESA_HEX8_BASELINE_NOINLINE
|
||||
void ScalarPacketAdd(const PairPacket& left, const PairPacket& right,
|
||||
bool pair_shortcuts, PairPacket* output) {
|
||||
PairPacket* output) {
|
||||
for (std::size_t lane = 0; lane < 4; ++lane) {
|
||||
const math_internal::HighLow a{left.values[lane].high, left.values[lane].low};
|
||||
const math_internal::HighLow b{right.values[lane].high, right.values[lane].low};
|
||||
const auto value = pair_shortcuts ? AddPair(a, b) : math_internal::Add(a, b);
|
||||
const auto value = math_internal::Add(a, b);
|
||||
output->values[lane] = {value.high, value.low};
|
||||
}
|
||||
}
|
||||
@@ -28,7 +28,7 @@ FESA_HEX8_BASELINE_NOINLINE
|
||||
void ScalarPacketMultiply(const PairPacket& left, const PairPacket& right,
|
||||
PairPacket* output) {
|
||||
for (std::size_t lane = 0; lane < 4; ++lane) {
|
||||
const auto value = MultiplyPair(
|
||||
const auto value = math_internal::Multiply(
|
||||
{left.values[lane].high, left.values[lane].low},
|
||||
{right.values[lane].high, right.values[lane].low});
|
||||
output->values[lane] = {value.high, value.low};
|
||||
@@ -51,14 +51,14 @@ void EvaluateGeometryPacketScalar(const GeometryPacketInput& input,
|
||||
for (std::size_t lane = 0; lane < 4; ++lane) {
|
||||
math_internal::HighLow dot;
|
||||
for (std::size_t i = 0; i < 3; ++i)
|
||||
dot = AddPair(dot, MultiplyPair(value(input.gradient_a[i]),
|
||||
dot = math_internal::Add(dot, math_internal::Multiply(value(input.gradient_a[i]),
|
||||
value(input.gradient_b[i].values[lane])));
|
||||
math_internal::HighLow geo;
|
||||
for (std::size_t j = 0; j < 3; ++j)
|
||||
for (std::size_t l = 0; l < 3; ++l)
|
||||
geo = AddPair(geo, MultiplyPair(value(input.gradient_stress[j][l]),
|
||||
geo = math_internal::Add(geo, math_internal::Multiply(value(input.gradient_stress[j][l]),
|
||||
value(input.gradient_b[l].values[lane])));
|
||||
const auto weighted = MultiplyPair(geo, value(input.weight));
|
||||
const auto weighted = math_internal::Multiply(geo, value(input.weight));
|
||||
output->gradient_dot.values[lane] = {dot.high, dot.low};
|
||||
output->weighted_geometric.values[lane] = {weighted.high, weighted.low};
|
||||
}
|
||||
@@ -84,15 +84,15 @@ void EvaluateMaterialPacketScalar(const MaterialPacketInput& input,
|
||||
return math_internal::HighLow{pair.high, pair.low};
|
||||
};
|
||||
for (std::size_t lane = 0; lane < 4; ++lane) {
|
||||
const auto first = MultiplyPair(value(input.deviatoric_left.values[lane]),
|
||||
const auto first = math_internal::Multiply(value(input.deviatoric_left.values[lane]),
|
||||
value(input.deviatoric_right.values[lane]));
|
||||
const auto second = MultiplyPair(value(input.shear_left.values[lane]),
|
||||
const auto second = math_internal::Multiply(value(input.shear_left.values[lane]),
|
||||
value(input.shear_right.values[lane]));
|
||||
const auto third = MultiplyPair(value(input.tensor_left.values[lane]),
|
||||
const auto third = math_internal::Multiply(value(input.tensor_left.values[lane]),
|
||||
value(input.gradient_dot.values[lane]));
|
||||
const auto material = AddPair(AddPair(first, second), third);
|
||||
const auto accumulated = AddPair(value(input.accumulated.values[lane]),
|
||||
MultiplyPair(material, value(input.weight)));
|
||||
const auto material = math_internal::Add(math_internal::Add(first, second), third);
|
||||
const auto accumulated = math_internal::Add(value(input.accumulated.values[lane]),
|
||||
math_internal::Multiply(material, value(input.weight)));
|
||||
output->values[lane] = {accumulated.high, accumulated.low};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,9 @@
|
||||
|
||||
#include "fesa/model/model_types.h"
|
||||
#include "math/dense_blas_internal.h"
|
||||
#include "math/finite_values.h"
|
||||
#include "math/vector3_operations.h"
|
||||
#include "elements/shell_kernel_values.h"
|
||||
|
||||
namespace fesa {
|
||||
|
||||
@@ -63,6 +66,13 @@ void Mitc4ShellDefinition::SynchronizeNodeIndices() {
|
||||
|
||||
namespace {
|
||||
|
||||
using shell_kernel_internal::FrameComponent;
|
||||
using shell_kernel_internal::LocalEngineeringComponents;
|
||||
using shell_kernel_internal::ElementIdentity;
|
||||
using math_internal::DerivativeSum;
|
||||
using math_internal::WeightedSum;
|
||||
using math_internal::IsFinite;
|
||||
|
||||
constexpr std::size_t kNodeCount = 4U;
|
||||
constexpr std::size_t kPhysicalDofsPerNode = 5U;
|
||||
constexpr std::size_t kGlobalDofsPerNode = 6U;
|
||||
@@ -71,24 +81,6 @@ constexpr std::size_t kGlobalDofCount = 24U;
|
||||
constexpr double kShearCorrection = 5.0 / 6.0;
|
||||
constexpr double kFrameTolerance = 1.0e-12;
|
||||
|
||||
Vector3 WeightedSum(const std::array<double, kNodeCount>& weights,
|
||||
const std::array<Vector3, kNodeCount>& values) {
|
||||
Vector3 result{};
|
||||
for (std::size_t node = 0U; node < kNodeCount; ++node) {
|
||||
result = result + weights[node] * values[node];
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
Vector3 DerivativeSum(const std::array<double, kNodeCount>& derivatives,
|
||||
const std::array<Vector3, kNodeCount>& values) {
|
||||
std::array<Vector3, kNodeCount> relative{};
|
||||
for (std::size_t node = 0U; node < kNodeCount; ++node) {
|
||||
relative[node] = values[node] - values[0];
|
||||
}
|
||||
return WeightedSum(derivatives, relative);
|
||||
}
|
||||
|
||||
bool SameCoordinates(const Vector3& left, const Vector3& right) {
|
||||
return left == right;
|
||||
}
|
||||
@@ -129,20 +121,6 @@ std::array<Vector3, kNodeCount> NodalTangentsB(
|
||||
return tangents;
|
||||
}
|
||||
|
||||
std::string ElementIdentity(const std::array<const Node*, kNodeCount>& nodes) {
|
||||
std::string identity;
|
||||
for (const Node* node : nodes) {
|
||||
if (node == nullptr) {
|
||||
continue;
|
||||
}
|
||||
if (!identity.empty()) {
|
||||
identity += "-";
|
||||
}
|
||||
identity += node->source_id.source_label_text;
|
||||
}
|
||||
return identity;
|
||||
}
|
||||
|
||||
Result<Mitc4Shell> ModelFailure(std::string code,
|
||||
const SourceLocation& location,
|
||||
const std::string& identity,
|
||||
@@ -169,58 +147,36 @@ std::array<std::array<double, 3>, 3> CovariantStrainColumn(
|
||||
return strain;
|
||||
}
|
||||
|
||||
/// @brief Reconstructs Cartesian strain in the approved reciprocal-basis
|
||||
/// reduction order.
|
||||
/// @brief Solves C^T E C = E_cov for the actual strain tensor.
|
||||
std::array<std::array<double, 3>, 3> ReconstructCartesianStrain(
|
||||
const std::array<std::array<double, 3>, 3>& covariant_strain,
|
||||
const std::array<Vector3, 3>& reciprocal) {
|
||||
const dense_blas_internal::DenseFactor& factor) {
|
||||
Matrix rhs{3U, 3U};
|
||||
for (std::size_t i = 0; i < 3U; ++i)
|
||||
for (std::size_t j = 0; j < 3U; ++j) rhs(i, j) = covariant_strain[i][j];
|
||||
const auto left = factor.Solve(rhs, true);
|
||||
if (!left.HasValue())
|
||||
throw std::invalid_argument{"MITC4 Cartesian strain is nonfinite."};
|
||||
const auto solved = factor.SolveRight(left.Value());
|
||||
if (!solved.HasValue())
|
||||
throw std::invalid_argument{"MITC4 Cartesian strain is nonfinite."};
|
||||
std::array<std::array<double, 3>, 3> tensor{};
|
||||
for (std::size_t row = 0U; row < 3U; ++row) {
|
||||
for (std::size_t column = 0U; column < 3U; ++column) {
|
||||
for (std::size_t first = 0U; first < 3U; ++first) {
|
||||
for (std::size_t second = 0U; second < 3U; ++second) {
|
||||
tensor[row][column] += covariant_strain[first][second] *
|
||||
reciprocal[first][row] *
|
||||
reciprocal[second][column];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for (std::size_t i = 0; i < 3U; ++i)
|
||||
for (std::size_t j = 0; j < 3U; ++j) tensor[i][j] = solved.Value()(i, j);
|
||||
return tensor;
|
||||
}
|
||||
|
||||
double FrameComponent(const Vector3& left,
|
||||
const std::array<std::array<double, 3>, 3>& tensor,
|
||||
const Vector3& right) {
|
||||
double value = 0.0;
|
||||
for (std::size_t row = 0U; row < 3U; ++row) {
|
||||
for (std::size_t column = 0U; column < 3U; ++column) {
|
||||
value += left[row] * tensor[row][column] * right[column];
|
||||
}
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
/// @brief Projects tensor strain into signed local engineering components.
|
||||
std::array<double, 5> LocalEngineeringComponents(
|
||||
const std::array<std::array<double, 3>, 3>& tensor,
|
||||
const Mitc4LocalFrame& frame) {
|
||||
const Vector3 e1{frame.e1};
|
||||
const Vector3 e2{frame.e2};
|
||||
const Vector3 e3{frame.e3};
|
||||
return {FrameComponent(e1, tensor, e1), FrameComponent(e2, tensor, e2),
|
||||
2.0 * FrameComponent(e1, tensor, e2),
|
||||
2.0 * FrameComponent(e1, tensor, e3),
|
||||
2.0 * FrameComponent(e2, tensor, e3)};
|
||||
/// @brief Packs covariant columns without constructing reciprocal vectors.
|
||||
Matrix CovariantMatrix(const std::array<Vector3, 3>& covariant) {
|
||||
Matrix coefficients{3U, 3U};
|
||||
for (std::size_t i = 0; i < 3U; ++i)
|
||||
for (std::size_t j = 0; j < 3U; ++j) coefficients(i, j) = covariant[j][i];
|
||||
return coefficients;
|
||||
}
|
||||
|
||||
Matrix ScaledMatrix(const Matrix& source, double factor) {
|
||||
Matrix result{source.Rows(), source.Columns()};
|
||||
for (std::size_t row = 0U; row < source.Rows(); ++row) {
|
||||
for (std::size_t column = 0U; column < source.Columns(); ++column) {
|
||||
result(row, column) = factor * source(row, column);
|
||||
}
|
||||
}
|
||||
Matrix result{source};
|
||||
result.Scale(factor);
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -241,26 +197,6 @@ Matrix Congruence(const Matrix& local, const Matrix& transformation) {
|
||||
return result;
|
||||
}
|
||||
|
||||
bool IsFinite(const Matrix& matrix) {
|
||||
for (std::size_t row = 0U; row < matrix.Rows(); ++row) {
|
||||
for (std::size_t column = 0U; column < matrix.Columns(); ++column) {
|
||||
if (!std::isfinite(matrix(row, column))) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool IsFinite(const Vector& vector) {
|
||||
for (std::size_t index = 0U; index < vector.Size(); ++index) {
|
||||
if (!std::isfinite(vector[index])) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
Result<Mitc4Stiffness> StiffnessFailure(const SourceLocation& location,
|
||||
const std::string& identity,
|
||||
std::string message) {
|
||||
@@ -1092,12 +1028,25 @@ bool Mitc4Shell::EvaluateGeometry(double xi, double eta, double zeta,
|
||||
return false;
|
||||
}
|
||||
|
||||
result.reciprocal[0] =
|
||||
(1.0 / result.jacobian) * result.covariant[1].Cross(result.covariant[2]);
|
||||
result.reciprocal[1] =
|
||||
(1.0 / result.jacobian) * result.covariant[2].Cross(result.covariant[0]);
|
||||
result.reciprocal[2] =
|
||||
(1.0 / result.jacobian) * result.covariant[0].Cross(result.covariant[1]);
|
||||
if (!std::isfinite(1.0 / result.jacobian) ||
|
||||
!result.covariant[1].Cross(result.covariant[2]).IsFinite() ||
|
||||
!result.covariant[2].Cross(result.covariant[0]).IsFinite() ||
|
||||
!result.covariant[0].Cross(result.covariant[1]).IsFinite()) return false;
|
||||
// Preserve the reciprocal finiteness gate, streaming each action directly
|
||||
// into validation rather than retaining a reciprocal-basis matrix.
|
||||
try {
|
||||
const auto factor = dense_blas_internal::DenseFactor::Lu(
|
||||
CovariantMatrix(result.covariant));
|
||||
if (!factor.HasValue()) return false;
|
||||
Matrix rhs{3U, 1U};
|
||||
for (std::size_t axis = 0; axis < 3U; ++axis) {
|
||||
if (axis != 0U) rhs(axis - 1U, 0U) = 0.0;
|
||||
rhs(axis, 0U) = 1.0;
|
||||
if (!factor.Value().Solve(rhs, true).HasValue()) return false;
|
||||
}
|
||||
} catch (const std::exception&) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const Vector3 area = midsurface_xi.Cross(midsurface_eta);
|
||||
const double director_norm = director_value.Norm();
|
||||
@@ -1120,8 +1069,7 @@ bool Mitc4Shell::EvaluateGeometry(double xi, double eta, double zeta,
|
||||
const Vector3 e2 = e3.Cross(e1);
|
||||
result.frame.e1 = e1.Components();
|
||||
result.frame.e2 = e2.Components();
|
||||
return result.reciprocal[0].IsFinite() && result.reciprocal[1].IsFinite() &&
|
||||
result.reciprocal[2].IsFinite() && e2.IsFinite();
|
||||
return e2.IsFinite();
|
||||
}
|
||||
|
||||
std::array<std::array<Vector3, 3>, 20> Mitc4Shell::BasisDerivatives(
|
||||
@@ -1165,6 +1113,11 @@ Matrix Mitc4Shell::StrainDisplacement(double xi, double eta, double zeta,
|
||||
throw std::invalid_argument{
|
||||
"MITC4 strain geometry is invalid at the requested point."};
|
||||
}
|
||||
const auto factor = dense_blas_internal::DenseFactor::Lu(
|
||||
CovariantMatrix(geometry.covariant));
|
||||
if (!factor.HasValue())
|
||||
throw std::invalid_argument{
|
||||
"MITC4 strain geometry is invalid at the requested point."};
|
||||
const auto derivatives = BasisDerivatives(xi, eta, zeta);
|
||||
const Mitc4TyingWeights weights = TyingWeights(xi, eta);
|
||||
Matrix result{5U, kPhysicalDofCount};
|
||||
@@ -1180,7 +1133,7 @@ Matrix Mitc4Shell::StrainDisplacement(double xi, double eta, double zeta,
|
||||
covariant[2U][1U] = covariant[1U][2U];
|
||||
}
|
||||
const auto engineering = LocalEngineeringComponents(
|
||||
ReconstructCartesianStrain(covariant, geometry.reciprocal),
|
||||
ReconstructCartesianStrain(covariant, factor.Value()),
|
||||
geometry.frame);
|
||||
for (std::size_t component = 0U; component < engineering.size();
|
||||
++component) {
|
||||
|
||||
@@ -11,32 +11,29 @@
|
||||
|
||||
#include "fesa/elements/finite_rotation.h"
|
||||
#include "math/dense_blas_internal.h"
|
||||
#include "math/jet_arithmetic.h"
|
||||
#include "math/jet_linear_solve.h"
|
||||
#include "math/finite_values.h"
|
||||
#include "math/vector3_operations.h"
|
||||
#include "elements/shell_kernel_values.h"
|
||||
#include "math/compensated_sum.h"
|
||||
|
||||
namespace fesa {
|
||||
namespace {
|
||||
|
||||
using shell_kernel_internal::Failure;
|
||||
using InertiaSum = math_internal::CompensatedSum;
|
||||
using shell_kernel_internal::FrameMatrix;
|
||||
using math_internal::DerivativeSum;
|
||||
using math_internal::WeightedSum;
|
||||
using math_internal::IsFinite;
|
||||
|
||||
constexpr std::size_t kNodes = 4U;
|
||||
constexpr std::size_t kDofs = 24U;
|
||||
const std::array<double, 3> kGauss{-std::sqrt(3.0 / 5.0), 0.0,
|
||||
std::sqrt(3.0 / 5.0)};
|
||||
constexpr std::array<double, 3> kWeights{5.0 / 9.0, 8.0 / 9.0, 5.0 / 9.0};
|
||||
|
||||
class InertiaSum {
|
||||
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;
|
||||
};
|
||||
|
||||
/// @brief Contracts two nodal 6-by-3 coefficient blocks through dense MKL.
|
||||
/// @note Nonfinite coefficients remain failures of the final inertia gate.
|
||||
void DotBlocks(const std::array<double, 18U>& left,
|
||||
@@ -47,25 +44,6 @@ void DotBlocks(const std::array<double, 18U>& left,
|
||||
result.fill(std::numeric_limits<double>::quiet_NaN());
|
||||
}
|
||||
|
||||
Vector3 WeightedSum(const std::array<double, kNodes>& weights,
|
||||
const std::array<Vector3, kNodes>& values) {
|
||||
Vector3 result{};
|
||||
for (std::size_t node = 0U; node < kNodes; ++node)
|
||||
result = result + weights[node] * values[node];
|
||||
return result;
|
||||
}
|
||||
|
||||
// A derivative partition has no absolute-position contribution. Anchoring
|
||||
// before reduction preserves that identity at large global offsets and keeps
|
||||
// the reference/current covariants in the same canonical form.
|
||||
Vector3 DerivativeSum(const std::array<double, kNodes>& derivatives,
|
||||
const std::array<Vector3, kNodes>& values) {
|
||||
std::array<Vector3, kNodes> relative{};
|
||||
for (std::size_t node = 0U; node < kNodes; ++node)
|
||||
relative[node] = values[node] - values[0U];
|
||||
return WeightedSum(derivatives, relative);
|
||||
}
|
||||
|
||||
Vector3 InterpolatedSum(const std::array<double, kNodes>& weights,
|
||||
const std::array<Vector3, kNodes>& values) {
|
||||
std::array<Vector3, kNodes> relative{};
|
||||
@@ -74,14 +52,6 @@ Vector3 InterpolatedSum(const std::array<double, kNodes>& weights,
|
||||
return values[0U] + WeightedSum(weights, relative);
|
||||
}
|
||||
|
||||
template <class T>
|
||||
Result<T> Failure(const std::string& code, const SourceLocation& location,
|
||||
const std::string& identity, std::string message) {
|
||||
return Result<T>::Failure(Status::Failure(
|
||||
FailureCategory::kModel, {{Severity::kError, code, location, "*ELEMENT",
|
||||
identity, std::move(message)}}));
|
||||
}
|
||||
|
||||
std::string Identity(const std::array<const Node*, kNodes>& nodes) {
|
||||
std::string identity;
|
||||
for (const Node* node : nodes) {
|
||||
@@ -93,36 +63,6 @@ std::string Identity(const std::array<const Node*, kNodes>& nodes) {
|
||||
return identity;
|
||||
}
|
||||
|
||||
Matrix FrameMatrix(const ShellNodeInitialFrame& frame) {
|
||||
Matrix result{3U, 3U};
|
||||
for (std::size_t row = 0U; row < 3U; ++row) {
|
||||
result(row, 0U) = frame.tangent_a[row];
|
||||
result(row, 1U) = frame.tangent_b[row];
|
||||
result(row, 2U) = frame.director[row];
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
bool IsFinite(const Matrix& matrix) {
|
||||
for (std::size_t row = 0U; row < matrix.Rows(); ++row) {
|
||||
for (std::size_t column = 0U; column < matrix.Columns(); ++column) {
|
||||
if (!std::isfinite(matrix(row, column))) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool IsFinite(const Vector& vector) {
|
||||
for (std::size_t index = 0U; index < vector.Size(); ++index) {
|
||||
if (!std::isfinite(vector[index])) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/// @brief Carries all derivatives through order three in one nodal rotation.
|
||||
struct Jet {
|
||||
double value{};
|
||||
@@ -189,31 +129,6 @@ Jet operator*(const Jet& left, const Jet& right) {
|
||||
return result;
|
||||
}
|
||||
|
||||
Jet Reciprocal(const Jet& value) {
|
||||
Jet result{};
|
||||
result.value = 1.0 / value.value;
|
||||
const double squared = result.value * result.value;
|
||||
const double cubed = squared * result.value;
|
||||
const double fourth = cubed * result.value;
|
||||
for (std::size_t i = 0U; i < 3U; ++i) {
|
||||
result.d[i] = -value.d[i] * squared;
|
||||
for (std::size_t j = 0U; j < 3U; ++j) {
|
||||
result.dd[i][j] =
|
||||
2.0 * value.d[i] * value.d[j] * cubed - value.dd[i][j] * squared;
|
||||
for (std::size_t k = 0U; k < 3U; ++k) {
|
||||
result.ddd[i][j][k] =
|
||||
-6.0 * value.d[i] * value.d[j] * value.d[k] * fourth +
|
||||
2.0 *
|
||||
(value.dd[i][j] * value.d[k] + value.dd[i][k] * value.d[j] +
|
||||
value.dd[j][k] * value.d[i]) *
|
||||
cubed -
|
||||
value.ddd[i][j][k] * squared;
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
Jet InverseSqrt(const Jet& value) {
|
||||
Jet result{};
|
||||
result.value = 1.0 / std::sqrt(value.value);
|
||||
@@ -246,27 +161,11 @@ using JetVector = std::array<Jet, 3>;
|
||||
using JetMatrix = std::array<std::array<Jet, 3>, 3>;
|
||||
|
||||
JetVector Multiply(const JetMatrix& matrix, const JetVector& vector) {
|
||||
JetVector result{Constant(0.0), Constant(0.0), Constant(0.0)};
|
||||
for (std::size_t row = 0U; row < 3U; ++row) {
|
||||
for (std::size_t column = 0U; column < 3U; ++column) {
|
||||
result[row] = result[row] + matrix[row][column] * vector[column];
|
||||
}
|
||||
}
|
||||
return result;
|
||||
return math_internal::MultiplyJetMatrixVector(matrix, vector, Constant);
|
||||
}
|
||||
|
||||
JetMatrix Multiply(const JetMatrix& left, const JetMatrix& right) {
|
||||
JetMatrix result{};
|
||||
for (std::size_t row = 0U; row < 3U; ++row) {
|
||||
for (std::size_t column = 0U; column < 3U; ++column) {
|
||||
result[row][column] = Constant(0.0);
|
||||
for (std::size_t index = 0U; index < 3U; ++index) {
|
||||
result[row][column] =
|
||||
result[row][column] + left[row][index] * right[index][column];
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
return math_internal::MultiplyJetMatrix(left, right, Constant);
|
||||
}
|
||||
|
||||
JetMatrix IdentityMatrix() {
|
||||
@@ -296,30 +195,32 @@ Jet Dot(const JetVector& left, const JetVector& right) {
|
||||
return result;
|
||||
}
|
||||
|
||||
JetMatrix Inverse(const JetMatrix& matrix) {
|
||||
JetMatrix cofactor{};
|
||||
for (std::size_t row = 0U; row < 3U; ++row) {
|
||||
for (std::size_t column = 0U; column < 3U; ++column) {
|
||||
const std::size_t a = (row + 1U) % 3U;
|
||||
const std::size_t b = (row + 2U) % 3U;
|
||||
const std::size_t c = (column + 1U) % 3U;
|
||||
const std::size_t d = (column + 2U) % 3U;
|
||||
cofactor[row][column] =
|
||||
matrix[a][c] * matrix[b][d] - matrix[a][d] * matrix[b][c];
|
||||
// Cyclic minor ordering already includes the cofactor sign.
|
||||
}
|
||||
}
|
||||
Jet determinant = Constant(0.0);
|
||||
for (std::size_t column = 0U; column < 3U; ++column) {
|
||||
determinant = determinant + matrix[0U][column] * cofactor[0U][column];
|
||||
}
|
||||
const Jet inverse_determinant = Reciprocal(determinant);
|
||||
JetMatrix result{};
|
||||
for (std::size_t row = 0U; row < 3U; ++row) {
|
||||
for (std::size_t column = 0U; column < 3U; ++column) {
|
||||
result[row][column] = cofactor[column][row] * inverse_determinant;
|
||||
}
|
||||
}
|
||||
/// @brief Reads every nodal-rotation derivative in increasing total order.
|
||||
double ReadChannel(const Jet& jet, std::size_t degree, std::size_t channel) {
|
||||
if (degree == 0) return jet.value;
|
||||
if (degree == 1) return jet.d[channel];
|
||||
if (degree == 2) return jet.dd[channel / 3][channel % 3];
|
||||
return jet.ddd[channel / 9][(channel / 3) % 3][channel % 3];
|
||||
}
|
||||
void WriteChannel(Jet& jet, std::size_t degree, std::size_t channel,
|
||||
double value) {
|
||||
if (degree == 0) jet.value = value;
|
||||
else if (degree == 1) jet.d[channel] = value;
|
||||
else if (degree == 2) jet.dd[channel / 3][channel % 3] = value;
|
||||
else jet.ddd[channel / 9][(channel / 3) % 3][channel % 3] = value;
|
||||
}
|
||||
/// @brief Solves the actual null-preimage RHS with all third derivatives.
|
||||
JetVector Solve(const JetMatrix& matrix, const JetVector& rhs) {
|
||||
Matrix primal{3, 3};
|
||||
for (std::size_t row = 0; row < 3; ++row)
|
||||
for (std::size_t column = 0; column < 3; ++column)
|
||||
primal(row, column) = matrix[row][column].value;
|
||||
const auto factor = dense_blas_internal::DenseFactor::Lu(primal);
|
||||
JetVector result{};
|
||||
if (factor.HasValue() && math_internal::SolveJetAction(
|
||||
matrix, rhs, factor.Value(), {1, 3, 9, 27}, Constant, ReadChannel,
|
||||
WriteChannel, result)) return result;
|
||||
result.fill(Constant(std::numeric_limits<double>::quiet_NaN()));
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -486,27 +387,12 @@ Status CheckPhysicalMass(const Matrix& mass,
|
||||
const Matrix mass_complement = scaled.Multiply(complement);
|
||||
const Matrix block =
|
||||
dense_blas_internal::MultiplyTransposeLeft(complement, mass_complement);
|
||||
double block_norm = 0.0;
|
||||
for (std::size_t row = 0U; row < kPhysical; ++row) {
|
||||
double row_sum = 0.0;
|
||||
for (std::size_t column = 0U; column < kPhysical; ++column) {
|
||||
row_sum += std::abs(block(row, column));
|
||||
}
|
||||
block_norm = std::max(block_norm, row_sum);
|
||||
}
|
||||
Matrix lower{kPhysical, kPhysical};
|
||||
Matrix inverse_lower{kPhysical, kPhysical};
|
||||
auto factor_failure = dense_blas_internal::CholeskyFailure::kNone;
|
||||
if (!dense_blas_internal::CholeskyLowerAndInverse(block, lower, inverse_lower,
|
||||
&factor_failure)) {
|
||||
if (factor_failure == dense_blas_internal::CholeskyFailure::kInverse)
|
||||
return fail(
|
||||
"ill-conditioned-nonlinear-inertia-positive-mass",
|
||||
"Physical mass reciprocal infinity condition is below 2^-32.");
|
||||
const auto factor = dense_blas_internal::DenseFactor::Cholesky(block);
|
||||
if (!factor.HasValue()) {
|
||||
return fail("invalid-nonlinear-inertia-positive-mass",
|
||||
"Physical mass requires strictly positive Cholesky pivots.");
|
||||
}
|
||||
std::array<double, kPhysical> inverse_row_sums{};
|
||||
const Matrix lower = std::move(factor.Value().Lower().Value());
|
||||
double reconstruction_error = 0.0;
|
||||
double block_frobenius = 0.0;
|
||||
Matrix transpose{kPhysical, kPhysical};
|
||||
@@ -526,16 +412,9 @@ Status CheckPhysicalMass(const Matrix& mass,
|
||||
return fail("invalid-nonlinear-inertia-positive-mass",
|
||||
"Physical mass Cholesky reconstruction failed.");
|
||||
}
|
||||
const Matrix inverse =
|
||||
dense_blas_internal::MultiplyTransposeLeft(inverse_lower, inverse_lower);
|
||||
for (std::size_t column = 0U; column < kPhysical; ++column)
|
||||
for (std::size_t row = 0U; row < kPhysical; ++row)
|
||||
inverse_row_sums[row] += std::abs(inverse(row, column));
|
||||
const double inverse_norm =
|
||||
*std::max_element(inverse_row_sums.begin(), inverse_row_sums.end());
|
||||
const double reciprocal_condition = 1.0 / block_norm / inverse_norm;
|
||||
if (!std::isfinite(inverse_norm) || !std::isfinite(reciprocal_condition) ||
|
||||
!(reciprocal_condition >= std::ldexp(1.0, -32))) {
|
||||
const auto reciprocal_condition = factor.Value().ReciprocalConditionInfinity();
|
||||
if (!reciprocal_condition.HasValue() ||
|
||||
!(reciprocal_condition.Value() >= std::ldexp(1.0, -32))) {
|
||||
return fail("ill-conditioned-nonlinear-inertia-positive-mass",
|
||||
"Physical mass reciprocal infinity condition is below 2^-32.");
|
||||
}
|
||||
@@ -566,7 +445,7 @@ Result<DynamicRotationKinematics> EvaluateDynamicRotation(
|
||||
Constant(initial_director.Z())};
|
||||
const JetVector director_jet = Multiply(ExpSkew(phi), initial);
|
||||
const JetMatrix left_jacobian = LeftJacobian(phi);
|
||||
JetVector null_jet = Multiply(Inverse(left_jacobian), director_jet);
|
||||
JetVector null_jet = Solve(left_jacobian, director_jet);
|
||||
const Jet null_norm_squared = Dot(null_jet, null_jet);
|
||||
if (!std::isfinite(null_norm_squared.value) ||
|
||||
!(null_norm_squared.value > 0.0)) {
|
||||
|
||||
@@ -6,12 +6,18 @@
|
||||
#include <cstddef>
|
||||
#include <limits>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
#include "fesa/elements/finite_rotation.h"
|
||||
#include "elements/nonlinear_mitc4_configuration_internal.h"
|
||||
#include "math/dense_blas_internal.h"
|
||||
#include "math/jet_arithmetic.h"
|
||||
#include "math/jet_linear_solve.h"
|
||||
#include "math/finite_values.h"
|
||||
#include "math/vector3_operations.h"
|
||||
#include "elements/shell_kernel_values.h"
|
||||
|
||||
namespace fesa {
|
||||
|
||||
@@ -40,6 +46,15 @@ nonlinear_mitc4_internal::ConfigurationSnapshot::Read(
|
||||
|
||||
namespace {
|
||||
|
||||
using shell_kernel_internal::Failure;
|
||||
using shell_kernel_internal::FrameComponent;
|
||||
using shell_kernel_internal::LocalEngineeringComponents;
|
||||
using shell_kernel_internal::FrameMatrix;
|
||||
using shell_kernel_internal::ElementIdentity;
|
||||
using math_internal::DerivativeSum;
|
||||
using math_internal::WeightedSum;
|
||||
using math_internal::IsFinite;
|
||||
|
||||
constexpr std::size_t kNodeCount = 4U;
|
||||
constexpr double kFrameTolerance = 1.0e-12;
|
||||
constexpr double kDirectorOppositionTolerance = 1.0e-8;
|
||||
@@ -53,7 +68,7 @@ struct Geometry {
|
||||
std::array<Vector3, 3> reference_covariant;
|
||||
std::array<Vector3, 3> current_covariant;
|
||||
std::array<Vector3, 3> covariant_increment;
|
||||
std::array<Vector3, 3> reciprocal;
|
||||
std::optional<dense_blas_internal::DenseFactor> reference_factor;
|
||||
Mitc4LocalFrame local_frame;
|
||||
double reference_jacobian;
|
||||
};
|
||||
@@ -64,61 +79,6 @@ struct PointEvaluation {
|
||||
EngineeringStress stress;
|
||||
};
|
||||
|
||||
/// @brief Builds one deterministic structured nonlinear-shell failure.
|
||||
template <class T>
|
||||
Result<T> Failure(const std::string& code, const SourceLocation& location,
|
||||
const std::string& identity, std::string message) {
|
||||
return Result<T>::Failure(Status::Failure(
|
||||
FailureCategory::kModel, {{Severity::kError, code, location, "*ELEMENT",
|
||||
identity, std::move(message)}}));
|
||||
}
|
||||
|
||||
/// @brief Builds the stable source label concatenation used by diagnostics.
|
||||
std::string ElementIdentity(const std::array<const Node*, kNodeCount>& nodes) {
|
||||
std::string identity;
|
||||
for (const Node* node : nodes) {
|
||||
if (node == nullptr) {
|
||||
continue;
|
||||
}
|
||||
if (!identity.empty()) {
|
||||
identity += "-";
|
||||
}
|
||||
identity += node->source_id.source_label_text;
|
||||
}
|
||||
return identity;
|
||||
}
|
||||
|
||||
/// @brief Forms a weighted vector sum in source-node order.
|
||||
Vector3 WeightedSum(const std::array<double, kNodeCount>& weights,
|
||||
const std::array<Vector3, kNodeCount>& values) {
|
||||
Vector3 result{};
|
||||
for (std::size_t node = 0U; node < kNodeCount; ++node) {
|
||||
result = result + weights[node] * values[node];
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/// @brief Forms a stable derivative sum after cancelling the constant term.
|
||||
Vector3 DerivativeSum(const std::array<double, kNodeCount>& derivatives,
|
||||
const std::array<Vector3, kNodeCount>& values) {
|
||||
std::array<Vector3, kNodeCount> relative{};
|
||||
for (std::size_t node = 0U; node < kNodeCount; ++node) {
|
||||
relative[node] = values[node] - values[0U];
|
||||
}
|
||||
return WeightedSum(derivatives, relative);
|
||||
}
|
||||
|
||||
/// @brief Returns a frame matrix whose columns are a, b and the director.
|
||||
Matrix FrameMatrix(const ShellNodeInitialFrame& frame) {
|
||||
Matrix result{3U, 3U};
|
||||
for (std::size_t row = 0U; row < 3U; ++row) {
|
||||
result(row, 0U) = frame.tangent_a[row];
|
||||
result(row, 1U) = frame.tangent_b[row];
|
||||
result(row, 2U) = frame.director[row];
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/// @brief Checks every scalar in a tensor before recovery commits it.
|
||||
bool IsFinite(const Tensor& tensor) {
|
||||
for (const auto& row : tensor) {
|
||||
@@ -131,13 +91,6 @@ bool IsFinite(const Tensor& tensor) {
|
||||
return true;
|
||||
}
|
||||
|
||||
/// @brief Checks every scalar in one fixed-size engineering quantity.
|
||||
template <std::size_t N>
|
||||
bool IsFinite(const std::array<double, N>& values) {
|
||||
return std::all_of(values.begin(), values.end(),
|
||||
[](const double value) { return std::isfinite(value); });
|
||||
}
|
||||
|
||||
/// @brief Evaluates a determinant from covariant basis columns.
|
||||
double Determinant(const std::array<Vector3, 3>& covariant) {
|
||||
return covariant[0U].Dot(covariant[1U].Cross(covariant[2U]));
|
||||
@@ -256,20 +209,24 @@ bool EvaluateGeometry(const std::array<Vector3, kNodeCount>& coordinates,
|
||||
return false;
|
||||
}
|
||||
|
||||
geometry.reciprocal[0U] =
|
||||
geometry.reference_covariant[1U].Cross(geometry.reference_covariant[2U]) /
|
||||
geometry.reference_jacobian;
|
||||
geometry.reciprocal[1U] =
|
||||
geometry.reference_covariant[2U].Cross(geometry.reference_covariant[0U]) /
|
||||
geometry.reference_jacobian;
|
||||
geometry.reciprocal[2U] =
|
||||
geometry.reference_covariant[0U].Cross(geometry.reference_covariant[1U]) /
|
||||
geometry.reference_jacobian;
|
||||
if (!geometry.reciprocal[0U].IsFinite() ||
|
||||
!geometry.reciprocal[1U].IsFinite() ||
|
||||
!geometry.reciprocal[2U].IsFinite()) {
|
||||
return false;
|
||||
// Retain the old unscaled cross-product finite gate before factor actions.
|
||||
for (std::size_t alpha = 0; alpha < 3; ++alpha)
|
||||
if (!geometry.reference_covariant[(alpha+1)%3].Cross(
|
||||
geometry.reference_covariant[(alpha+2)%3]).IsFinite()) return false;
|
||||
Matrix basis{3, 3};
|
||||
for (std::size_t row = 0; row < 3; ++row)
|
||||
for (std::size_t alpha = 0; alpha < 3; ++alpha)
|
||||
basis(row, alpha) = geometry.reference_covariant[alpha][row];
|
||||
auto factor = dense_blas_internal::DenseFactor::Lu(basis);
|
||||
if (!factor.HasValue()) return false;
|
||||
// Stream the former reciprocal finite gate without retaining an inverse.
|
||||
Matrix rhs{3, 1};
|
||||
for (std::size_t column = 0; column < 3; ++column) {
|
||||
if (column != 0) rhs(column-1, 0) = 0;
|
||||
rhs(column, 0) = 1;
|
||||
if (!factor.Value().Solve(rhs).HasValue()) return false;
|
||||
}
|
||||
geometry.reference_factor = std::move(factor.Value());
|
||||
return MakeLocalFrame(reference_xi, reference_eta, initial_director,
|
||||
geometry.local_frame);
|
||||
}
|
||||
@@ -293,47 +250,27 @@ Tensor DirectStrain(const Geometry& geometry) {
|
||||
}
|
||||
|
||||
/// @brief Reconstructs a fixed-reference Cartesian strain from covariant data.
|
||||
Tensor CartesianStrain(const Tensor& covariant,
|
||||
const std::array<Vector3, 3>& reciprocal) {
|
||||
Tensor CartesianStrain(const Tensor& covariant, const Geometry& reference) {
|
||||
Matrix rhs{3, 3};
|
||||
for (std::size_t row = 0; row < 3; ++row)
|
||||
for (std::size_t column = 0; column < 3; ++column)
|
||||
rhs(row, column) = covariant[row][column];
|
||||
const auto right = reference.reference_factor->SolveRight(rhs);
|
||||
Tensor cartesian{};
|
||||
for (std::size_t row = 0U; row < 3U; ++row) {
|
||||
for (std::size_t column = 0U; column < 3U; ++column) {
|
||||
for (std::size_t first = 0U; first < 3U; ++first) {
|
||||
for (std::size_t second = 0U; second < 3U; ++second) {
|
||||
cartesian[row][column] += covariant[first][second] *
|
||||
reciprocal[first][row] *
|
||||
reciprocal[second][column];
|
||||
}
|
||||
}
|
||||
if (right.HasValue()) {
|
||||
const auto left = reference.reference_factor->Solve(right.Value(), true);
|
||||
if (left.HasValue()) {
|
||||
for (std::size_t row = 0; row < 3; ++row)
|
||||
for (std::size_t column = 0; column < 3; ++column)
|
||||
cartesian[row][column] = left.Value()(row, column);
|
||||
return cartesian;
|
||||
}
|
||||
}
|
||||
for (auto& row : cartesian)
|
||||
row.fill(std::numeric_limits<double>::quiet_NaN());
|
||||
return cartesian;
|
||||
}
|
||||
|
||||
/// @brief Evaluates a tensor component in a fixed local reference frame.
|
||||
double FrameComponent(const Vector3& left, const Tensor& tensor,
|
||||
const Vector3& right) {
|
||||
double value = 0.0;
|
||||
for (std::size_t row = 0U; row < 3U; ++row) {
|
||||
for (std::size_t column = 0U; column < 3U; ++column) {
|
||||
value += left[row] * tensor[row][column] * right[column];
|
||||
}
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
/// @brief Converts fixed-reference Cartesian strain to engineering components.
|
||||
EngineeringStrain LocalEngineering(const Tensor& tensor,
|
||||
const Mitc4LocalFrame& frame) {
|
||||
const Vector3 e1{frame.e1};
|
||||
const Vector3 e2{frame.e2};
|
||||
const Vector3 e3{frame.e3};
|
||||
return {FrameComponent(e1, tensor, e1), FrameComponent(e2, tensor, e2),
|
||||
2.0 * FrameComponent(e1, tensor, e2),
|
||||
2.0 * FrameComponent(e1, tensor, e3),
|
||||
2.0 * FrameComponent(e2, tensor, e3)};
|
||||
}
|
||||
|
||||
/// @brief Applies the approved homogeneous plane-stress plus shear law.
|
||||
EngineeringStress Stress(const EngineeringStrain& strain, const double youngs,
|
||||
const double poisson) {
|
||||
@@ -386,8 +323,8 @@ PointStatus EvaluatePoint(const std::array<Vector3, kNodeCount>& coordinates,
|
||||
weights.eta_zeta[1U] * tying_samples[3U][1U][2U];
|
||||
covariant[2U][1U] = covariant[1U][2U];
|
||||
const Tensor cartesian =
|
||||
CartesianStrain(covariant, point.geometry.reciprocal);
|
||||
point.strain = LocalEngineering(cartesian, point.geometry.local_frame);
|
||||
CartesianStrain(covariant, point.geometry);
|
||||
point.strain = LocalEngineeringComponents(cartesian, point.geometry.local_frame);
|
||||
point.stress = Stress(point.strain, youngs, poisson);
|
||||
return IsFinite(covariant) && IsFinite(cartesian) && IsFinite(point.strain) &&
|
||||
IsFinite(point.stress)
|
||||
@@ -890,27 +827,12 @@ JetMatrix3 Skew(const JetVector3& value) {
|
||||
|
||||
/// @brief Multiplies two three-by-three Jet matrices.
|
||||
JetMatrix3 Multiply(const JetMatrix3& left, const JetMatrix3& right) {
|
||||
JetMatrix3 result = ConstantZeroMatrix();
|
||||
for (std::size_t row = 0U; row < 3U; ++row) {
|
||||
for (std::size_t column = 0U; column < 3U; ++column) {
|
||||
for (std::size_t index = 0U; index < 3U; ++index) {
|
||||
result[row][column] =
|
||||
result[row][column] + left[row][index] * right[index][column];
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
return math_internal::MultiplyJetMatrix(left, right, Constant);
|
||||
}
|
||||
|
||||
/// @brief Multiplies a Jet matrix by a Jet vector.
|
||||
JetVector3 Multiply(const JetMatrix3& matrix, const JetVector3& vector) {
|
||||
JetVector3 result{Constant(0.0), Constant(0.0), Constant(0.0)};
|
||||
for (std::size_t row = 0U; row < 3U; ++row) {
|
||||
for (std::size_t column = 0U; column < 3U; ++column) {
|
||||
result[row] = result[row] + matrix[row][column] * vector[column];
|
||||
}
|
||||
}
|
||||
return result;
|
||||
return math_internal::MultiplyJetMatrixVector(matrix, vector, Constant);
|
||||
}
|
||||
|
||||
/// @brief Transposes a three-by-three Jet matrix.
|
||||
@@ -957,38 +879,28 @@ JetMatrix3 Add(const JetMatrix3& left, const JetMatrix3& right) {
|
||||
return result;
|
||||
}
|
||||
|
||||
/// @brief Computes the analytic three-by-three inverse through its adjugate.
|
||||
JetMatrix3 Inverse(const JetMatrix3& matrix) {
|
||||
JetMatrix3 cofactor{};
|
||||
cofactor[0U][0U] =
|
||||
matrix[1U][1U] * matrix[2U][2U] - matrix[1U][2U] * matrix[2U][1U];
|
||||
cofactor[0U][1U] =
|
||||
matrix[1U][2U] * matrix[2U][0U] - matrix[1U][0U] * matrix[2U][2U];
|
||||
cofactor[0U][2U] =
|
||||
matrix[1U][0U] * matrix[2U][1U] - matrix[1U][1U] * matrix[2U][0U];
|
||||
cofactor[1U][0U] =
|
||||
matrix[0U][2U] * matrix[2U][1U] - matrix[0U][1U] * matrix[2U][2U];
|
||||
cofactor[1U][1U] =
|
||||
matrix[0U][0U] * matrix[2U][2U] - matrix[0U][2U] * matrix[2U][0U];
|
||||
cofactor[1U][2U] =
|
||||
matrix[0U][1U] * matrix[2U][0U] - matrix[0U][0U] * matrix[2U][1U];
|
||||
cofactor[2U][0U] =
|
||||
matrix[0U][1U] * matrix[1U][2U] - matrix[0U][2U] * matrix[1U][1U];
|
||||
cofactor[2U][1U] =
|
||||
matrix[0U][2U] * matrix[1U][0U] - matrix[0U][0U] * matrix[1U][2U];
|
||||
cofactor[2U][2U] =
|
||||
matrix[0U][0U] * matrix[1U][1U] - matrix[0U][1U] * matrix[1U][0U];
|
||||
const Jet determinant = matrix[0U][0U] * cofactor[0U][0U] +
|
||||
matrix[0U][1U] * cofactor[0U][1U] +
|
||||
matrix[0U][2U] * cofactor[0U][2U];
|
||||
JetMatrix3 result{};
|
||||
const Jet inverse_determinant = Reciprocal(determinant);
|
||||
for (std::size_t row = 0U; row < 3U; ++row) {
|
||||
for (std::size_t column = 0U; column < 3U; ++column) {
|
||||
result[row][column] = cofactor[column][row] * inverse_determinant;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
/// @brief Reads shell variations without conflating directional third order.
|
||||
double ReadChannel(const Jet& jet, std::size_t degree, std::size_t channel) {
|
||||
if (degree == 0) return jet.value;
|
||||
if (degree == 1) return channel < kGlobalDofCount ? jet.first[channel]
|
||||
: jet.directional_first;
|
||||
if (degree == 3) return jet.mixed_second[channel];
|
||||
if (channel < kHessianSize) return jet.second.ReadData()[channel];
|
||||
if (channel == kHessianSize) return jet.directional_second;
|
||||
return jet.mixed_first[channel - kHessianSize - 1];
|
||||
}
|
||||
void WriteChannel(Jet& jet, std::size_t degree, std::size_t channel,
|
||||
double value) {
|
||||
if (degree == 0) { jet.value = value; return; }
|
||||
// A null Hessian is not constant provenance, including zero first seeds.
|
||||
jet.constant_derivatives = false;
|
||||
if (degree == 1) {
|
||||
if (channel < kGlobalDofCount) jet.first[channel] = value;
|
||||
else jet.directional_first = value;
|
||||
} else if (degree == 3) jet.mixed_second[channel] = value;
|
||||
else if (channel < kHessianSize) jet.second.OverwriteData()[channel] = value;
|
||||
else if (channel == kHessianSize) jet.directional_second = value;
|
||||
else jet.mixed_first[channel - kHessianSize - 1] = value;
|
||||
}
|
||||
|
||||
/// @brief Evaluates Rodrigues' map with an analytic near-zero series branch.
|
||||
@@ -1119,19 +1031,32 @@ JetTensor DirectStrain(const std::array<JetVector3, 3>& increment,
|
||||
/// @brief Reconstructs a fixed-reference Cartesian Jet strain tensor.
|
||||
JetTensor CartesianStrain(const JetTensor& covariant,
|
||||
const Geometry& reference) {
|
||||
JetTensor result{};
|
||||
for (std::size_t row = 0U; row < 3U; ++row) {
|
||||
for (std::size_t column = 0U; column < 3U; ++column) {
|
||||
for (std::size_t first = 0U; first < 3U; ++first) {
|
||||
for (std::size_t second = 0U; second < 3U; ++second) {
|
||||
result[row][column] =
|
||||
result[row][column] + reference.reciprocal[first][row] *
|
||||
reference.reciprocal[second][column] *
|
||||
covariant[first][second];
|
||||
}
|
||||
}
|
||||
// E_cart = B^-T E_cov B^-1. First solve each row against B, then each
|
||||
// column against B^T, carrying every independent Jet channel unchanged.
|
||||
const std::array<std::size_t, 4> channels{
|
||||
1, kGlobalDofCount+1, kHessianSize+1+kGlobalDofCount, kGlobalDofCount};
|
||||
JetTensor right{}, result{};
|
||||
for (std::size_t row = 0; row < 3; ++row) {
|
||||
if (!math_internal::SolveConstantJetAction(
|
||||
covariant[row], *reference.reference_factor, true, channels,
|
||||
Constant, ReadChannel, WriteChannel, right[row])) {
|
||||
for (auto& entry : result)
|
||||
entry.fill(Constant(std::numeric_limits<double>::quiet_NaN()));
|
||||
return result;
|
||||
}
|
||||
}
|
||||
for (std::size_t column = 0; column < 3; ++column) {
|
||||
const JetVector3 rhs{right[0][column],right[1][column],right[2][column]};
|
||||
JetVector3 action{};
|
||||
if (!math_internal::SolveConstantJetAction(
|
||||
rhs, *reference.reference_factor, true, channels,
|
||||
Constant, ReadChannel, WriteChannel, action)) {
|
||||
for (auto& entry : result)
|
||||
entry.fill(Constant(std::numeric_limits<double>::quiet_NaN()));
|
||||
return result;
|
||||
}
|
||||
for (std::size_t row = 0; row < 3; ++row) result[row][column] = action[row];
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -1235,7 +1160,29 @@ Jet PhysicalEnergy(const std::array<Vector3, kNodeCount>& coordinates,
|
||||
JetMatrix3 PolarRotation(const JetMatrix3& deformation) {
|
||||
JetMatrix3 result = deformation;
|
||||
for (std::size_t iteration = 0U; iteration < 12U; ++iteration) {
|
||||
result = Scale(0.5, Add(result, Transpose(Inverse(result))));
|
||||
const JetMatrix3 coefficients = Transpose(result);
|
||||
Matrix primal{3, 3};
|
||||
for (std::size_t row = 0; row < 3; ++row)
|
||||
for (std::size_t column = 0; column < 3; ++column)
|
||||
primal(row, column) = coefficients[row][column].value;
|
||||
const auto factor = dense_blas_internal::DenseFactor::Lu(primal);
|
||||
JetMatrix3 candidate{};
|
||||
for (std::size_t column = 0; column < 3; ++column) {
|
||||
JetVector3 rhs{Constant(0), Constant(0), Constant(0)}, action{};
|
||||
rhs[column] = Constant(1);
|
||||
if (!factor.HasValue() || !math_internal::SolveJetAction(
|
||||
coefficients, rhs, factor.Value(),
|
||||
{1, kGlobalDofCount + 1, kHessianSize + 1 + kGlobalDofCount,
|
||||
kGlobalDofCount}, Constant, ReadChannel, WriteChannel, action)) {
|
||||
for (auto& row : candidate)
|
||||
row.fill(Constant(std::numeric_limits<double>::quiet_NaN()));
|
||||
return candidate;
|
||||
}
|
||||
for (std::size_t row = 0; row < 3; ++row)
|
||||
candidate[row][column] = 0.5 * (result[row][column] + action[row]);
|
||||
}
|
||||
// All actions used the previous snapshot; never a partial next iterate.
|
||||
result = std::move(candidate);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
@@ -1297,14 +1244,19 @@ Jet DrillingEnergy(const std::array<Vector3, kNodeCount>& coordinates,
|
||||
const auto current =
|
||||
CovariantIncrement(initial_directors, trial, thickness, 0.0, 0.0, 0.0);
|
||||
JetMatrix3 deformation = Identity();
|
||||
for (std::size_t row = 0U; row < 3U; ++row) {
|
||||
for (std::size_t column = 0U; column < 3U; ++column) {
|
||||
for (std::size_t alpha = 0U; alpha < 3U; ++alpha) {
|
||||
deformation[row][column] =
|
||||
deformation[row][column] +
|
||||
current[alpha][row] * reference.reciprocal[alpha][column];
|
||||
}
|
||||
const std::array<std::size_t, 4> channels{
|
||||
1, kGlobalDofCount+1, kHessianSize+1+kGlobalDofCount, kGlobalDofCount};
|
||||
for (std::size_t row = 0; row < 3; ++row) {
|
||||
const JetVector3 rhs{current[0][row],current[1][row],current[2][row]};
|
||||
JetVector3 action{};
|
||||
if (!math_internal::SolveConstantJetAction(
|
||||
rhs, *reference.reference_factor, true, channels,
|
||||
Constant, ReadChannel, WriteChannel, action)) {
|
||||
valid = false;
|
||||
return {};
|
||||
}
|
||||
for (std::size_t column = 0; column < 3; ++column)
|
||||
deformation[row][column] = deformation[row][column] + action[column];
|
||||
}
|
||||
const JetMatrix3 element_rotation = PolarRotation(deformation);
|
||||
Jet energy{};
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
#ifndef FESA_ELEMENTS_SHELL_KERNEL_VALUES_H_
|
||||
#define FESA_ELEMENTS_SHELL_KERNEL_VALUES_H_
|
||||
|
||||
#include <array>
|
||||
#include <cstddef>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
#include "fesa/elements/mitc4_shell.h"
|
||||
#include "fesa/math/matrix.h"
|
||||
#include "fesa/math/vector3.h"
|
||||
#include "fesa/model/model_types.h"
|
||||
|
||||
namespace fesa::shell_kernel_internal {
|
||||
|
||||
/// @brief Constructs the shared model-category shell-kernel failure envelope.
|
||||
template <class T>
|
||||
Result<T> Failure(const std::string& code, const SourceLocation& location,
|
||||
const std::string& identity, std::string message) {
|
||||
return Result<T>::Failure(Status::Failure(
|
||||
FailureCategory::kModel, {{Severity::kError, code, location, "*ELEMENT",
|
||||
identity, std::move(message)}}));
|
||||
}
|
||||
|
||||
/// @brief Preserves raw source labels and the existing null-skip diagnostic identity.
|
||||
inline std::string ElementIdentity(const std::array<const Node*, 4>& nodes) {
|
||||
std::string identity;
|
||||
for (const Node* node : nodes) {
|
||||
if (node == nullptr) {
|
||||
continue;
|
||||
}
|
||||
if (!identity.empty()) {
|
||||
identity += "-";
|
||||
}
|
||||
identity += node->source_id.source_label_text;
|
||||
}
|
||||
return identity;
|
||||
}
|
||||
|
||||
/// @brief Copies the initial semantic frame into its three ordered columns.
|
||||
inline Matrix FrameMatrix(const ShellNodeInitialFrame& frame) {
|
||||
Matrix result{3U, 3U};
|
||||
for (std::size_t row = 0U; row < 3U; ++row) {
|
||||
result(row, 0U) = frame.tangent_a[row];
|
||||
result(row, 1U) = frame.tangent_b[row];
|
||||
result(row, 2U) = frame.director[row];
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/// @brief Contracts one ordinary tensor component in the original row/column order.
|
||||
inline double FrameComponent(
|
||||
const Vector3& left, const std::array<std::array<double, 3>, 3>& tensor,
|
||||
const Vector3& right) {
|
||||
double value = 0.0;
|
||||
for (std::size_t row = 0U; row < 3U; ++row) {
|
||||
for (std::size_t column = 0U; column < 3U; ++column) {
|
||||
value += left[row] * tensor[row][column] * right[column];
|
||||
}
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
/// @brief Projects fixed-reference tensor strain to the five engineering components.
|
||||
inline std::array<double, 5> LocalEngineeringComponents(
|
||||
const std::array<std::array<double, 3>, 3>& tensor,
|
||||
const Mitc4LocalFrame& frame) {
|
||||
const Vector3 e1{frame.e1};
|
||||
const Vector3 e2{frame.e2};
|
||||
const Vector3 e3{frame.e3};
|
||||
return {FrameComponent(e1, tensor, e1), FrameComponent(e2, tensor, e2),
|
||||
2.0 * FrameComponent(e1, tensor, e2),
|
||||
2.0 * FrameComponent(e1, tensor, e3),
|
||||
2.0 * FrameComponent(e2, tensor, e3)};
|
||||
}
|
||||
|
||||
} // namespace fesa::shell_kernel_internal
|
||||
|
||||
#endif // FESA_ELEMENTS_SHELL_KERNEL_VALUES_H_
|
||||
@@ -12,6 +12,7 @@
|
||||
#include <vector>
|
||||
|
||||
#include "fesa/model/model_types.h"
|
||||
#include "math/dense_blas_internal.h"
|
||||
|
||||
namespace fesa {
|
||||
namespace thermal_internal {
|
||||
@@ -136,10 +137,20 @@ Result<Geometry> BuildGeometry(const std::array<Vector3, 4>& coordinates,
|
||||
const double uv = u.Dot(v);
|
||||
const double determinant = uu * vv - uv * uv;
|
||||
if (determinant > 0 && std::isfinite(determinant)) {
|
||||
const double xi = (uv * v.Dot(w0) - vv * u.Dot(w0)) / determinant;
|
||||
const double eta = (uv * u.Dot(w0) - uu * v.Dot(w0)) / determinant;
|
||||
if (std::abs(xi) <= 1 && std::abs(eta) <= 1) {
|
||||
candidates.push_back(w0 + xi * u + eta * v);
|
||||
Matrix metric{2, 2};
|
||||
metric(0,0) = uu; metric(0,1) = uv;
|
||||
metric(1,0) = uv; metric(1,1) = vv;
|
||||
Matrix rhs{2, 1};
|
||||
rhs(0,0) = -u.Dot(w0); rhs(1,0) = -v.Dot(w0);
|
||||
const auto factor = dense_blas_internal::DenseFactor::Lu(metric);
|
||||
if (factor.HasValue()) {
|
||||
const auto stationary = factor.Value().Solve(rhs);
|
||||
if (stationary.HasValue()) {
|
||||
const double xi = stationary.Value()(0,0);
|
||||
const double eta = stationary.Value()(1,0);
|
||||
if (std::abs(xi) <= 1 && std::abs(eta) <= 1)
|
||||
candidates.push_back(w0 + xi * u + eta * v);
|
||||
}
|
||||
}
|
||||
}
|
||||
// The center is also useful for affine or rank-one normal images.
|
||||
@@ -169,30 +180,39 @@ Result<SurfacePoint> EvaluateSurface(const Geometry& geometry, double xi,
|
||||
const double jacobian = w.Norm();
|
||||
const Interval determinant_interval = NormSquared(wi);
|
||||
const double determinant = w.Dot(w);
|
||||
// |a1 x a2|^2 is the Gram determinant identity, avoiding subtraction of
|
||||
// nearly equal g11*g22 and g12*g12. This is still the actual-point metric.
|
||||
const Interval inverse11 = Dot(a2i, a2i) / determinant_interval;
|
||||
const Interval inverse12 =
|
||||
(Interval{0} - Dot(a1i, a2i)) / determinant_interval;
|
||||
const Interval inverse22 = Dot(a1i, a1i) / determinant_interval;
|
||||
if (!(determinant_interval.lower > 0) || !inverse11.IsFinite() ||
|
||||
!inverse12.IsFinite() || !inverse22.IsFinite() || !(jacobian > 0) ||
|
||||
// Retain the original scalar range/scale certificate. Each canonical
|
||||
// metric action is consumed immediately by its finite gate, never retained
|
||||
// as inverse coefficients or used to compute a physical gradient.
|
||||
const auto finite_metric_action = [&](Interval numerator) {
|
||||
const Interval action = numerator / determinant_interval;
|
||||
return action.IsFinite() &&
|
||||
Scale(action, -2 * geometry.length_exponent).IsFinite();
|
||||
};
|
||||
if (!(determinant_interval.lower > 0) ||
|
||||
!finite_metric_action(Dot(a2i, a2i)) ||
|
||||
!finite_metric_action(Interval{0} - Dot(a1i, a2i)) ||
|
||||
!finite_metric_action(Dot(a1i, a1i)) || !(jacobian > 0) ||
|
||||
!std::isfinite(determinant) || determinant <= 0 ||
|
||||
!Scale(inverse11, -2 * geometry.length_exponent).IsFinite() ||
|
||||
!Scale(inverse12, -2 * geometry.length_exponent).IsFinite() ||
|
||||
!Scale(inverse22, -2 * geometry.length_exponent).IsFinite() ||
|
||||
!Scale(Dot(a1i, a1i), 2 * geometry.length_exponent).IsFinite() ||
|
||||
!Scale(Dot(a2i, a2i), 2 * geometry.length_exponent).IsFinite()) {
|
||||
return Result<SurfacePoint>::Failure(GeometryFailure(
|
||||
source, location, "uncertain-thermal-shell-metric",
|
||||
"Metric inverse enclosure is not finite or excludes no zero divisor."));
|
||||
}
|
||||
const Vector3 reciprocal1 = a2.Cross(w) / determinant;
|
||||
const Vector3 reciprocal2 = w.Cross(a1) / determinant;
|
||||
const auto reciprocal1i =
|
||||
(Interval{1} / determinant_interval) * Cross(a2i, wi);
|
||||
const auto reciprocal2i =
|
||||
(Interval{1} / determinant_interval) * Cross(wi, a1i);
|
||||
// Factor the physical tangent constraints rather than the rounded Gram
|
||||
// matrix, which can lose rank while the certified patch is still regular.
|
||||
Matrix constraints{3, 3};
|
||||
for (std::size_t j = 0; j < 3; ++j) {
|
||||
constraints(0,j) = a1[j];
|
||||
constraints(1,j) = a2[j];
|
||||
constraints(2,j) = w[j] / jacobian;
|
||||
}
|
||||
const auto factor = dense_blas_internal::DenseFactor::Lu(constraints);
|
||||
if (!factor.HasValue())
|
||||
return Result<SurfacePoint>::Failure(GeometryFailure(
|
||||
source, location, "uncertain-thermal-shell-metric",
|
||||
"Surface gradient enclosure is not finite."));
|
||||
const Interval reciprocal_determinant = Interval{1} / determinant_interval;
|
||||
SurfacePoint point{};
|
||||
point.jacobian = std::ldexp(jacobian, 2 * geometry.length_exponent);
|
||||
point.jacobian_interval =
|
||||
@@ -213,13 +233,26 @@ Result<SurfacePoint> EvaluateSurface(const Geometry& geometry, double xi,
|
||||
(Interval{1} + Interval{kEta[i]} * eta_interval);
|
||||
const Interval dyi = Interval{.25 * kEta[i]} *
|
||||
(Interval{1} + Interval{kXi[i]} * xi_interval);
|
||||
const Vector3 gradient = dx * reciprocal1 + dy * reciprocal2;
|
||||
const auto gradient_interval = dxi * reciprocal1i + dyi * reciprocal2i;
|
||||
Matrix rhs{3, 1};
|
||||
rhs(0,0) = dx; rhs(1,0) = dy;
|
||||
const auto gradient = factor.Value().Solve(rhs);
|
||||
if (!gradient.HasValue())
|
||||
return Result<SurfacePoint>::Failure(GeometryFailure(
|
||||
source, location, "uncertain-thermal-shell-metric",
|
||||
"Surface gradient enclosure is not finite."));
|
||||
// The real solution obeys a1.g=dx, a2.g=dy, w.g=0. Its exterior-product
|
||||
// identity encloses the solution directly. The original outward endpoint
|
||||
// graph is retained component by component; no interval factor pivot or
|
||||
// interval inverse/basis is introduced and no low-order term is discarded.
|
||||
const auto first_numerator = Cross(a2i, wi);
|
||||
const auto second_numerator = Cross(wi, a1i);
|
||||
std::array<double, 3> components{};
|
||||
for (std::size_t j = 0; j < 3; ++j) {
|
||||
components[j] = std::ldexp(gradient[j], -geometry.length_exponent);
|
||||
components[j] = std::ldexp(gradient.Value()(j,0), -geometry.length_exponent);
|
||||
point.gradient_interval[i][j] =
|
||||
Scale(gradient_interval[j], -geometry.length_exponent);
|
||||
Scale(dxi * (reciprocal_determinant * first_numerator[j]) +
|
||||
dyi * (reciprocal_determinant * second_numerator[j]),
|
||||
-geometry.length_exponent);
|
||||
}
|
||||
point.gradient[i] = Vector3{components};
|
||||
if (!point.gradient[i].IsFinite() ||
|
||||
|
||||
@@ -39,7 +39,7 @@ struct SurfacePoint {
|
||||
Result<Geometry> BuildGeometry(const std::array<Vector3, 4>& coordinates,
|
||||
const SourceEntityId& source,
|
||||
const SourceLocation& location);
|
||||
/// @brief Checks finite metric inverse and gradients at one used surface point.
|
||||
/// @brief Solves actual tangential-gradient RHS and certifies their enclosures.
|
||||
Result<SurfacePoint> EvaluateSurface(const Geometry& geometry, double xi,
|
||||
double eta, Interval xi_interval,
|
||||
Interval eta_interval,
|
||||
|
||||
Reference in New Issue
Block a user