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:
@@ -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) ||
|
||||
|
||||
Reference in New Issue
Block a user