621 lines
25 KiB
C++
621 lines
25 KiB
C++
#include "fesa/elements/euler_beam_3d.h"
|
|
|
|
#include <algorithm>
|
|
#include <array>
|
|
#include <cmath>
|
|
#include <limits>
|
|
#include <stdexcept>
|
|
#include <string>
|
|
#include <type_traits>
|
|
#include <utility>
|
|
#include <vector>
|
|
|
|
#include "fesa/math/vector3.h"
|
|
#include "fesa/model/model_types.h"
|
|
#include "math/dense_blas_internal.h"
|
|
|
|
namespace fesa {
|
|
|
|
EulerBeam3DDefinition::EulerBeam3DDefinition(
|
|
SourceEntityId source_id_value,
|
|
std::array<EntityIndex, 2> node_indices_value,
|
|
const EntityIndex material_index_value,
|
|
const EntityIndex section_index_value, SourceLocation location_value)
|
|
: source_id{std::move(source_id_value)},
|
|
node_indices{std::move(node_indices_value)},
|
|
material_index{material_index_value},
|
|
section_index{section_index_value},
|
|
location{std::move(location_value)},
|
|
node_indices_view_{node_indices.begin(), node_indices.end()} {}
|
|
|
|
ElementDefinitionKind EulerBeam3DDefinition::Kind() const noexcept {
|
|
return ElementDefinitionKind::kEulerBeam3D;
|
|
}
|
|
|
|
const SourceEntityId& EulerBeam3DDefinition::SourceId() const noexcept {
|
|
return source_id;
|
|
}
|
|
|
|
std::string_view EulerBeam3DDefinition::SourceElementType() const noexcept {
|
|
return "B33";
|
|
}
|
|
|
|
const std::vector<EntityIndex>& EulerBeam3DDefinition::NodeIndices()
|
|
const noexcept {
|
|
return node_indices_view_;
|
|
}
|
|
|
|
EntityIndex EulerBeam3DDefinition::MaterialIndex() const noexcept {
|
|
return material_index;
|
|
}
|
|
|
|
EntityIndex EulerBeam3DDefinition::PropertyIndex() const noexcept {
|
|
return section_index;
|
|
}
|
|
|
|
void EulerBeam3DDefinition::SynchronizeNodeIndices() {
|
|
node_indices_view_.assign(node_indices.begin(), node_indices.end());
|
|
}
|
|
|
|
namespace {
|
|
|
|
constexpr std::size_t kElementDofCount = 12U;
|
|
constexpr std::size_t kGeneralizedComponentCount = 4U;
|
|
constexpr double kGeometryTolerance = 1.0e-12;
|
|
constexpr double kStiffnessInvariantTolerance = 1.0e-12;
|
|
|
|
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,
|
|
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}}));
|
|
}
|
|
|
|
/// @brief Creates a structured failure at the runtime element boundary.
|
|
template <class T>
|
|
Result<T> RuntimeFailure(const SourceLocation& location,
|
|
const SourceEntityId& source_id,
|
|
const std::string& message) {
|
|
return Result<T>::Failure(
|
|
Status::Failure(FailureCategory::kModel,
|
|
{{Severity::kError, "invalid-runtime-element", location,
|
|
"*ELEMENT", source_id.source_label_text, message}}));
|
|
}
|
|
|
|
/// @brief Reports whether every fixed-size recovery component is finite.
|
|
template <class T, std::size_t Size>
|
|
bool IsFinite(const std::array<T, Size>& values) {
|
|
return std::all_of(values.begin(), values.end(), [](const auto& value) {
|
|
if constexpr (std::is_arithmetic_v<T>) {
|
|
return std::isfinite(value);
|
|
} else {
|
|
return IsFinite(value);
|
|
}
|
|
});
|
|
}
|
|
|
|
/// @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;
|
|
}
|
|
|
|
/// @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) = -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) * 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) * 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 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};
|
|
}
|
|
|
|
/// @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 add_bending_block = [&closed, length](
|
|
const std::array<std::size_t, 4>& indices,
|
|
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 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));
|
|
}
|
|
}
|
|
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();
|
|
}
|
|
|
|
/// @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{};
|
|
const auto product = b.Multiply(local_displacement);
|
|
for (std::size_t component = 0; component < strain.size(); ++component) {
|
|
strain[component] = product[component];
|
|
}
|
|
return strain;
|
|
}
|
|
|
|
/// @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;
|
|
}
|
|
|
|
/// @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;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
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 - first;
|
|
const double length = delta.Norm();
|
|
const double coordinate_scale =
|
|
(std::max)({1.0, first.Norm(), second.Norm()});
|
|
if (!first.IsFinite() || !second.IsFinite() || !delta.IsFinite() ||
|
|
!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 / length;
|
|
const Vector3 guide{section.first_axis};
|
|
const double guide_norm = guide.Norm();
|
|
const double guide_projection = guide.Dot(ex);
|
|
const Vector3 ey_trial = guide - guide_projection * ex;
|
|
const double ey_trial_norm = ey_trial.Norm();
|
|
if (!guide.IsFinite() || !std::isfinite(guide_norm) || !ey_trial.IsFinite() ||
|
|
!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.");
|
|
}
|
|
|
|
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 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 = ey_trial / ey_trial_norm;
|
|
const Vector3 ez = ex.Cross(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.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(
|
|
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 : gauss_points) {
|
|
const Matrix b = StrainDisplacement(xi, length_);
|
|
Matrix weighted_b = b;
|
|
for (std::size_t component = 0; component < diagonal.size(); ++component)
|
|
for (std::size_t column = 0; column < kElementDofCount; ++column)
|
|
weighted_b(component, column) *= diagonal[component];
|
|
const auto contribution =
|
|
dense_blas_internal::MultiplyTransposeLeft(b, weighted_b);
|
|
for (std::size_t row = 0; row < kElementDofCount; ++row) {
|
|
for (std::size_t column = 0; column < kElementDofCount; ++column) {
|
|
stiffness(row, column) += contribution(row, 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 local_times_transform = local.Multiply(transform);
|
|
// Kg=T^T*Kl*T while dl=T*dg.
|
|
return dense_blas_internal::MultiplyTransposeLeft(transform,
|
|
local_times_transform);
|
|
}
|
|
|
|
Vector EulerBeam3D::LocalEquivalentLoad(
|
|
const ConstantLocalLineLoad& load) const {
|
|
const std::array<double, 4> components = {load.px, load.py, load.pz, load.mx};
|
|
Matrix component_column{components.size(), 1U};
|
|
for (std::size_t component = 0; component < components.size(); ++component)
|
|
component_column(component, 0U) = components[component];
|
|
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_);
|
|
const auto contribution = dense_blas_internal::MultiplyTransposeLeft(
|
|
interpolation, component_column);
|
|
for (std::size_t dof = 0; dof < equivalent.Size(); ++dof) {
|
|
equivalent[dof] += contribution(dof, 0U) * jacobian;
|
|
}
|
|
}
|
|
return equivalent;
|
|
}
|
|
|
|
BeamRecovery EulerBeam3D::RecoverBeam(
|
|
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;
|
|
}
|
|
|
|
const ElementDofLayout& EulerBeam3D::DofLayout() const noexcept {
|
|
return dof_layout_;
|
|
}
|
|
|
|
Result<ElementStiffnessContribution> EulerBeam3D::ComputeStiffness() const {
|
|
if (dof_layout_.node_indices.size() != 2U ||
|
|
dof_layout_.components_per_node.size() != 6U) {
|
|
return RuntimeFailure<ElementStiffnessContribution>(
|
|
runtime_location_, dof_layout_.source_id,
|
|
"B33 runtime stiffness requires a bound two-node six-DOF layout.");
|
|
}
|
|
try {
|
|
return Result<ElementStiffnessContribution>::Success(
|
|
{dof_layout_, GlobalStiffness()});
|
|
} catch (const std::exception& error) {
|
|
return RuntimeFailure<ElementStiffnessContribution>(
|
|
runtime_location_, dof_layout_.source_id, error.what());
|
|
}
|
|
}
|
|
|
|
Result<ElementResultBundle> EulerBeam3D::Recover(
|
|
const Vector& element_displacement) const {
|
|
if (dof_layout_.node_indices.size() != 2U ||
|
|
dof_layout_.components_per_node.size() != 6U ||
|
|
node_source_ids_.size() != 2U ||
|
|
element_displacement.Size() != kElementDofCount) {
|
|
return RuntimeFailure<ElementResultBundle>(
|
|
runtime_location_, dof_layout_.source_id,
|
|
"B33 runtime recovery requires a bound two-node layout and 12 DOFs.");
|
|
}
|
|
for (std::size_t dof = 0U; dof < element_displacement.Size(); ++dof) {
|
|
if (!std::isfinite(element_displacement[dof])) {
|
|
return RuntimeFailure<ElementResultBundle>(
|
|
runtime_location_, dof_layout_.source_id,
|
|
"B33 runtime recovery displacement must be finite.");
|
|
}
|
|
}
|
|
|
|
BeamRecovery recovered{};
|
|
try {
|
|
recovered = RecoverBeam(element_displacement);
|
|
} catch (const std::exception& error) {
|
|
return RuntimeFailure<ElementResultBundle>(
|
|
runtime_location_, dof_layout_.source_id, error.what());
|
|
}
|
|
|
|
BeamElementResultRows rows{};
|
|
rows.endpoint_rows.reserve(2U);
|
|
rows.gauss_rows.reserve(2U);
|
|
rows.stress_rows.reserve(recovered.stress_points.size());
|
|
for (std::size_t endpoint = 0U; endpoint < 2U; ++endpoint) {
|
|
if (!IsFinite(recovered.equilibrium_end_actions[endpoint]) ||
|
|
!IsFinite(recovered.endpoint_section_resultants[endpoint])) {
|
|
return RuntimeFailure<ElementResultBundle>(
|
|
runtime_location_, dof_layout_.source_id,
|
|
"B33 endpoint recovery values must be finite.");
|
|
}
|
|
rows.endpoint_rows.push_back(
|
|
{element_index_, static_cast<int>(endpoint), node_source_ids_[endpoint],
|
|
recovered.equilibrium_end_actions[endpoint],
|
|
recovered.endpoint_section_resultants[endpoint]});
|
|
}
|
|
for (std::size_t gauss = 0U; gauss < 2U; ++gauss) {
|
|
if (!IsFinite(recovered.gauss_generalized_strains[gauss]) ||
|
|
!IsFinite(recovered.gauss_generalized_resultants[gauss])) {
|
|
return RuntimeFailure<ElementResultBundle>(
|
|
runtime_location_, dof_layout_.source_id,
|
|
"B33 Gauss recovery values must be finite.");
|
|
}
|
|
rows.gauss_rows.push_back({element_index_, static_cast<int>(gauss + 1U),
|
|
recovered.gauss_generalized_strains[gauss],
|
|
recovered.gauss_generalized_resultants[gauss]});
|
|
}
|
|
for (const auto& point : recovered.stress_points) {
|
|
if ((point.gauss_point != 1 && point.gauss_point != 2) ||
|
|
!std::isfinite(point.x1) || !std::isfinite(point.x2) ||
|
|
!std::isfinite(point.s11)) {
|
|
return RuntimeFailure<ElementResultBundle>(
|
|
runtime_location_, dof_layout_.source_id,
|
|
"B33 stress recovery identity and values must be finite.");
|
|
}
|
|
rows.stress_rows.push_back({element_index_, point.gauss_point,
|
|
point.section_point, point.x1, point.x2,
|
|
point.s11, point.source});
|
|
}
|
|
return Result<ElementResultBundle>::Success(
|
|
{dof_layout_.source_id, std::move(rows)});
|
|
}
|
|
|
|
void EulerBeam3D::BindRuntime(ElementDofLayout layout,
|
|
const EntityIndex element_index,
|
|
std::vector<SourceEntityId> node_source_ids,
|
|
SourceLocation location) {
|
|
dof_layout_ = std::move(layout);
|
|
element_index_ = element_index;
|
|
node_source_ids_ = std::move(node_source_ids);
|
|
runtime_location_ = std::move(location);
|
|
}
|
|
|
|
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},
|
|
youngs_modulus_{youngs_modulus},
|
|
shear_modulus_{shear_modulus},
|
|
area_{area},
|
|
iy_{iy},
|
|
iz_{iz},
|
|
torsional_constant_{torsional_constant},
|
|
rotation_{rotation},
|
|
section_points_{std::move(section_points)} {}
|
|
|
|
} // namespace fesa
|