feat(cpp-object-oriented-modular-refactoring): step 4 - model-element-google-style

This commit is contained in:
KOKO\Mimi
2026-08-16 05:37:40 +09:00
parent 34ab8b5bf1
commit 8bc0ea2f8e
46 changed files with 5184 additions and 5323 deletions
+390 -425
View File
@@ -1,4 +1,4 @@
#include "fesa/elements/euler_beam_3d.hpp"
#include "fesa/elements/euler_beam_3d.h"
#include <algorithm>
#include <array>
@@ -19,497 +19,462 @@ 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 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];
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]};
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]);
bool IsFinite(const Vector3& value) {
return std::isfinite(value[0]) && std::isfinite(value[1]) &&
std::isfinite(value[2]);
}
std::string elementIdentity(const Node& firstNode, const Node& secondNode) {
return firstNode.sourceId.instance_name + ":" +
firstNode.sourceId.source_label_text + "-" +
secondNode.sourceId.source_label_text;
std::string ElementIdentity(const Node& first_node, const Node& second_node) {
return first_node.source_id.instance_name + ":" +
first_node.source_id.source_label_text + "-" +
second_node.source_id.source_label_text;
}
Result<EulerBeam3D> modelFailure(const std::string& code,
Result<EulerBeam3D> ModelFailure(const std::string& code,
const SourceLocation& location,
const std::string& identity,
const std::string& message) {
return Result<EulerBeam3D>::Failure(Status::Failure(
FailureCategory::kModel,
{{Severity::kError, code, location, "*ELEMENT", identity, message}}));
return Result<EulerBeam3D>::Failure(Status::Failure(
FailureCategory::kModel,
{{Severity::kError, code, location, "*ELEMENT", identity, message}}));
}
Matrix transformation(const std::array<double, 9>& rotation) {
Matrix result{kElementDofCount, kElementDofCount};
// Blocks preserve [translation, rotation] at node 1 then node 2.
for (std::size_t block = 0; block < 4U; ++block) {
for (std::size_t row = 0; row < 3U; ++row) {
for (std::size_t column = 0; column < 3U; ++column) {
result(block * 3U + row, block * 3U + column) =
rotation[row * 3U + column];
}
}
/// @brief Repeats the local-axis rotation in stable nodal translation and
/// rotation blocks.
Matrix Transformation(const std::array<double, 9>& rotation) {
Matrix result{kElementDofCount, kElementDofCount};
// Blocks preserve [translation, rotation] at node 1 then node 2.
for (std::size_t block = 0; block < 4U; ++block) {
for (std::size_t row = 0; row < 3U; ++row) {
for (std::size_t column = 0; column < 3U; ++column) {
result(block * 3U + row, block * 3U + column) =
rotation[row * 3U + column];
}
}
return result;
}
return result;
}
Matrix strainDisplacement(double xi, double length) {
Matrix b{kGeneralizedComponentCount, kElementDofCount};
const double r = 0.5 * (1.0 + xi);
const double inverseLength = 1.0 / length;
const double inverseLengthSquared = inverseLength * inverseLength;
/// @brief Builds the reviewed axial, twist, and signed curvature operator.
/// @warning The theta-y and theta-z signs are formulation contracts.
Matrix StrainDisplacement(double xi, double length) {
Matrix b{kGeneralizedComponentCount, kElementDofCount};
const double r = 0.5 * (1.0 + xi);
const double inverse_length = 1.0 / length;
const double inverse_length_squared = inverse_length * inverse_length;
b(0U, 0U) = -inverseLength;
b(0U, 6U) = inverseLength;
b(1U, 3U) = -inverseLength;
b(1U, 9U) = inverseLength;
b(0U, 0U) = -inverse_length;
b(0U, 6U) = inverse_length;
b(1U, 3U) = -inverse_length;
b(1U, 9U) = inverse_length;
// theta_y=-w' makes kappa_y=-w''; theta_z=v' makes kappa_z=v''.
b(2U, 2U) = (6.0 - 12.0 * r) * inverseLengthSquared;
b(2U, 4U) = (-4.0 + 6.0 * r) * inverseLength;
b(2U, 8U) = (-6.0 + 12.0 * r) * inverseLengthSquared;
b(2U, 10U) = (-2.0 + 6.0 * r) * inverseLength;
// theta_y=-w' makes kappa_y=-w''; theta_z=v' makes kappa_z=v''.
b(2U, 2U) = (6.0 - 12.0 * r) * inverse_length_squared;
b(2U, 4U) = (-4.0 + 6.0 * r) * inverse_length;
b(2U, 8U) = (-6.0 + 12.0 * r) * inverse_length_squared;
b(2U, 10U) = (-2.0 + 6.0 * r) * inverse_length;
b(3U, 1U) = (-6.0 + 12.0 * r) * inverseLengthSquared;
b(3U, 5U) = (-4.0 + 6.0 * r) * inverseLength;
b(3U, 7U) = (6.0 - 12.0 * r) * inverseLengthSquared;
b(3U, 11U) = (-2.0 + 6.0 * r) * inverseLength;
return b;
b(3U, 1U) = (-6.0 + 12.0 * r) * inverse_length_squared;
b(3U, 5U) = (-4.0 + 6.0 * r) * inverse_length;
b(3U, 7U) = (6.0 - 12.0 * r) * inverse_length_squared;
b(3U, 11U) = (-2.0 + 6.0 * r) * inverse_length;
return b;
}
std::array<double, kGeneralizedComponentCount> constitutiveDiagonal(
double youngsModulus,
double shearModulus,
double area,
double iy,
double iz,
double torsionalConstant) {
return {
youngsModulus * area,
shearModulus * torsionalConstant,
youngsModulus * iy,
youngsModulus * iz};
std::array<double, kGeneralizedComponentCount> ConstitutiveDiagonal(
double youngs_modulus, double shear_modulus, double area, double iy,
double iz, double torsional_constant) {
return {youngs_modulus * area, shear_modulus * torsional_constant,
youngs_modulus * iy, youngs_modulus * iz};
}
Matrix closedStiffness(double length,
const std::array<double, kGeneralizedComponentCount>& diagonal) {
Matrix closed{kElementDofCount, kElementDofCount};
const auto addBlock = [&closed](const std::array<std::size_t, 2>& indices,
double coefficient) {
closed(indices[0], indices[0]) = coefficient;
closed(indices[0], indices[1]) = -coefficient;
closed(indices[1], indices[0]) = -coefficient;
closed(indices[1], indices[1]) = coefficient;
};
addBlock({0U, 6U}, diagonal[0U] / length);
addBlock({3U, 9U}, diagonal[1U] / length);
/// @brief Builds the independent closed-form stiffness used as a Gauss-rule
/// invariant.
Matrix ClosedStiffness(
double length,
const std::array<double, kGeneralizedComponentCount>& diagonal) {
Matrix closed{kElementDofCount, kElementDofCount};
const auto add_block = [&closed](const std::array<std::size_t, 2>& indices,
double coefficient) {
closed(indices[0], indices[0]) = coefficient;
closed(indices[0], indices[1]) = -coefficient;
closed(indices[1], indices[0]) = -coefficient;
closed(indices[1], indices[1]) = coefficient;
};
add_block({0U, 6U}, diagonal[0U] / length);
add_block({3U, 9U}, diagonal[1U] / length);
const auto addBendingBlock = [&closed, length](
const auto add_bending_block = [&closed, length](
const std::array<std::size_t, 4>& indices,
double flexuralRigidity,
double rotationSign) {
const double value = 12.0 * flexuralRigidity /
(length * length * length);
const double coupling = rotationSign * 6.0 * flexuralRigidity /
(length * length);
const double diagonalRotation = 4.0 * flexuralRigidity / length;
const double offDiagonalRotation = 2.0 * flexuralRigidity / length;
const std::array<double, 16> block = {
value, coupling, -value, coupling,
coupling, diagonalRotation, -coupling, offDiagonalRotation,
-value, -coupling, value, -coupling,
coupling, offDiagonalRotation, -coupling, diagonalRotation};
for (std::size_t row = 0; row < indices.size(); ++row) {
for (std::size_t column = 0; column < indices.size(); ++column) {
closed(indices[row], indices[column]) = block[row * indices.size() + column];
}
}
};
addBendingBlock({1U, 5U, 7U, 11U}, diagonal[3U], 1.0);
addBendingBlock({2U, 4U, 8U, 10U}, diagonal[2U], -1.0);
return closed;
double flexural_rigidity,
double rotation_sign) {
const double value = 12.0 * flexural_rigidity / (length * length * length);
const double coupling =
rotation_sign * 6.0 * flexural_rigidity / (length * length);
const double diagonal_rotation = 4.0 * flexural_rigidity / length;
const double off_diagonal_rotation = 2.0 * flexural_rigidity / length;
const std::array<double, 16> block = {value, coupling,
-value, coupling,
coupling, diagonal_rotation,
-coupling, off_diagonal_rotation,
-value, -coupling,
value, -coupling,
coupling, off_diagonal_rotation,
-coupling, diagonal_rotation};
for (std::size_t row = 0; row < indices.size(); ++row) {
for (std::size_t column = 0; column < indices.size(); ++column) {
closed(indices[row], indices[column]) =
block[row * indices.size() + column];
}
}
};
add_bending_block({1U, 5U, 7U, 11U}, diagonal[3U], 1.0);
add_bending_block({2U, 4U, 8U, 10U}, diagonal[2U], -1.0);
return closed;
}
double normalizedMatrixError(const Matrix& lhs, const Matrix& rhs) {
double maximumDifference = 0.0;
double scale = 1.0;
for (std::size_t row = 0; row < lhs.Rows(); ++row) {
for (std::size_t column = 0; column < lhs.Columns(); ++column) {
const double lhsValue = lhs(row, column);
const double rhsValue = rhs(row, column);
if (!std::isfinite(lhsValue) || !std::isfinite(rhsValue)) {
return std::numeric_limits<double>::infinity();
}
const double difference = std::abs(lhsValue - rhsValue);
if (!std::isfinite(difference)) {
return std::numeric_limits<double>::infinity();
}
maximumDifference = (std::max)(
maximumDifference,
difference);
scale = (std::max)(scale, std::abs(lhsValue));
scale = (std::max)(scale, std::abs(rhsValue));
}
}
if (!std::isfinite(maximumDifference) || !std::isfinite(scale) ||
!(scale > 0.0)) {
double NormalizedMatrixError(const Matrix& lhs, const Matrix& rhs) {
double maximum_difference = 0.0;
double scale_value = 1.0;
for (std::size_t row = 0; row < lhs.Rows(); ++row) {
for (std::size_t column = 0; column < lhs.Columns(); ++column) {
const double lhs_value = lhs(row, column);
const double rhs_value = rhs(row, column);
if (!std::isfinite(lhs_value) || !std::isfinite(rhs_value)) {
return std::numeric_limits<double>::infinity();
}
const double difference = std::abs(lhs_value - rhs_value);
if (!std::isfinite(difference)) {
return std::numeric_limits<double>::infinity();
}
maximum_difference = (std::max)(maximum_difference, difference);
scale_value = (std::max)(scale_value, std::abs(lhs_value));
scale_value = (std::max)(scale_value, std::abs(rhs_value));
}
const double normalizedError = maximumDifference / scale;
return std::isfinite(normalizedError)
? normalizedError
: std::numeric_limits<double>::infinity();
}
if (!std::isfinite(maximum_difference) || !std::isfinite(scale_value) ||
!(scale_value > 0.0)) {
return std::numeric_limits<double>::infinity();
}
const double normalized_error = maximum_difference / scale_value;
return std::isfinite(normalized_error)
? normalized_error
: std::numeric_limits<double>::infinity();
}
std::array<double, kGeneralizedComponentCount> generalizedStrain(
const Matrix& b,
const Vector& localDisplacement) {
std::array<double, kGeneralizedComponentCount> strain{};
for (std::size_t component = 0; component < strain.size(); ++component) {
for (std::size_t dof = 0; dof < localDisplacement.Size(); ++dof) {
strain[component] += b(component, dof) * localDisplacement[dof];
}
/// @brief Recovers generalized strain in fixed component and DOF order.
std::array<double, kGeneralizedComponentCount> GeneralizedStrain(
const Matrix& b, const Vector& local_displacement) {
std::array<double, kGeneralizedComponentCount> strain{};
for (std::size_t component = 0; component < strain.size(); ++component) {
for (std::size_t dof = 0; dof < local_displacement.Size(); ++dof) {
strain[component] += b(component, dof) * local_displacement[dof];
}
return strain;
}
return strain;
}
std::array<double, kGeneralizedComponentCount> generalizedResultant(
/// @brief Applies the diagonal section law without changing component order.
std::array<double, kGeneralizedComponentCount> GeneralizedResultant(
const std::array<double, kGeneralizedComponentCount>& strain,
const std::array<double, kGeneralizedComponentCount>& diagonal) {
std::array<double, kGeneralizedComponentCount> resultant{};
for (std::size_t component = 0; component < resultant.size(); ++component) {
resultant[component] = diagonal[component] * strain[component];
}
return resultant;
std::array<double, kGeneralizedComponentCount> resultant{};
for (std::size_t component = 0; component < resultant.size(); ++component) {
resultant[component] = diagonal[component] * strain[component];
}
return resultant;
}
Matrix kinematicInterpolation(double xi, double length) {
Matrix interpolation{4U, kElementDofCount};
const double r = 0.5 * (1.0 + xi);
const double rSquared = r * r;
const double rCubed = rSquared * r;
const double n1 = 1.0 - r;
const double n2 = r;
const double h1 = 1.0 - 3.0 * rSquared + 2.0 * rCubed;
const double h2 = length * (r - 2.0 * rSquared + rCubed);
const double h3 = 3.0 * rSquared - 2.0 * rCubed;
const double h4 = length * (-rSquared + rCubed);
/// @brief Builds signed Hermite interpolation for endpoint recovery.
Matrix KinematicInterpolation(double xi, double length) {
Matrix interpolation{4U, kElementDofCount};
const double r = 0.5 * (1.0 + xi);
const double r_squared = r * r;
const double r_cubed = r_squared * r;
const double n1 = 1.0 - r;
const double n2 = r;
const double h1 = 1.0 - 3.0 * r_squared + 2.0 * r_cubed;
const double h2 = length * (r - 2.0 * r_squared + r_cubed);
const double h3 = 3.0 * r_squared - 2.0 * r_cubed;
const double h4 = length * (-r_squared + r_cubed);
interpolation(0U, 0U) = n1;
interpolation(0U, 6U) = n2;
interpolation(1U, 1U) = h1;
interpolation(1U, 5U) = h2;
interpolation(1U, 7U) = h3;
interpolation(1U, 11U) = h4;
interpolation(2U, 2U) = h1;
interpolation(2U, 4U) = -h2;
interpolation(2U, 8U) = h3;
interpolation(2U, 10U) = -h4;
interpolation(3U, 3U) = n1;
interpolation(3U, 9U) = n2;
return interpolation;
interpolation(0U, 0U) = n1;
interpolation(0U, 6U) = n2;
interpolation(1U, 1U) = h1;
interpolation(1U, 5U) = h2;
interpolation(1U, 7U) = h3;
interpolation(1U, 11U) = h4;
interpolation(2U, 2U) = h1;
interpolation(2U, 4U) = -h2;
interpolation(2U, 8U) = h3;
interpolation(2U, 10U) = -h4;
interpolation(3U, 3U) = n1;
interpolation(3U, 9U) = n2;
return interpolation;
}
} // namespace
} // namespace
Result<EulerBeam3D> EulerBeam3D::create(
const Node& firstNode,
const Node& secondNode,
const GeneralBeamSection& section,
const LinearElasticMaterial& material) {
const std::string identity = elementIdentity(firstNode, secondNode);
const Vector3& first = firstNode.coordinates;
const Vector3& second = secondNode.coordinates;
const Vector3 delta = {
second[0] - first[0], second[1] - first[1], second[2] - first[2]};
const double length = norm(delta);
const double coordinateScale =
(std::max)({1.0, norm(first), norm(second)});
if (!isFinite(first) || !isFinite(second) || !isFinite(delta) ||
!std::isfinite(length) || !std::isfinite(coordinateScale) ||
!(length > kGeometryTolerance * coordinateScale)) {
return modelFailure(
"invalid-beam-length",
firstNode.location,
identity,
"Beam length must exceed the scale-aware geometry threshold.");
}
Result<EulerBeam3D> EulerBeam3D::Create(const Node& first_node,
const Node& second_node,
const GeneralBeamSection& section,
const LinearElasticMaterial& material) {
const std::string identity = ElementIdentity(first_node, second_node);
const Vector3& first = first_node.coordinates;
const Vector3& second = second_node.coordinates;
const Vector3 delta = {second[0] - first[0], second[1] - first[1],
second[2] - first[2]};
const double length = Norm(delta);
const double coordinate_scale = (std::max)({1.0, Norm(first), Norm(second)});
if (!IsFinite(first) || !IsFinite(second) || !IsFinite(delta) ||
!std::isfinite(length) || !std::isfinite(coordinate_scale) ||
!(length > kGeometryTolerance * coordinate_scale)) {
return ModelFailure(
"invalid-beam-length", first_node.location, identity,
"Beam length must exceed the scale-aware geometry threshold.");
}
const Vector3 ex = {delta[0] / length, delta[1] / length, delta[2] / length};
const Vector3& guide = section.firstAxis;
const double guideNorm = norm(guide);
const double guideProjection = dot(guide, ex);
const Vector3 eyTrial = {
guide[0] - guideProjection * ex[0],
guide[1] - guideProjection * ex[1],
guide[2] - guideProjection * ex[2]};
const double eyTrialNorm = norm(eyTrial);
if (!isFinite(guide) || !std::isfinite(guideNorm) || !isFinite(eyTrial) ||
!std::isfinite(eyTrialNorm) ||
!(eyTrialNorm > kGeometryTolerance * (std::max)(1.0, guideNorm))) {
return modelFailure(
"invalid-beam-guide-vector",
section.location,
identity,
"Beam guide vector must define a scale-aware transverse direction.");
}
const Vector3 ex = {delta[0] / length, delta[1] / length, delta[2] / length};
const Vector3& guide = section.first_axis;
const double guide_norm = Norm(guide);
const double guide_projection = Dot(guide, ex);
const Vector3 ey_trial = {guide[0] - guide_projection * ex[0],
guide[1] - guide_projection * ex[1],
guide[2] - guide_projection * ex[2]};
const double ey_trial_norm = Norm(ey_trial);
if (!IsFinite(guide) || !std::isfinite(guide_norm) || !IsFinite(ey_trial) ||
!std::isfinite(ey_trial_norm) ||
!(ey_trial_norm > kGeometryTolerance * (std::max)(1.0, guide_norm))) {
return ModelFailure(
"invalid-beam-guide-vector", section.location, identity,
"Beam guide vector must define a scale-aware transverse direction.");
}
if (!std::isfinite(section.i12)) {
return modelFailure(
"invalid-beam-property",
section.location,
identity,
"Beam section properties must be finite and positive.");
}
if (section.i12 != 0.0) {
return modelFailure(
"unsupported-coupled-section",
section.location,
identity,
"The Euler beam kernel requires exact I12=0.");
}
if (!std::isfinite(section.i12)) {
return ModelFailure("invalid-beam-property", section.location, identity,
"Beam section properties must be finite and positive.");
}
if (section.i12 != 0.0) {
return ModelFailure("unsupported-coupled-section", section.location,
identity,
"The Euler beam kernel requires exact I12=0.");
}
const double shearModulus =
material.youngsModulus / (2.0 * (1.0 + material.poissonRatio));
const std::array<double, 6> positiveProperties = {
material.youngsModulus,
shearModulus,
section.area,
section.i11,
section.i22,
section.torsionalConstant};
if (!std::isfinite(material.poissonRatio) ||
std::any_of(
positiveProperties.begin(),
positiveProperties.end(),
[](double property) { return !std::isfinite(property) || !(property > 0.0); })) {
return modelFailure(
"invalid-beam-property",
section.location,
identity,
"E, G, A, Iy, Iz, and J must be finite and positive.");
}
const double shear_modulus =
material.youngs_modulus / (2.0 * (1.0 + material.poisson_ratio));
const std::array<double, 6> positive_properties = {
material.youngs_modulus,
shear_modulus,
section.area,
section.i11,
section.i22,
section.torsional_constant};
if (!std::isfinite(material.poisson_ratio) ||
std::any_of(positive_properties.begin(), positive_properties.end(),
[](double property) {
return !std::isfinite(property) || !(property > 0.0);
})) {
return ModelFailure("invalid-beam-property", section.location, identity,
"E, G, A, Iy, Iz, and J must be finite and positive.");
}
const auto derivedRigidity = constitutiveDiagonal(
material.youngsModulus,
shearModulus,
section.area,
section.i11,
section.i22,
section.torsionalConstant);
const double lengthSquared = length * length;
const double lengthCubed = lengthSquared * length;
// These are every distinct positive magnitude used by the exact axial,
// torsion, and two bending closed-form blocks. Reject arithmetic
// overflow/underflow without introducing a conditioning threshold.
const std::array<double, 14> requiredStiffnessMagnitudes = {
derivedRigidity[0U],
derivedRigidity[1U],
derivedRigidity[2U],
derivedRigidity[3U],
derivedRigidity[0U] / length,
derivedRigidity[1U] / length,
12.0 * derivedRigidity[2U] / lengthCubed,
6.0 * derivedRigidity[2U] / lengthSquared,
4.0 * derivedRigidity[2U] / length,
2.0 * derivedRigidity[2U] / length,
12.0 * derivedRigidity[3U] / lengthCubed,
6.0 * derivedRigidity[3U] / lengthSquared,
4.0 * derivedRigidity[3U] / length,
2.0 * derivedRigidity[3U] / length};
if (std::any_of(
requiredStiffnessMagnitudes.begin(),
requiredStiffnessMagnitudes.end(),
[](double magnitude) {
return !std::isfinite(magnitude) || !(magnitude > 0.0);
})) {
return modelFailure(
"invalid-beam-property",
section.location,
identity,
"Derived beam stiffness coefficients must be finite and positive.");
}
const auto derived_rigidity = ConstitutiveDiagonal(
material.youngs_modulus, shear_modulus, section.area, section.i11,
section.i22, section.torsional_constant);
const double length_squared = length * length;
const double length_cubed = length_squared * length;
// These are every distinct positive magnitude used by the exact axial,
// torsion, and two bending closed-form blocks. Reject arithmetic
// overflow/underflow without introducing a conditioning threshold.
const std::array<double, 14> required_stiffness_magnitudes = {
derived_rigidity[0U],
derived_rigidity[1U],
derived_rigidity[2U],
derived_rigidity[3U],
derived_rigidity[0U] / length,
derived_rigidity[1U] / length,
12.0 * derived_rigidity[2U] / length_cubed,
6.0 * derived_rigidity[2U] / length_squared,
4.0 * derived_rigidity[2U] / length,
2.0 * derived_rigidity[2U] / length,
12.0 * derived_rigidity[3U] / length_cubed,
6.0 * derived_rigidity[3U] / length_squared,
4.0 * derived_rigidity[3U] / length,
2.0 * derived_rigidity[3U] / length};
if (std::any_of(required_stiffness_magnitudes.begin(),
required_stiffness_magnitudes.end(), [](double magnitude) {
return !std::isfinite(magnitude) || !(magnitude > 0.0);
})) {
return ModelFailure(
"invalid-beam-property", section.location, identity,
"Derived beam stiffness coefficients must be finite and positive.");
}
const Vector3 ey = {
eyTrial[0] / eyTrialNorm,
eyTrial[1] / eyTrialNorm,
eyTrial[2] / eyTrialNorm};
const Vector3 ez = cross(ex, ey);
// Rows map global vectors to the approved right-handed local (ex,ey,ez) basis.
const std::array<double, 9> rotation = {
ex[0], ex[1], ex[2],
ey[0], ey[1], ey[2],
ez[0], ez[1], ez[2]};
const Vector3 ey = {ey_trial[0] / ey_trial_norm, ey_trial[1] / ey_trial_norm,
ey_trial[2] / ey_trial_norm};
const Vector3 ez = Cross(ex, ey);
// Rows map global vectors to the approved right-handed local (ex,ey,ez)
// basis.
const std::array<double, 9> rotation = {ex[0], ex[1], ex[2], ey[0], ey[1],
ey[2], ez[0], ez[1], ez[2]};
return Result<EulerBeam3D>::Success(EulerBeam3D{
length,
material.youngsModulus,
shearModulus,
section.area,
section.i11,
section.i22,
section.torsionalConstant,
rotation,
section.sectionPoints});
return Result<EulerBeam3D>::Success(
EulerBeam3D{length, material.youngs_modulus, shear_modulus, section.area,
section.i11, section.i22, section.torsional_constant,
rotation, section.section_points});
}
Matrix EulerBeam3D::localStiffness() const {
const auto diagonal = constitutiveDiagonal(
youngsModulus_, shearModulus_, area_, iy_, iz_, torsionalConstant_);
Matrix stiffness{kElementDofCount, kElementDofCount};
const double inverseSqrtThree = 1.0 / std::sqrt(3.0);
const std::array<double, 2> gaussPoints = {-inverseSqrtThree, inverseSqrtThree};
const double jacobian = 0.5 * length_;
Matrix EulerBeam3D::LocalStiffness() const {
const auto diagonal = ConstitutiveDiagonal(
youngs_modulus_, shear_modulus_, area_, iy_, iz_, torsional_constant_);
Matrix stiffness{kElementDofCount, kElementDofCount};
const double inverse_sqrt_three = 1.0 / std::sqrt(3.0);
const std::array<double, 2> gauss_points = {-inverse_sqrt_three,
inverse_sqrt_three};
const double jacobian = 0.5 * length_;
// Both Gauss points are required: a one-point bending rule loses two ranks.
for (const double xi : gaussPoints) {
const Matrix b = strainDisplacement(xi, length_);
for (std::size_t row = 0; row < kElementDofCount; ++row) {
for (std::size_t column = 0; column < kElementDofCount; ++column) {
for (std::size_t component = 0; component < diagonal.size(); ++component) {
stiffness(row, column) +=
b(component, row) * diagonal[component] *
b(component, column) * jacobian;
}
}
}
}
const Matrix closed = closedStiffness(length_, diagonal);
if (normalizedMatrixError(stiffness, closed) > kStiffnessInvariantTolerance) {
throw std::logic_error{
"Two-point Euler beam stiffness violated the closed-form invariant."};
}
return stiffness;
}
Matrix EulerBeam3D::globalStiffness() const {
const Matrix local = localStiffness();
const Matrix transform = transformation(rotation_);
const Matrix localTimesTransform = local.Multiply(transform);
Matrix global{kElementDofCount, kElementDofCount};
// Kg=T^T*Kl*T while dl=T*dg.
// Both Gauss points are required: a one-point bending rule loses two ranks.
for (const double xi : gauss_points) {
const Matrix b = StrainDisplacement(xi, length_);
for (std::size_t row = 0; row < kElementDofCount; ++row) {
for (std::size_t column = 0; column < kElementDofCount; ++column) {
for (std::size_t inner = 0; inner < kElementDofCount; ++inner) {
global(row, column) +=
transform(inner, row) * localTimesTransform(inner, column);
}
for (std::size_t column = 0; column < kElementDofCount; ++column) {
for (std::size_t component = 0; component < diagonal.size();
++component) {
stiffness(row, column) += b(component, row) * diagonal[component] *
b(component, column) * jacobian;
}
}
}
return global;
}
const Matrix closed = ClosedStiffness(length_, diagonal);
if (NormalizedMatrixError(stiffness, closed) > kStiffnessInvariantTolerance) {
throw std::logic_error{
"Two-point Euler beam stiffness violated the closed-form invariant."};
}
return stiffness;
}
Vector EulerBeam3D::localEquivalentLoad(const ConstantLocalLineLoad& load) const {
const std::array<double, 4> components = {load.px, load.py, load.pz, load.mx};
Vector equivalent{kElementDofCount};
const double inverseSqrtThree = 1.0 / std::sqrt(3.0);
const std::array<double, 2> gaussPoints = {-inverseSqrtThree, inverseSqrtThree};
const double jacobian = 0.5 * length_;
for (const double xi : gaussPoints) {
const Matrix interpolation = kinematicInterpolation(xi, length_);
for (std::size_t dof = 0; dof < equivalent.Size(); ++dof) {
for (std::size_t component = 0; component < components.size(); ++component) {
equivalent[dof] +=
interpolation(component, dof) * components[component] * jacobian;
}
}
Matrix EulerBeam3D::GlobalStiffness() const {
const Matrix local = LocalStiffness();
const Matrix transform = Transformation(rotation_);
const Matrix local_times_transform = local.Multiply(transform);
Matrix global{kElementDofCount, kElementDofCount};
// Kg=T^T*Kl*T while dl=T*dg.
for (std::size_t row = 0; row < kElementDofCount; ++row) {
for (std::size_t column = 0; column < kElementDofCount; ++column) {
for (std::size_t inner = 0; inner < kElementDofCount; ++inner) {
global(row, column) +=
transform(inner, row) * local_times_transform(inner, column);
}
}
return equivalent;
}
return global;
}
BeamRecovery EulerBeam3D::recover(const Vector& globalElementDisplacement) const {
const Matrix transform = transformation(rotation_);
const Vector localDisplacement = transform.Multiply(globalElementDisplacement);
const auto diagonal = constitutiveDiagonal(
youngsModulus_, shearModulus_, area_, iy_, iz_, torsionalConstant_);
BeamRecovery recovery{};
// With parser/CLI distributed loading excluded, Kl*dl is the local outward end action.
const Vector endAction = localStiffness().Multiply(localDisplacement);
for (std::size_t endpoint = 0; endpoint < 2U; ++endpoint) {
for (std::size_t component = 0; component < 6U; ++component) {
recovery.equilibriumEndActions[endpoint][component] =
endAction[endpoint * 6U + component];
}
const double xi = endpoint == 0U ? -1.0 : 1.0;
recovery.endpointSectionResultants[endpoint] = generalizedResultant(
generalizedStrain(strainDisplacement(xi, length_), localDisplacement),
diagonal);
Vector EulerBeam3D::LocalEquivalentLoad(
const ConstantLocalLineLoad& load) const {
const std::array<double, 4> components = {load.px, load.py, load.pz, load.mx};
Vector equivalent{kElementDofCount};
const double inverse_sqrt_three = 1.0 / std::sqrt(3.0);
const std::array<double, 2> gauss_points = {-inverse_sqrt_three,
inverse_sqrt_three};
const double jacobian = 0.5 * length_;
for (const double xi : gauss_points) {
const Matrix interpolation = KinematicInterpolation(xi, length_);
for (std::size_t dof = 0; dof < equivalent.Size(); ++dof) {
for (std::size_t component = 0; component < components.size();
++component) {
equivalent[dof] +=
interpolation(component, dof) * components[component] * jacobian;
}
}
const double inverseSqrtThree = 1.0 / std::sqrt(3.0);
const std::array<double, 2> gaussPoints = {-inverseSqrtThree, inverseSqrtThree};
for (std::size_t point = 0; point < gaussPoints.size(); ++point) {
recovery.gaussGeneralizedStrains[point] = generalizedStrain(
strainDisplacement(gaussPoints[point], length_), localDisplacement);
recovery.gaussGeneralizedResultants[point] = generalizedResultant(
recovery.gaussGeneralizedStrains[point], diagonal);
if (sectionPoints_.empty()) {
recovery.stressPoints.push_back({
static_cast<int>(point + 1U),
0U,
0.0,
0.0,
youngsModulus_ * recovery.gaussGeneralizedStrains[point][0U],
"fesa-default"});
continue;
}
for (std::size_t sectionPoint = 0; sectionPoint < sectionPoints_.size();
++sectionPoint) {
const double x1 = sectionPoints_[sectionPoint][0U];
const double x2 = sectionPoints_[sectionPoint][1U];
const auto& strain = recovery.gaussGeneralizedStrains[point];
// x1=y and x2=z: S11=E(epsilon0+x2*kappa_y-x1*kappa_z).
recovery.stressPoints.push_back({
static_cast<int>(point + 1U),
sectionPoint + 1U,
x1,
x2,
youngsModulus_ *
(strain[0U] + x2 * strain[2U] - x1 * strain[3U]),
"input"});
}
}
return recovery;
}
return equivalent;
}
EulerBeam3D::EulerBeam3D(
double length,
double youngsModulus,
double shearModulus,
double area,
double iy,
double iz,
double torsionalConstant,
std::array<double, 9> rotation,
std::vector<std::array<double, 2>> sectionPoints)
BeamRecovery EulerBeam3D::Recover(
const Vector& global_element_displacement) const {
const Matrix transform = Transformation(rotation_);
const Vector local_displacement =
transform.Multiply(global_element_displacement);
const auto diagonal = ConstitutiveDiagonal(
youngs_modulus_, shear_modulus_, area_, iy_, iz_, torsional_constant_);
BeamRecovery recovery{};
// With parser/CLI distributed loading excluded, Kl*dl is the local outward
// end action.
const Vector end_action = LocalStiffness().Multiply(local_displacement);
for (std::size_t endpoint = 0; endpoint < 2U; ++endpoint) {
for (std::size_t component = 0; component < 6U; ++component) {
recovery.equilibrium_end_actions[endpoint][component] =
end_action[endpoint * 6U + component];
}
const double xi = endpoint == 0U ? -1.0 : 1.0;
recovery.endpoint_section_resultants[endpoint] = GeneralizedResultant(
GeneralizedStrain(StrainDisplacement(xi, length_), local_displacement),
diagonal);
}
const double inverse_sqrt_three = 1.0 / std::sqrt(3.0);
const std::array<double, 2> gauss_points = {-inverse_sqrt_three,
inverse_sqrt_three};
for (std::size_t point = 0; point < gauss_points.size(); ++point) {
recovery.gauss_generalized_strains[point] = GeneralizedStrain(
StrainDisplacement(gauss_points[point], length_), local_displacement);
recovery.gauss_generalized_resultants[point] = GeneralizedResultant(
recovery.gauss_generalized_strains[point], diagonal);
if (section_points_.empty()) {
recovery.stress_points.push_back(
{static_cast<int>(point + 1U), 0U, 0.0, 0.0,
youngs_modulus_ * recovery.gauss_generalized_strains[point][0U],
"fesa-default"});
continue;
}
for (std::size_t section_point = 0; section_point < section_points_.size();
++section_point) {
const double x1 = section_points_[section_point][0U];
const double x2 = section_points_[section_point][1U];
const auto& strain = recovery.gauss_generalized_strains[point];
// x1=y and x2=z: S11=E(epsilon0+x2*kappa_y-x1*kappa_z).
recovery.stress_points.push_back(
{static_cast<int>(point + 1U), section_point + 1U, x1, x2,
youngs_modulus_ * (strain[0U] + x2 * strain[2U] - x1 * strain[3U]),
"input"});
}
}
return recovery;
}
EulerBeam3D::EulerBeam3D(double length, double youngs_modulus,
double shear_modulus, double area, double iy,
double iz, double torsional_constant,
std::array<double, 9> rotation,
std::vector<std::array<double, 2>> section_points)
: length_{length},
youngsModulus_{youngsModulus},
shearModulus_{shearModulus},
youngs_modulus_{youngs_modulus},
shear_modulus_{shear_modulus},
area_{area},
iy_{iy},
iz_{iz},
torsionalConstant_{torsionalConstant},
torsional_constant_{torsional_constant},
rotation_{rotation},
sectionPoints_{std::move(sectionPoints)} {}
section_points_{std::move(section_points)} {}
} // namespace fesa
} // namespace fesa