350 lines
11 KiB
C++
350 lines
11 KiB
C++
#include <fesa/elements/beam/beam3d2.hpp>
|
|
|
|
#include <array>
|
|
#include <cmath>
|
|
#include <cstddef>
|
|
#include <optional>
|
|
#include <stdexcept>
|
|
#include <string>
|
|
#include <utility>
|
|
#include <vector>
|
|
|
|
#include <fesa/fem/gauss_rule.hpp>
|
|
#include <fesa/fem/line2_shape.hpp>
|
|
|
|
namespace fesa {
|
|
namespace {
|
|
|
|
using StrainMatrix =
|
|
std::array<std::array<double, 12>, 6>;
|
|
|
|
BeamKernelResult error_result(std::string code, std::string message) {
|
|
BeamKernelResult result;
|
|
result.diagnostics.push_back({
|
|
DiagnosticStage::model,
|
|
Severity::error,
|
|
std::move(code),
|
|
std::move(message),
|
|
std::nullopt,
|
|
});
|
|
return result;
|
|
}
|
|
|
|
bool is_positive_finite(const double value) {
|
|
return std::isfinite(value) && value > 0.0;
|
|
}
|
|
|
|
std::array<double, 6> constitutive_values(
|
|
const Beam3D2Input& input) {
|
|
const double shear_modulus =
|
|
input.material.young / (2.0 * (1.0 + input.material.poisson));
|
|
return {
|
|
input.material.young * input.section.area,
|
|
shear_modulus * input.section.shear_area_y,
|
|
shear_modulus * input.section.shear_area_z,
|
|
shear_modulus * input.section.torsion_j,
|
|
input.material.young * input.section.iy,
|
|
input.material.young * input.section.iz,
|
|
};
|
|
}
|
|
|
|
std::optional<BeamKernelResult> validate_properties(
|
|
const Beam3D2Input& input) {
|
|
if (!std::isfinite(input.material.young) ||
|
|
!std::isfinite(input.material.poisson)) {
|
|
return error_result(
|
|
"model.nonfinite_value",
|
|
"Beam kernel requires finite elastic constants.");
|
|
}
|
|
if (input.material.young <= 0.0 ||
|
|
input.material.poisson <= -1.0 ||
|
|
input.material.poisson >= 0.5) {
|
|
return error_result(
|
|
"model.invalid_material",
|
|
"Beam kernel requires E > 0 and -1 < nu < 0.5.");
|
|
}
|
|
|
|
if (!is_positive_finite(input.section.area) ||
|
|
!is_positive_finite(input.section.iy) ||
|
|
!is_positive_finite(input.section.iz) ||
|
|
!is_positive_finite(input.section.torsion_j) ||
|
|
!is_positive_finite(input.section.shear_area_y) ||
|
|
!is_positive_finite(input.section.shear_area_z)) {
|
|
return error_result(
|
|
"model.invalid_section",
|
|
"Beam kernel requires positive finite section properties.");
|
|
}
|
|
return std::nullopt;
|
|
}
|
|
|
|
StrainMatrix strain_matrix(
|
|
const double xi,
|
|
const double jacobian) {
|
|
StrainMatrix strain{};
|
|
const auto shape = line2_shape(xi);
|
|
const auto natural_derivative = line2_shape_derivative(xi);
|
|
|
|
for (std::size_t node = 0; node < shape.size(); ++node) {
|
|
const std::size_t offset = node * 6;
|
|
const double derivative = natural_derivative[node] / jacobian;
|
|
strain[0][offset] = derivative;
|
|
strain[1][offset + 1] = derivative;
|
|
strain[1][offset + 5] = -shape[node];
|
|
strain[2][offset + 2] = derivative;
|
|
strain[2][offset + 4] = shape[node];
|
|
strain[3][offset + 3] = derivative;
|
|
strain[4][offset + 4] = derivative;
|
|
strain[5][offset + 5] = derivative;
|
|
}
|
|
return strain;
|
|
}
|
|
|
|
template <std::size_t ComponentCount>
|
|
void integrate_components(
|
|
Matrix12& stiffness,
|
|
const std::span<const GaussPoint1D> rule,
|
|
const std::array<std::size_t, ComponentCount>& components,
|
|
const std::array<double, 6>& constitutive,
|
|
const double jacobian) {
|
|
for (const GaussPoint1D& point : rule) {
|
|
const StrainMatrix strain = strain_matrix(point.xi, jacobian);
|
|
const double integration_weight = jacobian * point.weight;
|
|
for (std::size_t row = 0; row < stiffness.size(); ++row) {
|
|
for (std::size_t column = row;
|
|
column < stiffness[row].size();
|
|
++column) {
|
|
double entry = 0.0;
|
|
for (const std::size_t component : components) {
|
|
entry +=
|
|
strain[component][row] *
|
|
constitutive[component] *
|
|
strain[component][column];
|
|
}
|
|
stiffness[row][column] +=
|
|
entry * integration_weight;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void mirror_upper_triangle(Matrix12& matrix) {
|
|
for (std::size_t row = 0; row < matrix.size(); ++row) {
|
|
for (std::size_t column = row + 1;
|
|
column < matrix[row].size();
|
|
++column) {
|
|
matrix[column][row] = matrix[row][column];
|
|
}
|
|
}
|
|
}
|
|
|
|
Matrix12 transform_stiffness(
|
|
const Matrix12& local,
|
|
const Matrix12& transformation) {
|
|
Matrix12 global{};
|
|
for (std::size_t row = 0; row < global.size(); ++row) {
|
|
for (std::size_t column = row;
|
|
column < global[row].size();
|
|
++column) {
|
|
double entry = 0.0;
|
|
for (std::size_t local_row = 0;
|
|
local_row < local.size();
|
|
++local_row) {
|
|
for (std::size_t local_column = 0;
|
|
local_column < local[local_row].size();
|
|
++local_column) {
|
|
entry +=
|
|
transformation[local_row][row] *
|
|
local[local_row][local_column] *
|
|
transformation[local_column][column];
|
|
}
|
|
}
|
|
global[row][column] = entry;
|
|
}
|
|
}
|
|
mirror_upper_triangle(global);
|
|
return global;
|
|
}
|
|
|
|
std::array<double, 12> transform_displacement(
|
|
const Matrix12& transformation,
|
|
const std::span<const double, 12> global_displacement) {
|
|
std::array<double, 12> local_displacement{};
|
|
for (std::size_t row = 0; row < transformation.size(); ++row) {
|
|
for (std::size_t column = 0;
|
|
column < transformation[row].size();
|
|
++column) {
|
|
local_displacement[row] +=
|
|
transformation[row][column] * global_displacement[column];
|
|
}
|
|
}
|
|
return local_displacement;
|
|
}
|
|
|
|
std::array<double, 6> evaluate_strain(
|
|
const StrainMatrix& strain_matrix,
|
|
const std::array<double, 12>& local_displacement) {
|
|
std::array<double, 6> strain{};
|
|
for (std::size_t component = 0; component < strain.size(); ++component) {
|
|
for (std::size_t dof = 0; dof < local_displacement.size(); ++dof) {
|
|
strain[component] +=
|
|
strain_matrix[component][dof] * local_displacement[dof];
|
|
}
|
|
}
|
|
return strain;
|
|
}
|
|
|
|
bool is_finite(const Matrix12& matrix) {
|
|
for (const auto& row : matrix) {
|
|
for (const double value : row) {
|
|
if (!std::isfinite(value)) {
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
BeamKernelResult compute_beam3d2(const Beam3D2Input& input) {
|
|
if (const auto validation = validate_properties(input);
|
|
validation.has_value()) {
|
|
return *validation;
|
|
}
|
|
|
|
BeamFrameResult frame_result = make_beam_frame(
|
|
input.coordinates[0],
|
|
input.coordinates[1],
|
|
input.section.orientation);
|
|
if (!frame_result.frame.has_value()) {
|
|
return {std::nullopt, std::move(frame_result.diagnostics)};
|
|
}
|
|
|
|
const Vec3 axis{
|
|
input.coordinates[1].x - input.coordinates[0].x,
|
|
input.coordinates[1].y - input.coordinates[0].y,
|
|
input.coordinates[1].z - input.coordinates[0].z,
|
|
};
|
|
const double length = std::hypot(axis.x, axis.y, axis.z);
|
|
const double jacobian = length / 2.0;
|
|
if (!std::isfinite(jacobian) || jacobian <= 0.0) {
|
|
return error_result(
|
|
"model.zero_length_element",
|
|
"Beam kernel requires a representable positive Jacobian.");
|
|
}
|
|
|
|
const std::array<double, 6> constitutive =
|
|
constitutive_values(input);
|
|
for (const double value : constitutive) {
|
|
if (!is_positive_finite(value)) {
|
|
return error_result(
|
|
"model.nonfinite_value",
|
|
"Beam constitutive stiffness is not finite and positive.");
|
|
}
|
|
}
|
|
|
|
Matrix12 local{};
|
|
integrate_components(
|
|
local,
|
|
gauss_rule_1d(2),
|
|
std::array<std::size_t, 4>{0, 3, 4, 5},
|
|
constitutive,
|
|
jacobian);
|
|
integrate_components(
|
|
local,
|
|
gauss_rule_1d(1),
|
|
std::array<std::size_t, 2>{1, 2},
|
|
constitutive,
|
|
jacobian);
|
|
mirror_upper_triangle(local);
|
|
|
|
const Matrix12 transformation =
|
|
beam_transformation(*frame_result.frame);
|
|
Matrix12 global = transform_stiffness(local, transformation);
|
|
if (!is_finite(local) || !is_finite(global)) {
|
|
return error_result(
|
|
"model.nonfinite_value",
|
|
"Beam stiffness contains a nonfinite value.");
|
|
}
|
|
|
|
return {
|
|
Beam3D2Contribution{
|
|
std::move(local),
|
|
std::move(global),
|
|
*frame_result.frame,
|
|
},
|
|
{},
|
|
};
|
|
}
|
|
|
|
std::vector<BeamSectionResult> recover_beam3d2(
|
|
const Beam3D2Input& input,
|
|
const std::span<const double, 12> element_displacement,
|
|
const std::span<const std::array<double, 2>> recovery_points) {
|
|
const BeamKernelResult kernel = compute_beam3d2(input);
|
|
if (!kernel.contribution.has_value()) {
|
|
const std::string message = kernel.diagnostics.empty()
|
|
? "Beam recovery requires a valid Beam3D2 input."
|
|
: kernel.diagnostics.front().message;
|
|
throw std::invalid_argument{message};
|
|
}
|
|
|
|
const Vec3 axis{
|
|
input.coordinates[1].x - input.coordinates[0].x,
|
|
input.coordinates[1].y - input.coordinates[0].y,
|
|
input.coordinates[1].z - input.coordinates[0].z,
|
|
};
|
|
const double jacobian = std::hypot(axis.x, axis.y, axis.z) / 2.0;
|
|
const Matrix12 transformation =
|
|
beam_transformation(kernel.contribution->frame);
|
|
const std::array<double, 12> local_displacement =
|
|
transform_displacement(transformation, element_displacement);
|
|
const std::array<double, 6> center_strain = evaluate_strain(
|
|
strain_matrix(0.0, jacobian),
|
|
local_displacement);
|
|
const std::array<double, 6> constitutive =
|
|
constitutive_values(input);
|
|
|
|
std::vector<BeamSectionResult> results;
|
|
results.reserve(input.node_ids.size());
|
|
for (std::size_t end = 0; end < input.node_ids.size(); ++end) {
|
|
const double xi = end == 0 ? -1.0 : 1.0;
|
|
std::array<double, 6> section_strain = evaluate_strain(
|
|
strain_matrix(xi, jacobian),
|
|
local_displacement);
|
|
section_strain[1] = center_strain[1];
|
|
section_strain[2] = center_strain[2];
|
|
|
|
std::array<double, 6> section_force{};
|
|
for (std::size_t component = 0;
|
|
component < section_force.size();
|
|
++component) {
|
|
section_force[component] =
|
|
constitutive[component] * section_strain[component];
|
|
}
|
|
|
|
std::vector<double> sigma_xx;
|
|
sigma_xx.reserve(recovery_points.size());
|
|
for (const auto& point : recovery_points) {
|
|
const double y = point[0];
|
|
const double z = point[1];
|
|
sigma_xx.push_back(
|
|
input.material.young *
|
|
(section_strain[0] + z * section_strain[4] -
|
|
y * section_strain[5]));
|
|
}
|
|
|
|
results.push_back({
|
|
xi,
|
|
input.node_ids[end],
|
|
section_strain,
|
|
section_force,
|
|
section_force[0] / input.section.area,
|
|
std::move(sigma_xx),
|
|
});
|
|
}
|
|
return results;
|
|
}
|
|
|
|
} // namespace fesa
|