Preserve empty MITC4 hidden-constraint boundary

This commit is contained in:
KOKO\Mimi
2026-10-20 09:27:58 +09:00
parent c6188b3ad7
commit df1bf94369
3 changed files with 90 additions and 12 deletions
+10 -10
View File
@@ -64,16 +64,6 @@ bool Within(const double value, const double scale, const double tolerance) {
Status Validate(const Domain& domain, const DofManager& dofs, Status Validate(const Domain& domain, const DofManager& dofs,
const DynamicAssembly& assembly, const double length) { const DynamicAssembly& assembly, const double length) {
const std::size_t count = dofs.FullDofCount(); const std::size_t count = dofs.FullDofCount();
if (assembly.frame.mitc4_drilling &&
assembly.frame.mitc4_drilling->mode == Mitc4DrillingMode::kDirectSpring) {
if (assembly.hidden_constraint.Size() != assembly.rotations.size())
return Failure(domain, "dynamic-invalid-algebra-state",
"Canonical hidden values must cover every node.");
for (std::size_t i = 0U; i < assembly.hidden_constraint.Size(); ++i)
if (!std::isfinite(assembly.hidden_constraint[i]))
return Failure(domain, "dynamic-invalid-algebra-state",
"Canonical hidden values must remain finite.");
}
if (!std::isfinite(length) || !(length > 0.0) || count % 6U != 0U || if (!std::isfinite(length) || !(length > 0.0) || count % 6U != 0U ||
assembly.rotations.size() != count / 6U) { assembly.rotations.size() != count / 6U) {
return Failure(domain, "dynamic-invalid-algebra-state", return Failure(domain, "dynamic-invalid-algebra-state",
@@ -559,6 +549,16 @@ Status HiddenEvidence(const Domain& domain, const DynamicAssembly& assembly,
const bool require_acceleration) { const bool require_acceleration) {
const bool direct = frame->mitc4_drilling && const bool direct = frame->mitc4_drilling &&
frame->mitc4_drilling->mode == Mitc4DrillingMode::kDirectSpring; frame->mitc4_drilling->mode == Mitc4DrillingMode::kDirectSpring;
if (direct) {
for (const std::size_t node : nodes) {
if (node >= assembly.hidden_constraint.Size())
return Failure(domain, "dynamic-invalid-algebra-state",
"Canonical hidden values must cover every admissible null node.");
if (!std::isfinite(assembly.hidden_constraint[node]))
return Failure(domain, "dynamic-invalid-algebra-state",
"Canonical hidden values must remain finite.");
}
}
Vector psi{nodes.size()}; Vector psi{nodes.size()};
Vector psi_scale{nodes.size()}; Vector psi_scale{nodes.size()};
Vector velocity_defect{nodes.size()}; Vector velocity_defect{nodes.size()};
@@ -28,7 +28,8 @@ struct Fixture {
Fixture MakeFixture(const std::size_t free_nodes = 1U, Fixture MakeFixture(const std::size_t free_nodes = 1U,
const bool partial_rotation = false, const bool partial_rotation = false,
const bool translations_free = true, const bool translations_free = true,
const double youngs_modulus = 1000.0) { const double youngs_modulus = 1000.0,
const bool all_rotations_constrained = false) {
fesa::ModelDefinition definition{}; fesa::ModelDefinition definition{};
definition.source_path = "hht-algebra.inp"; definition.source_path = "hht-algebra.inp";
definition.nodes = { definition.nodes = {
@@ -66,7 +67,10 @@ Fixture MakeFixture(const std::size_t free_nodes = 1U,
step.boundaries.push_back( step.boundaries.push_back(
{name, 1, 6, 0.0, {definition.source_path, 10U}}); {name, 1, 6, 0.0, {definition.source_path, 10U}});
} else { } else {
if (partial_rotation) if (all_rotations_constrained)
step.boundaries.push_back(
{name, 4, 6, 0.0, {definition.source_path, 10U}});
else if (partial_rotation)
step.boundaries.push_back( step.boundaries.push_back(
{name, 4, 5, 0.0, {definition.source_path, 10U}}); {name, 4, 5, 0.0, {definition.source_path, 10U}});
if (!translations_free) if (!translations_free)
@@ -705,3 +709,57 @@ TEST(HhtEndpointFields, MeasuresActualFullChartAcrossNonlinearRootAndPartialBoun
} }
} // namespace } // namespace
TEST(HhtDynamicAlgebra, NonDirectionalAssemblyRetainsValidEmptyNullBoundary) {
for (const std::size_t free_nodes : {4U, 0U}) {
SCOPED_TRACE(free_nodes);
const auto fixture = MakeFixture(free_nodes, false, true, 1000.0, true);
const auto shells = fesa::hht_internal::BuildRuntimeShells(*fixture.domain,
fixture.dofs);
ASSERT_TRUE(shells.HasValue());
auto trial = EmptyTrial(fixture);
trial.external_force[0U] = 0.4;
fesa::Vector load_scale(24U);
load_scale[0U] = 0.4;
fesa::SerialParallelFor serial;
const auto parameters = fesa::MakeHhtParameters(-0.05);
ASSERT_TRUE(parameters.HasValue());
const auto assembly = fesa::hht_internal::EvaluateAssembly(
*fixture.domain, fixture.dofs, shells.Value(), trial, load_scale,
0.1, parameters.Value(), serial, false);
ASSERT_TRUE(assembly.HasValue());
ASSERT_TRUE(assembly.Value().frame.mitc4_drilling);
EXPECT_EQ(assembly.Value().frame.mitc4_drilling->mode,
fesa::Mitc4DrillingMode::kDirectSpring);
EXPECT_EQ(assembly.Value().hidden_constraint.Size(), 0U);
EXPECT_FALSE(assembly.Value().hidden_gradient.has_value());
const auto nodes = fesa::hht_internal::NullNodes(fixture.dofs,
assembly.Value().rotations);
ASSERT_TRUE(nodes.empty());
fesa::MklPardisoSolver positive;
fesa::MklPardisoTangentSolver tangent;
const auto acceleration = fesa::hht_internal::InitializeAcceleration(
*fixture.domain, fixture.dofs, assembly.Value(), 2.0, positive, tangent);
EXPECT_TRUE(acceleration.HasValue())
<< (acceleration.GetStatus().Diagnostics().empty() ? "" :
acceleration.GetStatus().Diagnostics()[0U].message);
auto frame = assembly.Value().frame;
if (acceleration.HasValue()) {
frame.acceleration = acceleration.Value();
// rho*t*area/4 = .2 per node; the .4 force gives acceleration 2.
EXPECT_NEAR(acceleration.Value()[0U], free_nodes == 0U ? 0.0 : 2.0, 1e-12);
for (const auto full : fixture.dofs.ConstrainedDofs())
EXPECT_DOUBLE_EQ(acceleration.Value()[full], 0.0);
}
const auto complete = fesa::hht_internal::CompleteConstraintEvidence(
*fixture.domain, fixture.dofs, assembly.Value(), 2.0, nodes,
positive, tangent, &frame);
EXPECT_TRUE(complete.IsOk())
<< (complete.Diagnostics().empty() ? "" : complete.Diagnostics()[0U].message);
if (complete.IsOk()) {
EXPECT_EQ(frame.hidden_constraint.Size(), 0U);
EXPECT_EQ(frame.hidden_velocity_defect.Size(), 0U);
EXPECT_EQ(frame.hidden_acceleration_defect.Size(), 0U);
}
}
}
@@ -1938,4 +1938,24 @@ TEST(Mitc4DrillingEvidence, DirectHiddenCertificateUsesCanonicalPrimitive) {
EXPECT_DOUBLE_EQ(frame.hidden_velocity_defect[row], 0.0); EXPECT_DOUBLE_EQ(frame.hidden_velocity_defect[row], 0.0);
EXPECT_DOUBLE_EQ(frame.hidden_acceleration_defect[row], 0.0); EXPECT_DOUBLE_EQ(frame.hidden_acceleration_defect[row], 0.0);
} }
const auto accepted = frame;
for (const bool missing : {true, false}) {
auto invalid = assembly.Value();
if (missing)
invalid.hidden_constraint = fesa::Vector(0U);
else
invalid.hidden_constraint[0U] = std::numeric_limits<double>::infinity();
const auto rejected = fesa::hht_internal::CompleteConstraintEvidence(
*fixture.domain, fixture.dofs, invalid, length.Value(), null_nodes,
positive, tangent, &frame);
EXPECT_FALSE(rejected.IsOk());
for (const auto& pair : {
std::make_pair(&frame.hidden_constraint, &accepted.hidden_constraint),
std::make_pair(&frame.hidden_velocity_defect, &accepted.hidden_velocity_defect),
std::make_pair(&frame.hidden_acceleration_defect, &accepted.hidden_acceleration_defect)}) {
ASSERT_EQ(pair.first->Size(), pair.second->Size());
for (std::size_t row = 0U; row < pair.first->Size(); ++row)
ExpectSameBits((*pair.first)[row], (*pair.second)[row]);
}
}
} }