Integrate Refactor solver sources and dual MSVC CMake builds

This commit is contained in:
KOKO\Mimi
2026-10-18 02:10:51 +09:00
parent 7ee32e5837
commit 12d51480de
218 changed files with 17842 additions and 9689 deletions
+115
View File
@@ -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
+12
View File
@@ -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()) {
+17 -54
View File
@@ -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(
+35 -4
View File
@@ -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(
-13
View File
@@ -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
-2
View File
@@ -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_
+2 -3
View File
@@ -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);
-40
View File
@@ -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_
+3 -3
View File
@@ -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);
+6 -6
View File
@@ -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
+13 -13
View File
@@ -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};
}
}
+57 -104
View File
@@ -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) {
+48 -169
View File
@@ -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)) {
+133 -181
View File
@@ -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{};
+79
View File
@@ -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_
+58 -25
View File
@@ -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() ||
+1 -1
View File
@@ -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,