feat(hex8): use reference-weighted mean-volume energy
This commit is contained in:
@@ -99,8 +99,11 @@ std::vector<double> DenseOracle(const std::vector<std::size_t>& free) {
|
||||
for (std::size_t d = 0; d < 3; ++d)
|
||||
gradient_dot += GradientIntegral(a, b, d, d);
|
||||
k[row * n + col] =
|
||||
48.0 * (GradientIntegral(a, b, i, j) + GradientIntegral(a, b, j, i) +
|
||||
(i == j ? gradient_dot : 0.0));
|
||||
48.0 *
|
||||
(-(2.0 / 3.0) * GradientIntegral(a, b, i, j) +
|
||||
GradientIntegral(a, b, j, i) + (i == j ? gradient_dot : 0.0)) +
|
||||
(80.0 / 48.0) * (48.0 * kSigns[a][i] / (8.0 * (i + 1))) *
|
||||
(48.0 * kSigns[b][j] / (8.0 * (j + 1)));
|
||||
if (i == j) {
|
||||
m[row * n + col] = 2.0 * 48.0 / 64.0;
|
||||
for (std::size_t d = 0; d < 3; ++d)
|
||||
|
||||
@@ -573,7 +573,7 @@ TEST(SolidHhtDynamics,
|
||||
auto result = RunDynamics(OneFree());
|
||||
ASSERT_TRUE(result.HasValue()) << solid_hht_test::Message(result.GetStatus());
|
||||
const auto d = solid_hht_test::Brick();
|
||||
constexpr double lambda = 4.8;
|
||||
constexpr double bulk = 8.0;
|
||||
constexpr double mu = 4.8;
|
||||
const double roundoff = 512 * std::numeric_limits<double>::epsilon();
|
||||
const auto& frames = result.Value().state.AcceptedFrames();
|
||||
@@ -592,17 +592,20 @@ TEST(SolidHhtDynamics,
|
||||
double stiffness;
|
||||
double stiffness_term_scale;
|
||||
if (c == 0) {
|
||||
stiffness = (lambda + 2 * mu) * (-sign[0]) * product[1] * product[2] +
|
||||
stiffness = (4 * mu / 3) * (-sign[0]) * product[1] * product[2] +
|
||||
mu * (-sign[1]) * product[0] * product[2] +
|
||||
mu * (-sign[2]) * product[0] * product[1];
|
||||
stiffness_term_scale = (lambda + 2 * mu) * product[1] * product[2] +
|
||||
stiffness_term_scale = (4 * mu / 3) * product[1] * product[2] +
|
||||
mu * product[0] * product[2] +
|
||||
mu * product[0] * product[1];
|
||||
} else {
|
||||
const unsigned other = c == 1 ? 2 : 1;
|
||||
stiffness = -.25 * (lambda * sign[c] + mu * sign[0]) * product[other];
|
||||
stiffness_term_scale = .25 * (lambda + mu) * product[other];
|
||||
stiffness =
|
||||
-.25 * ((-2 * mu / 3) * sign[c] + mu * sign[0]) * product[other];
|
||||
stiffness_term_scale = .25 * (2 * mu / 3 + mu) * product[other];
|
||||
}
|
||||
stiffness -= bulk * sign[c] / 16;
|
||||
stiffness_term_scale += bulk / 16;
|
||||
const double inertia = c == 0 ? scalar_mass * f.acceleration[0] : 0;
|
||||
const double internal = stiffness * f.displacement[0];
|
||||
const double applied = b == 0 && c == 0 ? .01 : 0;
|
||||
|
||||
@@ -24,7 +24,7 @@ AffineMotion Motion(double time, bool nonlinear) {
|
||||
const double dq = 2 * std::sin(2 * time);
|
||||
const double ddq = 4 * std::cos(2 * time);
|
||||
AffineMotion m;
|
||||
constexpr double lambda = 4.8;
|
||||
constexpr double bulk = 8;
|
||||
constexpr double mu = 4.8;
|
||||
if (!nonlinear) {
|
||||
const M3 d{{{.015, .008, -.004}, {.006, -.01, .003}, {.005, .004, .012}}};
|
||||
@@ -35,8 +35,8 @@ AffineMotion Motion(double time, bool nonlinear) {
|
||||
m.b[i][j] = q * d[i][j];
|
||||
m.velocity[i][j] = dq * d[i][j];
|
||||
m.acceleration[i][j] = ddq * d[i][j];
|
||||
m.piola[i][j] =
|
||||
(i == j ? lambda * trace : 0) + mu * q * (d[i][j] + d[j][i]);
|
||||
m.piola[i][j] = (i == j ? (bulk * trace - 2 * mu * trace / 3) : 0) +
|
||||
mu * q * (d[i][j] + d[j][i]);
|
||||
}
|
||||
} else {
|
||||
const double s = .004 * q, ds = .004 * dq, dds = .004 * ddq;
|
||||
@@ -46,7 +46,7 @@ AffineMotion Motion(double time, bool nonlinear) {
|
||||
const M3 jr{{{-sn, -c, 0}, {c, -sn, 0}, {0, 0, 0}}};
|
||||
const M3 jjr{{{-c, sn, 0}, {-sn, -c, 0}, {0, 0, 0}}};
|
||||
const double eg = .5 * ((1 + s) * (1 + s) - 1);
|
||||
const double stress = (3 * lambda + 2 * mu) * eg;
|
||||
const double stress = 3 * bulk * eg;
|
||||
for (unsigned i = 0; i < 3; ++i)
|
||||
for (unsigned j = 0; j < 3; ++j) {
|
||||
m.b[i][j] = (1 + s) * r[i][j] - (i == j ? 1 : 0);
|
||||
@@ -506,7 +506,7 @@ TEST(SolidHhtTemporal,
|
||||
|
||||
TEST(SolidHhtTemporal, IndependentThreeDofBrickOscillatorHasSecondOrderUVA) {
|
||||
// Node0's three translations are free. Exact polynomial integrals yield
|
||||
// M=(rho/27)I, Kii=(lambda+4mu)/9, Kij=(lambda+mu)/12.
|
||||
// M=(rho/27)I, Kii=41/18, Kij=19/30 for mu=4.8 and direct kappa=8.
|
||||
std::array<std::array<double, 3>, 3> errors{};
|
||||
for (unsigned level = 0; level < 3; ++level) {
|
||||
const double h = .02 / static_cast<double>(1U << level);
|
||||
@@ -529,8 +529,8 @@ TEST(SolidHhtTemporal, IndependentThreeDofBrickOscillatorHasSecondOrderUVA) {
|
||||
<< solid_hht_test::Message(result.GetStatus());
|
||||
const auto& f = result.Value().state.CommittedState();
|
||||
const double m = 2.0 / 27;
|
||||
const double diagonal = (4.8 + 4 * 4.8) / 9;
|
||||
const double off = (4.8 + 4.8) / 12;
|
||||
const double diagonal = 41.0 / 18;
|
||||
const double off = 19.0 / 30;
|
||||
const std::array<double, 2> k{diagonal + 2 * off, diagonal - off};
|
||||
for (unsigned c = 0; c < 3; ++c) {
|
||||
std::array<double, 3> truth{};
|
||||
@@ -563,7 +563,7 @@ TEST(SolidHhtTemporal, IndependentThreeDofBrickOscillatorHasSecondOrderUVA) {
|
||||
TEST(SolidHhtTemporal, IndependentScalarBrickOscillatorHasSecondOrderUVA) {
|
||||
std::array<std::array<double, 3>, 3> errors{};
|
||||
const double m = 2.0 / 27;
|
||||
const double k = (4.8 + 4 * 4.8) / 9;
|
||||
const double k = 41.0 / 18;
|
||||
const double w = std::sqrt(k / m);
|
||||
for (unsigned level = 0; level < 3; ++level) {
|
||||
const double h = .02 / static_cast<double>(1U << level);
|
||||
@@ -608,46 +608,95 @@ TEST(SolidHhtTemporal, IndependentScalarBrickOscillatorHasSecondOrderUVA) {
|
||||
}
|
||||
}
|
||||
|
||||
TEST(SolidHhtTemporal,
|
||||
BendingMeshRefinementQuantifiesFullIntegrationShearLocking) {
|
||||
constexpr double thickness = .125;
|
||||
constexpr double curvature = .01;
|
||||
constexpr double lambda = 4.8;
|
||||
constexpr double mu = 4.8;
|
||||
const double continuum = .5 * (lambda + 2 * mu) * curvature * curvature *
|
||||
thickness * thickness * thickness / 12;
|
||||
double previous_error = 0;
|
||||
for (unsigned nx : {1U, 2U, 4U, 8U}) {
|
||||
double numerical = 0;
|
||||
for (unsigned e = 0; e < nx; ++e) {
|
||||
auto d = solid_hht_test::Brick();
|
||||
namespace {
|
||||
// Compatible nodal pure-bending field; no equilibrium solve is claimed.
|
||||
std::array<double, 3> BendingEnergy(unsigned nx, unsigned ny) {
|
||||
constexpr double thickness = .125, curvature = .01, bulk = 8;
|
||||
std::array<double, 3> result{};
|
||||
auto d = solid_hht_test::Brick();
|
||||
for (unsigned ex = 0; ex < nx; ++ex)
|
||||
for (unsigned ey = 0; ey < ny; ++ey) {
|
||||
const auto index = ex * ny + ey;
|
||||
fesa::Hex8Element::Coordinates x{};
|
||||
fesa::ElementDofLayout layout{
|
||||
{"I", e + 1, std::to_string(e + 1)},
|
||||
{"I", index + 1, std::to_string(index + 1)},
|
||||
{0, 1, 2, 3, 4, 5, 6, 7},
|
||||
{fesa::DofComponent::kUx, fesa::DofComponent::kUy,
|
||||
fesa::DofComponent::kUz}};
|
||||
fesa::Vector u(24);
|
||||
for (unsigned a = 0; a < 8; ++a) {
|
||||
x[a] = {static_cast<double>(e) / nx + d.nodes[a].coordinates[0] / nx,
|
||||
thickness * (d.nodes[a].coordinates[1] - .5),
|
||||
x[a] = {(ex + d.nodes[a].coordinates[0]) / nx,
|
||||
thickness * ((ey + d.nodes[a].coordinates[1]) / ny - .5),
|
||||
d.nodes[a].coordinates[2]};
|
||||
u[3 * a] = -curvature * x[a][0] * x[a][1];
|
||||
u[3 * a + 1] = .5 * curvature * x[a][0] * x[a][0];
|
||||
}
|
||||
auto element = fesa::Hex8Element::Create(e, layout, x, d.materials[0], 2);
|
||||
ASSERT_TRUE(element.HasValue());
|
||||
auto element =
|
||||
fesa::Hex8Element::Create(index, layout, x, d.materials[0], 2);
|
||||
EXPECT_TRUE(element.HasValue());
|
||||
if (!element.HasValue()) return {};
|
||||
auto response = element.Value().EvaluateLinear(u);
|
||||
ASSERT_TRUE(response.HasValue());
|
||||
numerical += response.Value().strain_energy;
|
||||
EXPECT_TRUE(response.HasValue());
|
||||
if (!response.HasValue()) return {};
|
||||
const auto weights = element.Value().ReferenceWeights();
|
||||
double volume = 0, trace_integral = 0;
|
||||
for (unsigned g = 0; g < 8; ++g) {
|
||||
volume += weights[g];
|
||||
const auto& strain = response.Value().points[g].strain;
|
||||
trace_integral += weights[g] * (strain[0] + strain[1] + strain[2]);
|
||||
}
|
||||
const double volumetric =
|
||||
.5 * bulk * trace_integral * trace_integral / volume;
|
||||
result[0] += response.Value().strain_energy;
|
||||
result[1] += response.Value().strain_energy - volumetric;
|
||||
result[2] += volumetric;
|
||||
}
|
||||
const double error = numerical / continuum - 1;
|
||||
const double exact_parasitic =
|
||||
mu / (lambda + 2 * mu) / std::pow(nx * thickness, 2);
|
||||
EXPECT_NEAR(error, exact_parasitic, 2e-11);
|
||||
if (previous_error != 0) EXPECT_NEAR(previous_error / error, 4, 1e-10);
|
||||
std::cout << "Bending nx=" << nx
|
||||
<< " energy/continuum=" << numerical / continuum << '\n';
|
||||
previous_error = error;
|
||||
return result;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
TEST(SolidHhtTemporal, FixedThicknessBendingRetainsDiscreteBulkBias) {
|
||||
constexpr double thickness = .125, curvature = .01, mu = 4.8, bulk = 8;
|
||||
constexpr double axial = bulk + 4 * mu / 3;
|
||||
const double continuum =
|
||||
axial * curvature * curvature * std::pow(thickness, 3) / 24;
|
||||
const double limit = 4 * mu / (3 * axial);
|
||||
double previous_parasitic = 0;
|
||||
for (unsigned nx : {1U, 2U, 4U, 8U}) {
|
||||
const auto energy = BendingEnergy(nx, 1);
|
||||
const double parasitic = mu / (axial * std::pow(nx * thickness, 2));
|
||||
EXPECT_NEAR(energy[0] / continuum, limit + parasitic, 2e-11);
|
||||
EXPECT_NEAR(energy[2] / continuum, 0, 2e-13);
|
||||
if (previous_parasitic != 0)
|
||||
EXPECT_NEAR(previous_parasitic / parasitic, 4, 1e-13);
|
||||
previous_parasitic = parasitic;
|
||||
std::cout << "Fixed thickness nx=" << nx
|
||||
<< " U/Ucont=" << energy[0] / continuum
|
||||
<< " limiting ratio=" << limit << " shear=" << parasitic << '\n';
|
||||
}
|
||||
}
|
||||
|
||||
TEST(SolidHhtTemporal,
|
||||
BendingMeshRefinementQuantifiesFullIntegrationShearLocking) {
|
||||
constexpr double thickness = .125, curvature = .01, mu = 4.8, bulk = 8;
|
||||
constexpr double axial = bulk + 4 * mu / 3;
|
||||
const double continuum =
|
||||
axial * curvature * curvature * std::pow(thickness, 3) / 24;
|
||||
double previous_error = 0;
|
||||
for (unsigned n : {1U, 2U, 4U, 8U}) {
|
||||
const auto energy = BendingEnergy(n, n);
|
||||
const double shear = mu / (axial * std::pow(n * thickness, 2));
|
||||
const double missing_bulk = bulk / (axial * n * n);
|
||||
const double error = energy[0] / continuum - 1;
|
||||
EXPECT_NEAR(error, shear - missing_bulk, 2e-11);
|
||||
// Independent moments separate competing errors, so cancellation cannot
|
||||
// make the continuum-convergence claim pass accidentally.
|
||||
EXPECT_NEAR(energy[1] / continuum, 4 * mu / (3 * axial) + shear, 2e-11);
|
||||
EXPECT_NEAR(energy[2] / continuum, bulk / axial - missing_bulk, 2e-11);
|
||||
if (previous_error != 0) EXPECT_NEAR(previous_error / error, 4, 1e-10);
|
||||
previous_error = error;
|
||||
std::cout << "Bending nx=ny=" << n << " U/Ucont=" << energy[0] / continuum
|
||||
<< " positive shear=" << shear
|
||||
<< " negative bulk=" << missing_bulk << '\n';
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user