feat(linear-static-mitc4-shell): step 5 - mitc4-physical-recovery
This commit is contained in:
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
#include "fesa/core/status.hpp"
|
#include "fesa/core/status.hpp"
|
||||||
#include "fesa/math/matrix.hpp"
|
#include "fesa/math/matrix.hpp"
|
||||||
|
#include "fesa/math/vector.hpp"
|
||||||
#include "fesa/model/model_types.hpp"
|
#include "fesa/model/model_types.hpp"
|
||||||
|
|
||||||
#include <array>
|
#include <array>
|
||||||
@@ -39,8 +40,21 @@ struct Mitc4Stiffness {
|
|||||||
double drillingStiffness;
|
double drillingStiffness;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Concrete small-rotation MITC4 kinematics, constitutive, and stiffness kernel.
|
struct Mitc4PhysicalRecoveryPoint {
|
||||||
// Global equation ownership, physical recovery, and assembly remain outside.
|
std::array<double, 2> naturalCoordinates;
|
||||||
|
Mitc4LocalFrame localFrame;
|
||||||
|
std::array<double, 8> generalizedStrain;
|
||||||
|
std::array<double, 8> sectionResultant;
|
||||||
|
std::array<std::array<double, 3>, 3> inPlaneStress;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Mitc4PhysicalRecovery {
|
||||||
|
std::array<Mitc4PhysicalRecoveryPoint, 4> points;
|
||||||
|
double strainEnergy;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Concrete small-rotation MITC4 kinematics, constitutive, stiffness, and
|
||||||
|
// physical-only recovery kernel. Global equation/result ownership remains outside.
|
||||||
class Mitc4Shell {
|
class Mitc4Shell {
|
||||||
public:
|
public:
|
||||||
static Result<Mitc4Shell> create(
|
static Result<Mitc4Shell> create(
|
||||||
@@ -73,6 +87,8 @@ public:
|
|||||||
[[nodiscard]] Matrix bendingSectionMatrix() const;
|
[[nodiscard]] Matrix bendingSectionMatrix() const;
|
||||||
[[nodiscard]] Matrix transverseShearSectionMatrix() const;
|
[[nodiscard]] Matrix transverseShearSectionMatrix() const;
|
||||||
[[nodiscard]] Result<Mitc4Stiffness> stiffness() const;
|
[[nodiscard]] Result<Mitc4Stiffness> stiffness() const;
|
||||||
|
[[nodiscard]] Result<Mitc4PhysicalRecovery> recoverPhysical(
|
||||||
|
const Vector& globalElementDisplacement24) const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
using Vector3 = std::array<double, 3>;
|
using Vector3 = std::array<double, 3>;
|
||||||
|
|||||||
@@ -249,6 +249,15 @@ bool isFinite(const Matrix& matrix) {
|
|||||||
return true;
|
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(
|
Result<Mitc4Stiffness> stiffnessFailure(
|
||||||
const SourceLocation& location,
|
const SourceLocation& location,
|
||||||
const std::string& identity,
|
const std::string& identity,
|
||||||
@@ -263,6 +272,20 @@ Result<Mitc4Stiffness> stiffnessFailure(
|
|||||||
std::move(message)}}));
|
std::move(message)}}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Result<Mitc4PhysicalRecovery> recoveryFailure(
|
||||||
|
const SourceLocation& location,
|
||||||
|
const std::string& identity,
|
||||||
|
std::string message) {
|
||||||
|
return Result<Mitc4PhysicalRecovery>::failure(Status::failure(
|
||||||
|
FailureCategory::model,
|
||||||
|
{{Severity::error,
|
||||||
|
"invalid-shell-recovery",
|
||||||
|
location,
|
||||||
|
"*ELEMENT",
|
||||||
|
identity,
|
||||||
|
std::move(message)}}));
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
Result<Mitc4Shell> Mitc4Shell::create(
|
Result<Mitc4Shell> Mitc4Shell::create(
|
||||||
@@ -673,6 +696,132 @@ Result<Mitc4Stiffness> Mitc4Shell::stiffness() const {
|
|||||||
drillingStiffness});
|
drillingStiffness});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Result<Mitc4PhysicalRecovery> Mitc4Shell::recoverPhysical(
|
||||||
|
const Vector& globalElementDisplacement24) const {
|
||||||
|
if (globalElementDisplacement24.size() != kGlobalDofCount) {
|
||||||
|
return recoveryFailure(
|
||||||
|
sourceLocation_, identity_,
|
||||||
|
"MITC4 physical recovery requires exactly 24 global element DOFs.");
|
||||||
|
}
|
||||||
|
if (!isFinite(globalElementDisplacement24)) {
|
||||||
|
return recoveryFailure(
|
||||||
|
sourceLocation_, identity_,
|
||||||
|
"MITC4 physical recovery displacement must be finite.");
|
||||||
|
}
|
||||||
|
|
||||||
|
const Vector physicalDisplacement =
|
||||||
|
physicalTransformation20().multiply(globalElementDisplacement24);
|
||||||
|
const Matrix tyingSamples = covariantTyingShearSamples20();
|
||||||
|
const Matrix constitutive = materialConstitutive5();
|
||||||
|
const Matrix planeStress = planeStressConstitutive();
|
||||||
|
const double gauss = 1.0 / std::sqrt(3.0);
|
||||||
|
const std::array<std::array<double, 2>, 4> surfacePoints{
|
||||||
|
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> thicknessPoints{-1.0, 1.0};
|
||||||
|
constexpr std::array<double, 3> sectionPositions{-1.0, 0.0, 1.0};
|
||||||
|
|
||||||
|
Mitc4PhysicalRecovery recovery{};
|
||||||
|
for (std::size_t surface = 0U; surface < surfacePoints.size(); ++surface) {
|
||||||
|
auto& point = recovery.points[surface];
|
||||||
|
point.naturalCoordinates = surfacePoints[surface];
|
||||||
|
GeometryData midsurfaceGeometry{};
|
||||||
|
if (!evaluateGeometry(
|
||||||
|
point.naturalCoordinates[0], point.naturalCoordinates[1], 0.0,
|
||||||
|
midsurfaceGeometry)) {
|
||||||
|
return recoveryFailure(
|
||||||
|
sourceLocation_, identity_,
|
||||||
|
"MITC4 midsurface recovery geometry is invalid.");
|
||||||
|
}
|
||||||
|
point.localFrame = midsurfaceGeometry.frame;
|
||||||
|
|
||||||
|
for (double thicknessSign : thicknessPoints) {
|
||||||
|
const double zeta = thicknessSign * gauss;
|
||||||
|
const Matrix strainMatrix = strainDisplacement(
|
||||||
|
point.naturalCoordinates[0], point.naturalCoordinates[1], zeta,
|
||||||
|
&tyingSamples);
|
||||||
|
const Vector strain = strainMatrix.multiply(physicalDisplacement);
|
||||||
|
const Vector stress = constitutive.multiply(strain);
|
||||||
|
GeometryData geometry{};
|
||||||
|
if (!evaluateGeometry(
|
||||||
|
point.naturalCoordinates[0], point.naturalCoordinates[1],
|
||||||
|
zeta, geometry)) {
|
||||||
|
return recoveryFailure(
|
||||||
|
sourceLocation_, identity_,
|
||||||
|
"Validated MITC4 recovery geometry became invalid.");
|
||||||
|
}
|
||||||
|
|
||||||
|
for (std::size_t component = 0U; component < 3U; ++component) {
|
||||||
|
point.generalizedStrain[component] += 0.5 * strain[component];
|
||||||
|
point.generalizedStrain[3U + component] +=
|
||||||
|
3.0 * zeta * strain[component] / thickness_;
|
||||||
|
point.sectionResultant[component] +=
|
||||||
|
0.5 * thickness_ * stress[component];
|
||||||
|
point.sectionResultant[3U + component] +=
|
||||||
|
0.25 * thickness_ * thickness_ * zeta * stress[component];
|
||||||
|
}
|
||||||
|
for (std::size_t component = 0U; component < 2U; ++component) {
|
||||||
|
point.generalizedStrain[6U + component] +=
|
||||||
|
0.5 * strain[3U + component];
|
||||||
|
point.sectionResultant[6U + component] +=
|
||||||
|
0.5 * thickness_ * stress[3U + component];
|
||||||
|
}
|
||||||
|
recovery.strainEnergy +=
|
||||||
|
0.5 * strain.dot(stress) * geometry.jacobian;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (std::size_t position = 0U;
|
||||||
|
position < sectionPositions.size(); ++position) {
|
||||||
|
GeometryData sectionGeometry{};
|
||||||
|
if (!evaluateGeometry(
|
||||||
|
point.naturalCoordinates[0], point.naturalCoordinates[1],
|
||||||
|
sectionPositions[position], sectionGeometry)) {
|
||||||
|
return recoveryFailure(
|
||||||
|
sourceLocation_, identity_,
|
||||||
|
"MITC4 section-position recovery geometry is invalid.");
|
||||||
|
}
|
||||||
|
const Vector strain = strainDisplacement(
|
||||||
|
point.naturalCoordinates[0], point.naturalCoordinates[1],
|
||||||
|
sectionPositions[position], &tyingSamples)
|
||||||
|
.multiply(physicalDisplacement);
|
||||||
|
Vector inPlaneStrain{3U};
|
||||||
|
for (std::size_t component = 0U; component < 3U; ++component) {
|
||||||
|
inPlaneStrain[component] = strain[component];
|
||||||
|
}
|
||||||
|
const Vector stress = planeStress.multiply(inPlaneStrain);
|
||||||
|
for (std::size_t component = 0U; component < 3U; ++component) {
|
||||||
|
point.inPlaneStress[position][component] = stress[component];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!std::isfinite(recovery.strainEnergy)) {
|
||||||
|
return recoveryFailure(
|
||||||
|
sourceLocation_, 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.generalizedStrain) ||
|
||||||
|
!finite(point.sectionResultant) ||
|
||||||
|
std::any_of(
|
||||||
|
point.inPlaneStress.begin(), point.inPlaneStress.end(),
|
||||||
|
[&finite](const auto& stress) { return !finite(stress); })) {
|
||||||
|
return recoveryFailure(
|
||||||
|
sourceLocation_, identity_,
|
||||||
|
"MITC4 physical recovery values must be finite.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result<Mitc4PhysicalRecovery>::success(std::move(recovery));
|
||||||
|
}
|
||||||
|
|
||||||
Mitc4Shell::Mitc4Shell(
|
Mitc4Shell::Mitc4Shell(
|
||||||
std::array<Vector3, 4> coordinates,
|
std::array<Vector3, 4> coordinates,
|
||||||
std::array<Vector3, 4> directors,
|
std::array<Vector3, 4> directors,
|
||||||
|
|||||||
@@ -823,3 +823,114 @@ TEST(Mitc4ShellDrilling, FailsNonfiniteReferenceAndStabilizesEachPureDrillCoordi
|
|||||||
repeatedFailure.status().diagnostics()[0].message,
|
repeatedFailure.status().diagnostics()[0].message,
|
||||||
failure.status().diagnostics()[0].message);
|
failure.status().diagnostics()[0].message);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MITC4-KERNEL-007
|
||||||
|
TEST(Mitc4ShellDrilling, ExcludesPureDrillFromPhysicalRecoveryAndEnergy) {
|
||||||
|
const auto nodes = planarNodes();
|
||||||
|
const auto shellCandidate = fesa::Mitc4Shell::create(
|
||||||
|
nodePointers(nodes), directors(), section(), material());
|
||||||
|
ASSERT_TRUE(shellCandidate.hasValue());
|
||||||
|
const auto& shell = shellCandidate.value();
|
||||||
|
const auto stiffnessCandidate = shell.stiffness();
|
||||||
|
ASSERT_TRUE(stiffnessCandidate.hasValue());
|
||||||
|
|
||||||
|
for (std::size_t nodeIndex = 0U; nodeIndex < nodes.size(); ++nodeIndex) {
|
||||||
|
fesa::Vector pureDrill{24U};
|
||||||
|
pureDrill[6U * nodeIndex + 5U] = 1.0;
|
||||||
|
EXPECT_GT(
|
||||||
|
stiffnessCandidate.value().stabilizedGlobal24.multiply(pureDrill).norm(),
|
||||||
|
0.0);
|
||||||
|
|
||||||
|
const auto recoveryCandidate = shell.recoverPhysical(pureDrill);
|
||||||
|
ASSERT_TRUE(recoveryCandidate.hasValue());
|
||||||
|
const auto& recovery = recoveryCandidate.value();
|
||||||
|
EXPECT_DOUBLE_EQ(recovery.strainEnergy, 0.0);
|
||||||
|
for (const auto& point : recovery.points) {
|
||||||
|
for (double value : point.generalizedStrain) {
|
||||||
|
EXPECT_DOUBLE_EQ(value, 0.0);
|
||||||
|
}
|
||||||
|
for (double value : point.sectionResultant) {
|
||||||
|
EXPECT_DOUBLE_EQ(value, 0.0);
|
||||||
|
}
|
||||||
|
for (const auto& stress : point.inPlaneStress) {
|
||||||
|
for (double value : stress) {
|
||||||
|
EXPECT_DOUBLE_EQ(value, 0.0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MITC4-PHYSREC-001
|
||||||
|
TEST(Mitc4ShellPhysicalRecovery, RecoversHandFieldAtFixedLocationsAndSectionPositions) {
|
||||||
|
const auto nodes = planarNodes();
|
||||||
|
const auto shellCandidate = fesa::Mitc4Shell::create(
|
||||||
|
nodePointers(nodes), directors(), section(), material());
|
||||||
|
ASSERT_TRUE(shellCandidate.hasValue());
|
||||||
|
const auto& shell = shellCandidate.value();
|
||||||
|
|
||||||
|
constexpr std::array<double, 8> generalized{
|
||||||
|
0.1, -0.05, 0.2, 0.3, -0.15, 0.25, 0.4, -0.3};
|
||||||
|
fesa::Vector globalField{24U};
|
||||||
|
for (std::size_t nodeIndex = 0U; nodeIndex < nodes.size(); ++nodeIndex) {
|
||||||
|
const double x = nodes[nodeIndex].coordinates[0];
|
||||||
|
const double y = nodes[nodeIndex].coordinates[1];
|
||||||
|
const std::size_t offset = 6U * nodeIndex;
|
||||||
|
globalField[offset] = generalized[0] * x + 0.5 * generalized[2] * y;
|
||||||
|
globalField[offset + 1U] =
|
||||||
|
generalized[1] * y + 0.5 * generalized[2] * x;
|
||||||
|
globalField[offset + 2U] =
|
||||||
|
generalized[6] * x + generalized[7] * y -
|
||||||
|
0.5 * generalized[5] * x * y;
|
||||||
|
globalField[offset + 3U] =
|
||||||
|
-generalized[4] * y - 0.5 * generalized[5] * x;
|
||||||
|
globalField[offset + 4U] =
|
||||||
|
generalized[3] * x + 0.5 * generalized[5] * y;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto recoveryCandidate = shell.recoverPhysical(globalField);
|
||||||
|
ASSERT_TRUE(recoveryCandidate.hasValue());
|
||||||
|
const auto& recovery = recoveryCandidate.value();
|
||||||
|
const double gauss = 1.0 / std::sqrt(3.0);
|
||||||
|
const std::array<std::array<double, 2>, 4> expectedCoordinates{
|
||||||
|
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, 8> expectedResultant{
|
||||||
|
22.4, -6.4, 19.2, 22.4, -6.4, 8.0, 32.0, -24.0};
|
||||||
|
constexpr std::array<std::array<double, 3>, 3> expectedStress{
|
||||||
|
std::array<double, 3>{-22.4, 6.4, -2.4},
|
||||||
|
std::array<double, 3>{11.2, -3.2, 9.6},
|
||||||
|
std::array<double, 3>{44.8, -12.8, 21.6}};
|
||||||
|
|
||||||
|
ASSERT_EQ(recovery.points.size(), expectedCoordinates.size());
|
||||||
|
for (std::size_t pointIndex = 0U;
|
||||||
|
pointIndex < recovery.points.size(); ++pointIndex) {
|
||||||
|
const auto& point = recovery.points[pointIndex];
|
||||||
|
EXPECT_EQ(point.naturalCoordinates, expectedCoordinates[pointIndex]);
|
||||||
|
expectOrthonormalRightHanded(point.localFrame);
|
||||||
|
expectVectorNear(point.localFrame.e1, {1.0, 0.0, 0.0});
|
||||||
|
expectVectorNear(point.localFrame.e2, {0.0, 1.0, 0.0});
|
||||||
|
expectVectorNear(point.localFrame.e3, {0.0, 0.0, 1.0});
|
||||||
|
for (std::size_t component = 0U;
|
||||||
|
component < generalized.size(); ++component) {
|
||||||
|
EXPECT_NEAR(
|
||||||
|
point.generalizedStrain[component], generalized[component],
|
||||||
|
1.0e-12);
|
||||||
|
EXPECT_NEAR(
|
||||||
|
point.sectionResultant[component], expectedResultant[component],
|
||||||
|
1.0e-12);
|
||||||
|
}
|
||||||
|
for (std::size_t position = 0U;
|
||||||
|
position < expectedStress.size(); ++position) {
|
||||||
|
for (std::size_t component = 0U;
|
||||||
|
component < expectedStress[position].size(); ++component) {
|
||||||
|
EXPECT_NEAR(
|
||||||
|
point.inPlaneStress[position][component],
|
||||||
|
expectedStress[position][component], 1.0e-12);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
EXPECT_NEAR(recovery.strainEnergy, 72.16, 1.0e-12);
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user