Files
FESADev/src/fesa/elements/mitc4_shell.cpp
T

885 lines
35 KiB
C++

#include "fesa/elements/mitc4_shell.h"
#include <algorithm>
#include <cmath>
#include <limits>
#include <stdexcept>
#include <string>
#include <utility>
namespace fesa {
namespace {
constexpr std::size_t kNodeCount = 4U;
constexpr std::size_t kPhysicalDofsPerNode = 5U;
constexpr std::size_t kGlobalDofsPerNode = 6U;
constexpr std::size_t kPhysicalDofCount = 20U;
constexpr std::size_t kGlobalDofCount = 24U;
constexpr double kShearCorrection = 5.0 / 6.0;
constexpr double kFrameTolerance = 1.0e-12;
Vector3 WeightedSum(const std::array<double, kNodeCount>& weights,
const std::array<Vector3, kNodeCount>& values) {
Vector3 result{};
for (std::size_t node = 0U; node < kNodeCount; ++node) {
result = result + weights[node] * values[node];
}
return result;
}
Vector3 DerivativeSum(const std::array<double, kNodeCount>& derivatives,
const std::array<Vector3, kNodeCount>& values) {
std::array<Vector3, kNodeCount> relative{};
for (std::size_t node = 0U; node < kNodeCount; ++node) {
relative[node] = values[node] - values[0];
}
return WeightedSum(derivatives, relative);
}
bool SameCoordinates(const Vector3& left, const Vector3& right) {
return left == right;
}
/// @brief Selects each director's least-aligned global axis deterministically
/// and projects it into the tangent plane.
std::array<Vector3, kNodeCount> NodalTangentsA(
const std::array<Vector3, kNodeCount>& directors) {
const std::array<Vector3, 3> global_axes{
Vector3{1.0, 0.0, 0.0}, Vector3{0.0, 1.0, 0.0}, Vector3{0.0, 0.0, 1.0}};
std::array<Vector3, kNodeCount> tangents{};
for (std::size_t node = 0U; node < kNodeCount; ++node) {
std::size_t selected = 0U;
double alignment = std::abs(global_axes[0].Dot(directors[node]));
for (std::size_t axis = 1U; axis < global_axes.size(); ++axis) {
const double candidate = std::abs(global_axes[axis].Dot(directors[node]));
if (candidate < alignment) {
selected = axis;
alignment = candidate;
}
}
const Vector3 tangent_candidate =
global_axes[selected] -
global_axes[selected].Dot(directors[node]) * directors[node];
tangents[node] = (1.0 / tangent_candidate.Norm()) * tangent_candidate;
}
return tangents;
}
/// @brief Completes each right-handed nodal director frame.
std::array<Vector3, kNodeCount> NodalTangentsB(
const std::array<Vector3, kNodeCount>& directors,
const std::array<Vector3, kNodeCount>& tangent_a) {
std::array<Vector3, kNodeCount> tangents{};
for (std::size_t node = 0U; node < kNodeCount; ++node) {
tangents[node] = directors[node].Cross(tangent_a[node]);
}
return tangents;
}
std::string ElementIdentity(const std::array<const Node*, kNodeCount>& nodes) {
std::string identity;
for (const Node* node : nodes) {
if (node == nullptr) {
continue;
}
if (!identity.empty()) {
identity += "-";
}
identity += node->source_id.source_label_text;
}
return identity;
}
Result<Mitc4Shell> ModelFailure(std::string code,
const SourceLocation& location,
const std::string& identity,
std::string message) {
return Result<Mitc4Shell>::Failure(Status::Failure(
FailureCategory::kModel, {{Severity::kError, std::move(code), location,
"*ELEMENT", identity, std::move(message)}}));
}
/// @brief Forms one physical covariant strain column with thickness stretch
/// excluded.
std::array<std::array<double, 3>, 3> CovariantStrainColumn(
const std::array<Vector3, 3>& covariant,
const std::array<Vector3, 3>& derivatives) {
std::array<std::array<double, 3>, 3> strain{};
for (std::size_t first = 0U; first < 3U; ++first) {
for (std::size_t second = 0U; second < 3U; ++second) {
strain[first][second] = 0.5 * (covariant[first].Dot(derivatives[second]) +
covariant[second].Dot(derivatives[first]));
}
}
// Thickness stretch is excluded from the five-component shell law.
strain[2U][2U] = 0.0;
return strain;
}
/// @brief Reconstructs Cartesian strain in the approved reciprocal-basis
/// reduction order.
std::array<std::array<double, 3>, 3> ReconstructCartesianStrain(
const std::array<std::array<double, 3>, 3>& covariant_strain,
const std::array<Vector3, 3>& reciprocal) {
std::array<std::array<double, 3>, 3> tensor{};
for (std::size_t row = 0U; row < 3U; ++row) {
for (std::size_t column = 0U; column < 3U; ++column) {
for (std::size_t first = 0U; first < 3U; ++first) {
for (std::size_t second = 0U; second < 3U; ++second) {
tensor[row][column] += covariant_strain[first][second] *
reciprocal[first][row] *
reciprocal[second][column];
}
}
}
}
return tensor;
}
double FrameComponent(const Vector3& left,
const std::array<std::array<double, 3>, 3>& tensor,
const Vector3& right) {
double value = 0.0;
for (std::size_t row = 0U; row < 3U; ++row) {
for (std::size_t column = 0U; column < 3U; ++column) {
value += left[row] * tensor[row][column] * right[column];
}
}
return value;
}
/// @brief Projects tensor strain into signed local engineering components.
std::array<double, 5> LocalEngineeringComponents(
const std::array<std::array<double, 3>, 3>& tensor,
const Mitc4LocalFrame& frame) {
const Vector3 e1{frame.e1};
const Vector3 e2{frame.e2};
const Vector3 e3{frame.e3};
return {FrameComponent(e1, tensor, e1), FrameComponent(e2, tensor, e2),
2.0 * FrameComponent(e1, tensor, e2),
2.0 * FrameComponent(e1, tensor, e3),
2.0 * FrameComponent(e2, tensor, e3)};
}
Matrix ScaledMatrix(const Matrix& source, double factor) {
Matrix result{source.Rows(), source.Columns()};
for (std::size_t row = 0U; row < source.Rows(); ++row) {
for (std::size_t column = 0U; column < source.Columns(); ++column) {
result(row, column) = factor * source(row, column);
}
}
return result;
}
/// @brief Lifts a local matrix by a deterministic serial congruence product.
/// @note The loop and accumulation order are part of reproducible stiffness.
Matrix Congruence(const Matrix& local, const Matrix& transformation) {
if (local.Rows() != local.Columns() ||
local.Rows() != transformation.Rows()) {
throw std::invalid_argument{
"MITC4 congruence dimensions are incompatible."};
}
Matrix result{transformation.Columns(), transformation.Columns()};
for (std::size_t row = 0U; row < result.Rows(); ++row) {
for (std::size_t column = row; column < result.Columns(); ++column) {
double value = 0.0;
for (std::size_t local_row = 0U; local_row < local.Rows(); ++local_row) {
for (std::size_t local_column = 0U; local_column < local.Columns();
++local_column) {
value += transformation(local_row, row) *
local(local_row, local_column) *
transformation(local_column, column);
}
}
result(row, column) = value;
result(column, row) = value;
}
}
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;
}
Result<Mitc4Stiffness> StiffnessFailure(const SourceLocation& location,
const std::string& identity,
std::string message) {
return Result<Mitc4Stiffness>::Failure(
Status::Failure(FailureCategory::kModel,
{{Severity::kError, "invalid-shell-stiffness", location,
"*ELEMENT", identity, std::move(message)}}));
}
Result<Mitc4PhysicalRecovery> RecoveryFailure(const SourceLocation& location,
const std::string& identity,
std::string message) {
return Result<Mitc4PhysicalRecovery>::Failure(
Status::Failure(FailureCategory::kModel,
{{Severity::kError, "invalid-shell-recovery", location,
"*ELEMENT", identity, std::move(message)}}));
}
} // namespace
Result<Mitc4Shell> Mitc4Shell::Create(
std::array<const Node*, 4> nodes,
std::array<std::array<double, 3>, 4> initial_directors,
const ShellSection& section, const LinearElasticMaterial& material) {
const std::string identity = ElementIdentity(nodes);
if (std::any_of(nodes.begin(), nodes.end(),
[](const Node* value) { return value == nullptr; })) {
return ModelFailure("invalid-shell-geometry", section.location, identity,
"MITC4 creation requires four valid node references.");
}
std::array<Vector3, kNodeCount> coordinates{};
for (std::size_t node = 0U; node < kNodeCount; ++node) {
coordinates[node] = Vector3{nodes[node]->coordinates};
if (!coordinates[node].IsFinite()) {
return ModelFailure("invalid-shell-geometry", nodes[node]->location,
identity, "MITC4 node coordinates must be finite.");
}
for (std::size_t previous = 0U; previous < node; ++previous) {
if (SameCoordinates(coordinates[node], coordinates[previous])) {
return ModelFailure("invalid-shell-geometry", nodes[node]->location,
identity,
"MITC4 nodes must have distinct coordinates.");
}
}
}
const std::array<Vector3, kNodeCount> directors{
Vector3{initial_directors[0U]}, Vector3{initial_directors[1U]},
Vector3{initial_directors[2U]}, Vector3{initial_directors[3U]}};
for (std::size_t node = 0U; node < kNodeCount; ++node) {
const double director_norm = directors[node].Norm();
if (!directors[node].IsFinite() || !std::isfinite(director_norm) ||
std::abs(director_norm - 1.0) > kFrameTolerance) {
return ModelFailure(
"invalid-shell-director", nodes[node]->location, identity,
"MITC4 initial directors must be finite unit vectors.");
}
}
if (!std::isfinite(section.thickness) || !(section.thickness > 0.0)) {
return ModelFailure("invalid-shell-section", section.location, identity,
"MITC4 shell thickness must be finite and positive.");
}
if (!std::isfinite(material.youngs_modulus) ||
!(material.youngs_modulus > 0.0) ||
!std::isfinite(material.poisson_ratio) ||
!(material.poisson_ratio > -1.0) || !(material.poisson_ratio < 0.5)) {
return ModelFailure(
"invalid-shell-material", material.location, identity,
"MITC4 isotropic material requires finite E>0 and -1<nu<0.5.");
}
const auto center_shape = ShapeFunctions(0.0, 0.0);
const Vector3 center_xi =
DerivativeSum(center_shape.xi_derivatives, coordinates);
const Vector3 center_eta =
DerivativeSum(center_shape.eta_derivatives, coordinates);
const Vector3 center_area = center_xi.Cross(center_eta);
const double center_measure = center_area.Norm();
if (!center_area.IsFinite() || !std::isfinite(center_measure) ||
!(center_measure > 0.0)) {
return ModelFailure(
"invalid-shell-geometry", nodes[0]->location, identity,
"MITC4 center surface basis must be finite and nonzero.");
}
const Vector3 normal_candidate = (1.0 / center_measure) * center_area;
if (std::any_of(directors.begin(), directors.end(),
[&normal_candidate](const Vector3& director) {
return !(normal_candidate.Dot(director) > 0.0);
})) {
return ModelFailure(
"invalid-shell-director", nodes[0]->location, identity,
"MITC4 directors must follow the source-order positive face.");
}
const auto tangent_a = NodalTangentsA(directors);
const auto tangent_b = NodalTangentsB(directors, tangent_a);
Mitc4Shell shell{coordinates,
directors,
tangent_a,
tangent_b,
normal_candidate,
section.thickness,
material.youngs_modulus,
material.poisson_ratio,
nodes[0]->location,
identity};
const double shear_modulus =
material.youngs_modulus / (2.0 * (1.0 + material.poisson_ratio));
const double plane_stress_factor =
material.youngs_modulus /
(1.0 - material.poisson_ratio * material.poisson_ratio);
const double thickness_cubed =
section.thickness * section.thickness * section.thickness;
const std::array<double, 6> derived{
shear_modulus,
plane_stress_factor,
kShearCorrection * shear_modulus,
plane_stress_factor * section.thickness,
plane_stress_factor * thickness_cubed / 12.0,
kShearCorrection * shear_modulus * section.thickness};
if (std::any_of(derived.begin(), derived.end(), [](double value) {
return !std::isfinite(value) || !(value > 0.0);
})) {
return ModelFailure(
"invalid-shell-material", material.location, identity,
"Derived MITC4 constitutive coefficients must be finite and positive.");
}
GeometryData geometry{};
if (!shell.EvaluateGeometry(0.0, 0.0, 0.0, geometry)) {
return ModelFailure("invalid-shell-jacobian", nodes[0]->location, identity,
"MITC4 center frame or Jacobian is invalid.");
}
for (const auto& point : VolumeQuadrature()) {
if (!shell.EvaluateGeometry(point.natural_coordinates[0],
point.natural_coordinates[1],
point.natural_coordinates[2], geometry)) {
return ModelFailure("invalid-shell-jacobian", nodes[0]->location,
identity,
"MITC4 quadrature frame or Jacobian is invalid.");
}
}
constexpr std::array<Vector3, 4> tying_points{
Vector3{0.0, -1.0, 0.0}, Vector3{0.0, 1.0, 0.0}, Vector3{-1.0, 0.0, 0.0},
Vector3{1.0, 0.0, 0.0}};
for (const auto& point : tying_points) {
if (!shell.EvaluateGeometry(point[0], point[1], point[2], geometry)) {
return ModelFailure("invalid-shell-jacobian", nodes[0]->location,
identity,
"MITC4 tying-point frame or Jacobian is invalid.");
}
}
return Result<Mitc4Shell>::Success(std::move(shell));
}
Mitc4ShapeFunctions Mitc4Shell::ShapeFunctions(double xi, double eta) noexcept {
constexpr std::array<double, kNodeCount> xi_signs{-1.0, 1.0, 1.0, -1.0};
constexpr std::array<double, kNodeCount> eta_signs{-1.0, -1.0, 1.0, 1.0};
Mitc4ShapeFunctions shape{};
for (std::size_t node = 0U; node < kNodeCount; ++node) {
shape.values[node] =
0.25 * (1.0 + xi_signs[node] * xi) * (1.0 + eta_signs[node] * eta);
shape.xi_derivatives[node] =
0.25 * xi_signs[node] * (1.0 + eta_signs[node] * eta);
shape.eta_derivatives[node] =
0.25 * eta_signs[node] * (1.0 + xi_signs[node] * xi);
}
return shape;
}
Mitc4TyingWeights Mitc4Shell::TyingWeights(double xi, double eta) noexcept {
return {{(1.0 - eta) * 0.5, (1.0 + eta) * 0.5},
{(1.0 - xi) * 0.5, (1.0 + xi) * 0.5}};
}
const std::array<Mitc4QuadraturePoint, 8>&
Mitc4Shell::VolumeQuadrature() noexcept {
static const std::array<Mitc4QuadraturePoint, 8> points = [] {
const double gauss = 1.0 / std::sqrt(3.0);
return std::array<Mitc4QuadraturePoint, 8>{
Mitc4QuadraturePoint{{-gauss, -gauss, -gauss}, 1.0},
{{-gauss, -gauss, gauss}, 1.0},
{{gauss, -gauss, -gauss}, 1.0},
{{gauss, -gauss, gauss}, 1.0},
{{gauss, gauss, -gauss}, 1.0},
{{gauss, gauss, gauss}, 1.0},
{{-gauss, gauss, -gauss}, 1.0},
{{-gauss, gauss, gauss}, 1.0}};
}();
return points;
}
Mitc4LocalFrame Mitc4Shell::LocalFrame(double xi, double eta) const {
GeometryData geometry{};
if (!EvaluateGeometry(xi, eta, 0.0, geometry)) {
throw std::invalid_argument{
"MITC4 local frame is invalid at the requested point."};
}
return geometry.frame;
}
Matrix Mitc4Shell::PhysicalTransformation20() const {
Matrix transformation{kPhysicalDofCount, kGlobalDofCount};
for (std::size_t node = 0U; node < kNodeCount; ++node) {
const std::size_t physical_offset = node * kPhysicalDofsPerNode;
const std::size_t global_offset = node * kGlobalDofsPerNode;
for (std::size_t component = 0U; component < 3U; ++component) {
transformation(physical_offset + component, global_offset + component) =
1.0;
transformation(physical_offset + 3U, global_offset + 3U + component) =
tangent_a_[node][component];
transformation(physical_offset + 4U, global_offset + 3U + component) =
tangent_b_[node][component];
}
}
return transformation;
}
Matrix Mitc4Shell::DrillingTransformation4() const {
Matrix transformation{kNodeCount, kGlobalDofCount};
for (std::size_t node = 0U; node < kNodeCount; ++node) {
const std::size_t global_offset = node * kGlobalDofsPerNode;
for (std::size_t component = 0U; component < 3U; ++component) {
transformation(node, global_offset + 3U + component) =
directors_[node][component];
}
}
return transformation;
}
Matrix Mitc4Shell::DirectStrainDisplacement20(double xi, double eta,
double zeta) const {
return StrainDisplacement(xi, eta, zeta, nullptr);
}
Matrix Mitc4Shell::CovariantTyingShearSamples20() const {
constexpr std::array<Vector3, 4> points{
Vector3{0.0, -1.0, 0.0}, Vector3{0.0, 1.0, 0.0}, Vector3{-1.0, 0.0, 0.0},
Vector3{1.0, 0.0, 0.0}};
Matrix samples{4U, kPhysicalDofCount};
for (std::size_t point = 0U; point < points.size(); ++point) {
GeometryData geometry{};
if (!EvaluateGeometry(points[point][0], points[point][1], 0.0, geometry)) {
throw std::logic_error{"Validated MITC4 tying geometry became invalid."};
}
const auto derivatives =
BasisDerivatives(points[point][0], points[point][1], 0.0);
const std::size_t first = point < 2U ? 0U : 1U;
for (std::size_t dof = 0U; dof < kPhysicalDofCount; ++dof) {
const auto strain =
CovariantStrainColumn(geometry.covariant, derivatives[dof]);
samples(point, dof) = strain[first][2U];
}
}
return samples;
}
Matrix Mitc4Shell::StrainDisplacement20(double xi, double eta,
double zeta) const {
const Matrix samples = CovariantTyingShearSamples20();
return StrainDisplacement(xi, eta, zeta, &samples);
}
Matrix Mitc4Shell::PlaneStressConstitutive() const {
const double factor =
youngs_modulus_ / (1.0 - poisson_ratio_ * poisson_ratio_);
Matrix constitutive{3U, 3U};
constitutive(0U, 0U) = factor;
constitutive(0U, 1U) = factor * poisson_ratio_;
constitutive(1U, 0U) = factor * poisson_ratio_;
constitutive(1U, 1U) = factor;
constitutive(2U, 2U) = factor * (1.0 - poisson_ratio_) * 0.5;
return constitutive;
}
Matrix Mitc4Shell::MaterialConstitutive5() const {
Matrix constitutive{5U, 5U};
const Matrix plane_stress = PlaneStressConstitutive();
for (std::size_t row = 0U; row < 3U; ++row) {
for (std::size_t column = 0U; column < 3U; ++column) {
constitutive(row, column) = plane_stress(row, column);
}
}
const double shear_modulus = youngs_modulus_ / (2.0 * (1.0 + poisson_ratio_));
constitutive(3U, 3U) = kShearCorrection * shear_modulus;
constitutive(4U, 4U) = kShearCorrection * shear_modulus;
return constitutive;
}
Matrix Mitc4Shell::MembraneSectionMatrix() const {
return ScaledMatrix(PlaneStressConstitutive(), thickness_);
}
Matrix Mitc4Shell::BendingSectionMatrix() const {
return ScaledMatrix(PlaneStressConstitutive(),
thickness_ * thickness_ * thickness_ / 12.0);
}
Matrix Mitc4Shell::TransverseShearSectionMatrix() const {
const double shear_modulus = youngs_modulus_ / (2.0 * (1.0 + poisson_ratio_));
Matrix result{2U, 2U};
result(0U, 0U) = kShearCorrection * shear_modulus * thickness_;
result(1U, 1U) = result(0U, 0U);
return result;
}
Result<Mitc4Stiffness> Mitc4Shell::Stiffness() const {
const Matrix constitutive = MaterialConstitutive5();
const Matrix tying_samples = CovariantTyingShearSamples20();
Matrix physical_local{kPhysicalDofCount, kPhysicalDofCount};
for (const auto& point : VolumeQuadrature()) {
GeometryData geometry{};
if (!EvaluateGeometry(point.natural_coordinates[0],
point.natural_coordinates[1],
point.natural_coordinates[2], geometry)) {
return StiffnessFailure(
source_location_, identity_,
"Validated MITC4 quadrature geometry became invalid.");
}
const Matrix strain = StrainDisplacement(
point.natural_coordinates[0], point.natural_coordinates[1],
point.natural_coordinates[2], &tying_samples);
for (std::size_t row = 0U; row < kPhysicalDofCount; ++row) {
for (std::size_t column = row; column < kPhysicalDofCount; ++column) {
double integrand = 0.0;
for (std::size_t first = 0U; first < 5U; ++first) {
for (std::size_t second = 0U; second < 5U; ++second) {
integrand += strain(first, row) * constitutive(first, second) *
strain(second, column);
}
}
const double contribution =
integrand * geometry.jacobian * point.weight;
physical_local(row, column) += contribution;
if (row != column) {
physical_local(column, row) += contribution;
}
}
}
}
double drilling_reference = (std::numeric_limits<double>::max)();
bool has_drilling_reference = false;
for (std::size_t node = 0U; node < kNodeCount; ++node) {
const std::size_t offset = node * kPhysicalDofsPerNode;
for (std::size_t rotation = 3U; rotation < 5U; ++rotation) {
const double diagonal =
physical_local(offset + rotation, offset + rotation);
if (std::isfinite(diagonal) && diagonal > 0.0) {
drilling_reference = (std::min)(drilling_reference, diagonal);
has_drilling_reference = true;
}
}
}
if (!has_drilling_reference) {
return StiffnessFailure(
source_location_, identity_,
"MITC4 drilling stabilization requires a finite positive physical "
"tangent-rotation diagonal.");
}
if (!IsFinite(physical_local)) {
return StiffnessFailure(
source_location_, identity_,
"MITC4 physical stiffness must contain only finite values.");
}
const double drilling_stiffness = 1.0e-3 * drilling_reference;
if (!std::isfinite(drilling_stiffness) || !(drilling_stiffness > 0.0)) {
return StiffnessFailure(
source_location_, identity_,
"MITC4 drilling stiffness must be finite and positive.");
}
Matrix physical_global =
Congruence(physical_local, PhysicalTransformation20());
Matrix drilling_local{kNodeCount, kNodeCount};
for (std::size_t node = 0U; node < kNodeCount; ++node) {
drilling_local(node, node) = drilling_stiffness;
}
Matrix drilling_global =
Congruence(drilling_local, DrillingTransformation4());
Matrix stabilized_global{kGlobalDofCount, kGlobalDofCount};
for (std::size_t row = 0U; row < kGlobalDofCount; ++row) {
for (std::size_t column = 0U; column < kGlobalDofCount; ++column) {
stabilized_global(row, column) =
physical_global(row, column) + drilling_global(row, column);
}
}
if (!IsFinite(physical_global) || !IsFinite(drilling_global) ||
!IsFinite(stabilized_global)) {
return StiffnessFailure(
source_location_, identity_,
"MITC4 transformed stiffness must contain only finite values.");
}
return Result<Mitc4Stiffness>::Success(
Mitc4Stiffness{std::move(physical_local), std::move(physical_global),
std::move(drilling_global), std::move(stabilized_global),
drilling_stiffness});
}
Result<Mitc4PhysicalRecovery> Mitc4Shell::RecoverPhysical(
const Vector& global_element_displacement24) const {
if (global_element_displacement24.Size() != kGlobalDofCount) {
return RecoveryFailure(
source_location_, identity_,
"MITC4 physical recovery requires exactly 24 global element DOFs.");
}
if (!IsFinite(global_element_displacement24)) {
return RecoveryFailure(
source_location_, identity_,
"MITC4 physical recovery displacement must be finite.");
}
const Vector physical_displacement =
PhysicalTransformation20().Multiply(global_element_displacement24);
const Matrix tying_samples = CovariantTyingShearSamples20();
const Matrix constitutive = MaterialConstitutive5();
const Matrix plane_stress = PlaneStressConstitutive();
const double gauss = 1.0 / std::sqrt(3.0);
const std::array<std::array<double, 2>, 4> surface_points{
std::array<double, 2>{-gauss, -gauss},
std::array<double, 2>{gauss, -gauss}, std::array<double, 2>{gauss, gauss},
std::array<double, 2>{-gauss, gauss}};
constexpr std::array<double, 2> thickness_points{-1.0, 1.0};
constexpr std::array<double, 3> section_positions{-1.0, 0.0, 1.0};
Mitc4PhysicalRecovery recovery{};
for (std::size_t surface = 0U; surface < surface_points.size(); ++surface) {
auto& point = recovery.points[surface];
point.natural_coordinates = surface_points[surface];
GeometryData midsurface_geometry{};
if (!EvaluateGeometry(point.natural_coordinates[0],
point.natural_coordinates[1], 0.0,
midsurface_geometry)) {
return RecoveryFailure(source_location_, identity_,
"MITC4 midsurface recovery geometry is invalid.");
}
point.local_frame = midsurface_geometry.frame;
for (double thickness_sign : thickness_points) {
const double zeta = thickness_sign * gauss;
const Matrix strain_matrix = StrainDisplacement(
point.natural_coordinates[0], point.natural_coordinates[1], zeta,
&tying_samples);
const Vector strain = strain_matrix.Multiply(physical_displacement);
const Vector stress = constitutive.Multiply(strain);
GeometryData geometry{};
if (!EvaluateGeometry(point.natural_coordinates[0],
point.natural_coordinates[1], zeta, geometry)) {
return RecoveryFailure(
source_location_, identity_,
"Validated MITC4 recovery geometry became invalid.");
}
for (std::size_t component = 0U; component < 3U; ++component) {
point.generalized_strain[component] += 0.5 * strain[component];
point.generalized_strain[3U + component] +=
3.0 * zeta * strain[component] / thickness_;
point.section_resultant[component] +=
0.5 * thickness_ * stress[component];
point.section_resultant[3U + component] +=
0.25 * thickness_ * thickness_ * zeta * stress[component];
}
for (std::size_t component = 0U; component < 2U; ++component) {
point.generalized_strain[6U + component] +=
0.5 * strain[3U + component];
point.section_resultant[6U + component] +=
0.5 * thickness_ * stress[3U + component];
}
recovery.strain_energy += 0.5 * strain.Dot(stress) * geometry.jacobian;
}
for (std::size_t position = 0U; position < section_positions.size();
++position) {
GeometryData section_geometry{};
if (!EvaluateGeometry(point.natural_coordinates[0],
point.natural_coordinates[1],
section_positions[position], section_geometry)) {
return RecoveryFailure(
source_location_, identity_,
"MITC4 section-position recovery geometry is invalid.");
}
const Vector strain =
StrainDisplacement(point.natural_coordinates[0],
point.natural_coordinates[1],
section_positions[position], &tying_samples)
.Multiply(physical_displacement);
Vector in_plane_strain{3U};
for (std::size_t component = 0U; component < 3U; ++component) {
in_plane_strain[component] = strain[component];
}
const Vector stress = plane_stress.Multiply(in_plane_strain);
for (std::size_t component = 0U; component < 3U; ++component) {
point.in_plane_stress[position][component] = stress[component];
}
}
}
if (!std::isfinite(recovery.strain_energy)) {
return RecoveryFailure(source_location_, identity_,
"MITC4 physical strain energy must be finite.");
}
for (const auto& point : recovery.points) {
const auto finite = [](const auto& values) {
return std::all_of(values.begin(), values.end(),
[](double value) { return std::isfinite(value); });
};
if (!finite(point.generalized_strain) || !finite(point.section_resultant) ||
std::any_of(
point.in_plane_stress.begin(), point.in_plane_stress.end(),
[&finite](const auto& stress) { return !finite(stress); })) {
return RecoveryFailure(source_location_, identity_,
"MITC4 physical recovery values must be finite.");
}
}
return Result<Mitc4PhysicalRecovery>::Success(std::move(recovery));
}
Mitc4Shell::Mitc4Shell(std::array<Vector3, 4> coordinates,
std::array<Vector3, 4> directors,
std::array<Vector3, 4> tangent_a,
std::array<Vector3, 4> tangent_b,
Vector3 normal_candidate, double thickness,
double youngs_modulus, double poisson_ratio,
SourceLocation source_location, std::string identity)
: coordinates_{std::move(coordinates)},
directors_{std::move(directors)},
tangent_a_{std::move(tangent_a)},
tangent_b_{std::move(tangent_b)},
normal_candidate_{std::move(normal_candidate)},
thickness_{thickness},
youngs_modulus_{youngs_modulus},
poisson_ratio_{poisson_ratio},
source_location_{std::move(source_location)},
identity_{std::move(identity)} {}
bool Mitc4Shell::EvaluateGeometry(double xi, double eta, double zeta,
GeometryData& result) const noexcept {
const auto shape = ShapeFunctions(xi, eta);
const Vector3 midsurface_xi =
DerivativeSum(shape.xi_derivatives, coordinates_);
const Vector3 midsurface_eta =
DerivativeSum(shape.eta_derivatives, coordinates_);
const Vector3 director_xi = DerivativeSum(shape.xi_derivatives, directors_);
const Vector3 director_eta = DerivativeSum(shape.eta_derivatives, directors_);
const Vector3 director_value = WeightedSum(shape.values, directors_);
const double half_thickness = 0.5 * thickness_;
result.covariant[0] = midsurface_xi + half_thickness * zeta * director_xi;
result.covariant[1] = midsurface_eta + half_thickness * zeta * director_eta;
result.covariant[2] = half_thickness * director_value;
result.jacobian =
result.covariant[0].Dot(result.covariant[1].Cross(result.covariant[2]));
if (!result.covariant[0].IsFinite() || !result.covariant[1].IsFinite() ||
!result.covariant[2].IsFinite() || !std::isfinite(result.jacobian) ||
!(result.jacobian > 0.0)) {
return false;
}
result.reciprocal[0] =
(1.0 / result.jacobian) * result.covariant[1].Cross(result.covariant[2]);
result.reciprocal[1] =
(1.0 / result.jacobian) * result.covariant[2].Cross(result.covariant[0]);
result.reciprocal[2] =
(1.0 / result.jacobian) * result.covariant[0].Cross(result.covariant[1]);
const Vector3 area = midsurface_xi.Cross(midsurface_eta);
const double director_norm = director_value.Norm();
if (!area.IsFinite() || !director_value.IsFinite() ||
!std::isfinite(director_norm) || !(director_norm > 0.0) ||
!(area.Dot(normal_candidate_) > 0.0)) {
return false;
}
const Vector3 e3 = (1.0 / director_norm) * director_value;
result.frame.e3 = e3.Components();
if (!(area.Dot(e3) > 0.0)) {
return false;
}
const Vector3 e1_candidate = midsurface_xi - midsurface_xi.Dot(e3) * e3;
const double e1_norm = e1_candidate.Norm();
if (!e1_candidate.IsFinite() || !std::isfinite(e1_norm) || !(e1_norm > 0.0)) {
return false;
}
const Vector3 e1 = (1.0 / e1_norm) * e1_candidate;
const Vector3 e2 = e3.Cross(e1);
result.frame.e1 = e1.Components();
result.frame.e2 = e2.Components();
return result.reciprocal[0].IsFinite() && result.reciprocal[1].IsFinite() &&
result.reciprocal[2].IsFinite() && e2.IsFinite();
}
std::array<std::array<Vector3, 3>, 20> Mitc4Shell::BasisDerivatives(
double xi, double eta, double zeta) const noexcept {
const auto shape = ShapeFunctions(xi, eta);
const double half_thickness = 0.5 * thickness_;
std::array<std::array<Vector3, 3>, kPhysicalDofCount> derivatives{};
constexpr std::array<Vector3, 3> global_axes{
Vector3{1.0, 0.0, 0.0}, Vector3{0.0, 1.0, 0.0}, Vector3{0.0, 0.0, 1.0}};
for (std::size_t node = 0U; node < kNodeCount; ++node) {
const std::size_t offset = node * kPhysicalDofsPerNode;
for (std::size_t component = 0U; component < 3U; ++component) {
derivatives[offset + component][0] =
shape.xi_derivatives[node] * global_axes[component];
derivatives[offset + component][1] =
shape.eta_derivatives[node] * global_axes[component];
}
const Vector3 alpha_direction = -half_thickness * tangent_b_[node];
derivatives[offset + 3U][0] =
zeta * shape.xi_derivatives[node] * alpha_direction;
derivatives[offset + 3U][1] =
zeta * shape.eta_derivatives[node] * alpha_direction;
derivatives[offset + 3U][2] = shape.values[node] * alpha_direction;
const Vector3 beta_direction = half_thickness * tangent_a_[node];
derivatives[offset + 4U][0] =
zeta * shape.xi_derivatives[node] * beta_direction;
derivatives[offset + 4U][1] =
zeta * shape.eta_derivatives[node] * beta_direction;
derivatives[offset + 4U][2] = shape.values[node] * beta_direction;
}
return derivatives;
}
Matrix Mitc4Shell::StrainDisplacement(double xi, double eta, double zeta,
const Matrix* tying_samples) const {
GeometryData geometry{};
if (!EvaluateGeometry(xi, eta, zeta, geometry)) {
throw std::invalid_argument{
"MITC4 strain geometry is invalid at the requested point."};
}
const auto derivatives = BasisDerivatives(xi, eta, zeta);
const Mitc4TyingWeights weights = TyingWeights(xi, eta);
Matrix result{5U, kPhysicalDofCount};
for (std::size_t dof = 0U; dof < kPhysicalDofCount; ++dof) {
auto covariant =
CovariantStrainColumn(geometry.covariant, derivatives[dof]);
if (tying_samples != nullptr) {
covariant[0U][2U] = weights.xi_zeta[0] * (*tying_samples)(0U, dof) +
weights.xi_zeta[1] * (*tying_samples)(1U, dof);
covariant[2U][0U] = covariant[0U][2U];
covariant[1U][2U] = weights.eta_zeta[0] * (*tying_samples)(2U, dof) +
weights.eta_zeta[1] * (*tying_samples)(3U, dof);
covariant[2U][1U] = covariant[1U][2U];
}
const auto engineering = LocalEngineeringComponents(
ReconstructCartesianStrain(covariant, geometry.reciprocal),
geometry.frame);
for (std::size_t component = 0U; component < engineering.size();
++component) {
result(component, dof) = engineering[component];
}
}
return result;
}
} // namespace fesa