#include "fesa/loads/shell_surface_pressure.h" #include #include #include #include #include #include #include "math/vector3_operations.h" namespace fesa { namespace { using math_internal::WeightedSum; constexpr std::size_t kNodeCount = 4U; constexpr std::size_t kDofsPerNode = 6U; constexpr std::size_t kDofCount = kNodeCount * kDofsPerNode; constexpr double kGeometryThreshold = 1.0e-12; struct ShapeData { std::array values; std::array xi_derivatives; std::array eta_derivatives; }; struct GeometryData { ShapeData shape; Vector3 xi_tangent; Vector3 eta_tangent; Vector3 area_vector; }; struct ValidatedGeometry { std::array gauss_points; }; /// @brief Creates one deterministic evaluator failure diagnostic. Status Failure(const FailureCategory category, const ShellSurfacePressureInput& input, std::string code, std::string message) { return Status::Failure( category, {{Severity::kError, std::move(code), input.location, "DSLOAD", input.entity_identity, std::move(message)}}); } /// @brief Evaluates four-node bilinear shape data in source connectivity order. ShapeData ShapeAt(const double xi, const double eta) noexcept { return {{{0.25 * (1.0 - xi) * (1.0 - eta), 0.25 * (1.0 + xi) * (1.0 - eta), 0.25 * (1.0 + xi) * (1.0 + eta), 0.25 * (1.0 - xi) * (1.0 + eta)}}, {{-0.25 * (1.0 - eta), 0.25 * (1.0 - eta), 0.25 * (1.0 + eta), -0.25 * (1.0 + eta)}}, {{-0.25 * (1.0 - xi), -0.25 * (1.0 + xi), 0.25 * (1.0 + xi), 0.25 * (1.0 - xi)}}}; } /// @brief Evaluates finite bilinear surface data at one natural point. bool EvaluateGeometry(const std::array& coordinates, const double xi, const double eta, GeometryData& geometry) { geometry.shape = ShapeAt(xi, eta); geometry.xi_tangent = WeightedSum(geometry.shape.xi_derivatives, coordinates); geometry.eta_tangent = WeightedSum(geometry.shape.eta_derivatives, coordinates); geometry.area_vector = geometry.xi_tangent.Cross(geometry.eta_tangent); if (!geometry.xi_tangent.IsFinite() || !geometry.eta_tangent.IsFinite() || !geometry.area_vector.IsFinite()) { return false; } for (const double value : geometry.shape.values) { if (!std::isfinite(value)) { return false; } } for (const double value : geometry.shape.xi_derivatives) { if (!std::isfinite(value)) { return false; } } for (const double value : geometry.shape.eta_derivatives) { if (!std::isfinite(value)) { return false; } } return true; } /// @brief Returns the exact reference or current coordinates selected by input. bool SelectCoordinates(const ShellSurfacePressureInput& input, std::array& coordinates) { for (std::size_t node = 0U; node < kNodeCount; ++node) { coordinates[node] = input.reference_coordinates[node]; if (input.configuration == SurfacePressureConfiguration::kCurrent) { coordinates[node] = coordinates[node] + input.translational_displacements[node]; } if (!coordinates[node].IsFinite()) { return false; } } return true; } /// @brief Validates all required geometry points before contribution assembly. Result ValidateGeometry( const ShellSurfacePressureInput& input, const std::array& coordinates) { const FailureCategory category = input.configuration == SurfacePressureConfiguration::kReference ? FailureCategory::kModel : FailureCategory::kSolver; const std::string code = input.configuration == SurfacePressureConfiguration::kReference ? "invalid-reference-surface-geometry" : "invalid-current-surface-geometry"; double chord_length = 0.0; for (std::size_t first = 0U; first < kNodeCount; ++first) { for (std::size_t second = first + 1U; second < kNodeCount; ++second) { const double chord = (coordinates[first] - coordinates[second]).Norm(); if (!std::isfinite(chord)) { return Result::Failure( Failure(category, input, code, "Surface chord is nonfinite.")); } chord_length = (std::max)(chord_length, chord); } } if (!(chord_length > 0.0)) { return Result::Failure( Failure(category, input, code, "Surface chord scale is zero.")); } const double chord_squared = chord_length * chord_length; if (!std::isfinite(chord_squared)) { return Result::Failure( Failure(category, input, code, "Surface chord scale is nonfinite.")); } constexpr double kGauss = 0.57735026918962576450914878050196; constexpr std::array, 4> kGaussLocations{{ {{-kGauss, -kGauss}}, {{kGauss, -kGauss}}, {{kGauss, kGauss}}, {{-kGauss, kGauss}}, }}; constexpr std::array, 4> kCorners{{ {{-1.0, -1.0}}, {{1.0, -1.0}}, {{1.0, 1.0}}, {{-1.0, 1.0}}, }}; ValidatedGeometry result{}; Vector3 mean_area{}; for (std::size_t point = 0U; point < kGaussLocations.size(); ++point) { if (!EvaluateGeometry(coordinates, kGaussLocations[point][0U], kGaussLocations[point][1U], result.gauss_points[point])) { return Result::Failure(Failure( category, input, code, "Surface geometry is nonfinite at Gauss point.")); } mean_area = mean_area + result.gauss_points[point].area_vector; } mean_area = 0.25 * mean_area; const double mean_norm = mean_area.Norm(); if (!mean_area.IsFinite() || !std::isfinite(mean_norm) || !(mean_norm / chord_squared > kGeometryThreshold)) { return Result::Failure( Failure(category, input, code, "Mean surface area is collapsed or below the geometry threshold.")); } const Vector3 mean_normal = mean_area / mean_norm; if (!mean_normal.IsFinite()) { return Result::Failure( Failure(category, input, code, "Mean surface normal is nonfinite.")); } for (const auto& corner : kCorners) { GeometryData corner_geometry{}; if (!EvaluateGeometry(coordinates, corner[0U], corner[1U], corner_geometry)) { return Result::Failure( Failure(category, input, code, "Surface geometry is nonfinite at corner point.")); } const double signed_scale = corner_geometry.area_vector.Dot(mean_normal) / chord_squared; if (!std::isfinite(signed_scale) || !(signed_scale > kGeometryThreshold)) { return Result::Failure( Failure(category, input, code, "Surface orientation is invalid at corner point.")); } } for (const auto& point : result.gauss_points) { const double signed_scale = point.area_vector.Dot(mean_normal) / chord_squared; if (!std::isfinite(signed_scale) || !(signed_scale > kGeometryThreshold)) { return Result::Failure( Failure(category, input, code, "Surface orientation is invalid at Gauss point.")); } } return Result::Success(result); } /// @brief Returns one row-major entry of the cross-product matrix [value]_x. double SkewEntry(const Vector3& value, const std::size_t row, const std::size_t column) noexcept { constexpr std::array, 3> kSigns{{ {{0, -1, 1}}, {{1, 0, -1}}, {{-1, 1, 0}}, }}; constexpr std::array, 3> kComponents{{ {{0U, 2U, 1U}}, {{2U, 0U, 0U}}, {{1U, 0U, 0U}}, }}; const int sign = kSigns[row][column]; if (sign == 0) { return 0.0; } return static_cast(sign) * value[kComponents[row][column]]; } } // namespace Result EvaluateShellSurfacePressure( const ShellSurfacePressureInput& input) { if (!std::isfinite(input.pressure_magnitude) || !std::isfinite(input.load_factor)) { return Result::Failure(Failure( FailureCategory::kSolver, input, "nonfinite-pressure-contribution", "Pressure magnitude or load factor is nonfinite.")); } const double pressure_factor = input.pressure_magnitude * input.load_factor; if (!std::isfinite(pressure_factor)) { return Result::Failure(Failure( FailureCategory::kSolver, input, "nonfinite-pressure-contribution", "Pressure magnitude and load factor have a nonfinite product.")); } std::array coordinates{}; if (!SelectCoordinates(input, coordinates)) { const FailureCategory category = input.configuration == SurfacePressureConfiguration::kReference ? FailureCategory::kModel : FailureCategory::kSolver; const std::string code = input.configuration == SurfacePressureConfiguration::kReference ? "invalid-reference-surface-geometry" : "invalid-current-surface-geometry"; return Result::Failure( Failure(category, input, code, "Surface coordinate is nonfinite.")); } const auto geometry = ValidateGeometry(input, coordinates); if (!geometry.HasValue()) { return Result::Failure( geometry.GetStatus()); } ShellSurfacePressureContribution contribution{}; if (pressure_factor == 0.0) { return Result::Success(contribution); } const double side_multiplier = input.side == ShellSurfaceSide::kSpos ? 1.0 : -1.0; const double force_scale = -pressure_factor * side_multiplier; const double tangent_scale = pressure_factor * side_multiplier; for (const auto& point : geometry.Value().gauss_points) { for (std::size_t node = 0U; node < kNodeCount; ++node) { const Vector3 force_increment = force_scale * point.shape.values[node] * point.area_vector; if (!force_increment.IsFinite()) { return Result::Failure(Failure( FailureCategory::kSolver, input, "nonfinite-pressure-contribution", "Pressure force integrand is nonfinite.")); } const std::size_t row_offset = node * kDofsPerNode; for (std::size_t component = 0U; component < 3U; ++component) { contribution.force[row_offset + component] += force_increment[component]; if (!std::isfinite(contribution.force[row_offset + component])) { return Result::Failure(Failure( FailureCategory::kSolver, input, "nonfinite-pressure-contribution", "Accumulated pressure force is nonfinite.")); } } } if (input.configuration == SurfacePressureConfiguration::kReference) { continue; } for (std::size_t row_node = 0U; row_node < kNodeCount; ++row_node) { for (std::size_t column_node = 0U; column_node < kNodeCount; ++column_node) { for (std::size_t row_component = 0U; row_component < 3U; ++row_component) { for (std::size_t column_component = 0U; column_component < 3U; ++column_component) { const double derivative = point.shape.xi_derivatives[column_node] * SkewEntry(point.eta_tangent, row_component, column_component) - point.shape.eta_derivatives[column_node] * SkewEntry(point.xi_tangent, row_component, column_component); const double tangent_increment = tangent_scale * point.shape.values[row_node] * derivative; const std::size_t index = (row_node * kDofsPerNode + row_component) * kDofCount + column_node * kDofsPerNode + column_component; contribution.external_tangent[index] += tangent_increment; if (!std::isfinite(contribution.external_tangent[index])) { return Result::Failure(Failure( FailureCategory::kSolver, input, "nonfinite-pressure-tangent", "Accumulated pressure tangent is nonfinite.")); } } } } } } return Result::Success(contribution); } } // namespace fesa