Files
FESADev/src/fesa/loads/shell_surface_pressure.cpp
T

332 lines
12 KiB
C++

#include "fesa/loads/shell_surface_pressure.h"
#include <algorithm>
#include <array>
#include <cmath>
#include <cstddef>
#include <string>
#include <utility>
#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<double, kNodeCount> values;
std::array<double, kNodeCount> xi_derivatives;
std::array<double, kNodeCount> eta_derivatives;
};
struct GeometryData {
ShapeData shape;
Vector3 xi_tangent;
Vector3 eta_tangent;
Vector3 area_vector;
};
struct ValidatedGeometry {
std::array<GeometryData, 4> 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<Vector3, kNodeCount>& 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<Vector3, kNodeCount>& 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<ValidatedGeometry> ValidateGeometry(
const ShellSurfacePressureInput& input,
const std::array<Vector3, kNodeCount>& 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<ValidatedGeometry>::Failure(
Failure(category, input, code, "Surface chord is nonfinite."));
}
chord_length = (std::max)(chord_length, chord);
}
}
if (!(chord_length > 0.0)) {
return Result<ValidatedGeometry>::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<ValidatedGeometry>::Failure(
Failure(category, input, code, "Surface chord scale is nonfinite."));
}
constexpr double kGauss = 0.57735026918962576450914878050196;
constexpr std::array<std::array<double, 2>, 4> kGaussLocations{{
{{-kGauss, -kGauss}},
{{kGauss, -kGauss}},
{{kGauss, kGauss}},
{{-kGauss, kGauss}},
}};
constexpr std::array<std::array<double, 2>, 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<ValidatedGeometry>::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<ValidatedGeometry>::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<ValidatedGeometry>::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<ValidatedGeometry>::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<ValidatedGeometry>::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<ValidatedGeometry>::Failure(
Failure(category, input, code,
"Surface orientation is invalid at Gauss point."));
}
}
return Result<ValidatedGeometry>::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<std::array<int, 3>, 3> kSigns{{
{{0, -1, 1}},
{{1, 0, -1}},
{{-1, 1, 0}},
}};
constexpr std::array<std::array<std::size_t, 3>, 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<double>(sign) * value[kComponents[row][column]];
}
} // namespace
Result<ShellSurfacePressureContribution> EvaluateShellSurfacePressure(
const ShellSurfacePressureInput& input) {
if (!std::isfinite(input.pressure_magnitude) ||
!std::isfinite(input.load_factor)) {
return Result<ShellSurfacePressureContribution>::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<ShellSurfacePressureContribution>::Failure(Failure(
FailureCategory::kSolver, input, "nonfinite-pressure-contribution",
"Pressure magnitude and load factor have a nonfinite product."));
}
std::array<Vector3, kNodeCount> 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<ShellSurfacePressureContribution>::Failure(
Failure(category, input, code, "Surface coordinate is nonfinite."));
}
const auto geometry = ValidateGeometry(input, coordinates);
if (!geometry.HasValue()) {
return Result<ShellSurfacePressureContribution>::Failure(
geometry.GetStatus());
}
ShellSurfacePressureContribution contribution{};
if (pressure_factor == 0.0) {
return Result<ShellSurfacePressureContribution>::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<ShellSurfacePressureContribution>::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<ShellSurfacePressureContribution>::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<ShellSurfacePressureContribution>::Failure(Failure(
FailureCategory::kSolver, input,
"nonfinite-pressure-tangent",
"Accumulated pressure tangent is nonfinite."));
}
}
}
}
}
}
return Result<ShellSurfacePressureContribution>::Success(contribution);
}
} // namespace fesa