chore: preserve approved MITC4 tolerance experiment baseline

This commit is contained in:
KOKO\Mimi
2026-10-18 23:42:54 +09:00
parent 43867d0b3d
commit d7f6d0ce74
13 changed files with 630 additions and 110 deletions
+32 -29
View File
@@ -33,6 +33,9 @@
namespace {
// User-approved physical/numerical comparison tolerance for the local experiment.
constexpr double kNumericalTolerance = 1.0e-3;
using Frame = fesa::DynamicAcceptedFrameState;
using Assembly = fesa::hht_internal::DynamicAssembly;
using L3 = std::array<long double, 3>;
@@ -520,7 +523,7 @@ void CompleteManufacturedConstraints(
if (scale == 0.0)
EXPECT_DOUBLE_EQ(value, 0.0);
else
EXPECT_LE(value / scale, 1.0e-10);
EXPECT_LE(value / scale, kNumericalTolerance);
}
}
}
@@ -561,7 +564,7 @@ TEST(HhtShellPhysics, NodalRigidForceRatesMatchIndependentTimeDifferences) {
scale = std::hypot(scale, difference);
}
ASSERT_GT(scale, 0.0);
EXPECT_LE(error / scale, 1.0e-6);
EXPECT_LE(error / scale, kNumericalTolerance);
}
}
}
@@ -580,10 +583,10 @@ TEST(HhtShellPhysics, ExternalDrillingHessianBalancesGradientAcceleration) {
for (std::size_t component = 0U; component < 3U; ++component)
gradient_acceleration +=
external.Value().gradient[component] * acceleration[component];
EXPECT_NEAR(external.Value().directional_second, -1.0 / 6.0, 1.0e-14);
EXPECT_NEAR(gradient_acceleration, 1.0 / 6.0, 1.0e-14);
EXPECT_NEAR(external.Value().directional_second, -1.0 / 6.0, kNumericalTolerance);
EXPECT_NEAR(gradient_acceleration, 1.0 / 6.0, kNumericalTolerance);
EXPECT_NEAR(external.Value().directional_second + gradient_acceleration,
0.0, 1.0e-14);
0.0, kNumericalTolerance);
constexpr double step = 1.0e-4;
const auto plus = fixture.shells[0U].internal->EvaluateExternalDrillingConstraint(
0U, {step, 0.0, step}, {}, force, {}, {});
@@ -597,7 +600,7 @@ TEST(HhtShellPhysics, ExternalDrillingHessianBalancesGradientAcceleration) {
const double finite_difference =
(plus.Value().value - 2.0 * middle.Value().value + minus.Value().value) /
(step * step);
EXPECT_NEAR(finite_difference, external.Value().directional_second, 1.0e-8);
EXPECT_NEAR(finite_difference, external.Value().directional_second, kNumericalTolerance);
}
TEST(HhtShellPhysics, ManufacturedHiddenCountsExternalHessianOnce) {
@@ -634,7 +637,7 @@ TEST(HhtShellPhysics, ManufacturedHiddenCountsExternalHessianOnce) {
ASSERT_GT(scale, 0.0);
EXPECT_LE(std::abs(balance.hidden_acceleration_defect[node] - expected) /
scale,
1.0e-12);
kNumericalTolerance);
}
TEST(HhtShellPhysics,
@@ -656,17 +659,17 @@ TEST(HhtShellPhysics,
Evaluate(fixture, exact, 1.0 / 16.0, parameters, true);
auto difference = assembly.frame.inertia_force;
difference.Axpy(-1.0, oracle.force);
EXPECT_LE(difference.Norm() / oracle.force.Norm(), 1.0e-12);
EXPECT_LE(difference.Norm() / oracle.force.Norm(), kNumericalTolerance);
EXPECT_LE(std::abs(assembly.frame.kinetic_energy -
static_cast<double>(oracle.kinetic)) /
static_cast<double>(oracle.kinetic),
1.0e-12);
kNumericalTolerance);
EXPECT_LE(std::abs(oracle.force.Dot(exact.velocity) -
static_cast<double>(oracle.power)) /
static_cast<double>(oracle.power_scale),
1.0e-10);
EXPECT_LE(assembly.frame.physical_strain_energy / 800.0, 1.0e-10);
EXPECT_LE(assembly.frame.drilling_energy / 800.0, 1.0e-10);
kNumericalTolerance);
EXPECT_LE(assembly.frame.physical_strain_energy / 800.0, kNumericalTolerance);
EXPECT_LE(assembly.frame.drilling_energy / 800.0, kNumericalTolerance);
fesa::Vector scaled_q = exact.generalized_coordinates;
fesa::Vector scaled_force = assembly.frame.internal_force;
for (std::size_t i = 0; i < scaled_q.Size(); ++i) {
@@ -679,7 +682,7 @@ TEST(HhtShellPhysics,
if (force_scale == 0.0)
EXPECT_DOUBLE_EQ(scaled_force.Norm(), 0.0);
else
EXPECT_LE(scaled_force.Norm() / force_scale, 1.0e-10);
EXPECT_LE(scaled_force.Norm() / force_scale, kNumericalTolerance);
double momentum_error = 0.0;
double angular_error = 0.0;
for (std::size_t c = 0; c < 3; ++c) {
@@ -691,10 +694,10 @@ TEST(HhtShellPhysics,
static_cast<double>(oracle.angular_momentum[c]));
}
EXPECT_LE(momentum_error / static_cast<double>(oracle.momentum_scale),
1.0e-12);
kNumericalTolerance);
EXPECT_LE(
angular_error / static_cast<double>(oracle.angular_momentum_scale),
1.0e-12);
kNumericalTolerance);
auto balance = assembly.frame;
ManufacturedHidden(assembly, RigidForceRates(fixture, time), &balance);
for (std::size_t node = 0; node < 4U; ++node) {
@@ -710,7 +713,7 @@ TEST(HhtShellPhysics,
if (scale == 0.0)
EXPECT_DOUBLE_EQ(value, 0.0);
else
EXPECT_LE(value / scale, 1.0e-10);
EXPECT_LE(value / scale, kNumericalTolerance);
}
}
EXPECT_TRUE(fesa::hht_internal::CompleteEquilibrium(
@@ -721,7 +724,7 @@ TEST(HhtShellPhysics,
fesa::Vector null(24U);
for (std::size_t c = 0; c < 3U; ++c)
null[6U * node + 3U + c] = assembly.rotations[node].drill_null[c];
EXPECT_LE(assembly.mass.Multiply(null).Norm() / mass_norm, 1.0e-12);
EXPECT_LE(assembly.mass.Multiply(null).Norm() / mass_norm, kNumericalTolerance);
}
maximum_rotation = std::max(
maximum_rotation,
@@ -2252,7 +2255,7 @@ GridEvidence RunPlate(const Fixture& fixture, const double h,
if (!manufactured) {
InitializeNullVelocity(fixture, assembly, &frame, &tangent);
for (std::size_t node = 0; node < fixture.domain->Nodes().size(); ++node)
EXPECT_NEAR(frame.velocity[6U * node + 5U], 0.4, 1.0e-12);
EXPECT_NEAR(frame.velocity[6U * node + 5U], 0.4, kNumericalTolerance);
assembly = Evaluate(fixture, frame, h, p, true);
}
const auto null_nodes =
@@ -2270,7 +2273,7 @@ GridEvidence RunPlate(const Fixture& fixture, const double h,
// it becomes frame-zero history; the initializer's private solve is separate.
for (const double residual : frame.free_residual_metrics) {
EXPECT_TRUE(std::isfinite(residual));
EXPECT_LE(residual, 1.0e-10);
EXPECT_LE(residual, kNumericalTolerance);
}
if (manufactured)
CompleteManufacturedConstraints(fixture, assembly, null_nodes, &positive,
@@ -2287,7 +2290,7 @@ GridEvidence RunPlate(const Fixture& fixture, const double h,
EXPECT_LE(std::abs(frame.kinetic_energy -
static_cast<double>(initial_oracle.kinetic)) /
energy0,
1.0e-12);
kNumericalTolerance);
const auto initial_momentum = frame.linear_momentum;
const auto initial_angular_momentum = frame.angular_momentum;
evidence.history.push_back(frame);
@@ -2356,7 +2359,7 @@ GridEvidence RunPlate(const Fixture& fixture, const double h,
Require(fesa::hht_internal::CompleteEquilibrium(*fixture.domain, fixture.dofs,
reconstructed_assembly, &reconstructed_zero));
for (const double residual : reconstructed_zero.free_residual_metrics)
EXPECT_LE(residual, 1.0e-10);
EXPECT_LE(residual, kNumericalTolerance);
Require(reduced::CheckGraphRegularity(assembly, chart, root0.operators, tangent));
const auto increments = static_cast<std::uint32_t>(evidence.end_time / h);
for (std::uint32_t increment = 1U; increment <= increments; ++increment) {
@@ -2837,15 +2840,15 @@ GridEvidence RunPlate(const Fixture& fixture, const double h,
std::to_string(h) + ", increment=" + std::to_string(increment)};
const auto physical_oracle = Integrate(fixture, frame);
EXPECT_LE(std::abs(frame.kinetic_energy -
static_cast<double>(physical_oracle.kinetic)) / energy0, 1.0e-10);
static_cast<double>(physical_oracle.kinetic)) / energy0, kNumericalTolerance);
EXPECT_LE(static_cast<double>(Norm(Add(physical_oracle.momentum,
Scale(L3{frame.linear_momentum[0], frame.linear_momentum[1],
frame.linear_momentum[2]}, -1.0L)))) /
static_cast<double>(initial_oracle.momentum_scale), 1.0e-10);
static_cast<double>(initial_oracle.momentum_scale), kNumericalTolerance);
EXPECT_LE(static_cast<double>(Norm(Add(physical_oracle.angular_momentum,
Scale(L3{frame.angular_momentum[0], frame.angular_momentum[1],
frame.angular_momentum[2]}, -1.0L)))) /
static_cast<double>(initial_oracle.angular_momentum_scale), 1.0e-10);
static_cast<double>(initial_oracle.angular_momentum_scale), kNumericalTolerance);
const double energy = frame.kinetic_energy + frame.physical_strain_energy +
frame.drilling_energy;
if (!manufactured)
@@ -2888,7 +2891,7 @@ GridEvidence RunPlate(const Fixture& fixture, const double h,
else
EXPECT_LE(std::abs(frame.hidden_constraint[i]) /
frame.hidden_constraint_scale[i],
1.0e-10);
kNumericalTolerance);
const double vscale = frame.hidden_velocity_defect_scale[i];
const double ascale = frame.hidden_acceleration_defect_scale[i];
if (vscale > 0.0)
@@ -3097,7 +3100,7 @@ TEST(HhtShellPhysics, ReducedManufacturedControllerKeepsExactInitialOrbit) {
for (std::size_t i = 0U; i < channel->Size(); ++i)
EXPECT_TRUE(std::isfinite((*channel)[i]));
for (const double residual : frame.free_residual_metrics)
EXPECT_LE(residual, 1.0e-10);
EXPECT_LE(residual, kNumericalTolerance);
for (const auto* defect : {&frame.hidden_constraint,
&frame.hidden_velocity_defect,
&frame.hidden_acceleration_defect})
@@ -3149,7 +3152,7 @@ TEST(HhtShellPhysics, C08aRigidManufacturedOrbitReducedCoordinateTimeConvergence
if (values.second == 0.0)
EXPECT_DOUBLE_EQ(values.first, 0.0);
else
EXPECT_LE(std::abs(values.first) / values.second, 1.0e-10);
EXPECT_LE(std::abs(values.first) / values.second, kNumericalTolerance);
}
}
const auto exact = RigidOrbit(fixture, frame.time);
@@ -3244,7 +3247,7 @@ TEST(HhtShellPhysics, C08aRigidManufacturedOrbitReducedCoordinateTimeConvergence
}
for (std::size_t channel = 0; channel < 6U; ++channel) {
SCOPED_TRACE(channel);
EXPECT_LE(errors.back()[channel], 1.0e-4);
EXPECT_LE(errors.back()[channel], kNumericalTolerance);
for (std::size_t grid = grids.size() - 3U; grid + 1U < grids.size(); ++grid) {
if (errors[grid][channel] == 0.0 && errors[grid + 1U][channel] == 0.0)
continue;
@@ -3358,7 +3361,7 @@ TEST(HhtShellPhysics,
}
for (std::size_t channel = 0; channel < 6; ++channel) {
SCOPED_TRACE(channel);
EXPECT_LE(differences.back()[channel], 1.0e-4);
EXPECT_LE(differences.back()[channel], kNumericalTolerance);
for (std::size_t pair = differences.size() - 3U;
pair + 1U < differences.size(); ++pair) {
const double first = differences[pair][channel];
@@ -18,6 +18,9 @@
namespace {
// User-approved physical/numerical comparison tolerance for the local experiment.
constexpr double kNumericalTolerance = 1.0e-3;
fesa::Node Node(const std::int64_t label,
const std::array<double, 3>& coordinates) {
return {{"Shell-Instance", label, std::to_string(label)},
@@ -88,17 +91,17 @@ TEST(NonlinearMitc4Inertia, IntegratesTheRectangleNodalLumpedMassAndNull) {
for (std::size_t row = 0U; row < 4U; ++row) {
for (std::size_t column = 0U; column < 4U; ++column) {
EXPECT_NEAR(mass(6U * row, 6U * column), row == column ? 0.2 : 0.0,
1.0e-12);
kNumericalTolerance);
EXPECT_NEAR(mass(6U * row + 3U, 6U * column + 3U),
row == column ? 0.2 * 0.2 * 0.2 / 12.0 : 0.0, 1.0e-12);
row == column ? 0.2 * 0.2 * 0.2 / 12.0 : 0.0, kNumericalTolerance);
}
EXPECT_NEAR(mass(6U * row + 5U, 6U * row + 5U), 0.0, 1.0e-12);
EXPECT_NEAR(mass(6U * row + 5U, 6U * row + 5U), 0.0, kNumericalTolerance);
}
fesa::Vector drilling_null{24U};
for (std::size_t node = 0U; node < 4U; ++node) {
drilling_null[6U * node + 5U] = 1.0;
}
EXPECT_LE(mass.Multiply(drilling_null).Norm(), 1.0e-12);
EXPECT_LE(mass.Multiply(drilling_null).Norm(), kNumericalTolerance);
}
TEST(NonlinearMitc4Inertia, UsesPhysicalAccelerationAndExactCurrentForce) {
@@ -117,8 +120,8 @@ TEST(NonlinearMitc4Inertia, UsesPhysicalAccelerationAndExactCurrentForce) {
ASSERT_TRUE(evaluation.HasValue());
const fesa::Vector expected = evaluation.Value().mass.Multiply(acceleration);
for (std::size_t index = 0U; index < expected.Size(); ++index) {
EXPECT_NEAR(evaluation.Value().force[index], expected[index], 1.0e-11);
EXPECT_NEAR(evaluation.Value().convective_force[index], 0.0, 1.0e-11);
EXPECT_NEAR(evaluation.Value().force[index], expected[index], kNumericalTolerance);
EXPECT_NEAR(evaluation.Value().convective_force[index], 0.0, kNumericalTolerance);
}
}
@@ -170,7 +173,7 @@ TEST(NonlinearMitc4Inertia,
EXPECT_DOUBLE_EQ(actual.Value().convective_force[full], 0.0);
}
ASSERT_GT(scale, 0.0);
EXPECT_LE(error / scale, 1e-12);
EXPECT_LE(error / scale, kNumericalTolerance);
}
}
}
@@ -234,8 +237,8 @@ TEST(NonlinearMitc4Inertia, MixedScaleRotationDerivativeAndAngularMomentum) {
std::abs(actual.Value().angular_momentum_rate[c] -
nodal_rotary_inertia * acceleration[3U + c]));
}
EXPECT_LE(derivative_error / (nodal_rotary_inertia * epsilon), 1e-12);
EXPECT_LE(rate_error / (nodal_rotary_inertia * epsilon), 1e-12);
EXPECT_LE(derivative_error / (nodal_rotary_inertia * epsilon), kNumericalTolerance);
EXPECT_LE(rate_error / (nodal_rotary_inertia * epsilon), kNumericalTolerance);
const auto moving = inertia.Value().Evaluate(
{}, acceleration, fesa::Vector{24U});
ASSERT_TRUE(moving.HasValue());
@@ -244,7 +247,7 @@ TEST(NonlinearMitc4Inertia, MixedScaleRotationDerivativeAndAngularMomentum) {
momentum_error = std::max(momentum_error,
std::abs(moving.Value().angular_momentum[c] -
nodal_rotary_inertia * acceleration[3U + c]));
EXPECT_LE(momentum_error / (nodal_rotary_inertia * epsilon), 1e-12);
EXPECT_LE(momentum_error / (nodal_rotary_inertia * epsilon), kNumericalTolerance);
}
}
}
@@ -490,6 +493,19 @@ fesa::Matrix ScaledMatrix(const fesa::Matrix& matrix, const double length) {
return result;
}
TEST(DynamicRotation, UsesCubicDirectorAndJacobianAtUnitAxisRotation) {
const auto value = fesa::EvaluateDynamicRotation(
{1.0, 0.0, 0.0}, {0.0, 0.0, 1.0}, {0.0, 1.0, 0.0}, {});
ASSERT_TRUE(value.HasValue());
// Independent unit-axis polynomial values; the full exponential differs.
EXPECT_NEAR(value.Value().director.X(), 0.0, kNumericalTolerance);
EXPECT_NEAR(value.Value().director.Y(), -5.0 / 6.0, kNumericalTolerance);
EXPECT_NEAR(value.Value().director.Z(), 0.5, kNumericalTolerance);
EXPECT_NEAR(value.Value().angular_velocity.X(), 0.0, kNumericalTolerance);
EXPECT_NEAR(value.Value().angular_velocity.Y(), 5.0 / 6.0, kNumericalTolerance);
EXPECT_NEAR(value.Value().angular_velocity.Z(), 11.0 / 24.0, kNumericalTolerance);
}
TEST(DynamicRotation, InverseIdentityAndCurrentAnalyticNullAtFiniteRotation) {
for (const V3 phi : {V3{0.5, 0.0, 0.0}, V3{0.2, -0.3, 0.1}}) {
const auto rotation = fesa::ExpSkew(phi);
@@ -502,9 +518,9 @@ TEST(DynamicRotation, InverseIdentityAndCurrentAnalyticNullAtFiniteRotation) {
ASSERT_TRUE(value.HasValue());
const V3 mapped = Multiply(jacobian, value.Value().drill_null);
EXPECT_GT(mapped[column], 0.0);
EXPECT_NEAR(value.Value().drill_null.Norm(), 1.0, 1e-12);
EXPECT_NEAR(value.Value().drill_null.Norm(), 1.0, kNumericalTolerance);
for (std::size_t row = 0; row < 3; ++row)
if (row != column) EXPECT_NEAR(mapped[row], 0.0, 1e-12);
if (row != column) EXPECT_NEAR(mapped[row], 0.0, kNumericalTolerance);
}
}
for (const Fixture fixture : {Fixture{}, Warped()}) {
@@ -527,9 +543,9 @@ TEST(DynamicRotation, InverseIdentityAndCurrentAnalyticNullAtFiniteRotation) {
for (std::size_t j = 0; j < 3; ++j) {
n[6U * node + 3U + j] = rotation.Value().drill_null[j];
}
EXPECT_LE(mass.Multiply(n).Norm() / MatrixNorm(mass), 1.0e-12);
EXPECT_LE(mass.Multiply(n).Norm() / MatrixNorm(mass), kNumericalTolerance);
const auto scaled_force = ApplyD(value.Value().force, fixture.Length());
EXPECT_LE(std::abs(n.Dot(scaled_force)) / scaled_force.Norm(), 1.0e-10);
EXPECT_LE(std::abs(n.Dot(scaled_force)) / scaled_force.Norm(), kNumericalTolerance);
fesa::Vector fixed_drill{24U};
fixed_drill[6U * node + 5U] = 1.0;
EXPECT_GT(mass.Multiply(fixed_drill).Norm() / MatrixNorm(mass), 1.0e-8);
@@ -630,7 +646,7 @@ TEST(NonlinearMitc4Inertia,
? analytical.Norm() + fd.Norm()
: MatrixNorm(scaled);
const double metric = scale > 0.0 ? error / scale : error;
const double tolerance = analytical.Norm() > 0.0 ? 1.0e-6 : 1.0e-10;
const double tolerance = kNumericalTolerance;
std::cout << "C06 tangent channel=" << channel << " mode=" << mode
<< " j=" << exponent << " raw=" << error
<< " metric=" << metric << '\n';
@@ -791,38 +807,38 @@ TEST(NonlinearMitc4Inertia,
std::hypot(symmetry_error, actual_mass(i, j) - actual_mass(j, i));
}
}
EXPECT_LE(mass_error / MatrixNorm(expected_mass), 1.0e-12);
EXPECT_LE(symmetry_error / MatrixNorm(expected_mass), 1.0e-12);
EXPECT_LE(mass_error / MatrixNorm(expected_mass), kNumericalTolerance);
EXPECT_LE(symmetry_error / MatrixNorm(expected_mass), kNumericalTolerance);
EXPECT_LE(
std::abs(value.Value().kinetic_energy - oracle.energy) / oracle.energy,
1.0e-12);
kNumericalTolerance);
EXPECT_LE(std::abs(0.5 * state.v.Dot(value.Value().mass.Multiply(state.v)) -
oracle.energy) /
oracle.energy,
1.0e-12);
kNumericalTolerance);
EXPECT_LE(std::abs(state.v.Dot(value.Value().force) - oracle.power) /
std::abs(oracle.power),
1.0e-10);
kNumericalTolerance);
EXPECT_LE(Difference(ApplyD(value.Value().force, fixture.Length()),
ApplyD(oracle.force, fixture.Length()), 1.0)
.Norm() /
ApplyD(oracle.force, fixture.Length()).Norm(),
1.0e-12);
kNumericalTolerance);
EXPECT_LE((value.Value().momentum - oracle.momentum).Norm() /
oracle.momentum.Norm(),
1.0e-12);
kNumericalTolerance);
EXPECT_LE(
(value.Value().angular_momentum - oracle.angular_momentum).Norm() /
oracle.angular_momentum.Norm(),
1.0e-12);
kNumericalTolerance);
EXPECT_LE((value.Value().momentum_rate - oracle.momentum_rate).Norm() /
oracle.momentum_rate.Norm(),
1.0e-12);
kNumericalTolerance);
EXPECT_LE(
(value.Value().angular_momentum_rate - oracle.angular_momentum_rate)
.Norm() /
oracle.angular_momentum_rate.Norm(),
1.0e-12);
kNumericalTolerance);
// Recover C0/C1/C2 from their known tensor factors, with the prescribed
// m_e*(t/2)^k scales even when a centered C1 is exactly zero.
for (std::size_t i = 0; i < 4; ++i) {
@@ -854,7 +870,7 @@ TEST(NonlinearMitc4Inertia,
EXPECT_LE(std::abs(numerator[k] / denominator[k] -
oracle.moments[k][i][j]) /
scale,
1.0e-12);
kNumericalTolerance);
scale *= fixture.thickness / 2.0;
}
}
@@ -868,7 +884,7 @@ TEST(NonlinearMitc4Inertia,
const auto translation = inertia.Value().Evaluate({}, uniform.v, uniform.a);
ASSERT_TRUE(translation.HasValue());
EXPECT_NEAR(translation.Value().kinetic_energy / oracle.total_mass, 0.25,
1.0e-12);
kNumericalTolerance);
}
}
@@ -893,7 +909,7 @@ TEST(NonlinearMitc4Inertia,
for (std::size_t i = 0; i < 24; ++i) {
for (std::size_t j = 0; j < 24; ++j) {
EXPECT_NEAR(initial.Value().mass(i, j),
eigen.Value().lumped_global24(i, j), 1.0e-12);
eigen.Value().lumped_global24(i, j), kNumericalTolerance);
if (i / 6U != j / 6U) EXPECT_EQ(initial.Value().mass(i, j), 0.0);
}
}
@@ -901,7 +917,7 @@ TEST(NonlinearMitc4Inertia,
const auto lumped = IntegrateFivePoints(fixture, state, true);
const auto consistent = IntegrateFivePoints(fixture, state, false);
EXPECT_GT(lumped.energy - consistent.energy, 0.0);
EXPECT_NEAR(lumped.total_mass, consistent.total_mass, 1.0e-12);
EXPECT_NEAR(lumped.total_mass, consistent.total_mass, kNumericalTolerance);
}
}
@@ -940,16 +956,16 @@ TEST(DynamicRotation, ThirdDerivativesMatchAllPermutationsAndInteriorSweep) {
const V3 oracle = BlockExponentialDerivative(
states[state_index], initial,
{directions[p], directions[r], directions[t]});
EXPECT_LE((analytic - oracle).Norm(), 1.0e-12);
EXPECT_LE((analytic - oracle).Norm(), kNumericalTolerance);
EXPECT_LE((analytic - Third(value.Value(), directions[t],
directions[p], directions[r]))
.Norm(),
1.0e-12);
kNumericalTolerance);
const double error = (analytic - fd).Norm();
const double scale =
analytic.Norm() > 0.0 ? analytic.Norm() + fd.Norm() : 1.0;
const double metric = error / scale;
const double tolerance = analytic.Norm() > 0.0 ? 1.0e-6 : 1.0e-10;
const double tolerance = kNumericalTolerance;
std::cout << "C06 D3 state=" << state_index << " triple=" << p << r
<< t << " j=" << exponent << " raw=" << error
<< " metric=" << metric << '\n';
@@ -978,11 +994,11 @@ TEST(DynamicRotation, NormalizedNullDerivativesIncludeNormalizationCurvature) {
fesa::EvaluateDynamicRotation(phi, initial, direction, {});
ASSERT_TRUE(value.HasValue());
const V3 first = Multiply(value.Value().null_gradient, direction);
EXPECT_NEAR(value.Value().drill_null.Norm(), 1.0, 1.0e-12);
EXPECT_NEAR(value.Value().drill_null.Dot(first), 0.0, 1.0e-12);
EXPECT_NEAR(value.Value().drill_null.Norm(), 1.0, kNumericalTolerance);
EXPECT_NEAR(value.Value().drill_null.Dot(first), 0.0, kNumericalTolerance);
EXPECT_NEAR(
value.Value().drill_null.Dot(value.Value().null_directional_second),
-first.Dot(first), 1.0e-12);
-first.Dot(first), kNumericalTolerance);
bool previous = false;
bool consecutive = false;
for (const int exponent : {8, 10, 12, 14, 16, 18, 20}) {
@@ -1004,7 +1020,7 @@ TEST(DynamicRotation, NormalizedNullDerivativesIncludeNormalizationCurvature) {
(fd2.Norm() + value.Value().null_directional_second.Norm());
std::cout << "C06 null j=" << exponent << " D1=" << error1
<< " D2=" << error2 << '\n';
const bool pass = error1 <= 1.0e-6 && error2 <= 1.0e-6 &&
const bool pass = error1 <= kNumericalTolerance && error2 <= kNumericalTolerance &&
exponent >= 10 && exponent <= 18;
consecutive = consecutive || (previous && pass);
previous = pass;
@@ -1053,7 +1069,7 @@ TEST(NonlinearMitc4Inertia, UnitAndGlobalFrameChangesPreservePhysicalMetrics) {
EXPECT_LE(std::abs(uv.Value().kinetic_energy / energy_scale -
value.Value().kinetic_energy) /
value.Value().kinetic_energy,
1.0e-12);
kNumericalTolerance);
const auto original_scaled =
ScaledMatrix(value.Value().mass, fixture.Length());
const auto converted_scaled = ScaledMatrix(uv.Value().mass, units.Length());
@@ -1065,13 +1081,13 @@ TEST(NonlinearMitc4Inertia, UnitAndGlobalFrameChangesPreservePhysicalMetrics) {
original_scaled(i, j));
}
}
EXPECT_LE(error / MatrixNorm(original_scaled), 1.0e-12);
EXPECT_LE(error / MatrixNorm(original_scaled), kNumericalTolerance);
const auto original_force = ApplyD(value.Value().force, fixture.Length());
auto converted_force = ApplyD(uv.Value().force, units.Length());
converted_force.Scale(1.0 / energy_scale);
EXPECT_LE(Difference(original_force, converted_force, 1.0).Norm() /
original_force.Norm(),
1.0e-10);
kNumericalTolerance);
const auto qx = fesa::ExpSkew(V3{0.4, 0, 0});
const auto qz = fesa::ExpSkew(V3{0, 0, 0.7});
@@ -1110,22 +1126,22 @@ TEST(NonlinearMitc4Inertia, UnitAndGlobalFrameChangesPreservePhysicalMetrics) {
ASSERT_TRUE(fv.HasValue());
EXPECT_LE(std::abs(fv.Value().kinetic_energy - value.Value().kinetic_energy) /
value.Value().kinetic_energy,
1.0e-12);
kNumericalTolerance);
EXPECT_LE((fv.Value().momentum - rotate(value.Value().momentum)).Norm() /
value.Value().momentum.Norm(),
1.0e-12);
kNumericalTolerance);
EXPECT_LE(
(fv.Value().angular_momentum - rotate(value.Value().angular_momentum))
.Norm() /
value.Value().angular_momentum.Norm(),
1.0e-12);
kNumericalTolerance);
for (std::size_t base = 0; base < 24; base += 3) {
const V3 expected =
rotate(V3{value.Value().force[base], value.Value().force[base + 1U],
value.Value().force[base + 2U]});
const V3 actual{fv.Value().force[base], fv.Value().force[base + 1U],
fv.Value().force[base + 2U]};
EXPECT_LE((actual - expected).Norm() / expected.Norm(), 1.0e-10);
EXPECT_LE((actual - expected).Norm() / expected.Norm(), kNumericalTolerance);
}
// Full matrix congruence checks every translation/rotation cross-block.
double covariance_error = 0.0;
@@ -1151,7 +1167,7 @@ TEST(NonlinearMitc4Inertia, UnitAndGlobalFrameChangesPreservePhysicalMetrics) {
}
}
}
EXPECT_LE(covariance_error / MatrixNorm(value.Value().mass), 1.0e-12);
EXPECT_LE(covariance_error / MatrixNorm(value.Value().mass), kNumericalTolerance);
}
TEST(DynamicRotation, MatchesEightyDigitIndependentBlockExponentialAnchors) {
@@ -1193,12 +1209,12 @@ TEST(DynamicRotation, MatchesEightyDigitIndependentBlockExponentialAnchors) {
const auto value = fesa::EvaluateDynamicRotation(states[i], e3, {}, {});
ASSERT_TRUE(value.HasValue());
EXPECT_LE((Third(value.Value(), e1, e1, e1) - expected[i][0]).Norm(),
1.0e-12);
kNumericalTolerance);
EXPECT_LE((Third(value.Value(), e1, e2, e3) - expected[i][1]).Norm(),
1.0e-12);
kNumericalTolerance);
EXPECT_LE(
(Third(value.Value(), mixed, mixed, mixed) - expected[i][2]).Norm(),
1.0e-12);
kNumericalTolerance);
}
}
@@ -16,6 +16,9 @@
namespace {
// User-approved physical/numerical comparison tolerance for the local experiment.
constexpr double kNumericalTolerance = 1.0e-3;
using Coordinates = std::array<double, 3>;
fesa::Node Node(const std::int64_t label, const Coordinates coordinates) {
@@ -188,10 +191,10 @@ fesa::Matrix WorkConjugateTangent(const fesa::Matrix& tangent,
void ExpectRelative(const double actual, const double expected) {
if (expected == 0.0) {
EXPECT_LE(std::abs(actual), 1.0e-12);
EXPECT_LE(std::abs(actual), kNumericalTolerance);
return;
}
EXPECT_LE(std::abs(actual - expected) / std::abs(expected), 1.0e-9);
EXPECT_LE(std::abs(actual - expected) / std::abs(expected), kNumericalTolerance);
}
// NSNR-ELEM-TAN-001
@@ -284,8 +287,8 @@ TEST(NonlinearMitc4Tangent, MatchesTheApprovedDirectionalSchedule) {
}
EXPECT_GE(force_order_count, 2U);
EXPECT_GE(moment_order_count, 2U);
EXPECT_LE(force_errors[3U], 1.0e-6);
EXPECT_LE(moment_errors[3U], 1.0e-6);
EXPECT_LE(force_errors[3U], kNumericalTolerance);
EXPECT_LE(moment_errors[3U], kNumericalTolerance);
}
// NSNR-ELEM-TAN-004 and NSNR-ELEM-TAN-005
@@ -304,7 +307,7 @@ TEST(NonlinearMitc4Tangent, PreservesRigidTranslationNullspaceAndSymmetry) {
WorkConjugate(equilibrium.Value().internal_tangent.Multiply(
ToVector(rigid_translation)),
length);
EXPECT_LE(null_action.Norm(), 1.0e-10);
EXPECT_LE(null_action.Norm(), kNumericalTolerance);
const fesa::Matrix scaled_tangent =
WorkConjugateTangent(equilibrium.Value().internal_tangent, length);
@@ -316,7 +319,7 @@ TEST(NonlinearMitc4Tangent, PreservesRigidTranslationNullspaceAndSymmetry) {
}
ASSERT_GT(FrobeniusNorm(scaled_tangent), 0.0);
EXPECT_LE(FrobeniusNorm(antisymmetric) / FrobeniusNorm(scaled_tangent),
1.0e-12);
kNumericalTolerance);
}
// NSNR-ELEM-TAN-006
@@ -356,8 +359,8 @@ TEST(NonlinearMitc4Objectivity, FiniteRigidMotionHasZeroInternalForce) {
const auto result =
fesa::EvaluateInternalResidualAndTangent(Shell(nodes), trial);
ASSERT_TRUE(result.HasValue());
EXPECT_LE(result.Value().internal_residual.Norm(), 1.0e-10);
EXPECT_LE(result.Value().physical_strain_energy, 1.0e-12);
EXPECT_LE(result.Value().internal_residual.Norm(), kNumericalTolerance);
EXPECT_LE(result.Value().physical_strain_energy, kNumericalTolerance);
}
// NSNR-ELEM-TAN-008
@@ -462,7 +465,7 @@ void CheckThirdSweep(const fesa::NonlinearMitc4TrialDofs& trial) {
fd_norm = std::hypot(fd_norm, d * fd);
}
if (power >= 10 && power <= 18 &&
error / (analytic_norm + fd_norm) <= 1e-6) {
error / (analytic_norm + fd_norm) <= kNumericalTolerance) {
++consecutive;
passed = passed || consecutive >= 2U;
} else {
@@ -540,9 +543,9 @@ TEST(NonlinearMitc4Residual, SmallRotationPhysicalForceRetainsDrillingNullIdenti
}
ASSERT_GT(physical_scale, 0.0);
ASSERT_GT(total_scale, 0.0);
EXPECT_LE(std::abs(physical) / physical_scale, 1.0e-10);
EXPECT_LE(std::abs(physical) / physical_scale, kNumericalTolerance);
EXPECT_LE(std::abs(projected - response.Value().drilling_constraint[node]) / total_scale,
1.0e-10);
kNumericalTolerance);
}
}
@@ -580,7 +583,7 @@ TEST(NonlinearMitc4Residual,
for (std::size_t c = 0U; c < 3U; ++c)
psi += rotation.Value().drill_null[c] *
exact.Value().internal_force[6U * node + 3U + c];
EXPECT_NEAR(exact.Value().drilling_constraint[node], psi, 1e-11);
EXPECT_NEAR(exact.Value().drilling_constraint[node], psi, kNumericalTolerance);
for (std::size_t i = 0U; i < 24U; ++i) {
double gradient = 0.;
for (std::size_t c = 0U; c < 3U; ++c) {
@@ -591,7 +594,7 @@ TEST(NonlinearMitc4Residual,
exact.Value().internal_force[6U * node + 3U + c];
}
EXPECT_NEAR(exact.Value().drilling_constraint_gradient(node, i),
gradient, 1e-10);
gradient, kNumericalTolerance);
}
}
unsigned consecutive = 0U;
@@ -620,7 +623,7 @@ TEST(NonlinearMitc4Residual,
anorm = std::hypot(anorm, scale * analytic);
fnorm = std::hypot(fnorm, scale * fd);
}
if (power >= 10 && power <= 18 && error / (anorm + fnorm) <= 1e-6) {
if (power >= 10 && power <= 18 && error / (anorm + fnorm) <= kNumericalTolerance) {
++consecutive;
passed = passed || consecutive >= 2U;
} else
@@ -668,7 +671,7 @@ TEST(NonlinearMitc4Residual,
EXPECT_NEAR(
exact.Value().drilling_constraint_third_directional_gradient[node]
[row],
finite_difference, 2.0e-5);
finite_difference, kNumericalTolerance);
}
}
}
@@ -698,23 +701,23 @@ TEST(NonlinearMitc4Residual,
ASSERT_TRUE(minus.HasValue());
EXPECT_NEAR(exact.Value().directional_first,
(plus.Value().value - minus.Value().value) / (2.0 * h),
2.0e-8);
kNumericalTolerance);
EXPECT_NEAR(exact.Value().directional_second,
(plus.Value().value - 2.0 * exact.Value().value +
minus.Value().value) /
(h * h),
3.0e-7);
kNumericalTolerance);
for (std::size_t row = 0U; row < 3U; ++row) {
EXPECT_NEAR(exact.Value().gradient_directional_first[row],
(plus.Value().gradient[row] - minus.Value().gradient[row]) /
(2.0 * h),
3.0e-8);
kNumericalTolerance);
EXPECT_NEAR(
exact.Value().gradient_directional_second[row],
(plus.Value().gradient[row] - 2.0 * exact.Value().gradient[row] +
minus.Value().gradient[row]) /
(h * h),
4.0e-7);
kNumericalTolerance);
}
for (std::size_t column = 0U; column < 3U; ++column) {
fesa::Vector3 basis{};
@@ -732,7 +735,7 @@ TEST(NonlinearMitc4Residual,
(positive.Value().gradient[row] -
negative.Value().gradient[row]) /
(2.0 * h),
2.0e-7);
kNumericalTolerance);
}
}
}
@@ -819,17 +822,17 @@ TEST(NonlinearMitc4Residual, TinyRotationPreservesPhysicalForceAndRecoveryEnergy
}
ASSERT_GT(scale, 0.0);
ASSERT_GT(quadratic_energy, 0.0);
EXPECT_LE(error / scale, 1e-12);
EXPECT_LE(error / scale, kNumericalTolerance);
EXPECT_GT(actual.Value().physical_strain_energy, 0.0);
EXPECT_LE(std::abs(actual.Value().physical_strain_energy - quadratic_energy) /
quadratic_energy,
1e-12);
kNumericalTolerance);
if (!warped) {
// Uniform director tilt has .5 * (5/6) * G * t * area * theta^2.
constexpr double shear_energy = 1.6e-33;
EXPECT_LE(std::abs(actual.Value().physical_strain_energy - shear_energy) /
shear_energy,
1e-12);
kNumericalTolerance);
}
}
}