628 lines
26 KiB
C++
628 lines
26 KiB
C++
#include "fesa/elements/hex8_element.h"
|
|
|
|
#include "fesa/math/vector3.h"
|
|
|
|
#include <cmath>
|
|
#include <memory>
|
|
#include <set>
|
|
#include <stdexcept>
|
|
#include <utility>
|
|
|
|
#include "../math/dense_blas_internal.h"
|
|
#include "hex8_geometry.h"
|
|
#include "hex8_linear_tangent_cache.h"
|
|
|
|
namespace fesa {
|
|
namespace {
|
|
using hex8_internal::Tensor;
|
|
using hex8_internal::Triple;
|
|
/// @brief Checks every tensor component before recovery or inverse use.
|
|
bool Finite(const Tensor& value) {
|
|
for (const auto& row : value)
|
|
for (double entry : row)
|
|
if (!std::isfinite(entry)) return false;
|
|
return true;
|
|
}
|
|
/// @brief Packs actual tensor shear, never engineering shear, in schema order.
|
|
std::array<double, 6> Pack(const Tensor& value) {
|
|
return {value[0][0], value[1][1], value[2][2],
|
|
value[0][1], value[0][2], value[1][2]};
|
|
}
|
|
/// @brief Computes a Euclidean contraction in deterministic component order.
|
|
double Dot(const Triple& a, const Triple& b) {
|
|
return a[0] * b[0] + a[1] * b[1] + a[2] * b[2];
|
|
}
|
|
/// @brief Forms a scaled 24-component outer product through dense MKL.
|
|
Matrix Outer(const std::array<Triple, 8>& values, double coefficient) {
|
|
Matrix left{24U, 1U};
|
|
Matrix right{1U, 24U};
|
|
for (std::size_t a = 0; a < 8; ++a)
|
|
for (std::size_t i = 0; i < 3; ++i) {
|
|
left(3U * a + i, 0) = coefficient * values[a][i];
|
|
right(0, 3U * a + i) = values[a][i];
|
|
}
|
|
return left.Multiply(right);
|
|
}
|
|
/// @brief Uses MKL entry products and the original ordered Gram/stress sums.
|
|
Matrix GradientProduct(const std::array<Triple, 8>& gradients,
|
|
const Tensor* stress = nullptr) {
|
|
const std::size_t terms = stress == nullptr ? 3U : 9U;
|
|
Vector left{64U * terms};
|
|
Vector right{64U * terms};
|
|
Vector final_factor{64U * terms};
|
|
for (std::size_t a = 0; a < 8; ++a)
|
|
for (std::size_t b = 0; b < 8; ++b)
|
|
for (std::size_t j = 0; j < 3; ++j)
|
|
for (std::size_t l = 0; l < (stress == nullptr ? 1U : 3U); ++l) {
|
|
const auto entry = (8U * a + b) * terms +
|
|
(stress == nullptr ? j : 3U * j + l);
|
|
left[entry] = gradients[a][j];
|
|
right[entry] = stress == nullptr ? gradients[b][j] : (*stress)[l][j];
|
|
final_factor[entry] = gradients[b][l];
|
|
}
|
|
auto products = dense_blas_internal::MultiplyDiagonal(left, right);
|
|
if (stress != nullptr)
|
|
products = dense_blas_internal::MultiplyDiagonal(products, final_factor);
|
|
Matrix result{8U, 8U};
|
|
for (std::size_t a = 0; a < 8; ++a)
|
|
for (std::size_t b = 0; b < 8; ++b) {
|
|
const auto first = (8U * a + b) * terms;
|
|
if (stress == nullptr) {
|
|
result(a, b) = (products[first] + products[first + 1U]) +
|
|
products[first + 2U];
|
|
} else {
|
|
for (std::size_t term = 0; term < terms; ++term)
|
|
result(a, b) += products[first + term];
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
/// @brief Retains product and cancellation errors in fixed-order contractions.
|
|
void AccumulateProduct(double left, double right, double& sum,
|
|
double& correction) {
|
|
const double product = left * right;
|
|
const double next = sum + product;
|
|
correction += std::fma(left, right, -product) +
|
|
(std::abs(sum) >= std::abs(product)
|
|
? (sum - next) + product
|
|
: (product - next) + sum);
|
|
sum = next;
|
|
}
|
|
/// @brief Recovers low bits lost when subtracting unequal nodal translations.
|
|
double DifferenceCorrection(double left, double right, double difference) {
|
|
const double right_virtual = left - difference;
|
|
const double left_virtual = difference + right_virtual;
|
|
return (left - left_virtual) + (right_virtual - right);
|
|
}
|
|
/// @brief Gives the finite-control failure without accepting a geometry guess.
|
|
const char* CertificateMessage(hex8_internal::Certificate certificate) {
|
|
switch (certificate) {
|
|
case hex8_internal::Certificate::kNonpositive:
|
|
return "Whole-cell Jacobian has a nonpositive witness.";
|
|
case hex8_internal::Certificate::kUncertain:
|
|
return "Whole-cell Jacobian positivity is unresolved at depth 10 or cell "
|
|
"budget 65536.";
|
|
case hex8_internal::Certificate::kNonfinite:
|
|
return "Whole-cell Jacobian interval arithmetic is nonfinite.";
|
|
case hex8_internal::Certificate::kPositive:
|
|
break;
|
|
}
|
|
return "Invalid Jacobian certificate.";
|
|
}
|
|
} // namespace
|
|
|
|
Hex8Element::Hex8Element(EntityIndex index, ElementDofLayout layout,
|
|
Coordinates coordinates, double kappa, double mu,
|
|
std::optional<double> density, SourceLocation location)
|
|
: element_index_{index},
|
|
layout_{std::move(layout)},
|
|
coordinates_{coordinates},
|
|
kappa_{kappa},
|
|
mu_{mu},
|
|
density_{density},
|
|
location_{std::move(location)},
|
|
paired_linear_tangent_{
|
|
std::make_shared<hex8_internal::LinearTangentCache>()} {}
|
|
|
|
Status Hex8Element::Failure(const char* message) const {
|
|
return Status::Failure(FailureCategory::kModel,
|
|
{{Severity::kError, "invalid-hex8", location_, "C3D8",
|
|
layout_.source_id.instance_name + ":" +
|
|
layout_.source_id.source_label_text,
|
|
message}});
|
|
}
|
|
|
|
Result<Hex8Element> Hex8Element::Create(EntityIndex index,
|
|
ElementDofLayout layout,
|
|
Coordinates coordinates,
|
|
const LinearElasticMaterial& material,
|
|
std::optional<double> density,
|
|
SourceLocation location) {
|
|
const double e = material.YoungsModulus();
|
|
const double nu = material.PoissonsRatio();
|
|
Hex8Element candidate{index, std::move(layout), coordinates, 0.0,
|
|
0.0, density, std::move(location)};
|
|
const std::vector<DofComponent> components{
|
|
DofComponent::kUx, DofComponent::kUy, DofComponent::kUz};
|
|
const std::set<EntityIndex> nodes(candidate.layout_.node_indices.begin(),
|
|
candidate.layout_.node_indices.end());
|
|
if (candidate.layout_.node_indices.size() != 8U || nodes.size() != 8U ||
|
|
candidate.layout_.components_per_node != components)
|
|
return Result<Hex8Element>::Failure(candidate.Failure(
|
|
"Hex8 requires eight distinct nodes with exactly UX, UY, UZ."));
|
|
if (!std::isfinite(e) || e <= 0.0 || !std::isfinite(nu) || nu <= -1.0 ||
|
|
nu >= 0.5)
|
|
return Result<Hex8Element>::Failure(
|
|
candidate.Failure("Hex8 requires finite E>0 and -1<nu<0.5."));
|
|
candidate.mu_ = e / (2.0 * (1.0 + nu));
|
|
candidate.kappa_ = e / (3.0 * (1.0 - 2.0 * nu));
|
|
if (!std::isfinite(candidate.mu_) || candidate.mu_ <= 0.0 ||
|
|
!std::isfinite(candidate.kappa_) || candidate.kappa_ <= 0.0)
|
|
return Result<Hex8Element>::Failure(
|
|
candidate.Failure("Hex8 shear and direct bulk moduli must remain "
|
|
"finite and representably positive."));
|
|
if (density && (!std::isfinite(*density) || *density <= 0.0))
|
|
return Result<Hex8Element>::Failure(candidate.Failure(
|
|
"Hex8 reference density must be finite and positive."));
|
|
const auto certificate = hex8_internal::Certify(coordinates);
|
|
if (certificate != hex8_internal::Certificate::kPositive)
|
|
return Result<Hex8Element>::Failure(
|
|
candidate.Failure(CertificateMessage(certificate)));
|
|
const double gauss = 1.0 / std::sqrt(3.0);
|
|
for (std::size_t g = 0; g < 8; ++g) {
|
|
auto& point = candidate.points_[g];
|
|
point.natural = {g % 2U == 0U ? -gauss : gauss,
|
|
(g / 2U) % 2U == 0U ? -gauss : gauss,
|
|
g < 4U ? -gauss : gauss};
|
|
std::array<Triple, 8> derivatives{};
|
|
hex8_internal::Shape(point.natural, point.shape, derivatives);
|
|
const auto jacobian = hex8_internal::Jacobian(coordinates, derivatives);
|
|
point.weight = hex8_internal::Determinant(jacobian);
|
|
if (!Finite(jacobian) || !std::isfinite(point.weight) ||
|
|
point.weight <= 0.0)
|
|
return Result<Hex8Element>::Failure(candidate.Failure(
|
|
"Reference quadrature Jacobian must be finite and positive."));
|
|
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]);
|
|
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(
|
|
"Reference gradient or point coordinate is nonfinite."));
|
|
}
|
|
}
|
|
// Zero Hessian of the same reference-relative mean-volume energy.
|
|
std::array<Triple, 8> integrated_gradients{};
|
|
double volume = 0.0;
|
|
bool backend_inputs_failed = false;
|
|
for (const auto& point : candidate.points_) {
|
|
auto gradients = point.gradients;
|
|
gradients[0] = {};
|
|
for (std::size_t a = 1; a < 8; ++a)
|
|
for (std::size_t i = 0; i < 3; ++i) gradients[0][i] -= gradients[a][i];
|
|
volume += point.weight;
|
|
for (std::size_t a = 0; a < 8; ++a)
|
|
for (std::size_t i = 0; i < 3; ++i)
|
|
integrated_gradients[a][i] += point.weight * gradients[a][i];
|
|
Matrix deviatoric{0U, 0U};
|
|
Matrix shear{0U, 0U};
|
|
Matrix gram{0U, 0U};
|
|
bool point_products_failed = false;
|
|
try {
|
|
deviatoric = Outer(gradients, -(2.0 / 3.0) * candidate.mu_);
|
|
shear = Outer(gradients, candidate.mu_);
|
|
gram = GradientProduct(gradients);
|
|
} catch (const std::invalid_argument&) {
|
|
backend_inputs_failed = true;
|
|
point_products_failed = true;
|
|
}
|
|
for (std::size_t a = 0; a < 8; ++a)
|
|
for (std::size_t b = 0; b < 8; ++b) {
|
|
for (std::size_t i = 0; i < 3; ++i)
|
|
for (std::size_t k = 0; k < 3; ++k) {
|
|
if (point_products_failed) {
|
|
// Diagnostic replay only: this candidate can never succeed.
|
|
const auto& ga = gradients[a];
|
|
const auto& gb = gradients[b];
|
|
candidate.stiffness_(3U * a + i, 3U * b + k) +=
|
|
point.weight *
|
|
(-(2.0 / 3.0) * candidate.mu_ * ga[i] * gb[k] +
|
|
candidate.mu_ * ga[k] * gb[i] +
|
|
(i == k ? candidate.mu_ * Dot(ga, gb) : 0.0));
|
|
} else {
|
|
candidate.stiffness_(3U * a + i, 3U * b + k) +=
|
|
point.weight * (deviatoric(3U * a + i, 3U * b + k) +
|
|
shear(3U * a + k, 3U * b + i) +
|
|
(i == k ? candidate.mu_ * gram(a, b) : 0.0));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (!std::isfinite(volume) || volume <= 0.0)
|
|
return Result<Hex8Element>::Failure(
|
|
candidate.Failure("Hex8 reference volume is nonfinite."));
|
|
Matrix bulk{0U, 0U};
|
|
try {
|
|
bulk = Outer(integrated_gradients, candidate.kappa_ / volume);
|
|
} catch (const std::invalid_argument&) {
|
|
backend_inputs_failed = true;
|
|
bulk = Matrix{24U, 24U};
|
|
for (std::size_t a = 0; a < 8; ++a)
|
|
for (std::size_t b = 0; b < 8; ++b)
|
|
for (std::size_t i = 0; i < 3; ++i)
|
|
for (std::size_t k = 0; k < 3; ++k)
|
|
bulk(3U * a + i, 3U * b + k) =
|
|
(candidate.kappa_ / volume) * integrated_gradients[a][i] *
|
|
integrated_gradients[b][k];
|
|
}
|
|
for (std::size_t i = 0; i < 24; ++i)
|
|
for (std::size_t j = 0; j < 24; ++j)
|
|
candidate.stiffness_(i, j) += bulk(i, j);
|
|
for (std::size_t i = 0; i < 24; ++i)
|
|
for (std::size_t j = 0; j < 24; ++j)
|
|
if (!std::isfinite(candidate.stiffness_(i, j)))
|
|
return Result<Hex8Element>::Failure(
|
|
candidate.Failure("Hex8 reference stiffness is nonfinite."));
|
|
for (std::size_t i = 0; i < 24; ++i)
|
|
if (candidate.stiffness_(i, i) <= 0.0)
|
|
return Result<Hex8Element>::Failure(
|
|
candidate.Failure("Hex8 reference stiffness has an unrepresentable "
|
|
"positive diagonal."));
|
|
if (backend_inputs_failed)
|
|
return Result<Hex8Element>::Failure(candidate.Failure(
|
|
"Hex8 dense MKL operands failed finite validation."));
|
|
return Result<Hex8Element>::Success(std::move(candidate));
|
|
}
|
|
|
|
const ElementDofLayout& Hex8Element::DofLayout() const noexcept {
|
|
return layout_;
|
|
}
|
|
Result<ElementStiffnessContribution> Hex8Element::ComputeStiffness() const {
|
|
return Result<ElementStiffnessContribution>::Success({layout_, stiffness_});
|
|
}
|
|
std::array<double, 8> Hex8Element::ReferenceWeights() const noexcept {
|
|
std::array<double, 8> weights{};
|
|
for (std::size_t g = 0; g < weights.size(); ++g)
|
|
weights[g] = points_[g].weight;
|
|
return weights;
|
|
}
|
|
|
|
Result<ElementStiffnessContribution> Hex8Element::ComputeMass() const {
|
|
if (!density_)
|
|
return Result<ElementStiffnessContribution>::Failure(
|
|
Failure("Hex8 mass requires reference density."));
|
|
Matrix consistent{24U, 24U};
|
|
for (const auto& point : points_) {
|
|
Matrix left{8U, 1U};
|
|
Matrix right{1U, 8U};
|
|
for (std::size_t a = 0; a < 8; ++a) {
|
|
left(a, 0) = *density_ * point.shape[a];
|
|
right(0, a) = point.shape[a];
|
|
}
|
|
Matrix products{0U, 0U};
|
|
try {
|
|
products = left.Multiply(right);
|
|
} catch (const std::invalid_argument&) {
|
|
return Result<ElementStiffnessContribution>::Failure(
|
|
Failure("Hex8 reference mass is nonfinite."));
|
|
}
|
|
for (std::size_t a = 0; a < 8; ++a)
|
|
for (std::size_t b = 0; b < 8; ++b) {
|
|
const double value = products(a, b) * point.weight;
|
|
if (!std::isfinite(value))
|
|
return Result<ElementStiffnessContribution>::Failure(
|
|
Failure("Hex8 reference mass is nonfinite."));
|
|
for (std::size_t i = 0; i < 3; ++i)
|
|
consistent(3U * a + i, 3U * b + i) += value;
|
|
}
|
|
}
|
|
Matrix mass{24U, 24U};
|
|
for (std::size_t i = 0; i < 24; ++i)
|
|
for (std::size_t j = 0; j < 24; ++j) {
|
|
if (!std::isfinite(consistent(i, j)))
|
|
return Result<ElementStiffnessContribution>::Failure(
|
|
Failure("Hex8 accumulated reference mass is nonfinite."));
|
|
mass(i, i) += consistent(i, j);
|
|
}
|
|
for (std::size_t i = 0; i < 24; ++i)
|
|
if (!std::isfinite(mass(i, i)) || mass(i, i) <= 0.0)
|
|
return Result<ElementStiffnessContribution>::Failure(Failure(
|
|
"Hex8 reference mass has an unrepresentable positive diagonal."));
|
|
return Result<ElementStiffnessContribution>::Success(
|
|
{layout_, std::move(mass)});
|
|
}
|
|
Result<ElementResultBundle> Hex8Element::Recover(const Vector& u) const {
|
|
auto response = EvaluateLinear(u);
|
|
if (!response.HasValue())
|
|
return Result<ElementResultBundle>::Failure(response.GetStatus());
|
|
SolidElementResultRows rows;
|
|
rows.rows.assign(response.Value().points.begin(),
|
|
response.Value().points.end());
|
|
rows.physical_strain_energy = response.Value().strain_energy;
|
|
return Result<ElementResultBundle>::Success(
|
|
{layout_.source_id, std::move(rows)});
|
|
}
|
|
Result<Hex8Response> Hex8Element::EvaluateLinear(const Vector& u) const {
|
|
return Evaluate(u, false);
|
|
}
|
|
Result<Hex8Response> Hex8Element::EvaluateNonlinear(const Vector& u) const {
|
|
return Evaluate(u, true);
|
|
}
|
|
Result<Hex8HighLowResponse> Hex8Element::EvaluateLinearHighLow(
|
|
const Vector& high, const Vector& low) const {
|
|
return EvaluateHighLow(high, low, false);
|
|
}
|
|
Result<Hex8HighLowResponse> Hex8Element::EvaluateNonlinearHighLow(
|
|
const Vector& high, const Vector& low) const {
|
|
return EvaluateHighLow(high, low, true);
|
|
}
|
|
Result<Hex8Response> Hex8Element::Evaluate(const Vector& u,
|
|
bool nonlinear) const {
|
|
if (u.Size() != 24U)
|
|
return Result<Hex8Response>::Failure(
|
|
Failure("Hex8 trial requires 24 translations."));
|
|
const auto certificate = hex8_internal::Certify(coordinates_, &u);
|
|
if (certificate != hex8_internal::Certificate::kPositive)
|
|
return Result<Hex8Response>::Failure(
|
|
Failure(CertificateMessage(certificate)));
|
|
Hex8Response response;
|
|
Vector relative{24U};
|
|
Vector relative_correction{24U};
|
|
for (std::size_t a = 0; a < 8; ++a)
|
|
for (std::size_t i = 0; i < 3; ++i) {
|
|
const auto row = 3U * a + i;
|
|
relative[row] = u[row] - u[i];
|
|
relative_correction[row] =
|
|
DifferenceCorrection(u[row], u[i], relative[row]);
|
|
}
|
|
if (!nonlinear) {
|
|
response.material_tangent = stiffness_;
|
|
response.tangent = stiffness_;
|
|
}
|
|
std::array<Tensor, 8> deformations{}, strains{};
|
|
std::array<std::array<Triple, 8>, 8> gradients{};
|
|
double volume = 0.0, trace_integral = 0.0;
|
|
Vector force_correction{24U};
|
|
for (std::size_t g = 0; g < 8; ++g) {
|
|
const auto& point = points_[g];
|
|
gradients[g] = point.gradients;
|
|
Triple sum{};
|
|
for (std::size_t a = 1; a < 8; ++a)
|
|
for (std::size_t j = 0; j < 3; ++j) sum[j] += gradients[g][a][j];
|
|
for (std::size_t j = 0; j < 3; ++j) gradients[g][0][j] = -sum[j];
|
|
Tensor gradient{};
|
|
Tensor gradient_correction{};
|
|
for (std::size_t a = 1; a < 8; ++a)
|
|
for (std::size_t i = 0; i < 3; ++i)
|
|
for (std::size_t j = 0; j < 3; ++j) {
|
|
AccumulateProduct(relative[3U * a + i], point.gradients[a][j],
|
|
gradient[i][j], gradient_correction[i][j]);
|
|
AccumulateProduct(relative_correction[3U * a + i],
|
|
point.gradients[a][j], gradient[i][j],
|
|
gradient_correction[i][j]);
|
|
}
|
|
for (std::size_t i = 0; i < 3; ++i)
|
|
for (std::size_t j = 0; j < 3; ++j)
|
|
gradient[i][j] += gradient_correction[i][j];
|
|
Tensor f = nonlinear ? gradient : Tensor{};
|
|
for (std::size_t i = 0; i < 3; ++i) f[i][i] += 1.0;
|
|
Tensor strain{};
|
|
for (std::size_t i = 0; i < 3; ++i)
|
|
for (std::size_t j = 0; j < 3; ++j) {
|
|
strain[i][j] = 0.5 * (gradient[i][j] + gradient[j][i]);
|
|
// E = sym(H) + H^T*H/2 avoids subtracting I at tiny strain.
|
|
if (nonlinear)
|
|
for (std::size_t k = 0; k < 3; ++k)
|
|
strain[i][j] += 0.5 * gradient[k][i] * gradient[k][j];
|
|
}
|
|
deformations[g] = f;
|
|
strains[g] = strain;
|
|
volume += point.weight;
|
|
trace_integral +=
|
|
point.weight * (strain[0][0] + strain[1][1] + strain[2][2]);
|
|
if (!Finite(gradient) || !Finite(f) || !Finite(strain))
|
|
return Result<Hex8Response>::Failure(
|
|
Failure("Hex8 trial tensor is nonfinite."));
|
|
}
|
|
const double mean_trace = trace_integral / volume;
|
|
if (!std::isfinite(volume) || volume <= 0 || !std::isfinite(mean_trace))
|
|
return Result<Hex8Response>::Failure(
|
|
Failure("Hex8 mean volume is nonfinite."));
|
|
response.strain_energy = (0.5 * kappa_ * mean_trace) * (volume * mean_trace);
|
|
std::array<Triple, 8> integrated_fg{};
|
|
bool backend_inputs_failed = false;
|
|
for (std::size_t g = 0; g < 8; ++g) {
|
|
const auto& point = points_[g];
|
|
const auto& f = deformations[g];
|
|
const auto& strain = strains[g];
|
|
const double trace = strain[0][0] + strain[1][1] + strain[2][2];
|
|
Tensor s{}, p{}, fft{};
|
|
double density_energy = 0.0;
|
|
for (std::size_t i = 0; i < 3; ++i)
|
|
for (std::size_t j = 0; j < 3; ++j) {
|
|
const double dev = strain[i][j] - (i == j ? trace / 3.0 : 0.0);
|
|
s[i][j] = 2.0 * mu_ * dev + (i == j ? kappa_ * mean_trace : 0.0);
|
|
density_energy += mu_ * dev * dev;
|
|
}
|
|
for (std::size_t i = 0; i < 3; ++i)
|
|
for (std::size_t j = 0; j < 3; ++j) {
|
|
for (std::size_t k = 0; k < 3; ++k) {
|
|
p[i][j] += f[i][k] * s[k][j];
|
|
fft[i][j] += f[i][k] * f[j][k];
|
|
}
|
|
}
|
|
response.strain_energy += density_energy * point.weight;
|
|
Tensor stress = s;
|
|
if (nonlinear) {
|
|
const double det_f = hex8_internal::Determinant(f);
|
|
if (!std::isfinite(det_f) || det_f <= 0.0)
|
|
return Result<Hex8Response>::Failure(Failure(
|
|
"Hex8 trial deformation determinant must be finite and positive."));
|
|
stress = {};
|
|
for (std::size_t i = 0; i < 3; ++i)
|
|
for (std::size_t j = 0; j < 3; ++j)
|
|
for (std::size_t k = 0; k < 3; ++k)
|
|
stress[i][j] += p[i][k] * f[j][k] / det_f;
|
|
}
|
|
for (std::size_t a = 0; a < 8; ++a) {
|
|
for (std::size_t i = 0; i < 3; ++i)
|
|
integrated_fg[a][i] += point.weight * Dot(f[i], gradients[g][a]);
|
|
}
|
|
Matrix deviatoric{0U, 0U};
|
|
Matrix shear{0U, 0U};
|
|
Matrix gram{0U, 0U};
|
|
Matrix geometric{0U, 0U};
|
|
bool point_products_failed = false;
|
|
if (!Finite(f) || !Finite(strain) || !Finite(s) || !Finite(p) ||
|
|
!Finite(fft) || !Finite(stress))
|
|
return Result<Hex8Response>::Failure(
|
|
Failure("Hex8 trial tensor is nonfinite."));
|
|
if (nonlinear) {
|
|
std::array<Triple, 8> fg{};
|
|
for (std::size_t a = 0; a < 8; ++a)
|
|
for (std::size_t i = 0; i < 3; ++i)
|
|
fg[a][i] = Dot(f[i], gradients[g][a]);
|
|
try {
|
|
deviatoric = Outer(fg, -(2.0 / 3.0) * mu_);
|
|
shear = Outer(fg, mu_);
|
|
gram = GradientProduct(gradients[g]);
|
|
geometric = GradientProduct(gradients[g], &s);
|
|
} catch (const std::invalid_argument&) {
|
|
backend_inputs_failed = true;
|
|
point_products_failed = true;
|
|
}
|
|
}
|
|
for (std::size_t a = 0; a < 8; ++a) {
|
|
const auto& ga = gradients[g][a];
|
|
for (std::size_t i = 0; i < 3; ++i) {
|
|
double contraction = 0.0;
|
|
double contraction_correction = 0.0;
|
|
for (std::size_t j = 0; j < 3; ++j)
|
|
AccumulateProduct(p[i][j], ga[j], contraction,
|
|
contraction_correction);
|
|
AccumulateProduct(contraction, point.weight,
|
|
response.internal_force[3U * a + i],
|
|
force_correction[3U * a + i]);
|
|
AccumulateProduct(contraction_correction, point.weight,
|
|
response.internal_force[3U * a + i],
|
|
force_correction[3U * a + i]);
|
|
}
|
|
if (nonlinear)
|
|
for (std::size_t b = 0; b < 8; ++b) {
|
|
const auto& gb = gradients[g][b];
|
|
double diagnostic_geometric = 0.0;
|
|
if (point_products_failed)
|
|
for (std::size_t j = 0; j < 3; ++j)
|
|
for (std::size_t l = 0; l < 3; ++l)
|
|
diagnostic_geometric += ga[j] * s[l][j] * gb[l];
|
|
for (std::size_t i = 0; i < 3; ++i)
|
|
for (std::size_t k = 0; k < 3; ++k) {
|
|
const auto row = 3U * a + i;
|
|
const auto column = 3U * b + k;
|
|
if (point_products_failed) {
|
|
// Reproduce original diagnostic priority, never acceptance.
|
|
response.material_tangent(row, column) +=
|
|
point.weight *
|
|
(-(2.0 / 3.0) * mu_ * Dot(f[i], ga) * Dot(f[k], gb) +
|
|
mu_ * Dot(f[i], gb) * Dot(f[k], ga) +
|
|
mu_ * fft[i][k] * Dot(ga, gb));
|
|
} else {
|
|
response.material_tangent(row, column) +=
|
|
point.weight *
|
|
(deviatoric(row, column) +
|
|
shear(3U * b + i, 3U * a + k) +
|
|
mu_ * fft[i][k] * gram(a, b));
|
|
}
|
|
if (i == k)
|
|
response.geometric_tangent(row, column) +=
|
|
(point_products_failed ? diagnostic_geometric
|
|
: geometric(a, b)) * point.weight;
|
|
}
|
|
}
|
|
}
|
|
response.points[g] = {element_index_, static_cast<int>(g + 1U),
|
|
point.natural, point.position,
|
|
Pack(stress), Pack(strain)};
|
|
}
|
|
for (std::size_t i = 0; i < 24; ++i)
|
|
response.internal_force[i] += force_correction[i];
|
|
if (nonlinear) {
|
|
Matrix bulk{0U, 0U};
|
|
try {
|
|
bulk = Outer(integrated_fg, kappa_ / volume);
|
|
} catch (const std::invalid_argument&) {
|
|
backend_inputs_failed = true;
|
|
bulk = Matrix{24U, 24U};
|
|
for (std::size_t a = 0; a < 8; ++a)
|
|
for (std::size_t b = 0; b < 8; ++b)
|
|
for (std::size_t i = 0; i < 3; ++i)
|
|
for (std::size_t k = 0; k < 3; ++k)
|
|
bulk(3U * a + i, 3U * b + k) =
|
|
(kappa_ / volume) * integrated_fg[a][i] * integrated_fg[b][k];
|
|
}
|
|
for (std::size_t i = 0; i < 24; ++i)
|
|
for (std::size_t j = 0; j < 24; ++j)
|
|
response.material_tangent(i, j) += bulk(i, j);
|
|
}
|
|
if (!std::isfinite(response.strain_energy))
|
|
return Result<Hex8Response>::Failure(
|
|
Failure("Hex8 trial energy is nonfinite."));
|
|
for (std::size_t i = 0; i < 24; ++i) {
|
|
if (!std::isfinite(response.internal_force[i]))
|
|
return Result<Hex8Response>::Failure(
|
|
Failure("Hex8 trial force is nonfinite."));
|
|
for (std::size_t j = 0; j < 24; ++j) {
|
|
if (nonlinear)
|
|
response.tangent(i, j) =
|
|
response.material_tangent(i, j) + response.geometric_tangent(i, j);
|
|
if (!std::isfinite(response.tangent(i, j)) ||
|
|
!std::isfinite(response.material_tangent(i, j)) ||
|
|
!std::isfinite(response.geometric_tangent(i, j)))
|
|
return Result<Hex8Response>::Failure(
|
|
Failure("Hex8 trial tangent is nonfinite."));
|
|
}
|
|
}
|
|
if (backend_inputs_failed)
|
|
return Result<Hex8Response>::Failure(
|
|
Failure("Hex8 dense MKL operands failed finite validation."));
|
|
return Result<Hex8Response>::Success(std::move(response));
|
|
}
|
|
} // namespace fesa
|