332 lines
12 KiB
C++
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
|