feat(cpp-object-oriented-modular-refactoring): step 8 - element-geometry-vector3

This commit is contained in:
KOKO\Mimi
2026-08-16 07:27:51 +09:00
parent cbad5c3592
commit a9ff75b3fa
9 changed files with 320 additions and 233 deletions
+1 -2
View File
@@ -7,6 +7,7 @@
#include "fesa/core/status.h" #include "fesa/core/status.h"
#include "fesa/math/matrix.h" #include "fesa/math/matrix.h"
#include "fesa/math/vector.h" #include "fesa/math/vector.h"
#include "fesa/math/vector3.h"
#include "fesa/model/model_types.h" #include "fesa/model/model_types.h"
namespace fesa { namespace fesa {
@@ -132,8 +133,6 @@ class Mitc4Shell {
const Vector& global_element_displacement24) const; const Vector& global_element_displacement24) const;
private: private:
using Vector3 = std::array<double, 3>;
/// @brief Stores covariant, reciprocal, frame, and Jacobian data at one /// @brief Stores covariant, reciprocal, frame, and Jacobian data at one
/// point. /// point.
struct GeometryData { struct GeometryData {
+26 -1
View File
@@ -21,6 +21,10 @@ class Vector3 {
constexpr Vector3(double x, double y, double z) noexcept constexpr Vector3(double x, double y, double z) noexcept
: components_{{x, y, z}} {} : components_{{x, y, z}} {}
/// @brief Copies components from an existing array-backed carrier.
explicit constexpr Vector3(const std::array<double, 3>& components) noexcept
: components_{components} {}
/// @brief Returns the first component. /// @brief Returns the first component.
constexpr double X() const noexcept { return components_[0]; } constexpr double X() const noexcept { return components_[0]; }
@@ -36,6 +40,11 @@ class Vector3 {
return components_[index]; return components_[index];
} }
/// @brief Returns the immutable array-backed component carrier.
constexpr const std::array<double, 3>& Components() const noexcept {
return components_;
}
/// @brief Adds corresponding vector components. /// @brief Adds corresponding vector components.
constexpr Vector3 operator+(const Vector3& rhs) const noexcept { constexpr Vector3 operator+(const Vector3& rhs) const noexcept {
return Vector3{X() + rhs.X(), Y() + rhs.Y(), Z() + rhs.Z()}; return Vector3{X() + rhs.X(), Y() + rhs.Y(), Z() + rhs.Z()};
@@ -51,6 +60,22 @@ class Vector3 {
return Vector3{X() * scalar, Y() * scalar, Z() * scalar}; return Vector3{X() * scalar, Y() * scalar, Z() * scalar};
} }
/// @brief Divides every component by a scalar.
constexpr Vector3 operator/(double scalar) const noexcept {
return Vector3{X() / scalar, Y() / scalar, Z() / scalar};
}
/// @brief Multiplies every component with the scalar as the left operand.
friend constexpr Vector3 operator*(double scalar,
const Vector3& rhs) noexcept {
return Vector3{scalar * rhs.X(), scalar * rhs.Y(), scalar * rhs.Z()};
}
/// @brief Compares every component exactly.
constexpr bool operator==(const Vector3& rhs) const noexcept {
return X() == rhs.X() && Y() == rhs.Y() && Z() == rhs.Z();
}
/// @brief Computes the Euclidean dot product with rhs. /// @brief Computes the Euclidean dot product with rhs.
double Dot(const Vector3& rhs) const noexcept { double Dot(const Vector3& rhs) const noexcept {
return X() * rhs.X() + Y() * rhs.Y() + Z() * rhs.Z(); return X() * rhs.X() + Y() * rhs.Y() + Z() * rhs.Z();
@@ -63,7 +88,7 @@ class Vector3 {
} }
/// @brief Computes the Euclidean norm. /// @brief Computes the Euclidean norm.
double Norm() const noexcept { return std::sqrt(Dot(*this)); } double Norm() const noexcept { return std::hypot(X(), Y(), Z()); }
/// @brief Returns a unit vector when the norm is usable. /// @brief Returns a unit vector when the norm is usable.
/// @return Empty when the norm is exactly zero or nonfinite. /// @return Empty when the norm is exactly zero or nonfinite.
+18 -39
View File
@@ -9,6 +9,8 @@
#include <utility> #include <utility>
#include <vector> #include <vector>
#include "fesa/math/vector3.h"
namespace fesa { namespace fesa {
namespace { namespace {
@@ -17,26 +19,6 @@ constexpr std::size_t kGeneralizedComponentCount = 4U;
constexpr double kGeometryTolerance = 1.0e-12; constexpr double kGeometryTolerance = 1.0e-12;
constexpr double kStiffnessInvariantTolerance = 1.0e-12; constexpr double kStiffnessInvariantTolerance = 1.0e-12;
using Vector3 = std::array<double, 3>;
double Norm(const Vector3& value) {
return std::hypot(value[0], value[1], value[2]);
}
double Dot(const Vector3& lhs, const Vector3& rhs) {
return lhs[0] * rhs[0] + lhs[1] * rhs[1] + lhs[2] * rhs[2];
}
Vector3 Cross(const Vector3& lhs, const Vector3& rhs) {
return {lhs[1] * rhs[2] - lhs[2] * rhs[1], lhs[2] * rhs[0] - lhs[0] * rhs[2],
lhs[0] * rhs[1] - lhs[1] * rhs[0]};
}
bool IsFinite(const Vector3& value) {
return std::isfinite(value[0]) && std::isfinite(value[1]) &&
std::isfinite(value[2]);
}
std::string ElementIdentity(const Node& first_node, const Node& second_node) { std::string ElementIdentity(const Node& first_node, const Node& second_node) {
return first_node.source_id.instance_name + ":" + return first_node.source_id.instance_name + ":" +
first_node.source_id.source_label_text + "-" + first_node.source_id.source_label_text + "-" +
@@ -233,13 +215,13 @@ Result<EulerBeam3D> EulerBeam3D::Create(const Node& first_node,
const GeneralBeamSection& section, const GeneralBeamSection& section,
const LinearElasticMaterial& material) { const LinearElasticMaterial& material) {
const std::string identity = ElementIdentity(first_node, second_node); const std::string identity = ElementIdentity(first_node, second_node);
const Vector3& first = first_node.coordinates; const Vector3 first{first_node.coordinates};
const Vector3& second = second_node.coordinates; const Vector3 second{second_node.coordinates};
const Vector3 delta = {second[0] - first[0], second[1] - first[1], const Vector3 delta = second - first;
second[2] - first[2]}; const double length = delta.Norm();
const double length = Norm(delta); const double coordinate_scale =
const double coordinate_scale = (std::max)({1.0, Norm(first), Norm(second)}); (std::max)({1.0, first.Norm(), second.Norm()});
if (!IsFinite(first) || !IsFinite(second) || !IsFinite(delta) || if (!first.IsFinite() || !second.IsFinite() || !delta.IsFinite() ||
!std::isfinite(length) || !std::isfinite(coordinate_scale) || !std::isfinite(length) || !std::isfinite(coordinate_scale) ||
!(length > kGeometryTolerance * coordinate_scale)) { !(length > kGeometryTolerance * coordinate_scale)) {
return ModelFailure( return ModelFailure(
@@ -247,15 +229,13 @@ Result<EulerBeam3D> EulerBeam3D::Create(const Node& first_node,
"Beam length must exceed the scale-aware geometry threshold."); "Beam length must exceed the scale-aware geometry threshold.");
} }
const Vector3 ex = {delta[0] / length, delta[1] / length, delta[2] / length}; const Vector3 ex = delta / length;
const Vector3& guide = section.first_axis; const Vector3 guide{section.first_axis};
const double guide_norm = Norm(guide); const double guide_norm = guide.Norm();
const double guide_projection = Dot(guide, ex); const double guide_projection = guide.Dot(ex);
const Vector3 ey_trial = {guide[0] - guide_projection * ex[0], const Vector3 ey_trial = guide - guide_projection * ex;
guide[1] - guide_projection * ex[1], const double ey_trial_norm = ey_trial.Norm();
guide[2] - guide_projection * ex[2]}; if (!guide.IsFinite() || !std::isfinite(guide_norm) || !ey_trial.IsFinite() ||
const double ey_trial_norm = Norm(ey_trial);
if (!IsFinite(guide) || !std::isfinite(guide_norm) || !IsFinite(ey_trial) ||
!std::isfinite(ey_trial_norm) || !std::isfinite(ey_trial_norm) ||
!(ey_trial_norm > kGeometryTolerance * (std::max)(1.0, guide_norm))) { !(ey_trial_norm > kGeometryTolerance * (std::max)(1.0, guide_norm))) {
return ModelFailure( return ModelFailure(
@@ -323,9 +303,8 @@ Result<EulerBeam3D> EulerBeam3D::Create(const Node& first_node,
"Derived beam stiffness coefficients must be finite and positive."); "Derived beam stiffness coefficients must be finite and positive.");
} }
const Vector3 ey = {ey_trial[0] / ey_trial_norm, ey_trial[1] / ey_trial_norm, const Vector3 ey = ey_trial / ey_trial_norm;
ey_trial[2] / ey_trial_norm}; const Vector3 ez = ex.Cross(ey);
const Vector3 ez = Cross(ex, ey);
// Rows map global vectors to the approved right-handed local (ex,ey,ez) // Rows map global vectors to the approved right-handed local (ex,ey,ez)
// basis. // basis.
const std::array<double, 9> rotation = {ex[0], ex[1], ex[2], ey[0], ey[1], const std::array<double, 9> rotation = {ex[0], ex[1], ex[2], ey[0], ey[1],
+72 -106
View File
@@ -10,8 +10,6 @@
namespace fesa { namespace fesa {
namespace { namespace {
using Vector3 = std::array<double, 3>;
constexpr std::size_t kNodeCount = 4U; constexpr std::size_t kNodeCount = 4U;
constexpr std::size_t kPhysicalDofsPerNode = 5U; constexpr std::size_t kPhysicalDofsPerNode = 5U;
constexpr std::size_t kGlobalDofsPerNode = 6U; constexpr std::size_t kGlobalDofsPerNode = 6U;
@@ -20,46 +18,11 @@ constexpr std::size_t kGlobalDofCount = 24U;
constexpr double kShearCorrection = 5.0 / 6.0; constexpr double kShearCorrection = 5.0 / 6.0;
constexpr double kFrameTolerance = 1.0e-12; constexpr double kFrameTolerance = 1.0e-12;
Vector3 Add(const Vector3& left, const Vector3& right) {
return {left[0] + right[0], left[1] + right[1], left[2] + right[2]};
}
Vector3 Subtract(const Vector3& left, const Vector3& right) {
return {left[0] - right[0], left[1] - right[1], left[2] - right[2]};
}
Vector3 Scale(double factor, const Vector3& value) {
return {factor * value[0], factor * value[1], factor * value[2]};
}
double Dot(const Vector3& left, const Vector3& right) {
return left[0] * right[0] + left[1] * right[1] + left[2] * right[2];
}
Vector3 Cross(const Vector3& left, const Vector3& right) {
return {left[1] * right[2] - left[2] * right[1],
left[2] * right[0] - left[0] * right[2],
left[0] * right[1] - left[1] * right[0]};
}
double Norm(const Vector3& value) {
return std::hypot(value[0], value[1], value[2]);
}
bool IsFinite(const Vector3& value) {
return std::all_of(value.begin(), value.end(),
[](double component) { return std::isfinite(component); });
}
Vector3 Normalized(const Vector3& value) {
return Scale(1.0 / Norm(value), value);
}
Vector3 WeightedSum(const std::array<double, kNodeCount>& weights, Vector3 WeightedSum(const std::array<double, kNodeCount>& weights,
const std::array<Vector3, kNodeCount>& values) { const std::array<Vector3, kNodeCount>& values) {
Vector3 result{}; Vector3 result{};
for (std::size_t node = 0U; node < kNodeCount; ++node) { for (std::size_t node = 0U; node < kNodeCount; ++node) {
result = Add(result, Scale(weights[node], values[node])); result = result + weights[node] * values[node];
} }
return result; return result;
} }
@@ -68,7 +31,7 @@ Vector3 DerivativeSum(const std::array<double, kNodeCount>& derivatives,
const std::array<Vector3, kNodeCount>& values) { const std::array<Vector3, kNodeCount>& values) {
std::array<Vector3, kNodeCount> relative{}; std::array<Vector3, kNodeCount> relative{};
for (std::size_t node = 0U; node < kNodeCount; ++node) { for (std::size_t node = 0U; node < kNodeCount; ++node) {
relative[node] = Subtract(values[node], values[0]); relative[node] = values[node] - values[0];
} }
return WeightedSum(derivatives, relative); return WeightedSum(derivatives, relative);
} }
@@ -86,18 +49,18 @@ std::array<Vector3, kNodeCount> NodalTangentsA(
std::array<Vector3, kNodeCount> tangents{}; std::array<Vector3, kNodeCount> tangents{};
for (std::size_t node = 0U; node < kNodeCount; ++node) { for (std::size_t node = 0U; node < kNodeCount; ++node) {
std::size_t selected = 0U; std::size_t selected = 0U;
double alignment = std::abs(Dot(global_axes[0], directors[node])); double alignment = std::abs(global_axes[0].Dot(directors[node]));
for (std::size_t axis = 1U; axis < global_axes.size(); ++axis) { for (std::size_t axis = 1U; axis < global_axes.size(); ++axis) {
const double candidate = const double candidate = std::abs(global_axes[axis].Dot(directors[node]));
std::abs(Dot(global_axes[axis], directors[node]));
if (candidate < alignment) { if (candidate < alignment) {
selected = axis; selected = axis;
alignment = candidate; alignment = candidate;
} }
} }
tangents[node] = Normalized(Subtract( const Vector3 tangent_candidate =
global_axes[selected], global_axes[selected] -
Scale(Dot(global_axes[selected], directors[node]), directors[node]))); global_axes[selected].Dot(directors[node]) * directors[node];
tangents[node] = (1.0 / tangent_candidate.Norm()) * tangent_candidate;
} }
return tangents; return tangents;
} }
@@ -108,7 +71,7 @@ std::array<Vector3, kNodeCount> NodalTangentsB(
const std::array<Vector3, kNodeCount>& tangent_a) { const std::array<Vector3, kNodeCount>& tangent_a) {
std::array<Vector3, kNodeCount> tangents{}; std::array<Vector3, kNodeCount> tangents{};
for (std::size_t node = 0U; node < kNodeCount; ++node) { for (std::size_t node = 0U; node < kNodeCount; ++node) {
tangents[node] = Cross(directors[node], tangent_a[node]); tangents[node] = directors[node].Cross(tangent_a[node]);
} }
return tangents; return tangents;
} }
@@ -144,9 +107,8 @@ std::array<std::array<double, 3>, 3> CovariantStrainColumn(
std::array<std::array<double, 3>, 3> strain{}; std::array<std::array<double, 3>, 3> strain{};
for (std::size_t first = 0U; first < 3U; ++first) { for (std::size_t first = 0U; first < 3U; ++first) {
for (std::size_t second = 0U; second < 3U; ++second) { for (std::size_t second = 0U; second < 3U; ++second) {
strain[first][second] = strain[first][second] = 0.5 * (covariant[first].Dot(derivatives[second]) +
0.5 * (Dot(covariant[first], derivatives[second]) + covariant[second].Dot(derivatives[first]));
Dot(covariant[second], derivatives[first]));
} }
} }
// Thickness stretch is excluded from the five-component shell law. // Thickness stretch is excluded from the five-component shell law.
@@ -190,11 +152,13 @@ double FrameComponent(const Vector3& left,
std::array<double, 5> LocalEngineeringComponents( std::array<double, 5> LocalEngineeringComponents(
const std::array<std::array<double, 3>, 3>& tensor, const std::array<std::array<double, 3>, 3>& tensor,
const Mitc4LocalFrame& frame) { const Mitc4LocalFrame& frame) {
return {FrameComponent(frame.e1, tensor, frame.e1), const Vector3 e1{frame.e1};
FrameComponent(frame.e2, tensor, frame.e2), const Vector3 e2{frame.e2};
2.0 * FrameComponent(frame.e1, tensor, frame.e2), const Vector3 e3{frame.e3};
2.0 * FrameComponent(frame.e1, tensor, frame.e3), return {FrameComponent(e1, tensor, e1), FrameComponent(e2, tensor, e2),
2.0 * FrameComponent(frame.e2, tensor, frame.e3)}; 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 ScaledMatrix(const Matrix& source, double factor) {
@@ -287,8 +251,8 @@ Result<Mitc4Shell> Mitc4Shell::Create(
std::array<Vector3, kNodeCount> coordinates{}; std::array<Vector3, kNodeCount> coordinates{};
for (std::size_t node = 0U; node < kNodeCount; ++node) { for (std::size_t node = 0U; node < kNodeCount; ++node) {
coordinates[node] = nodes[node]->coordinates; coordinates[node] = Vector3{nodes[node]->coordinates};
if (!IsFinite(coordinates[node])) { if (!coordinates[node].IsFinite()) {
return ModelFailure("invalid-shell-geometry", nodes[node]->location, return ModelFailure("invalid-shell-geometry", nodes[node]->location,
identity, "MITC4 node coordinates must be finite."); identity, "MITC4 node coordinates must be finite.");
} }
@@ -301,9 +265,12 @@ Result<Mitc4Shell> Mitc4Shell::Create(
} }
} }
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) { for (std::size_t node = 0U; node < kNodeCount; ++node) {
const double director_norm = Norm(initial_directors[node]); const double director_norm = directors[node].Norm();
if (!IsFinite(initial_directors[node]) || !std::isfinite(director_norm) || if (!directors[node].IsFinite() || !std::isfinite(director_norm) ||
std::abs(director_norm - 1.0) > kFrameTolerance) { std::abs(director_norm - 1.0) > kFrameTolerance) {
return ModelFailure( return ModelFailure(
"invalid-shell-director", nodes[node]->location, identity, "invalid-shell-director", nodes[node]->location, identity,
@@ -329,28 +296,28 @@ Result<Mitc4Shell> Mitc4Shell::Create(
DerivativeSum(center_shape.xi_derivatives, coordinates); DerivativeSum(center_shape.xi_derivatives, coordinates);
const Vector3 center_eta = const Vector3 center_eta =
DerivativeSum(center_shape.eta_derivatives, coordinates); DerivativeSum(center_shape.eta_derivatives, coordinates);
const Vector3 center_area = Cross(center_xi, center_eta); const Vector3 center_area = center_xi.Cross(center_eta);
const double center_measure = Norm(center_area); const double center_measure = center_area.Norm();
if (!IsFinite(center_area) || !std::isfinite(center_measure) || if (!center_area.IsFinite() || !std::isfinite(center_measure) ||
!(center_measure > 0.0)) { !(center_measure > 0.0)) {
return ModelFailure( return ModelFailure(
"invalid-shell-geometry", nodes[0]->location, identity, "invalid-shell-geometry", nodes[0]->location, identity,
"MITC4 center surface basis must be finite and nonzero."); "MITC4 center surface basis must be finite and nonzero.");
} }
const Vector3 normal_candidate = Scale(1.0 / center_measure, center_area); const Vector3 normal_candidate = (1.0 / center_measure) * center_area;
if (std::any_of(initial_directors.begin(), initial_directors.end(), if (std::any_of(directors.begin(), directors.end(),
[&normal_candidate](const Vector3& director) { [&normal_candidate](const Vector3& director) {
return !(Dot(normal_candidate, director) > 0.0); return !(normal_candidate.Dot(director) > 0.0);
})) { })) {
return ModelFailure( return ModelFailure(
"invalid-shell-director", nodes[0]->location, identity, "invalid-shell-director", nodes[0]->location, identity,
"MITC4 directors must follow the source-order positive face."); "MITC4 directors must follow the source-order positive face.");
} }
const auto tangent_a = NodalTangentsA(initial_directors); const auto tangent_a = NodalTangentsA(directors);
const auto tangent_b = NodalTangentsB(initial_directors, tangent_a); const auto tangent_b = NodalTangentsB(directors, tangent_a);
Mitc4Shell shell{coordinates, Mitc4Shell shell{coordinates,
initial_directors, directors,
tangent_a, tangent_a,
tangent_b, tangent_b,
normal_candidate, normal_candidate,
@@ -805,48 +772,47 @@ bool Mitc4Shell::EvaluateGeometry(double xi, double eta, double zeta,
const Vector3 director_value = WeightedSum(shape.values, directors_); const Vector3 director_value = WeightedSum(shape.values, directors_);
const double half_thickness = 0.5 * thickness_; const double half_thickness = 0.5 * thickness_;
result.covariant[0] = result.covariant[0] = midsurface_xi + half_thickness * zeta * director_xi;
Add(midsurface_xi, Scale(half_thickness * zeta, director_xi)); result.covariant[1] = midsurface_eta + half_thickness * zeta * director_eta;
result.covariant[1] = result.covariant[2] = half_thickness * director_value;
Add(midsurface_eta, Scale(half_thickness * zeta, director_eta));
result.covariant[2] = Scale(half_thickness, director_value);
result.jacobian = result.jacobian =
Dot(result.covariant[0], Cross(result.covariant[1], result.covariant[2])); result.covariant[0].Dot(result.covariant[1].Cross(result.covariant[2]));
if (!IsFinite(result.covariant[0]) || !IsFinite(result.covariant[1]) || if (!result.covariant[0].IsFinite() || !result.covariant[1].IsFinite() ||
!IsFinite(result.covariant[2]) || !std::isfinite(result.jacobian) || !result.covariant[2].IsFinite() || !std::isfinite(result.jacobian) ||
!(result.jacobian > 0.0)) { !(result.jacobian > 0.0)) {
return false; return false;
} }
result.reciprocal[0] = Scale(1.0 / result.jacobian, result.reciprocal[0] =
Cross(result.covariant[1], result.covariant[2])); (1.0 / result.jacobian) * result.covariant[1].Cross(result.covariant[2]);
result.reciprocal[1] = Scale(1.0 / result.jacobian, result.reciprocal[1] =
Cross(result.covariant[2], result.covariant[0])); (1.0 / result.jacobian) * result.covariant[2].Cross(result.covariant[0]);
result.reciprocal[2] = Scale(1.0 / result.jacobian, result.reciprocal[2] =
Cross(result.covariant[0], result.covariant[1])); (1.0 / result.jacobian) * result.covariant[0].Cross(result.covariant[1]);
const Vector3 area = Cross(midsurface_xi, midsurface_eta); const Vector3 area = midsurface_xi.Cross(midsurface_eta);
const double director_norm = Norm(director_value); const double director_norm = director_value.Norm();
if (!IsFinite(area) || !IsFinite(director_value) || if (!area.IsFinite() || !director_value.IsFinite() ||
!std::isfinite(director_norm) || !(director_norm > 0.0) || !std::isfinite(director_norm) || !(director_norm > 0.0) ||
!(Dot(area, normal_candidate_) > 0.0)) { !(area.Dot(normal_candidate_) > 0.0)) {
return false; return false;
} }
result.frame.e3 = Scale(1.0 / director_norm, director_value); const Vector3 e3 = (1.0 / director_norm) * director_value;
if (!(Dot(area, result.frame.e3) > 0.0)) { result.frame.e3 = e3.Components();
if (!(area.Dot(e3) > 0.0)) {
return false; return false;
} }
const Vector3 e1_candidate = const Vector3 e1_candidate = midsurface_xi - midsurface_xi.Dot(e3) * e3;
Subtract(midsurface_xi, const double e1_norm = e1_candidate.Norm();
Scale(Dot(midsurface_xi, result.frame.e3), result.frame.e3)); if (!e1_candidate.IsFinite() || !std::isfinite(e1_norm) || !(e1_norm > 0.0)) {
const double e1_norm = Norm(e1_candidate);
if (!IsFinite(e1_candidate) || !std::isfinite(e1_norm) || !(e1_norm > 0.0)) {
return false; return false;
} }
result.frame.e1 = Scale(1.0 / e1_norm, e1_candidate); const Vector3 e1 = (1.0 / e1_norm) * e1_candidate;
result.frame.e2 = Cross(result.frame.e3, result.frame.e1); const Vector3 e2 = e3.Cross(e1);
return IsFinite(result.reciprocal[0]) && IsFinite(result.reciprocal[1]) && result.frame.e1 = e1.Components();
IsFinite(result.reciprocal[2]) && IsFinite(result.frame.e2); 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( std::array<std::array<Vector3, 3>, 20> Mitc4Shell::BasisDerivatives(
@@ -861,24 +827,24 @@ std::array<std::array<Vector3, 3>, 20> Mitc4Shell::BasisDerivatives(
const std::size_t offset = node * kPhysicalDofsPerNode; const std::size_t offset = node * kPhysicalDofsPerNode;
for (std::size_t component = 0U; component < 3U; ++component) { for (std::size_t component = 0U; component < 3U; ++component) {
derivatives[offset + component][0] = derivatives[offset + component][0] =
Scale(shape.xi_derivatives[node], global_axes[component]); shape.xi_derivatives[node] * global_axes[component];
derivatives[offset + component][1] = derivatives[offset + component][1] =
Scale(shape.eta_derivatives[node], global_axes[component]); shape.eta_derivatives[node] * global_axes[component];
} }
const Vector3 alpha_direction = Scale(-half_thickness, tangent_b_[node]); const Vector3 alpha_direction = -half_thickness * tangent_b_[node];
derivatives[offset + 3U][0] = derivatives[offset + 3U][0] =
Scale(zeta * shape.xi_derivatives[node], alpha_direction); zeta * shape.xi_derivatives[node] * alpha_direction;
derivatives[offset + 3U][1] = derivatives[offset + 3U][1] =
Scale(zeta * shape.eta_derivatives[node], alpha_direction); zeta * shape.eta_derivatives[node] * alpha_direction;
derivatives[offset + 3U][2] = Scale(shape.values[node], alpha_direction); derivatives[offset + 3U][2] = shape.values[node] * alpha_direction;
const Vector3 beta_direction = Scale(half_thickness, tangent_a_[node]); const Vector3 beta_direction = half_thickness * tangent_a_[node];
derivatives[offset + 4U][0] = derivatives[offset + 4U][0] =
Scale(zeta * shape.xi_derivatives[node], beta_direction); zeta * shape.xi_derivatives[node] * beta_direction;
derivatives[offset + 4U][1] = derivatives[offset + 4U][1] =
Scale(zeta * shape.eta_derivatives[node], beta_direction); zeta * shape.eta_derivatives[node] * beta_direction;
derivatives[offset + 4U][2] = Scale(shape.values[node], beta_direction); derivatives[offset + 4U][2] = shape.values[node] * beta_direction;
} }
return derivatives; return derivatives;
} }
+55 -85
View File
@@ -5,11 +5,11 @@
#include <tuple> #include <tuple>
#include <utility> #include <utility>
#include "fesa/math/vector3.h"
namespace fesa { namespace fesa {
namespace { namespace {
using Vector3 = std::array<double, 3>;
constexpr std::array<double, 4> kXiSigns{-1.0, 1.0, 1.0, -1.0}; constexpr std::array<double, 4> kXiSigns{-1.0, 1.0, 1.0, -1.0};
constexpr std::array<double, 4> kEtaSigns{-1.0, -1.0, 1.0, 1.0}; constexpr std::array<double, 4> kEtaSigns{-1.0, -1.0, 1.0, 1.0};
@@ -25,37 +25,6 @@ struct ElementWork {
double area_weight; double area_weight;
}; };
Vector3 Add(const Vector3& left, const Vector3& right) {
return {left[0] + right[0], left[1] + right[1], left[2] + right[2]};
}
Vector3 Subtract(const Vector3& left, const Vector3& right) {
return {left[0] - right[0], left[1] - right[1], left[2] - right[2]};
}
Vector3 Scale(double factor, const Vector3& value) {
return {factor * value[0], factor * value[1], factor * value[2]};
}
double Dot(const Vector3& left, const Vector3& right) {
return left[0] * right[0] + left[1] * right[1] + left[2] * right[2];
}
Vector3 Cross(const Vector3& left, const Vector3& right) {
return {left[1] * right[2] - left[2] * right[1],
left[2] * right[0] - left[0] * right[2],
left[0] * right[1] - left[1] * right[0]};
}
double Norm(const Vector3& value) {
return std::hypot(value[0], value[1], value[2]);
}
bool IsFinite(const Vector3& value) {
return std::all_of(value.begin(), value.end(),
[](double component) { return std::isfinite(component); });
}
ShapeData ShapeDataAt(double xi, double eta) { ShapeData ShapeDataAt(double xi, double eta) {
ShapeData data{}; ShapeData data{};
for (std::size_t node = 0U; node < 4U; ++node) { for (std::size_t node = 0U; node < 4U; ++node) {
@@ -73,7 +42,7 @@ Vector3 WeightedSum(const std::array<double, 4>& weights,
const std::array<Vector3, 4>& values) { const std::array<Vector3, 4>& values) {
Vector3 result{}; Vector3 result{};
for (std::size_t node = 0U; node < values.size(); ++node) { for (std::size_t node = 0U; node < values.size(); ++node) {
result = Add(result, Scale(weights[node], values[node])); result = result + weights[node] * values[node];
} }
return result; return result;
} }
@@ -85,7 +54,7 @@ Vector3 DerivativeSum(const std::array<double, 4>& derivatives,
// numerical cancellation without changing the covariant tangent. // numerical cancellation without changing the covariant tangent.
std::array<Vector3, 4> relative{}; std::array<Vector3, 4> relative{};
for (std::size_t node = 0U; node < coordinates.size(); ++node) { for (std::size_t node = 0U; node < coordinates.size(); ++node) {
relative[node] = Subtract(coordinates[node], coordinates[0]); relative[node] = coordinates[node] - coordinates[0];
} }
return WeightedSum(derivatives, relative); return WeightedSum(derivatives, relative);
} }
@@ -106,7 +75,7 @@ bool SameCoordinates(const Vector3& left, const Vector3& right) {
double Orientation(const Vector3& first, const Vector3& second, double Orientation(const Vector3& first, const Vector3& second,
const Vector3& third, const Vector3& normal) { const Vector3& third, const Vector3& normal) {
return Dot(Cross(Subtract(second, first), Subtract(third, first)), normal); return (second - first).Cross(third - first).Dot(normal);
} }
bool HasOppositeSigns(double first, double second) { bool HasOppositeSigns(double first, double second) {
@@ -197,8 +166,8 @@ Result<ShellGeometry> PreprocessShellGeometry(
"Shell geometry references an unavailable internal node."); "Shell geometry references an unavailable internal node.");
} }
current.coordinates[local_node] = current.coordinates[local_node] =
nodes[element.node_indices[local_node]].coordinates; Vector3{nodes[element.node_indices[local_node]].coordinates};
if (!IsFinite(current.coordinates[local_node])) { if (!current.coordinates[local_node].IsFinite()) {
return GeometryFailure( return GeometryFailure(
"invalid-shell-geometry", element.location, "ELEMENT", "invalid-shell-geometry", element.location, "ELEMENT",
element.source_id.source_label_text, element.source_id.source_label_text,
@@ -222,17 +191,17 @@ Result<ShellGeometry> PreprocessShellGeometry(
DerivativeSum(center.xi_derivatives, current.coordinates); DerivativeSum(center.xi_derivatives, current.coordinates);
const Vector3 center_eta = const Vector3 center_eta =
DerivativeSum(center.eta_derivatives, current.coordinates); DerivativeSum(center.eta_derivatives, current.coordinates);
const Vector3 center_cross = Cross(center_xi, center_eta); const Vector3 center_cross = center_xi.Cross(center_eta);
const double center_measure = Norm(center_cross); const double center_measure = center_cross.Norm();
if (!IsFinite(center_xi) || !IsFinite(center_eta) || if (!center_xi.IsFinite() || !center_eta.IsFinite() ||
!IsFinite(center_cross) || !std::isfinite(center_measure) || !center_cross.IsFinite() || !std::isfinite(center_measure) ||
!(center_measure > 0.0)) { !(center_measure > 0.0)) {
return GeometryFailure( return GeometryFailure(
"invalid-shell-geometry", element.location, "ELEMENT", "invalid-shell-geometry", element.location, "ELEMENT",
element.source_id.source_label_text, element.source_id.source_label_text,
"Shell center has no finite nonzero normal candidate."); "Shell center has no finite nonzero normal candidate.");
} }
current.normal = Scale(1.0 / center_measure, center_cross); current.normal = (1.0 / center_measure) * center_cross;
if (SegmentsProperlyIntersect( if (SegmentsProperlyIntersect(
current.coordinates[0], current.coordinates[1], current.coordinates[0], current.coordinates[1],
@@ -253,11 +222,11 @@ Result<ShellGeometry> PreprocessShellGeometry(
DerivativeSum(shape.xi_derivatives, current.coordinates); DerivativeSum(shape.xi_derivatives, current.coordinates);
const Vector3 tangent_eta = const Vector3 tangent_eta =
DerivativeSum(shape.eta_derivatives, current.coordinates); DerivativeSum(shape.eta_derivatives, current.coordinates);
const Vector3 area_vector = Cross(tangent_xi, tangent_eta); const Vector3 area_vector = tangent_xi.Cross(tangent_eta);
const double measure = Norm(area_vector); const double measure = area_vector.Norm();
if (!IsFinite(tangent_xi) || !IsFinite(tangent_eta) || if (!tangent_xi.IsFinite() || !tangent_eta.IsFinite() ||
!IsFinite(area_vector) || !std::isfinite(measure) || !area_vector.IsFinite() || !std::isfinite(measure) ||
!(measure > 0.0) || !(Dot(area_vector, current.normal) > 0.0)) { !(measure > 0.0) || !(area_vector.Dot(current.normal) > 0.0)) {
return GeometryFailure("invalid-shell-geometry", element.location, return GeometryFailure("invalid-shell-geometry", element.location,
"ELEMENT", element.source_id.source_label_text, "ELEMENT", element.source_id.source_label_text,
"Shell surface is zero-area or locally reversed " "Shell surface is zero-area or locally reversed "
@@ -272,8 +241,8 @@ Result<ShellGeometry> PreprocessShellGeometry(
} }
current.area_weight = area_weight; current.area_weight = area_weight;
work.push_back(current); work.push_back(current);
geometry.element_data.push_back( geometry.element_data.push_back({static_cast<EntityIndex>(element_index),
{static_cast<EntityIndex>(element_index), current.normal, area_weight}); current.normal.Components(), area_weight});
} }
std::vector<std::vector<std::size_t>> incident(nodes.size()); std::vector<std::vector<std::size_t>> incident(nodes.size());
@@ -301,8 +270,8 @@ Result<ShellGeometry> PreprocessShellGeometry(
for (std::size_t first = 0U; first < node_incident.size(); ++first) { for (std::size_t first = 0U; first < node_incident.size(); ++first) {
for (std::size_t second = first + 1U; second < node_incident.size(); for (std::size_t second = first + 1U; second < node_incident.size();
++second) { ++second) {
const double pair_dot = Dot(work[node_incident[first]].normal, const double pair_dot = work[node_incident[first]].normal.Dot(
work[node_incident[second]].normal); work[node_incident[second]].normal);
if (!std::isfinite(pair_dot) || !(pair_dot > 0.0)) { if (!std::isfinite(pair_dot) || !(pair_dot > 0.0)) {
return GeometryFailure("opposed-incident-normal", return GeometryFailure("opposed-incident-normal",
nodes[node_index].location, "NODE", nodes[node_index].location, "NODE",
@@ -320,52 +289,53 @@ Result<ShellGeometry> PreprocessShellGeometry(
} }
Vector3 director_sum{}; Vector3 director_sum{};
for (const std::size_t element_index : node_incident) { for (const std::size_t element_index : node_incident) {
director_sum = Add(director_sum, director_sum =
Scale(work[element_index].area_weight / maximum_weight, director_sum + (work[element_index].area_weight / maximum_weight) *
work[element_index].normal)); work[element_index].normal;
} }
const double director_norm = Norm(director_sum); const double director_norm = director_sum.Norm();
if (!IsFinite(director_sum) || !std::isfinite(director_norm) || if (!director_sum.IsFinite() || !std::isfinite(director_norm) ||
!(director_norm > 0.0)) { !(director_norm > 0.0)) {
return GeometryFailure( return GeometryFailure(
"invalid-shell-director", nodes[node_index].location, "NODE", "invalid-shell-director", nodes[node_index].location, "NODE",
nodes[node_index].source_id.source_label_text, nodes[node_index].source_id.source_label_text,
"Area-weighted shell director is nonfinite or zero."); "Area-weighted shell director is nonfinite or zero.");
} }
const Vector3 director = Scale(1.0 / director_norm, director_sum); const Vector3 director = (1.0 / director_norm) * director_sum;
const std::array<Vector3, 3> global_axes{ 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}}; Vector3{1.0, 0.0, 0.0}, Vector3{0.0, 1.0, 0.0}, Vector3{0.0, 0.0, 1.0}};
std::size_t selected_axis = 0U; std::size_t selected_axis = 0U;
double selected_alignment = std::abs(Dot(global_axes[0], director)); double selected_alignment = std::abs(global_axes[0].Dot(director));
for (std::size_t axis = 1U; axis < global_axes.size(); ++axis) { for (std::size_t axis = 1U; axis < global_axes.size(); ++axis) {
const double alignment = std::abs(Dot(global_axes[axis], director)); const double alignment = std::abs(global_axes[axis].Dot(director));
if (alignment < selected_alignment) { if (alignment < selected_alignment) {
selected_alignment = alignment; selected_alignment = alignment;
selected_axis = axis; selected_axis = axis;
} }
} }
const Vector3 tangent_candidate = const Vector3 tangent_candidate =
Subtract(global_axes[selected_axis], global_axes[selected_axis] -
Scale(Dot(global_axes[selected_axis], director), director)); global_axes[selected_axis].Dot(director) * director;
const double tangent_norm = Norm(tangent_candidate); const double tangent_norm = tangent_candidate.Norm();
if (!IsFinite(tangent_candidate) || !std::isfinite(tangent_norm) || if (!tangent_candidate.IsFinite() || !std::isfinite(tangent_norm) ||
!(tangent_norm > 0.0)) { !(tangent_norm > 0.0)) {
return GeometryFailure( return GeometryFailure(
"invalid-shell-director", nodes[node_index].location, "NODE", "invalid-shell-director", nodes[node_index].location, "NODE",
nodes[node_index].source_id.source_label_text, nodes[node_index].source_id.source_label_text,
"Least-aligned-axis tangent frame construction failed."); "Least-aligned-axis tangent frame construction failed.");
} }
const Vector3 tangent_a = Scale(1.0 / tangent_norm, tangent_candidate); const Vector3 tangent_a = (1.0 / tangent_norm) * tangent_candidate;
const Vector3 tangent_b = Cross(director, tangent_a); const Vector3 tangent_b = director.Cross(tangent_a);
if (!IsFinite(tangent_b) || !(Norm(tangent_b) > 0.0)) { if (!tangent_b.IsFinite() || !(tangent_b.Norm() > 0.0)) {
return GeometryFailure( return GeometryFailure(
"invalid-shell-director", nodes[node_index].location, "NODE", "invalid-shell-director", nodes[node_index].location, "NODE",
nodes[node_index].source_id.source_label_text, nodes[node_index].source_id.source_label_text,
"Right-handed shell tangent frame construction failed."); "Right-handed shell tangent frame construction failed.");
} }
geometry.nodal_frames.push_back( geometry.nodal_frames.push_back(
{static_cast<EntityIndex>(node_index), director, tangent_a, tangent_b}); {static_cast<EntityIndex>(node_index), director.Components(),
tangent_a.Components(), tangent_b.Components()});
} }
// Build lookup only after frame storage is complete so later code never // Build lookup only after frame storage is complete so later code never
@@ -392,7 +362,7 @@ Result<ShellGeometry> PreprocessShellGeometry(
"ELEMENT", element.source_id.source_label_text, "ELEMENT", element.source_id.source_label_text,
"Shell element is missing a nodal director."); "Shell element is missing a nodal director.");
} }
directors[local_node] = frame->director; directors[local_node] = Vector3{frame->director};
} }
for (const auto& point : ShellGeometryValidationPoints()) { for (const auto& point : ShellGeometryValidationPoints()) {
@@ -404,12 +374,12 @@ Result<ShellGeometry> PreprocessShellGeometry(
DerivativeSum(shape.xi_derivatives, work[element_index].coordinates); DerivativeSum(shape.xi_derivatives, work[element_index].coordinates);
const Vector3 midsurface_eta = const Vector3 midsurface_eta =
DerivativeSum(shape.eta_derivatives, work[element_index].coordinates); DerivativeSum(shape.eta_derivatives, work[element_index].coordinates);
const Vector3 area_vector = Cross(midsurface_xi, midsurface_eta); const Vector3 area_vector = midsurface_xi.Cross(midsurface_eta);
const double surface_measure = Norm(area_vector); const double surface_measure = area_vector.Norm();
if (!IsFinite(midsurface_xi) || !IsFinite(midsurface_eta) || if (!midsurface_xi.IsFinite() || !midsurface_eta.IsFinite() ||
!IsFinite(area_vector) || !std::isfinite(surface_measure) || !area_vector.IsFinite() || !std::isfinite(surface_measure) ||
!(surface_measure > 0.0) || !(surface_measure > 0.0) ||
!(Dot(area_vector, work[element_index].normal) > 0.0)) { !(area_vector.Dot(work[element_index].normal) > 0.0)) {
return GeometryFailure("invalid-shell-geometry", element.location, return GeometryFailure("invalid-shell-geometry", element.location,
"ELEMENT", element.source_id.source_label_text, "ELEMENT", element.source_id.source_label_text,
"Shell surface basis is nonfinite, zero, or " "Shell surface basis is nonfinite, zero, or "
@@ -421,14 +391,14 @@ Result<ShellGeometry> PreprocessShellGeometry(
WeightedSum(shape.eta_derivatives, directors); WeightedSum(shape.eta_derivatives, directors);
const Vector3 director_value = WeightedSum(shape.values, directors); const Vector3 director_value = WeightedSum(shape.values, directors);
const Vector3 covariant_xi = const Vector3 covariant_xi =
Add(midsurface_xi, Scale(0.5 * thickness * zeta, director_xi)); midsurface_xi + 0.5 * thickness * zeta * director_xi;
const Vector3 covariant_eta = const Vector3 covariant_eta =
Add(midsurface_eta, Scale(0.5 * thickness * zeta, director_eta)); midsurface_eta + 0.5 * thickness * zeta * director_eta;
const Vector3 covariant_zeta = Scale(0.5 * thickness, director_value); const Vector3 covariant_zeta = 0.5 * thickness * director_value;
const double jacobian = const double jacobian =
Dot(covariant_xi, Cross(covariant_eta, covariant_zeta)); covariant_xi.Dot(covariant_eta.Cross(covariant_zeta));
if (!IsFinite(covariant_xi) || !IsFinite(covariant_eta) || if (!covariant_xi.IsFinite() || !covariant_eta.IsFinite() ||
!IsFinite(covariant_zeta) || !std::isfinite(jacobian) || !covariant_zeta.IsFinite() || !std::isfinite(jacobian) ||
!(jacobian > 0.0)) { !(jacobian > 0.0)) {
return GeometryFailure( return GeometryFailure(
"invalid-shell-jacobian", element.location, "ELEMENT", "invalid-shell-jacobian", element.location, "ELEMENT",
@@ -436,13 +406,13 @@ Result<ShellGeometry> PreprocessShellGeometry(
"Shell Jacobian is nonfinite or nonpositive at a required point."); "Shell Jacobian is nonfinite or nonpositive at a required point.");
} }
const Vector3 reciprocal_xi = const Vector3 reciprocal_xi =
Scale(1.0 / jacobian, Cross(covariant_eta, covariant_zeta)); (1.0 / jacobian) * covariant_eta.Cross(covariant_zeta);
const Vector3 reciprocal_eta = const Vector3 reciprocal_eta =
Scale(1.0 / jacobian, Cross(covariant_zeta, covariant_xi)); (1.0 / jacobian) * covariant_zeta.Cross(covariant_xi);
const Vector3 reciprocal_zeta = const Vector3 reciprocal_zeta =
Scale(1.0 / jacobian, Cross(covariant_xi, covariant_eta)); (1.0 / jacobian) * covariant_xi.Cross(covariant_eta);
if (!IsFinite(reciprocal_xi) || !IsFinite(reciprocal_eta) || if (!reciprocal_xi.IsFinite() || !reciprocal_eta.IsFinite() ||
!IsFinite(reciprocal_zeta)) { !reciprocal_zeta.IsFinite()) {
return GeometryFailure( return GeometryFailure(
"invalid-shell-jacobian", element.location, "ELEMENT", "invalid-shell-jacobian", element.location, "ELEMENT",
element.source_id.source_label_text, element.source_id.source_label_text,
@@ -12,6 +12,8 @@
#include <utility> #include <utility>
#include <vector> #include <vector>
#include "fesa/math/vector3.h"
namespace fesa { namespace fesa {
namespace { namespace {
@@ -720,6 +722,40 @@ TEST(EulerBeam3D, RotatedTransformPreservesWorkAndEnergy) {
(local_displacement[6U] - local_displacement[0U]) / 3.0, 1.0e-14); (local_displacement[6U] - local_displacement[0U]) / 3.0, 1.0e-14);
} }
TEST(EulerBeam3D, PreservesExactRotatedResultsAcrossVector3Migration) {
const auto section = MakeSection({-2.0, 2.0, 0.0});
const auto material = MakeMaterial();
const Node first_node = MakeNode({1.0, -2.0, 0.5}, 1U);
const Node second_node = MakeNode({3.0, 0.0, 1.5}, 2U);
const Vector3 delta =
Vector3(second_node.coordinates) - Vector3(first_node.coordinates);
EXPECT_DOUBLE_EQ(delta.Norm(), 3.0);
const auto beam = RequireBeam(first_node, second_node, section, material);
const Matrix global = beam.GlobalStiffness();
Vector displacement{kElementDofCount};
for (std::size_t index = 0U; index < displacement.Size(); ++index) {
displacement[index] = 0.01 * static_cast<double>(index + 1U) - 0.04;
}
const BeamRecovery recovery = beam.Recover(displacement);
EXPECT_DOUBLE_EQ(global(0U, 0U), 0x1.65f135da12f68p+28);
EXPECT_DOUBLE_EQ(global(0U, 1U), 0x1.6261c084bda12p+28);
EXPECT_DOUBLE_EQ(global(0U, 2U), 0x1.630ca684bda13p+27);
EXPECT_DOUBLE_EQ(global(4U, 4U), 0x1.068e359b59b58p+22);
EXPECT_DOUBLE_EQ(global(5U, 11U), 0x1.2d14a7ee7ee7bp+22);
EXPECT_DOUBLE_EQ(recovery.gauss_generalized_strains[0U][0U],
0x1.1111111111110p-5);
EXPECT_DOUBLE_EQ(recovery.gauss_generalized_strains[0U][1U],
0x1.1111111111111p-5);
EXPECT_DOUBLE_EQ(recovery.gauss_generalized_strains[0U][2U],
-0x1.382425a7d2473p-6);
EXPECT_DOUBLE_EQ(recovery.gauss_generalized_strains[0U][3U],
-0x1.a9389137b051dp-6);
EXPECT_DOUBLE_EQ(recovery.endpoint_section_resultants[1U][2U],
0x1.525c94a87359fp+17);
}
TEST(EulerBeam3D, ConstantLineLoadMatchesAllSignedComponents) { TEST(EulerBeam3D, ConstantLineLoadMatchesAllSignedComponents) {
const double length = 4.0; const double length = 4.0;
const ConstantLocalLineLoad load{2.5, -3.0, 5.5, -7.0}; const ConstantLocalLineLoad load{2.5, -3.0, 5.5, -7.0};
+60
View File
@@ -12,6 +12,8 @@
#include <string> #include <string>
#include <vector> #include <vector>
#include "fesa/math/vector3.h"
namespace { namespace {
using Vector3 = std::array<double, 3>; using Vector3 = std::array<double, 3>;
@@ -588,6 +590,64 @@ TEST(Mitc4ShellKernel,
EXPECT_DOUBLE_EQ(repeated.drilling_stiffness, stiffness.drilling_stiffness); EXPECT_DOUBLE_EQ(repeated.drilling_stiffness, stiffness.drilling_stiffness);
} }
TEST(Mitc4ShellKernel,
PreservesExactStiffnessRecoveryAndPatchAcrossVector3Migration) {
const auto nodes = PlanarNodes();
const auto shell_candidate = fesa::Mitc4Shell::Create(
NodePointers(nodes), Directors(), Section(), Material());
ASSERT_TRUE(shell_candidate.HasValue());
const auto& shell = shell_candidate.Value();
const auto stiffness_candidate = shell.Stiffness();
ASSERT_TRUE(stiffness_candidate.HasValue());
const auto& stiffness = stiffness_candidate.Value();
const fesa::Vector3 frame_e3{shell.LocalFrame(0.0, 0.0).e3};
EXPECT_DOUBLE_EQ(frame_e3.X(), 0.0);
EXPECT_DOUBLE_EQ(frame_e3.Y(), 0.0);
EXPECT_DOUBLE_EQ(frame_e3.Z(), 1.0);
constexpr std::array<double, 8> kGeneralized{0.1, -0.05, 0.2, 0.3,
-0.15, 0.25, 0.4, -0.3};
fesa::Vector global_field{24U};
for (std::size_t node_index = 0U; node_index < nodes.size(); ++node_index) {
const double x = nodes[node_index].coordinates[0];
const double y = nodes[node_index].coordinates[1];
const std::size_t offset = 6U * node_index;
global_field[offset] = kGeneralized[0] * x + 0.5 * kGeneralized[2] * y;
global_field[offset + 1U] = kGeneralized[1] * y + 0.5 * kGeneralized[2] * x;
global_field[offset + 2U] = kGeneralized[6] * x + kGeneralized[7] * y -
0.5 * kGeneralized[5] * x * y;
global_field[offset + 3U] =
-kGeneralized[4] * y - 0.5 * kGeneralized[5] * x;
global_field[offset + 4U] = kGeneralized[3] * x + 0.5 * kGeneralized[5] * y;
}
const auto recovery_candidate = shell.RecoverPhysical(global_field);
ASSERT_TRUE(recovery_candidate.HasValue());
const auto& recovery = recovery_candidate.Value();
std::array<std::array<double, 5>, 4> e11_values{};
for (std::size_t node_index = 0U; node_index < nodes.size(); ++node_index) {
e11_values[node_index][0U] = 0.2 * nodes[node_index].coordinates[0U];
}
const double gauss = 1.0 / std::sqrt(3.0);
const fesa::Vector patch = shell.StrainDisplacement20(gauss, -gauss, gauss)
.Multiply(PhysicalField(e11_values));
EXPECT_DOUBLE_EQ(stiffness.physical_local20(0U, 0U), 0x1.d555555555554p+6);
EXPECT_DOUBLE_EQ(stiffness.physical_local20(0U, 4U), 0.0);
EXPECT_DOUBLE_EQ(stiffness.physical_global24(2U, 2U), 0x1.aaaaaaaaaaaadp+5);
EXPECT_DOUBLE_EQ(stiffness.drilling_stiffness, 0x1.0d6cffc5beeb5p-4);
EXPECT_DOUBLE_EQ(recovery.strain_energy, 0x1.20a3d70a3d70bp+6);
constexpr std::array<double, 8> kExpectedStrain{
0x1.999999999999ap-4, -0x1.999999999999bp-5, 0x1.9999999999998p-3,
0x1.3333333333335p-2, -0x1.3333333333335p-3, 0x1.0000000000000p-2,
0x1.999999999999cp-2, -0x1.3333333333334p-2};
EXPECT_EQ(recovery.points[0U].generalized_strain, kExpectedStrain);
EXPECT_DOUBLE_EQ(patch[0U], 0x1.9999999999999p-3);
for (std::size_t component = 1U; component < patch.Size(); ++component) {
EXPECT_DOUBLE_EQ(patch[component], 0.0);
}
}
// MITC4-KERNEL-002 // MITC4-KERNEL-002
TEST(Mitc4ShellKernel, TEST(Mitc4ShellKernel,
PreservesPhysicalEnergyUnderTwentyToTwentyFourCongruence) { PreservesPhysicalEnergyUnderTwentyToTwentyFourCongruence) {
+22
View File
@@ -2,6 +2,8 @@
#include <gtest/gtest.h> #include <gtest/gtest.h>
#include <array>
#include <cmath>
#include <limits> #include <limits>
#include <optional> #include <optional>
@@ -19,6 +21,7 @@ TEST(Vector3, StoresComponentsWithExactArithmetic) {
constexpr Vector3 sum = lhs + rhs; constexpr Vector3 sum = lhs + rhs;
constexpr Vector3 difference = lhs - rhs; constexpr Vector3 difference = lhs - rhs;
constexpr Vector3 scaled = lhs * -2.0; constexpr Vector3 scaled = lhs * -2.0;
constexpr Vector3 divided = lhs / -2.0;
EXPECT_DOUBLE_EQ(lhs.X(), 1.0); EXPECT_DOUBLE_EQ(lhs.X(), 1.0);
EXPECT_DOUBLE_EQ(lhs.Y(), -2.0); EXPECT_DOUBLE_EQ(lhs.Y(), -2.0);
@@ -35,6 +38,22 @@ TEST(Vector3, StoresComponentsWithExactArithmetic) {
EXPECT_DOUBLE_EQ(scaled[0], -2.0); EXPECT_DOUBLE_EQ(scaled[0], -2.0);
EXPECT_DOUBLE_EQ(scaled[1], 4.0); EXPECT_DOUBLE_EQ(scaled[1], 4.0);
EXPECT_DOUBLE_EQ(scaled[2], -6.0); EXPECT_DOUBLE_EQ(scaled[2], -6.0);
EXPECT_DOUBLE_EQ(divided[0], -0.5);
EXPECT_DOUBLE_EQ(divided[1], 1.0);
EXPECT_DOUBLE_EQ(divided[2], -1.5);
}
TEST(Vector3, ConvertsArrayAndPreservesScalarLeftEvaluationOrder) {
constexpr std::array<double, 3> kComponents{1.25, -2.5, 5.0};
constexpr Vector3 value{kComponents};
constexpr Vector3 scaled = -2.0 * value;
EXPECT_TRUE(value == Vector3(1.25, -2.5, 5.0));
EXPECT_FALSE(value == Vector3(1.25, -2.5, 4.0));
EXPECT_EQ(value.Components(), kComponents);
EXPECT_DOUBLE_EQ(scaled.X(), -2.5);
EXPECT_DOUBLE_EQ(scaled.Y(), 5.0);
EXPECT_DOUBLE_EQ(scaled.Z(), -10.0);
} }
TEST(Vector3, ComputesDotProduct) { TEST(Vector3, ComputesDotProduct) {
@@ -61,6 +80,9 @@ TEST(Vector3, UsesRightHandedCrossProductOrientation) {
TEST(Vector3, ComputesEuclideanNorm) { TEST(Vector3, ComputesEuclideanNorm) {
EXPECT_DOUBLE_EQ(Vector3(2.0, -3.0, 6.0).Norm(), 7.0); EXPECT_DOUBLE_EQ(Vector3(2.0, -3.0, 6.0).Norm(), 7.0);
const double expected = std::hypot(1.0e308, 1.0e308, 0.0);
EXPECT_DOUBLE_EQ(Vector3(1.0e308, 1.0e308, 0.0).Norm(), expected);
} }
TEST(Vector3, NormalizesNonzeroVectors) { TEST(Vector3, NormalizesNonzeroVectors) {
+30
View File
@@ -10,6 +10,8 @@
#include <string> #include <string>
#include <vector> #include <vector>
#include "fesa/math/vector3.h"
namespace { namespace {
using Vector3 = std::array<double, 3>; using Vector3 = std::array<double, 3>;
@@ -134,6 +136,34 @@ TEST(Mitc4Geometry,
} }
} }
TEST(Mitc4Geometry, PreservesExactWarpedResultsAcrossVector3Migration) {
const std::vector<fesa::Node> warped_nodes{
Node(0U, {0.0, 0.0, 0.0}), Node(1U, {2.0, 0.0, 0.0}),
Node(2U, {2.0, 1.0, 0.2}), Node(3U, {0.0, 1.0, 0.0})};
const auto warped = fesa::PreprocessShellGeometry(
warped_nodes, {Element(12U, {0U, 1U, 2U, 3U})}, Sections());
ASSERT_TRUE(warped.HasValue());
const auto& element = warped.Value().element_data[0U];
const auto& frame = FrameFor(warped.Value(), 0U);
const fesa::Vector3 normal{element.normal_candidate};
const fesa::Vector3 director{frame.director};
const fesa::Vector3 tangent_a{frame.tangent_a};
const fesa::Vector3 tangent_b{frame.tangent_b};
EXPECT_DOUBLE_EQ(normal.X(), -0x1.97105218e28c2p-5);
EXPECT_DOUBLE_EQ(normal.Y(), -0x1.97105218e28c2p-4);
EXPECT_DOUBLE_EQ(normal.Z(), 0x1.fcd4669f1b2f2p-1);
EXPECT_DOUBLE_EQ(element.surface_area_weight, 0x1.021ebe8f40622p+1);
EXPECT_DOUBLE_EQ(director.X(), -0x1.97105218e28c2p-5);
EXPECT_DOUBLE_EQ(director.Y(), -0x1.97105218e28c2p-4);
EXPECT_DOUBLE_EQ(director.Z(), 0x1.fcd4669f1b2f2p-1);
EXPECT_DOUBLE_EQ(tangent_a.X(), 0x1.ff5e152c2d2abp-1);
EXPECT_DOUBLE_EQ(tangent_a.Y(), -0x1.4408ec773d925p-8);
EXPECT_DOUBLE_EQ(tangent_a.Z(), 0x1.950b27950cf6dp-5);
EXPECT_DOUBLE_EQ(tangent_b.X(), 0.0);
EXPECT_DOUBLE_EQ(tangent_b.Y(), 0x1.fd7583bc82e28p-1);
EXPECT_DOUBLE_EQ(tangent_b.Z(), 0x1.9791363068b53p-4);
}
// MITC4-GEO-002 // MITC4-GEO-002
TEST(Mitc4Geometry, AreaWeightsSharedDirectorsInStableSourceIdentityOrder) { TEST(Mitc4Geometry, AreaWeightsSharedDirectorsInStableSourceIdentityOrder) {
const std::vector<fesa::Node> nodes{ const std::vector<fesa::Node> nodes{