#include "fesa/elements/nonlinear_mitc4_inertia.h" #include #include #include #include #include #include #include #include "fesa/elements/finite_rotation.h" namespace fesa { namespace { constexpr std::size_t kNodes = 4U; constexpr std::size_t kDofs = 24U; const std::array kGauss{-std::sqrt(3.0 / 5.0), 0.0, std::sqrt(3.0 / 5.0)}; constexpr std::array kWeights{5.0 / 9.0, 8.0 / 9.0, 5.0 / 9.0}; template Result Failure(const std::string& code, const SourceLocation& location, const std::string& identity, std::string message) { return Result::Failure(Status::Failure( FailureCategory::kModel, {{Severity::kError, code, location, "*ELEMENT", identity, std::move(message)}})); } std::string Identity(const std::array& nodes) { std::string identity; for (const Node* node : nodes) { if (!identity.empty()) { identity += "-"; } identity += node == nullptr ? "?" : node->source_id.source_label_text; } return identity; } Matrix FrameMatrix(const ShellNodeInitialFrame& frame) { Matrix result{3U, 3U}; for (std::size_t row = 0U; row < 3U; ++row) { result(row, 0U) = frame.tangent_a[row]; result(row, 1U) = frame.tangent_b[row]; result(row, 2U) = frame.director[row]; } return result; } bool IsFinite(const Matrix& matrix) { for (std::size_t row = 0U; row < matrix.Rows(); ++row) { for (std::size_t column = 0U; column < matrix.Columns(); ++column) { if (!std::isfinite(matrix(row, column))) { return false; } } } return true; } bool IsFinite(const Vector& vector) { for (std::size_t index = 0U; index < vector.Size(); ++index) { if (!std::isfinite(vector[index])) { return false; } } return true; } /// @brief Carries all derivatives through order three in one nodal rotation. struct Jet { double value{}; std::array d{}; std::array, 3> dd{}; std::array, 3>, 3> ddd{}; }; Jet Constant(const double value) { return Jet{value, {}, {}, {}}; } Jet Variable(const double value, const std::size_t component) { Jet result = Constant(value); result.d[component] = 1.0; return result; } Jet operator+(const Jet& left, const Jet& right) { Jet result{}; result.value = left.value + right.value; for (std::size_t i = 0U; i < 3U; ++i) { result.d[i] = left.d[i] + right.d[i]; for (std::size_t j = 0U; j < 3U; ++j) { result.dd[i][j] = left.dd[i][j] + right.dd[i][j]; for (std::size_t k = 0U; k < 3U; ++k) { result.ddd[i][j][k] = left.ddd[i][j][k] + right.ddd[i][j][k]; } } } return result; } Jet operator-(const Jet& left, const Jet& right) { Jet result{}; result.value = left.value - right.value; for (std::size_t i = 0U; i < 3U; ++i) { result.d[i] = left.d[i] - right.d[i]; for (std::size_t j = 0U; j < 3U; ++j) { result.dd[i][j] = left.dd[i][j] - right.dd[i][j]; for (std::size_t k = 0U; k < 3U; ++k) { result.ddd[i][j][k] = left.ddd[i][j][k] - right.ddd[i][j][k]; } } } return result; } Jet operator*(const Jet& left, const Jet& right) { Jet result{}; result.value = left.value * right.value; for (std::size_t i = 0U; i < 3U; ++i) { result.d[i] = left.d[i] * right.value + left.value * right.d[i]; for (std::size_t j = 0U; j < 3U; ++j) { result.dd[i][j] = left.dd[i][j] * right.value + left.d[i] * right.d[j] + left.d[j] * right.d[i] + left.value * right.dd[i][j]; for (std::size_t k = 0U; k < 3U; ++k) { result.ddd[i][j][k] = left.ddd[i][j][k] * right.value + left.dd[i][j] * right.d[k] + left.dd[i][k] * right.d[j] + left.dd[j][k] * right.d[i] + left.d[i] * right.dd[j][k] + left.d[j] * right.dd[i][k] + left.d[k] * right.dd[i][j] + left.value * right.ddd[i][j][k]; } } } return result; } Jet Reciprocal(const Jet& value) { Jet result{}; result.value = 1.0 / value.value; const double squared = result.value * result.value; const double cubed = squared * result.value; const double fourth = cubed * result.value; for (std::size_t i = 0U; i < 3U; ++i) { result.d[i] = -value.d[i] * squared; for (std::size_t j = 0U; j < 3U; ++j) { result.dd[i][j] = 2.0 * value.d[i] * value.d[j] * cubed - value.dd[i][j] * squared; for (std::size_t k = 0U; k < 3U; ++k) { result.ddd[i][j][k] = -6.0 * value.d[i] * value.d[j] * value.d[k] * fourth + 2.0 * (value.dd[i][j] * value.d[k] + value.dd[i][k] * value.d[j] + value.dd[j][k] * value.d[i]) * cubed - value.ddd[i][j][k] * squared; } } } return result; } Jet InverseSqrt(const Jet& value) { Jet result{}; result.value = 1.0 / std::sqrt(value.value); const double cubed = result.value * result.value * result.value; const double fifth = cubed * result.value * result.value; const double seventh = fifth * result.value * result.value; for (std::size_t i = 0U; i < 3U; ++i) { result.d[i] = -0.5 * cubed * value.d[i]; for (std::size_t j = 0U; j < 3U; ++j) { result.dd[i][j] = 0.75 * fifth * value.d[i] * value.d[j] - 0.5 * cubed * value.dd[i][j]; for (std::size_t k = 0U; k < 3U; ++k) { result.ddd[i][j][k] = -1.875 * seventh * value.d[i] * value.d[j] * value.d[k] + 0.75 * fifth * (value.dd[i][j] * value.d[k] + value.dd[i][k] * value.d[j] + value.dd[j][k] * value.d[i]) - 0.5 * cubed * value.ddd[i][j][k]; } } } return result; } Jet Scale(const double scalar, const Jet& value) { return Constant(scalar) * value; } using JetVector = std::array; using JetMatrix = std::array, 3>; JetVector Multiply(const JetMatrix& matrix, const JetVector& vector) { JetVector result{Constant(0.0), Constant(0.0), Constant(0.0)}; for (std::size_t row = 0U; row < 3U; ++row) { for (std::size_t column = 0U; column < 3U; ++column) { result[row] = result[row] + matrix[row][column] * vector[column]; } } return result; } JetMatrix Multiply(const JetMatrix& left, const JetMatrix& right) { JetMatrix result{}; for (std::size_t row = 0U; row < 3U; ++row) { for (std::size_t column = 0U; column < 3U; ++column) { result[row][column] = Constant(0.0); for (std::size_t index = 0U; index < 3U; ++index) { result[row][column] = result[row][column] + left[row][index] * right[index][column]; } } } return result; } JetMatrix IdentityMatrix() { JetMatrix result{}; for (std::size_t index = 0U; index < 3U; ++index) { result[index][index] = Constant(1.0); } return result; } JetMatrix Skew(const JetVector& value) { JetMatrix result{}; result[0U][1U] = Scale(-1.0, value[2U]); result[0U][2U] = value[1U]; result[1U][0U] = value[2U]; result[1U][2U] = Scale(-1.0, value[0U]); result[2U][0U] = Scale(-1.0, value[1U]); result[2U][1U] = value[0U]; return result; } Jet Dot(const JetVector& left, const JetVector& right) { Jet result = Constant(0.0); for (std::size_t component = 0U; component < 3U; ++component) { result = result + left[component] * right[component]; } return result; } JetMatrix Inverse(const JetMatrix& matrix) { JetMatrix cofactor{}; for (std::size_t row = 0U; row < 3U; ++row) { for (std::size_t column = 0U; column < 3U; ++column) { const std::size_t a = (row + 1U) % 3U; const std::size_t b = (row + 2U) % 3U; const std::size_t c = (column + 1U) % 3U; const std::size_t d = (column + 2U) % 3U; cofactor[row][column] = matrix[a][c] * matrix[b][d] - matrix[a][d] * matrix[b][c]; // Cyclic minor ordering already includes the cofactor sign. } } Jet determinant = Constant(0.0); for (std::size_t column = 0U; column < 3U; ++column) { determinant = determinant + matrix[0U][column] * cofactor[0U][column]; } const Jet inverse_determinant = Reciprocal(determinant); JetMatrix result{}; for (std::size_t row = 0U; row < 3U; ++row) { for (std::size_t column = 0U; column < 3U; ++column) { result[row][column] = cofactor[column][row] * inverse_determinant; } } return result; } JetMatrix LeftJacobian(const JetVector& phi) { const JetMatrix skew = Skew(phi); JetMatrix result = IdentityMatrix(); JetMatrix term = IdentityMatrix(); double factorial = 1.0; for (std::size_t order = 1U; order <= 32U; ++order) { term = Multiply(term, skew); factorial *= static_cast(order + 1U); for (std::size_t row = 0U; row < 3U; ++row) { for (std::size_t column = 0U; column < 3U; ++column) { result[row][column] = result[row][column] + Scale(1.0 / factorial, term[row][column]); } } } return result; } /// @brief Uses a converged exponential series so D1/D2/D3 share one analytic /// map. JetMatrix ExpSkew(const JetVector& phi) { const JetMatrix skew = Skew(phi); JetMatrix result = IdentityMatrix(); JetMatrix term = IdentityMatrix(); double factorial = 1.0; for (std::size_t order = 1U; order <= 32U; ++order) { term = Multiply(term, skew); factorial *= static_cast(order); for (std::size_t row = 0U; row < 3U; ++row) { for (std::size_t column = 0U; column < 3U; ++column) { result[row][column] = result[row][column] + Scale(1.0 / factorial, term[row][column]); } } } return result; } double Determinant(const Vector3& a, const Vector3& b, const Vector3& c) { return a.Dot(b.Cross(c)); } /// @brief Checks the scaled physical twenty-dimensional mass without a rank /// cut. Status CheckPhysicalMass(const Matrix& mass, const std::array& nulls, const double length, const SourceLocation& location, const std::string& identity) { const auto fail = [&](const std::string& code, const std::string& message) { return Status::Failure( FailureCategory::kModel, {{Severity::kError, code, location, "*ELEMENT", identity, message}}); }; constexpr std::size_t kPhysical = 20U; Matrix scaled{kDofs, kDofs}; double scale = 0.0; for (std::size_t row = 0U; row < kDofs; ++row) { for (std::size_t column = 0U; column < kDofs; ++column) { scaled(row, column) = mass(row, column) * (row % 6U < 3U ? length : 1.0) * (column % 6U < 3U ? length : 1.0); if (mass(row, column) != 0.0 && scaled(row, column) == 0.0) { return fail("invalid-nonlinear-inertia-positive-mass", "Physical mass scaling underflowed a nonzero entry."); } scale = std::max(scale, std::abs(scaled(row, column))); } } if (!IsFinite(scaled) || !std::isfinite(scale) || !(scale > 0.0)) { return fail("invalid-nonlinear-inertia-positive-mass", "Scaled physical mass must have a finite positive scale."); } // Scaling by a positive entry preserves conditioning and avoids density // overflow in the Cholesky and inverse-action norms. for (std::size_t row = 0U; row < kDofs; ++row) { for (std::size_t column = 0U; column < kDofs; ++column) { scaled(row, column) /= scale; } } Matrix complement{kDofs, kPhysical}; for (std::size_t node = 0U; node < kNodes; ++node) { const Vector3& normal = nulls[node]; std::array axes{0U, 1U, 2U}; std::stable_sort(axes.begin(), axes.end(), [&](const std::size_t a, const std::size_t b) { return std::abs(normal[a]) < std::abs(normal[b]); }); std::array tangent{}; for (std::size_t column = 0U; column < 2U; ++column) { std::array axis{}; axis[axes[column]] = 1.0; Vector3 direction{axis}; direction = direction - direction.Dot(normal) * normal; for (std::size_t previous = 0U; previous < column; ++previous) { direction = direction - direction.Dot(tangent[previous]) * tangent[previous]; } const double norm = direction.Norm(); if (!std::isfinite(norm) || !(norm > 0.0)) { return fail("invalid-nonlinear-inertia-null", "The analytic physical complement must remain finite."); } tangent[column] = direction / norm; } for (std::size_t row = 0U; row < 3U; ++row) { complement(6U * node + row, 5U * node + row) = 1.0; for (std::size_t column = 0U; column < 2U; ++column) { complement(6U * node + 3U + row, 5U * node + 3U + column) = tangent[column][row]; } } } double mass_norm = 0.0; double symmetry_norm = 0.0; double null_action_norm = 0.0; for (std::size_t row = 0U; row < kDofs; ++row) { for (std::size_t column = 0U; column < kDofs; ++column) { mass_norm = std::hypot(mass_norm, scaled(row, column)); symmetry_norm = std::hypot(symmetry_norm, scaled(row, column) - scaled(column, row)); } for (std::size_t node = 0U; node < kNodes; ++node) { double action = 0.0; for (std::size_t component = 0U; component < 3U; ++component) { action += scaled(row, 6U * node + 3U + component) * nulls[node][component]; } null_action_norm = std::hypot(null_action_norm, action); } } double gram_error = 0.0; double orthogonality_error = 0.0; for (std::size_t row = 0U; row < kPhysical; ++row) { for (std::size_t column = 0U; column < kPhysical; ++column) { double entry = row == column ? -1.0 : 0.0; for (std::size_t inner = 0U; inner < kDofs; ++inner) { entry += complement(inner, row) * complement(inner, column); } gram_error = std::hypot(gram_error, entry); } for (std::size_t node = 0U; node < kNodes; ++node) { double entry = 0.0; for (std::size_t component = 0U; component < 3U; ++component) { entry += complement(6U * node + 3U + component, row) * nulls[node][component]; } orthogonality_error = std::hypot(orthogonality_error, entry); } } double null_gram_error = 0.0; for (const Vector3& normal : nulls) { null_gram_error = std::hypot(null_gram_error, normal.Dot(normal) - 1.0); } const double full_gram_error = std::hypot( gram_error, std::hypot(null_gram_error, std::sqrt(2.0) * orthogonality_error)); if (!(symmetry_norm / mass_norm <= 1.0e-12) || !(null_action_norm / mass_norm / 2.0 <= 1.0e-12) || !(full_gram_error <= 1.0e-12)) { return fail("invalid-nonlinear-inertia-null", "Scaled mass symmetry and analytic null identities failed."); } Matrix block{kPhysical, kPhysical}; Matrix mass_complement{kDofs, kPhysical}; for (std::size_t row = 0U; row < kDofs; ++row) { for (std::size_t column = 0U; column < kPhysical; ++column) { for (std::size_t inner = 0U; inner < kDofs; ++inner) { mass_complement(row, column) += scaled(row, inner) * complement(inner, column); } } } double block_norm = 0.0; for (std::size_t row = 0U; row < kPhysical; ++row) { double row_sum = 0.0; for (std::size_t column = 0U; column < kPhysical; ++column) { for (std::size_t inner = 0U; inner < kDofs; ++inner) { block(row, column) += complement(inner, row) * mass_complement(inner, column); } row_sum += std::abs(block(row, column)); } block_norm = std::max(block_norm, row_sum); } Matrix lower{kPhysical, kPhysical}; for (std::size_t row = 0U; row < kPhysical; ++row) { for (std::size_t column = 0U; column <= row; ++column) { double entry = block(row, column); for (std::size_t inner = 0U; inner < column; ++inner) { entry -= lower(row, inner) * lower(column, inner); } if (!std::isfinite(entry) || (row == column && !(entry > 0.0))) { return fail( "invalid-nonlinear-inertia-positive-mass", "Physical mass requires strictly positive Cholesky pivots."); } lower(row, column) = row == column ? std::sqrt(entry) : entry / lower(column, column); } } std::array inverse_row_sums{}; double reconstruction_error = 0.0; double block_frobenius = 0.0; for (std::size_t row = 0U; row < kPhysical; ++row) { for (std::size_t column = 0U; column < kPhysical; ++column) { double reconstructed = 0.0; for (std::size_t inner = 0U; inner <= std::min(row, column); ++inner) { reconstructed += lower(row, inner) * lower(column, inner); } reconstruction_error = std::hypot(reconstruction_error, reconstructed - block(row, column)); block_frobenius = std::hypot(block_frobenius, block(row, column)); } } if (!(reconstruction_error / block_frobenius <= 1.0e-12)) { return fail("invalid-nonlinear-inertia-positive-mass", "Physical mass Cholesky reconstruction failed."); } for (std::size_t column = 0U; column < kPhysical; ++column) { std::array solution{}; for (std::size_t row = 0U; row < kPhysical; ++row) { double rhs = row == column ? 1.0 : 0.0; for (std::size_t inner = 0U; inner < row; ++inner) { rhs -= lower(row, inner) * solution[inner]; } solution[row] = rhs / lower(row, row); } for (std::size_t reverse = kPhysical; reverse > 0U; --reverse) { const std::size_t row = reverse - 1U; double rhs = solution[row]; for (std::size_t inner = row + 1U; inner < kPhysical; ++inner) { rhs -= lower(inner, row) * solution[inner]; } solution[row] = rhs / lower(row, row); inverse_row_sums[row] += std::abs(solution[row]); } } const double inverse_norm = *std::max_element(inverse_row_sums.begin(), inverse_row_sums.end()); const double reciprocal_condition = 1.0 / block_norm / inverse_norm; if (!std::isfinite(inverse_norm) || !std::isfinite(reciprocal_condition) || !(reciprocal_condition >= std::ldexp(1.0, -32))) { return fail("ill-conditioned-nonlinear-inertia-positive-mass", "Physical mass reciprocal infinity condition is below 2^-32."); } return Status::Ok(); } } // namespace Result EvaluateDynamicRotation( const Vector3& phi_value, const Vector3& initial_director, const Vector3& velocity, const Vector3& acceleration) { if (!phi_value.IsFinite() || !initial_director.IsFinite() || !velocity.IsFinite() || !acceleration.IsFinite() || !(std::abs(initial_director.Norm() - 1.0) <= 1.0e-12) || !(std::acos(-1.0) - phi_value.Norm() > std::sqrt(std::numeric_limits::epsilon()))) { return Failure( "invalid-dynamic-rotation", {{}, 0U}, "DYNAMIC_ROTATION", "Dynamic rotation requires finite principal coordinates and a unit " "initial director."); } JetVector phi{}; for (std::size_t component = 0U; component < 3U; ++component) { phi[component] = Variable(phi_value[component], component); } const JetVector initial{Constant(initial_director.X()), Constant(initial_director.Y()), Constant(initial_director.Z())}; const JetVector director_jet = Multiply(ExpSkew(phi), initial); const JetMatrix left_jacobian = LeftJacobian(phi); JetVector null_jet = Multiply(Inverse(left_jacobian), director_jet); const Jet null_norm_squared = Dot(null_jet, null_jet); if (!std::isfinite(null_norm_squared.value) || !(null_norm_squared.value > 0.0)) { return Failure( "invalid-dynamic-rotation", {{}, 0U}, "DYNAMIC_ROTATION", "Dynamic rotation produced a nonpositive drilling-null norm."); } const Jet inverse_norm = InverseSqrt(null_norm_squared); for (Jet& component : null_jet) { component = component * inverse_norm; } std::array directional_second{}; std::array angular_velocity{}; std::array angular_acceleration{}; Matrix gradient{3U, 3U}; for (std::size_t row = 0U; row < 3U; ++row) { for (std::size_t column = 0U; column < 3U; ++column) { gradient(row, column) = null_jet[row].d[column]; } } for (std::size_t row = 0U; row < 3U; ++row) { double second = 0.0; double omega = 0.0; double omega_dot = 0.0; for (std::size_t column = 0U; column < 3U; ++column) { omega += left_jacobian[row][column].value * velocity[column]; omega_dot += left_jacobian[row][column].value * acceleration[column]; for (std::size_t other = 0U; other < 3U; ++other) { second += null_jet[row].dd[column][other] * velocity[column] * velocity[other]; omega_dot += left_jacobian[row][column].d[other] * velocity[other] * velocity[column]; } } directional_second[row] = second; angular_velocity[row] = omega; angular_acceleration[row] = omega_dot; } const Vector3 director{director_jet[0U].value, director_jet[1U].value, director_jet[2U].value}; const Vector3 drill_null{null_jet[0U].value, null_jet[1U].value, null_jet[2U].value}; const Vector3 directional_second_value{directional_second}; const Vector3 angular_velocity_value{angular_velocity}; const Vector3 angular_acceleration_value{angular_acceleration}; if (!director.IsFinite() || !drill_null.IsFinite() || !directional_second_value.IsFinite() || !angular_velocity_value.IsFinite() || !angular_acceleration_value.IsFinite() || !IsFinite(gradient)) { return Failure( "invalid-dynamic-rotation", {{}, 0U}, "DYNAMIC_ROTATION", "Dynamic rotation evaluation must remain finite."); } DynamicRotationKinematics result{director, drill_null, std::move(gradient), directional_second_value, angular_velocity_value, angular_acceleration_value, {}, {}, {}}; for (std::size_t i = 0U; i < 3U; ++i) { result.director_first[i] = {director_jet[0].d[i], director_jet[1].d[i], director_jet[2].d[i]}; for (std::size_t j = 0U; j < 3U; ++j) { result.director_second[i][j] = {director_jet[0].dd[i][j], director_jet[1].dd[i][j], director_jet[2].dd[i][j]}; for (std::size_t k = 0U; k < 3U; ++k) { result.director_third[i][j][k] = {director_jet[0].ddd[i][j][k], director_jet[1].ddd[i][j][k], director_jet[2].ddd[i][j][k]}; } } } return Result::Success(std::move(result)); } NonlinearMitc4Inertia::NonlinearMitc4Inertia( std::array coordinates, std::array initial_frames, std::array initial_directors, const double thickness, const double density, SourceLocation source_location, std::string identity) : coordinates_(std::move(coordinates)), initial_frames_(std::move(initial_frames)), initial_directors_(std::move(initial_directors)), thickness_(thickness), density_(density), source_location_(std::move(source_location)), identity_(std::move(identity)) {} Result NonlinearMitc4Inertia::Create( std::array nodes, std::array initial_frames, const ShellSection& section, const LinearElasticMaterial& material, const double density) { const std::string identity = Identity(nodes); if (!std::isfinite(density) || !(density > 0.0)) { return Failure( "invalid-nonlinear-inertia-density", section.location, identity, "Nonlinear MITC4 inertia requires a finite positive density."); } const auto shell = NonlinearMitc4Shell::Create(nodes, initial_frames, section, material); if (!shell.HasValue()) { return Result::Failure(shell.GetStatus()); } std::array coordinates{}; std::array frames{Matrix{3U, 3U}, Matrix{3U, 3U}, Matrix{3U, 3U}, Matrix{3U, 3U}}; std::array directors{}; for (std::size_t node = 0U; node < kNodes; ++node) { coordinates[node] = Vector3{nodes[node]->coordinates}; frames[node] = FrameMatrix(initial_frames[node]); directors[node] = Vector3{initial_frames[node].director}; } NonlinearMitc4Inertia inertia{ coordinates, std::move(frames), directors, section.thickness, density, nodes[0U]->location, identity}; const auto initial = inertia.Evaluate({}, Vector{kDofs}, Vector{kDofs}); if (!initial.HasValue()) { return Result::Failure(initial.GetStatus()); } return Result::Success(std::move(inertia)); } Result NonlinearMitc4Inertia::Evaluate( const NonlinearMitc4TrialDofs& trial, const Vector& velocity, const Vector& acceleration) const { if (velocity.Size() != kDofs || acceleration.Size() != kDofs || !IsFinite(velocity) || !IsFinite(acceleration)) { return Failure( "invalid-nonlinear-inertia-rate", source_location_, identity_, "Nonlinear MITC4 inertia requires 24 finite rates."); } std::array rotations{}; std::array nulls{}; double length = 0.0; for (std::size_t node = 0U; node < kNodes; ++node) { length = std::max( length, (coordinates_[(node + 1U) % kNodes] - coordinates_[node]).Norm()); if (!trial.displacement[node].IsFinite() || !MakeFiniteRotationState(trial.rotation_vector[node], initial_frames_[node]) .HasValue()) { return Failure( "invalid-nonlinear-inertia-trial", source_location_, identity_, "Nonlinear MITC4 inertia trial must be in the principal chart."); } const auto rotation = EvaluateDynamicRotation( trial.rotation_vector[node], initial_directors_[node], {}, {}); if (!rotation.HasValue()) { return Result::Failure( rotation.GetStatus()); } rotations[node] = rotation.Value(); nulls[node] = rotation.Value().drill_null; for (std::size_t previous = 0U; previous < node; ++previous) { if (!(rotations[node].director.Dot(rotations[previous].director) > -1.0 + 1.0e-8)) { return Failure( "invalid-nonlinear-inertia-director", source_location_, identity_, "Nonlinear MITC4 inertia trial directors must not be opposed."); } } } if (!std::isfinite(length) || !(length > 0.0)) { return Failure( "invalid-nonlinear-inertia-geometry", source_location_, identity_, "Nonlinear MITC4 inertia requires a finite positive initial edge."); } NonlinearMitc4InertiaEvaluation result{Matrix{kDofs, kDofs}, Matrix{kDofs, kDofs}, Matrix{kDofs, kDofs}, Vector{kDofs}, Vector{kDofs}, 0.0, {}, {}, {}, {}}; 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]); 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 h{}; std::array velocity_derivative{}; std::array 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 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]; } } } } 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( "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( "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); } result.position_tangent(row, column) += scale * position_entry; } } } } } if (!IsFinite(result.mass) || !IsFinite(result.position_tangent) || !IsFinite(result.velocity_tangent) || !IsFinite(result.force) || !IsFinite(result.convective_force) || !std::isfinite(result.kinetic_energy) || !result.momentum.IsFinite() || !result.angular_momentum.IsFinite() || !result.momentum_rate.IsFinite() || !result.angular_momentum_rate.IsFinite() || !std::isfinite(result.momentum_rate_scale) || !std::isfinite(result.angular_momentum_rate_scale)) { return Failure( "invalid-nonlinear-inertia-evaluation", source_location_, identity_, "Nonlinear MITC4 inertia evaluation must remain finite."); } const Status physical_mass = CheckPhysicalMass(result.mass, nulls, length, source_location_, identity_); if (!physical_mass.IsOk()) { return Result::Failure(physical_mass); } return Result::Success(std::move(result)); } } // namespace fesa