feat(euler-beam-3d): add local beam kernel

This commit is contained in:
김경종
2026-06-12 18:02:05 +09:00
parent 5b01642cbc
commit 95ca95180a
4 changed files with 265 additions and 1 deletions
+2 -1
View File
@@ -73,7 +73,8 @@
{ {
"step": 7, "step": 7,
"name": "local-stiffness-kernel", "name": "local-stiffness-kernel",
"status": "pending", "status": "completed",
"summary": "local 3D Euler beam stiffness and local end-force kernel added",
"allowed_paths": [ "allowed_paths": [
"src/fesa/elements/", "src/fesa/elements/",
"tests/unit/euler_beam_3d_*_test.cpp" "tests/unit/euler_beam_3d_*_test.cpp"
+113
View File
@@ -0,0 +1,113 @@
#include <fesa/elements/euler_beam_3d.hpp>
#include <cmath>
#include <stdexcept>
namespace fesa::elements {
namespace {
constexpr int matrix_size = 12;
std::size_t index(int row, int column)
{
return static_cast<std::size_t>(row * matrix_size + column);
}
void require_positive_finite(double value, const char* name)
{
if (!std::isfinite(value) || value <= 0.0) {
throw std::invalid_argument(name);
}
}
void validate_section(double length, const EulerBeam3DSection& section)
{
require_positive_finite(length, "Euler beam length must be positive and finite");
require_positive_finite(section.young_modulus, "Euler beam Young modulus must be positive and finite");
require_positive_finite(section.shear_modulus, "Euler beam shear modulus must be positive and finite");
require_positive_finite(section.area, "Euler beam area must be positive and finite");
require_positive_finite(section.torsion_constant, "Euler beam torsion constant must be positive and finite");
require_positive_finite(section.second_moment_y, "Euler beam Iy must be positive and finite");
require_positive_finite(section.second_moment_z, "Euler beam Iz must be positive and finite");
}
void set_symmetric(Matrix12& matrix, int row, int column, double value)
{
matrix[index(row, column)] = value;
matrix[index(column, row)] = value;
}
} // namespace
Matrix12 euler_beam_3d_local_stiffness(double length, const EulerBeam3DSection& section)
{
validate_section(length, section);
const double length2 = length * length;
const double length3 = length2 * length;
const double axial = section.young_modulus * section.area / length;
const double torsion = section.shear_modulus * section.torsion_constant / length;
const double eiy = section.young_modulus * section.second_moment_y;
const double cy1 = 12.0 * eiy / length3;
const double cy2 = 6.0 * eiy / length2;
const double cy3 = 4.0 * eiy / length;
const double cy4 = 2.0 * eiy / length;
const double eiz = section.young_modulus * section.second_moment_z;
const double cz1 = 12.0 * eiz / length3;
const double cz2 = 6.0 * eiz / length2;
const double cz3 = 4.0 * eiz / length;
const double cz4 = 2.0 * eiz / length;
Matrix12 stiffness{};
set_symmetric(stiffness, 0, 0, axial);
set_symmetric(stiffness, 0, 6, -axial);
set_symmetric(stiffness, 6, 6, axial);
set_symmetric(stiffness, 3, 3, torsion);
set_symmetric(stiffness, 3, 9, -torsion);
set_symmetric(stiffness, 9, 9, torsion);
set_symmetric(stiffness, 1, 1, cz1);
set_symmetric(stiffness, 1, 5, cz2);
set_symmetric(stiffness, 1, 7, -cz1);
set_symmetric(stiffness, 1, 11, cz2);
set_symmetric(stiffness, 5, 5, cz3);
set_symmetric(stiffness, 5, 7, -cz2);
set_symmetric(stiffness, 5, 11, cz4);
set_symmetric(stiffness, 7, 7, cz1);
set_symmetric(stiffness, 7, 11, -cz2);
set_symmetric(stiffness, 11, 11, cz3);
set_symmetric(stiffness, 2, 2, cy1);
set_symmetric(stiffness, 2, 4, -cy2);
set_symmetric(stiffness, 2, 8, -cy1);
set_symmetric(stiffness, 2, 10, -cy2);
set_symmetric(stiffness, 4, 4, cy3);
set_symmetric(stiffness, 4, 8, cy2);
set_symmetric(stiffness, 4, 10, cy4);
set_symmetric(stiffness, 8, 8, cy1);
set_symmetric(stiffness, 8, 10, cy2);
set_symmetric(stiffness, 10, 10, cy3);
return stiffness;
}
Vector12 euler_beam_3d_local_end_forces(double length,
const EulerBeam3DSection& section,
const Vector12& local_displacements)
{
const auto stiffness = euler_beam_3d_local_stiffness(length, section);
Vector12 forces{};
for (int row = 0; row < matrix_size; ++row) {
for (int column = 0; column < matrix_size; ++column) {
forces[static_cast<std::size_t>(row)] +=
stiffness[index(row, column)] * local_displacements[static_cast<std::size_t>(column)];
}
}
return forces;
}
} // namespace fesa::elements
+25
View File
@@ -0,0 +1,25 @@
#pragma once
#include <array>
namespace fesa::elements {
using Vector12 = std::array<double, 12>;
using Matrix12 = std::array<double, 144>;
struct EulerBeam3DSection {
double young_modulus;
double shear_modulus;
double area;
double torsion_constant;
double second_moment_y;
double second_moment_z;
};
Matrix12 euler_beam_3d_local_stiffness(double length, const EulerBeam3DSection& section);
Vector12 euler_beam_3d_local_end_forces(double length,
const EulerBeam3DSection& section,
const Vector12& local_displacements);
} // namespace fesa::elements
@@ -0,0 +1,125 @@
#include <fesa/elements/euler_beam_3d.hpp>
#include <cmath>
#include <cstddef>
#include <stdexcept>
namespace {
constexpr double tolerance = 1.0e-10;
bool close(double actual, double expected, double tol = tolerance)
{
return std::abs(actual - expected) <= tol;
}
double entry(const fesa::elements::Matrix12& matrix, int row, int column)
{
return matrix[static_cast<std::size_t>(row * 12 + column)];
}
fesa::elements::Vector12 multiply(const fesa::elements::Matrix12& matrix,
const fesa::elements::Vector12& vector)
{
fesa::elements::Vector12 result{};
for (int row = 0; row < 12; ++row) {
for (int column = 0; column < 12; ++column) {
result[static_cast<std::size_t>(row)] +=
entry(matrix, row, column) * vector[static_cast<std::size_t>(column)];
}
}
return result;
}
bool throws_invalid_length(const fesa::elements::EulerBeam3DSection& section)
{
try {
(void)fesa::elements::euler_beam_3d_local_stiffness(0.0, section);
} catch (const std::invalid_argument&) {
return true;
}
return false;
}
bool throws_invalid_section(fesa::elements::EulerBeam3DSection section)
{
section.second_moment_z = 0.0;
try {
(void)fesa::elements::euler_beam_3d_local_stiffness(2.0, section);
} catch (const std::invalid_argument&) {
return true;
}
return false;
}
} // namespace
int main()
{
const double length = 2.0;
const fesa::elements::EulerBeam3DSection section{
210.0,
80.0,
3.0,
4.0,
5.0,
6.0
};
const auto stiffness = fesa::elements::euler_beam_3d_local_stiffness(length, section);
if (!close(entry(stiffness, 0, 0), 315.0) ||
!close(entry(stiffness, 0, 6), -315.0) ||
!close(entry(stiffness, 6, 6), 315.0)) {
return 1;
}
if (!close(entry(stiffness, 3, 3), 160.0) ||
!close(entry(stiffness, 3, 9), -160.0) ||
!close(entry(stiffness, 9, 9), 160.0)) {
return 2;
}
if (!close(entry(stiffness, 1, 1), 1890.0) ||
!close(entry(stiffness, 1, 5), 1890.0) ||
!close(entry(stiffness, 5, 5), 2520.0)) {
return 3;
}
if (!close(entry(stiffness, 2, 2), 1575.0) ||
!close(entry(stiffness, 2, 4), -1575.0) ||
!close(entry(stiffness, 4, 4), 2100.0)) {
return 4;
}
for (int row = 0; row < 12; ++row) {
for (int column = 0; column < 12; ++column) {
if (!close(entry(stiffness, row, column), entry(stiffness, column, row))) {
return 5;
}
}
}
fesa::elements::Vector12 displacement{};
displacement[0] = 0.1;
displacement[5] = 0.2;
displacement[8] = -0.05;
const auto expected_forces = multiply(stiffness, displacement);
const auto recovered_forces =
fesa::elements::euler_beam_3d_local_end_forces(length, section, displacement);
for (std::size_t index = 0; index < expected_forces.size(); ++index) {
if (!close(recovered_forces[index], expected_forces[index])) {
return 6;
}
}
if (!throws_invalid_length(section)) {
return 7;
}
if (!throws_invalid_section(section)) {
return 8;
}
return 0;
}