feat(hex8): use reference-weighted mean-volume energy
This commit is contained in:
@@ -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."));
|
||||
|
||||
Reference in New Issue
Block a user