feat: adopt lumped mass and reduced-coordinate shell HHT

Integrate Hex8 mass verification and fixed-null-basis shell dynamics with numerical and physics handoff evidence. Preserve interrupted reference and full-validation status; full integration validation is deferred until after performance optimization.
This commit is contained in:
KOKO\Mimi
2026-10-13 16:57:19 +09:00
parent ede137c6b4
commit eaa5cbeb21
110 changed files with 15274 additions and 971 deletions
+255 -111
View File
@@ -19,6 +19,49 @@ const std::array<double, 3> kGauss{-std::sqrt(3.0 / 5.0), 0.0,
std::sqrt(3.0 / 5.0)};
constexpr std::array<double, 3> kWeights{5.0 / 9.0, 8.0 / 9.0, 5.0 / 9.0};
class InertiaSum {
public:
void Add(const double term) {
const double next = sum_ + term;
correction_ += std::abs(sum_) >= std::abs(term)
? (sum_ - next) + term
: (term - next) + sum_;
sum_ = next;
}
double Value() const { return sum_ + correction_; }
private:
double sum_ = 0.0;
double correction_ = 0.0;
};
Vector3 WeightedSum(const std::array<double, kNodes>& weights,
const std::array<Vector3, kNodes>& values) {
Vector3 result{};
for (std::size_t node = 0U; node < kNodes; ++node)
result = result + weights[node] * values[node];
return result;
}
// A derivative partition has no absolute-position contribution. Anchoring
// before reduction preserves that identity at large global offsets and keeps
// the reference/current covariants in the same canonical form.
Vector3 DerivativeSum(const std::array<double, kNodes>& derivatives,
const std::array<Vector3, kNodes>& values) {
std::array<Vector3, kNodes> relative{};
for (std::size_t node = 0U; node < kNodes; ++node)
relative[node] = values[node] - values[0U];
return WeightedSum(derivatives, relative);
}
Vector3 InterpolatedSum(const std::array<double, kNodes>& weights,
const std::array<Vector3, kNodes>& values) {
std::array<Vector3, kNodes> relative{};
for (std::size_t node = 0U; node < kNodes; ++node)
relative[node] = values[node] - values[0U];
return values[0U] + WeightedSum(weights, relative);
}
template <class T>
Result<T> Failure(const std::string& code, const SourceLocation& location,
const std::string& identity, std::string message) {
@@ -723,131 +766,232 @@ Result<NonlinearMitc4InertiaEvaluation> NonlinearMitc4Inertia::Evaluate(
{},
{},
{}};
using LocalDerivative = std::array<std::array<std::array<double, 6U>, 6U>, 3U>;
std::array<LocalDerivative, kNodes> mass_derivative{};
// Integrate acceleration/velocity coefficients before their contraction.
// This preserves rotary contributions until odd-in-z translation cancels.
std::array<Vector3, kDofs> momentum_coefficients{};
std::array<Vector3, kDofs> angular_coefficients{};
Vector3 convective_momentum{};
Vector3 convective_angular{};
for (std::size_t xi = 0U; xi < kGauss.size(); ++xi) {
for (std::size_t eta = 0U; eta < kGauss.size(); ++eta) {
const Mitc4ShapeFunctions shape =
Mitc4Shell::ShapeFunctions(kGauss[xi], kGauss[eta]);
std::array<Vector3, kNodes> displacements{};
std::array<Vector3, kNodes> current_coordinates{};
std::array<Vector3, kNodes> current_directors{};
for (std::size_t node = 0U; node < kNodes; ++node) {
displacements[node] = trial.displacement[node];
current_coordinates[node] = coordinates_[node] + displacements[node];
current_directors[node] = rotations[node].director;
}
const Vector3 reference_xi_anchor =
DerivativeSum(shape.xi_derivatives, coordinates_);
const Vector3 reference_eta_anchor =
DerivativeSum(shape.eta_derivatives, coordinates_);
const Vector3 reference_xi_director =
DerivativeSum(shape.xi_derivatives, initial_directors_);
const Vector3 reference_eta_director =
DerivativeSum(shape.eta_derivatives, initial_directors_);
const Vector3 reference_zeta =
0.5 * thickness_ * InterpolatedSum(shape.values, initial_directors_);
const Vector3 current_xi_anchor =
reference_xi_anchor + DerivativeSum(shape.xi_derivatives, displacements);
const Vector3 current_eta_anchor =
reference_eta_anchor + DerivativeSum(shape.eta_derivatives, displacements);
const Vector3 current_xi_director =
DerivativeSum(shape.xi_derivatives, current_directors);
const Vector3 current_eta_director =
DerivativeSum(shape.eta_derivatives, current_directors);
const Vector3 current_zeta =
0.5 * thickness_ * InterpolatedSum(shape.values, current_directors);
for (std::size_t zeta = 0U; zeta < kGauss.size(); ++zeta) {
const double z = 0.5 * thickness_ * kGauss[zeta];
const double weight = kWeights[xi] * kWeights[eta] * kWeights[zeta];
Vector3 ref_xi{};
Vector3 ref_eta{};
Vector3 ref_zeta{};
Vector3 current_xi{};
Vector3 current_eta{};
Vector3 current_zeta{};
Vector3 position{};
Vector3 velocity_point{};
Vector3 acceleration_point{};
Vector3 convective_point{};
std::array<Vector3, kDofs> h{};
std::array<Vector3, kDofs> velocity_derivative{};
std::array<Vector3, kDofs> position_derivative{};
for (std::size_t node = 0U; node < kNodes; ++node) {
const std::size_t base = 6U * node;
const auto& rotation = rotations[node];
const Vector3 reference =
coordinates_[node] + z * initial_directors_[node];
const Vector3 current = coordinates_[node] +
trial.displacement[node] +
z * rotation.director;
ref_xi = ref_xi + shape.xi_derivatives[node] * reference;
ref_eta = ref_eta + shape.eta_derivatives[node] * reference;
ref_zeta = ref_zeta + 0.5 * thickness_ * shape.values[node] *
initial_directors_[node];
current_xi = current_xi + shape.xi_derivatives[node] * current;
current_eta = current_eta + shape.eta_derivatives[node] * current;
current_zeta = current_zeta + 0.5 * thickness_ * shape.values[node] *
rotation.director;
position = position + shape.values[node] * current;
for (std::size_t component = 0U; component < 3U; ++component) {
std::array<double, 3> translation{};
translation[component] = shape.values[node];
h[base + component] = Vector3{translation};
h[base + 3U + component] =
z * shape.values[node] * rotation.director_first[component];
for (std::size_t other = 0U; other < 3U; ++other) {
const Vector3 second = z * shape.values[node] *
rotation.director_second[component][other];
convective_point =
convective_point + second * velocity[base + 3U + component] *
velocity[base + 3U + other];
velocity_derivative[base + 3U + component] =
velocity_derivative[base + 3U + component] +
2.0 * second * velocity[base + 3U + other];
position_derivative[base + 3U + component] =
position_derivative[base + 3U + component] +
second * acceleration[base + 3U + other];
for (std::size_t third = 0U; third < 3U; ++third) {
position_derivative[base + 3U + component] =
position_derivative[base + 3U + component] +
z * shape.values[node] *
rotation.director_third[component][other][third] *
velocity[base + 3U + other] *
velocity[base + 3U + third];
// Use the nodal kinetic measure rho * N_i * dV0.
for (std::size_t mass_node = 0U; mass_node < kNodes; ++mass_node) {
std::array<double, kNodes> kinetic_shape{};
kinetic_shape[mass_node] = 1.0;
const double z = 0.5 * thickness_ * kGauss[zeta];
const double weight = kWeights[xi] * kWeights[eta] * kWeights[zeta];
const Vector3 ref_xi = reference_xi_anchor + z * reference_xi_director;
const Vector3 ref_eta = reference_eta_anchor + z * reference_eta_director;
const Vector3 current_xi = current_xi_anchor + z * current_xi_director;
const Vector3 current_eta = current_eta_anchor + z * current_eta_director;
Vector3 position{};
Vector3 velocity_point{};
Vector3 acceleration_point{};
Vector3 convective_point{};
std::array<Vector3, kDofs> h{};
std::array<Vector3, kDofs> velocity_derivative{};
std::array<Vector3, kDofs> position_derivative{};
for (std::size_t node = 0U; node < kNodes; ++node) {
const std::size_t base = 6U * node;
const auto& rotation = rotations[node];
const Vector3 current =
current_coordinates[node] + z * current_directors[node];
position = position + kinetic_shape[node] * current;
for (std::size_t component = 0U; component < 3U; ++component) {
std::array<double, 3> translation{};
translation[component] = kinetic_shape[node];
h[base + component] = Vector3{translation};
h[base + 3U + component] =
z * kinetic_shape[node] * rotation.director_first[component];
for (std::size_t other = 0U; other < 3U; ++other) {
const Vector3 second =
z * kinetic_shape[node] *
rotation.director_second[component][other];
convective_point = convective_point +
second * velocity[base + 3U + component] *
velocity[base + 3U + other];
velocity_derivative[base + 3U + component] =
velocity_derivative[base + 3U + component] +
2.0 * second * velocity[base + 3U + other];
for (std::size_t third = 0U; third < 3U; ++third) {
position_derivative[base + 3U + component] =
position_derivative[base + 3U + component] +
z * kinetic_shape[node] *
rotation.director_third[component][other][third] *
velocity[base + 3U + other] *
velocity[base + 3U + third];
}
}
}
}
}
const double jacobian = Determinant(ref_xi, ref_eta, ref_zeta);
const double current_jacobian =
Determinant(current_xi, current_eta, current_zeta);
if (!std::isfinite(jacobian) || !(jacobian > 0.0) ||
!std::isfinite(current_jacobian) || !(current_jacobian > 0.0)) {
return Failure<NonlinearMitc4InertiaEvaluation>(
"invalid-nonlinear-inertia-geometry", source_location_, identity_,
"Inertia requires positive reference and current mass-point "
"Jacobians.");
}
for (std::size_t row = 0U; row < kDofs; ++row) {
velocity_point = velocity_point + h[row] * velocity[row];
acceleration_point = acceleration_point + h[row] * acceleration[row];
}
acceleration_point = acceleration_point + convective_point;
const double scale = density_ * weight * jacobian;
if (!std::isfinite(scale) || !(scale > 0.0)) {
return Failure<NonlinearMitc4InertiaEvaluation>(
"invalid-nonlinear-inertia-positive-mass", source_location_,
identity_,
"Mass-point integration weight must be finite and positive.");
}
result.kinetic_energy +=
0.5 * scale * velocity_point.Dot(velocity_point);
result.momentum = result.momentum + scale * velocity_point;
result.momentum_rate =
result.momentum_rate + scale * acceleration_point;
result.angular_momentum =
result.angular_momentum + scale * position.Cross(velocity_point);
result.angular_momentum_rate =
result.angular_momentum_rate +
scale * position.Cross(acceleration_point);
result.momentum_rate_scale += scale * acceleration_point.Norm();
result.angular_momentum_rate_scale +=
scale * position.Cross(acceleration_point).Norm();
for (std::size_t row = 0U; row < kDofs; ++row) {
result.force[row] += scale * h[row].Dot(acceleration_point);
// Keep c independent of a; subtracting M*a destroys small c values.
result.convective_force[row] += scale * h[row].Dot(convective_point);
for (std::size_t column = 0U; column < kDofs; ++column) {
result.mass(row, column) += scale * h[row].Dot(h[column]);
result.velocity_tangent(row, column) +=
scale * h[row].Dot(velocity_derivative[column]);
double position_entry = h[row].Dot(position_derivative[column]);
if (row / 6U == column / 6U && row % 6U >= 3U &&
column % 6U >= 3U) {
const std::size_t node = row / 6U;
position_entry +=
z * shape.values[node] *
rotations[node]
.director_second[row % 6U - 3U][column % 6U - 3U]
.Dot(acceleration_point);
const double jacobian =
Determinant(ref_xi, ref_eta, reference_zeta);
const double current_jacobian =
Determinant(current_xi, current_eta, current_zeta);
if (!std::isfinite(jacobian) || !(jacobian > 0.0) ||
!std::isfinite(current_jacobian) || !(current_jacobian > 0.0)) {
return Failure<NonlinearMitc4InertiaEvaluation>(
"invalid-nonlinear-inertia-geometry", source_location_,
identity_,
"Inertia requires positive reference and current mass-point "
"Jacobians.");
}
for (std::size_t row = 6U * mass_node; row < 6U * (mass_node + 1U);
++row) {
velocity_point = velocity_point + h[row] * velocity[row];
acceleration_point =
acceleration_point + h[row] * acceleration[row];
}
acceleration_point = acceleration_point + convective_point;
const double scale =
density_ * weight * jacobian * shape.values[mass_node];
if (!std::isfinite(scale) || !(scale > 0.0)) {
return Failure<NonlinearMitc4InertiaEvaluation>(
"invalid-nonlinear-inertia-positive-mass", source_location_,
identity_,
"Mass-point integration weight must be finite and positive.");
}
result.kinetic_energy +=
0.5 * scale * velocity_point.Dot(velocity_point);
for (std::size_t field = 6U * mass_node;
field < 6U * (mass_node + 1U); ++field) {
momentum_coefficients[field] = momentum_coefficients[field] + scale * h[field];
angular_coefficients[field] =
angular_coefficients[field] + scale * position.Cross(h[field]);
}
convective_momentum =
convective_momentum + scale * convective_point;
convective_angular =
convective_angular + scale * position.Cross(convective_point);
result.momentum_rate_scale += scale * acceleration_point.Norm();
result.angular_momentum_rate_scale +=
scale * position.Cross(acceleration_point).Norm();
for (std::size_t row = 6U * mass_node; row < 6U * (mass_node + 1U);
++row) {
// Keep c independent of a; subtracting M*a destroys small c values.
result.convective_force[row] +=
scale * h[row].Dot(convective_point);
for (std::size_t column = 6U * mass_node;
column < 6U * (mass_node + 1U); ++column) {
result.mass(row, column) += scale * h[row].Dot(h[column]);
result.velocity_tangent(row, column) +=
scale * h[row].Dot(velocity_derivative[column]);
double position_entry = h[row].Dot(position_derivative[column]);
if (row / 6U == column / 6U && row % 6U >= 3U &&
column % 6U >= 3U) {
const std::size_t node = row / 6U;
position_entry +=
z * kinetic_shape[node] *
rotations[node]
.director_second[row % 6U - 3U][column % 6U - 3U]
.Dot(convective_point);
}
result.position_tangent(row, column) += scale * position_entry;
if (column % 6U >= 3U) {
const auto direction = column % 6U - 3U;
const Vector3 dh_row =
row % 6U < 3U
? Vector3{}
: z * rotations[mass_node]
.director_second[row % 6U - 3U][direction];
for (std::size_t local = 0U; local < 6U; ++local) {
const auto field = 6U * mass_node + local;
const Vector3 dh_field =
local < 3U
? Vector3{}
: z * rotations[mass_node]
.director_second[local - 3U][direction];
mass_derivative[mass_node][direction][row % 6U][local] +=
scale * (dh_row.Dot(h[field]) + h[row].Dot(dh_field));
}
}
}
result.position_tangent(row, column) += scale * position_entry;
}
}
}
}
}
// Integrate M and c before contracting acceleration. Mixing a large
// translation and tiny rotary acceleration at a mass point can lose the
// latter before the odd-in-z translational moment cancels in integration.
for (std::size_t row = 0U; row < kDofs; ++row) {
InertiaSum sum;
for (std::size_t column = 0U; column < kDofs; ++column)
sum.Add(result.mass(row, column) * acceleration[column]);
sum.Add(result.convective_force[row]);
result.force[row] = sum.Value();
}
// DqI = (DqM)*a + Dqc. The integrated tangent above contains only Dqc;
// its acceleration contribution uses the independently integrated DqM.
for (std::size_t node = 0U; node < kNodes; ++node)
for (std::size_t direction = 0U; direction < 3U; ++direction)
for (std::size_t row = 0U; row < 6U; ++row) {
InertiaSum sum;
for (std::size_t field = 0U; field < 6U; ++field)
sum.Add(mass_derivative[node][direction][row][field] *
acceleration[6U * node + field]);
sum.Add(result.position_tangent(6U * node + row,
6U * node + 3U + direction));
result.position_tangent(6U * node + row,
6U * node + 3U + direction) = sum.Value();
}
std::array<double, 3U> momentum{};
std::array<double, 3U> angular{};
std::array<double, 3U> momentum_rate{};
std::array<double, 3U> angular_rate{};
for (std::size_t component = 0U; component < 3U; ++component) {
const auto contract = [&](const std::array<Vector3, kDofs>& coefficients,
const Vector& channel, const double extra) {
InertiaSum sum;
for (std::size_t field = 0U; field < kDofs; ++field)
sum.Add(coefficients[field][component] * channel[field]);
sum.Add(extra);
return sum.Value();
};
momentum[component] = contract(momentum_coefficients, velocity, 0.0);
angular[component] = contract(angular_coefficients, velocity, 0.0);
momentum_rate[component] = contract(momentum_coefficients, acceleration,
convective_momentum[component]);
angular_rate[component] = contract(angular_coefficients, acceleration,
convective_angular[component]);
}
result.momentum = Vector3{momentum};
result.angular_momentum = Vector3{angular};
result.momentum_rate = Vector3{momentum_rate};
result.angular_momentum_rate = Vector3{angular_rate};
if (!IsFinite(result.mass) || !IsFinite(result.position_tangent) ||
!IsFinite(result.velocity_tangent) || !IsFinite(result.force) ||
!IsFinite(result.convective_force) ||