feat(hex8): use reference-weighted mean-volume energy

This commit is contained in:
KOKO\Mimi
2026-10-10 23:32:40 +09:00
parent 29670e168d
commit 18b36c1c22
8 changed files with 537 additions and 155 deletions
+114 -85
View File
@@ -61,12 +61,12 @@ const char* CertificateMessage(hex8_internal::Certificate certificate) {
} // namespace
Hex8Element::Hex8Element(EntityIndex index, ElementDofLayout layout,
Coordinates coordinates, double lambda, double mu,
Coordinates coordinates, double kappa, double mu,
std::optional<double> density, SourceLocation location)
: element_index_{index},
layout_{std::move(layout)},
coordinates_{coordinates},
lambda_{lambda},
kappa_{kappa},
mu_{mu},
density_{density},
location_{std::move(location)} {}
@@ -102,23 +102,12 @@ Result<Hex8Element> Hex8Element::Create(EntityIndex index,
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.lambda_ = e * nu / ((1.0 + nu) * (1.0 - 2.0 * nu));
candidate.kappa_ = e / (3.0 * (1.0 - 2.0 * nu));
if (!std::isfinite(candidate.mu_) || candidate.mu_ <= 0.0 ||
!std::isfinite(candidate.lambda_) ||
!std::isfinite(candidate.lambda_ + 2.0 * candidate.mu_))
!std::isfinite(candidate.kappa_) || candidate.kappa_ <= 0.0)
return Result<Hex8Element>::Failure(
candidate.Failure("Hex8 Lame moduli must remain finite with "
"representable positive shear modulus."));
// Validate the rounded moduli actually used by the kernel, not merely the
// positive exact-real E/nu formula: cancellation near nu=-1 can erase bulk.
const double bulk = candidate.lambda_ + (2.0 * candidate.mu_) / 3.0;
const double volumetric_stress =
3.0 * candidate.lambda_ + 2.0 * candidate.mu_;
if (!std::isfinite(bulk) || bulk <= 0.0 ||
!std::isfinite(volumetric_stress) || volumetric_stress <= 0.0)
return Result<Hex8Element>::Failure(
candidate.Failure("Hex8 rounded Lame moduli require finite "
"representable positive bulk stiffness."));
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."));
@@ -156,20 +145,43 @@ Result<Hex8Element> Hex8Element::Create(EntityIndex index,
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;
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];
for (std::size_t a = 0; a < 8; ++a)
for (std::size_t b = 0; b < 8; ++b) {
const auto& ga = point.gradients[a];
const auto& gb = point.gradients[b];
const auto& ga = gradients[a];
const auto& gb = gradients[b];
const double dot = Dot(ga, gb);
for (std::size_t i = 0; i < 3; ++i)
for (std::size_t k = 0; k < 3; ++k) {
candidate.stiffness_(3U * a + i, 3U * b + k) +=
point.weight * (candidate.lambda_ * ga[i] * gb[k] +
point.weight * (-(2.0 / 3.0) * candidate.mu_ * ga[i] * gb[k] +
candidate.mu_ * ga[k] * gb[i] +
(i == k ? candidate.mu_ * dot : 0.0));
}
}
}
if (!std::isfinite(volume) || volume <= 0.0)
return Result<Hex8Element>::Failure(
candidate.Failure("Hex8 reference volume is nonfinite."));
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)
candidate.stiffness_(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)
if (!std::isfinite(candidate.stiffness_(i, j)))
@@ -278,48 +290,33 @@ Result<Hex8Response> Hex8Element::Evaluate(const Vector& u,
if (!nonlinear) {
response.material_tangent = stiffness_;
response.tangent = stiffness_;
// K0 annihilates uniform translation. Use the same relative field as
// strain recovery to avoid cancellation of a large rigid displacement.
// Retain low product bits and cancelled sum terms in bending-dominated
// rows; the stored stiffness and its tangent are unchanged.
for (std::size_t i = 0; i < 24; ++i) {
double sum = 0.0;
double correction = 0.0;
for (std::size_t j = 0; j < 24; ++j) {
AccumulateProduct(stiffness_(i, j), relative[j], sum, correction);
AccumulateProduct(stiffness_(i, j), relative_correction[j], sum,
correction);
}
response.internal_force[i] = sum + correction;
}
double energy = 0.0;
double energy_correction = 0.0;
for (std::size_t i = 0; i < 24; ++i) {
AccumulateProduct(relative[i], response.internal_force[i], energy,
energy_correction);
AccumulateProduct(relative_correction[i], response.internal_force[i],
energy, energy_correction);
}
response.strain_energy = 0.5 * (energy + energy_correction);
}
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 = 0; a < 8; ++a)
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]);
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 = gradient;
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)
@@ -330,51 +327,76 @@ Result<Hex8Response> Hex8Element::Evaluate(const Vector& u,
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{};
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{};
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)
s[i][j] = 2.0 * mu_ * strain[i][j] + (i == j ? lambda_ * trace : 0.0);
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."));
Tensor p{};
Tensor fft{};
double energy = 0.5 * lambda_ * trace * trace;
for (std::size_t i = 0; i < 3; ++i)
for (std::size_t j = 0; j < 3; ++j) {
energy += mu_ * strain[i][j] * strain[i][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 += energy * point.weight;
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) {
const auto& ga = point.gradients[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]);
}
}
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]);
}
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 = point.gradients[b];
const auto& gb = gradients[g][b];
double geometric = 0.0;
for (std::size_t j = 0; j < 3; ++j)
for (std::size_t l = 0; l < 3; ++l)
@@ -384,26 +406,33 @@ Result<Hex8Response> Hex8Element::Evaluate(const Vector& u,
const auto row = 3U * a + i;
const auto column = 3U * b + k;
response.material_tangent(row, column) +=
point.weight * (lambda_ * Dot(f[i], ga) * Dot(f[k], gb) +
mu_ * Dot(f[i], gb) * Dot(f[k], ga) +
mu_ * fft[i][k] * Dot(ga, gb));
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));
if (i == k)
response.geometric_tangent(row, column) +=
geometric * point.weight;
}
}
}
}
if (!Finite(f) || !Finite(strain) || !Finite(s) || !Finite(stress))
if (!Finite(f) || !Finite(strain) || !Finite(s) || !Finite(p) ||
!Finite(fft) || !Finite(stress))
return Result<Hex8Response>::Failure(
Failure("Hex8 trial tensor is nonfinite."));
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)
for (std::size_t i = 0; i < 24; ++i)
response.internal_force[i] += force_correction[i];
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)
response.material_tangent(3 * a + i, 3 * b + k) +=
(kappa_ / volume) * integrated_fg[a][i] * integrated_fg[b][k];
if (!std::isfinite(response.strain_energy))
return Result<Hex8Response>::Failure(
Failure("Hex8 trial energy is nonfinite."));