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
+11 -11
View File
@@ -6,18 +6,18 @@
namespace fesa {
Result<AnalysisModel> AnalysisModel::create(const Domain& domain) {
if (domain.steps().empty()) {
if (domain.Steps().empty()) {
return Result<AnalysisModel>::Failure(Status::Failure(
FailureCategory::kInput,
{{Severity::kError,
"invalid-model-cardinality",
{domain.sourcePath(), 0U},
{domain.SourcePath(), 0U},
"STEP",
"0",
"AnalysisModel requires exactly one static step."}}));
}
if (domain.steps().size() > 1U) {
const auto& secondStep = domain.steps()[1];
if (domain.Steps().size() > 1U) {
const auto& secondStep = domain.Steps()[1];
return Result<AnalysisModel>::Failure(Status::Failure(
FailureCategory::kInput,
{{Severity::kError,
@@ -35,7 +35,7 @@ const Domain& AnalysisModel::domain() const noexcept {
}
const StaticStepDefinition& AnalysisModel::step() const noexcept {
return domain_->steps().front();
return domain_->Steps().front();
}
const std::vector<EntityIndex>& AnalysisModel::activeElements() const noexcept {
@@ -60,14 +60,14 @@ const std::vector<EntityIndex>& AnalysisModel::activeLoads() const noexcept {
}
AnalysisModel::AnalysisModel(const Domain& domain) : domain_{&domain} {
std::vector<bool> reachableMaterials(domain.materials().size(), false);
std::vector<bool> reachableSections(domain.sections().size(), false);
std::vector<bool> reachableMaterials(domain.Materials().size(), false);
std::vector<bool> reachableSections(domain.Sections().size(), false);
for (std::size_t index = 0U; index < domain.elements().size(); ++index) {
const auto& element = domain.elements()[index];
for (std::size_t index = 0U; index < domain.Elements().size(); ++index) {
const auto& element = domain.Elements()[index];
activeElements_.push_back(static_cast<EntityIndex>(index));
reachableMaterials[element.materialIndex] = true;
reachableSections[element.sectionIndex] = true;
reachableMaterials[element.material_index] = true;
reachableSections[element.section_index] = true;
}
// Ascending vector positions are the stable internal order, independent
+1 -1
View File
@@ -76,7 +76,7 @@ Status LinearStaticAnalysis::initialize(const AnalysisRequest& request) {
}
domain_ = std::make_unique<Domain>(std::move(domain.Value()));
diagnostics_ = domain_->warnings();
diagnostics_ = domain_->Warnings();
SortDiagnostics(diagnostics_);
return Status::Ok();
}
+29 -29
View File
@@ -146,7 +146,7 @@ Result<std::vector<EntityIndex>> resolveTarget(
const Domain& domain,
const NodalLoad& load) {
std::vector<const NodeSet*> matchingSets;
for (const auto& set : domain.nodeSets()) {
for (const auto& set : domain.NodeSets()) {
if (equalName(set.name, load.target)) {
matchingSets.push_back(&set);
}
@@ -155,8 +155,8 @@ Result<std::vector<EntityIndex>> resolveTarget(
std::vector<EntityIndex> matchingNodes;
std::int64_t label = 0;
if (tryPositiveInteger(load.target, label)) {
for (std::size_t index = 0U; index < domain.nodes().size(); ++index) {
if (domain.nodes()[index].sourceId.source_label == label) {
for (std::size_t index = 0U; index < domain.Nodes().size(); ++index) {
if (domain.Nodes()[index].source_id.source_label == label) {
matchingNodes.push_back(static_cast<EntityIndex>(index));
}
}
@@ -172,10 +172,10 @@ Result<std::vector<EntityIndex>> resolveTarget(
"The load target must resolve unambiguously to one node or one expanded node set."));
}
if (!matchingSets.empty()) {
const auto& nodes = matchingSets.front()->nodeIndices;
std::vector<unsigned char> seen(domain.nodes().size(), 0U);
const auto& nodes = matchingSets.front()->node_indices;
std::vector<unsigned char> seen(domain.Nodes().size(), 0U);
for (const EntityIndex node : nodes) {
if (node >= domain.nodes().size() || seen[node] != 0U) {
if (node >= domain.Nodes().size() || seen[node] != 0U) {
return Result<std::vector<EntityIndex>>::Failure(loadFailure(
"invalid-load-target",
load.location,
@@ -219,26 +219,26 @@ Status validateFiniteVector(
Status validateShellMoments(
const Domain& domain,
const Vector& fullLoad) {
if (domain.shellElements().empty()) {
if (domain.ShellElements().empty()) {
return Status::Ok();
}
std::vector<const ShellNodeInitialFrame*> frameByNode(
domain.nodes().size(), nullptr);
for (const auto& frame : domain.shellNodeInitialFrames()) {
if (frame.nodeIndex >= frameByNode.size() ||
frameByNode[frame.nodeIndex] != nullptr) {
domain.Nodes().size(), nullptr);
for (const auto& frame : domain.ShellNodeInitialFrames()) {
if (frame.node_index >= frameByNode.size() ||
frameByNode[frame.node_index] != nullptr) {
return loadFailure(
"invalid-shell-director",
{domain.sourcePath(), 0U},
{domain.SourcePath(), 0U},
"NODE",
std::to_string(frame.nodeIndex),
std::to_string(frame.node_index),
"Shell nodal directors must have unique in-range node identities.");
}
frameByNode[frame.nodeIndex] = &frame;
frameByNode[frame.node_index] = &frame;
}
for (std::size_t node = 0U; node < domain.nodes().size(); ++node) {
for (std::size_t node = 0U; node < domain.Nodes().size(); ++node) {
const double momentX = fullLoad[node * dofsPerNode + 3U];
const double momentY = fullLoad[node * dofsPerNode + 4U];
const double momentZ = fullLoad[node * dofsPerNode + 5U];
@@ -250,9 +250,9 @@ Status validateShellMoments(
if (frame == nullptr) {
return loadFailure(
"invalid-shell-director",
domain.nodes()[node].location,
domain.Nodes()[node].location,
"NODE",
domain.nodes()[node].sourceId.source_label_text,
domain.Nodes()[node].source_id.source_label_text,
"A loaded shell node must have an approved initial director.");
}
@@ -271,9 +271,9 @@ Status validateShellMoments(
if (!(projectionRatio <= shellMomentProjectionTolerance)) {
return loadFailure(
"unsupported-drilling-load",
domain.nodes()[node].location,
domain.Nodes()[node].location,
"CLOAD",
domain.nodes()[node].sourceId.source_label_text,
domain.Nodes()[node].source_id.source_label_text,
"The aggregate nodal moment has an unsupported director-parallel component.");
}
}
@@ -286,23 +286,23 @@ Result<Vector> LoadAssembler::assembleFullNodalLoad(
const AnalysisModel& model,
const DofManager& dofs) {
const Domain& domain = model.domain();
if (domain.nodes().size() >
if (domain.Nodes().size() >
(std::numeric_limits<std::size_t>::max)() / dofsPerNode) {
return Result<Vector>::Failure(loadFailure(
"invalid-load-dimensions",
{domain.sourcePath(), 0U},
{domain.SourcePath(), 0U},
"LOAD_ASSEMBLER",
domain.sourceContentIdentity(),
domain.SourceContentIdentity(),
"The semantic node count cannot be represented in full-DOF order."));
}
const std::size_t expectedFullCount =
domain.nodes().size() * dofsPerNode;
domain.Nodes().size() * dofsPerNode;
const Status dofStatus = validateDofOrder(
dofs, expectedFullCount, {domain.sourcePath(), 0U});
dofs, expectedFullCount, {domain.SourcePath(), 0U});
if (!dofStatus.IsOk()) {
return Result<Vector>::Failure(dofStatus);
}
for (std::size_t node = 0U; node < domain.nodes().size(); ++node) {
for (std::size_t node = 0U; node < domain.Nodes().size(); ++node) {
for (std::size_t component = 0U;
component < dofsPerNode;
++component) {
@@ -313,17 +313,17 @@ Result<Vector> LoadAssembler::assembleFullNodalLoad(
node * dofsPerNode + component) {
return Result<Vector>::Failure(loadFailure(
"invalid-load-order",
domain.nodes()[node].location,
domain.Nodes()[node].location,
"LOAD_ASSEMBLER",
domain.nodes()[node].sourceId.source_label_text,
domain.Nodes()[node].source_id.source_label_text,
"DofManager node/component identity must match full-DOF order."));
}
} catch (const std::out_of_range&) {
return Result<Vector>::Failure(loadFailure(
"invalid-load-dimensions",
domain.nodes()[node].location,
domain.Nodes()[node].location,
"LOAD_ASSEMBLER",
domain.nodes()[node].sourceId.source_label_text,
domain.Nodes()[node].source_id.source_label_text,
"DofManager must provide all six DOFs for every semantic node."));
}
}
+55 -55
View File
@@ -2,8 +2,8 @@
#include "fesa/analysis/analysis_model.hpp"
#include "fesa/assembly/parallel_for.hpp"
#include "fesa/elements/euler_beam_3d.hpp"
#include "fesa/elements/mitc4_shell.hpp"
#include "fesa/elements/euler_beam_3d.h"
#include "fesa/elements/mitc4_shell.h"
#include "fesa/fem/dof_manager.hpp"
#include <array>
@@ -52,46 +52,46 @@ Result<SparseMatrix> SparseAssembler::assembleStiffness(
const DofManager& dofs,
const ParallelFor& parallelFor) {
const Domain& domain = model.domain();
if (domain.nodes().size() >
if (domain.Nodes().size() >
(std::numeric_limits<std::size_t>::max)() / kDofsPerNode ||
dofs.fullDofCount() != domain.nodes().size() * kDofsPerNode) {
dofs.fullDofCount() != domain.Nodes().size() * kDofsPerNode) {
return assemblyFailure(
"invalid-assembly-dimensions",
{domain.sourcePath(), 0U},
{domain.SourcePath(), 0U},
std::to_string(dofs.fullDofCount()),
"DofManager dimensions do not match the active model nodes.");
}
if (!model.activeElements().empty() && !domain.shellElements().empty()) {
if (!model.activeElements().empty() && !domain.ShellElements().empty()) {
return assemblyFailure(
"unsupported-mixed-element-model",
{domain.sourcePath(), 0U},
{domain.SourcePath(), 0U},
"B33:FESA-MITC4",
"Sparse assembly does not support mixed beam and shell models.");
}
if (!domain.shellElements().empty()) {
if (domain.shellElements().size() >
if (!domain.ShellElements().empty()) {
if (domain.ShellElements().size() >
(std::numeric_limits<std::size_t>::max)() /
kShellContributionCount) {
return assemblyFailure(
"invalid-assembly-dimensions",
{domain.sourcePath(), 0U},
std::to_string(domain.shellElements().size()),
{domain.SourcePath(), 0U},
std::to_string(domain.ShellElements().size()),
"Shell contribution storage exceeds the addressable range.");
}
std::vector<std::optional<std::array<double, 3>>> directorsByNode(
domain.nodes().size());
for (const auto& frame : domain.shellNodeInitialFrames()) {
if (frame.nodeIndex >= directorsByNode.size() ||
directorsByNode[frame.nodeIndex]) {
domain.Nodes().size());
for (const auto& frame : domain.ShellNodeInitialFrames()) {
if (frame.node_index >= directorsByNode.size() ||
directorsByNode[frame.node_index]) {
return assemblyFailure(
"invalid-assembly-element",
{domain.sourcePath(), 0U},
std::to_string(frame.nodeIndex),
{domain.SourcePath(), 0U},
std::to_string(frame.node_index),
"Shell initial frames must map uniquely to model nodes.");
}
directorsByNode[frame.nodeIndex] = frame.director;
directorsByNode[frame.node_index] = frame.director;
}
struct ShellInput {
@@ -102,23 +102,23 @@ Result<SparseMatrix> SparseAssembler::assembleStiffness(
std::array<std::size_t, kShellElementDofCount> scatter;
};
std::vector<ShellInput> inputs;
inputs.reserve(domain.shellElements().size());
inputs.reserve(domain.ShellElements().size());
for (std::size_t elementOrder = 0U;
elementOrder < domain.shellElements().size();
elementOrder < domain.ShellElements().size();
++elementOrder) {
const auto& element = domain.shellElements()[elementOrder];
if (element.materialIndex >= domain.materials().size() ||
element.sectionIndex >= domain.shellSections().size()) {
const auto& element = domain.ShellElements()[elementOrder];
if (element.material_index >= domain.Materials().size() ||
element.section_index >= domain.ShellSections().size()) {
return assemblyFailure(
"invalid-assembly-element",
element.location,
element.sourceId.source_label_text,
element.source_id.source_label_text,
"Shell element references an entity outside the Domain.");
}
ShellInput input{};
input.section = &domain.shellSections()[element.sectionIndex];
input.material = &domain.materials()[element.materialIndex];
input.section = &domain.ShellSections()[element.section_index];
input.material = &domain.Materials()[element.material_index];
try {
input.scatter = dofs.shellElementScatter(
static_cast<EntityIndex>(elementOrder));
@@ -126,22 +126,22 @@ Result<SparseMatrix> SparseAssembler::assembleStiffness(
return assemblyFailure(
"invalid-assembly-scatter",
element.location,
element.sourceId.source_label_text,
element.source_id.source_label_text,
"DofManager does not contain the active shell scatter.");
}
for (std::size_t nodePosition = 0U;
nodePosition < element.nodeIndices.size();
nodePosition < element.node_indices.size();
++nodePosition) {
const EntityIndex nodeIndex = element.nodeIndices[nodePosition];
if (nodeIndex >= domain.nodes().size() ||
const EntityIndex nodeIndex = element.node_indices[nodePosition];
if (nodeIndex >= domain.Nodes().size() ||
!directorsByNode[nodeIndex]) {
return assemblyFailure(
"invalid-assembly-element",
element.location,
element.sourceId.source_label_text,
element.source_id.source_label_text,
"Shell element requires a valid node and initial director.");
}
input.nodes[nodePosition] = &domain.nodes()[nodeIndex];
input.nodes[nodePosition] = &domain.Nodes()[nodeIndex];
input.directors[nodePosition] = *directorsByNode[nodeIndex];
for (std::size_t component = 0U;
component < kDofsPerNode;
@@ -156,7 +156,7 @@ Result<SparseMatrix> SparseAssembler::assembleStiffness(
return assemblyFailure(
"invalid-assembly-scatter",
element.location,
element.sourceId.source_label_text,
element.source_id.source_label_text,
"Shell scatter does not match the active model topology.");
}
}
@@ -170,7 +170,7 @@ Result<SparseMatrix> SparseAssembler::assembleStiffness(
inputs.size(),
[&](const std::size_t elementOrder) {
const auto& input = inputs[elementOrder];
const auto shell = Mitc4Shell::create(
const auto shell = Mitc4Shell::Create(
input.nodes,
input.directors,
*input.section,
@@ -179,7 +179,7 @@ Result<SparseMatrix> SparseAssembler::assembleStiffness(
localFailures[elementOrder] = shell.GetStatus();
return;
}
const auto stiffness = shell.Value().stiffness();
const auto stiffness = shell.Value().Stiffness();
if (!stiffness.HasValue()) {
localFailures[elementOrder] = stiffness.GetStatus();
return;
@@ -197,7 +197,7 @@ Result<SparseMatrix> SparseAssembler::assembleStiffness(
buffer[localOrder] = {
input.scatter[localRow],
input.scatter[localColumn],
stiffness.Value().stabilizedGlobal24(
stiffness.Value().stabilized_global24(
localRow, localColumn),
elementOrder,
localOrder};
@@ -235,7 +235,7 @@ Result<SparseMatrix> SparseAssembler::assembleStiffness(
kBeamContributionCount) {
return assemblyFailure(
"invalid-assembly-dimensions",
{domain.sourcePath(), 0U},
{domain.SourcePath(), 0U},
std::to_string(model.activeElements().size()),
"Element contribution storage exceeds the addressable range.");
}
@@ -243,22 +243,22 @@ Result<SparseMatrix> SparseAssembler::assembleStiffness(
std::vector<std::array<std::size_t, kBeamElementDofCount>> scatters;
scatters.reserve(model.activeElements().size());
for (const EntityIndex elementIndex : model.activeElements()) {
if (elementIndex >= domain.elements().size()) {
if (elementIndex >= domain.Elements().size()) {
return assemblyFailure(
"invalid-assembly-element",
{domain.sourcePath(), 0U},
{domain.SourcePath(), 0U},
std::to_string(elementIndex),
"Active element index is outside the Domain.");
}
const auto& element = domain.elements()[elementIndex];
if (element.nodeIndices[0U] >= domain.nodes().size() ||
element.nodeIndices[1U] >= domain.nodes().size() ||
element.materialIndex >= domain.materials().size() ||
element.sectionIndex >= domain.sections().size()) {
const auto& element = domain.Elements()[elementIndex];
if (element.node_indices[0U] >= domain.Nodes().size() ||
element.node_indices[1U] >= domain.Nodes().size() ||
element.material_index >= domain.Materials().size() ||
element.section_index >= domain.Sections().size()) {
return assemblyFailure(
"invalid-assembly-element",
element.location,
element.sourceId.source_label_text,
element.source_id.source_label_text,
"Element references an entity outside the Domain.");
}
@@ -269,7 +269,7 @@ Result<SparseMatrix> SparseAssembler::assembleStiffness(
return assemblyFailure(
"invalid-assembly-scatter",
element.location,
element.sourceId.source_label_text,
element.source_id.source_label_text,
"DofManager does not contain the active element scatter.");
}
for (std::size_t endpoint = 0U; endpoint < 2U; ++endpoint) {
@@ -278,7 +278,7 @@ Result<SparseMatrix> SparseAssembler::assembleStiffness(
++component) {
const std::size_t local = endpoint * kDofsPerNode + component;
const std::size_t expected =
static_cast<std::size_t>(element.nodeIndices[endpoint]) *
static_cast<std::size_t>(element.node_indices[endpoint]) *
kDofsPerNode +
component;
if (scatter[local] != expected ||
@@ -286,7 +286,7 @@ Result<SparseMatrix> SparseAssembler::assembleStiffness(
return assemblyFailure(
"invalid-assembly-scatter",
element.location,
element.sourceId.source_label_text,
element.source_id.source_label_text,
"Element scatter does not match the active model topology.");
}
}
@@ -301,18 +301,18 @@ Result<SparseMatrix> SparseAssembler::assembleStiffness(
model.activeElements().size(),
[&](const std::size_t elementOrder) {
const EntityIndex elementIndex = model.activeElements()[elementOrder];
const auto& definition = domain.elements()[elementIndex];
const auto beam = EulerBeam3D::create(
domain.nodes()[definition.nodeIndices[0U]],
domain.nodes()[definition.nodeIndices[1U]],
domain.sections()[definition.sectionIndex],
domain.materials()[definition.materialIndex]);
const auto& definition = domain.Elements()[elementIndex];
const auto beam = EulerBeam3D::Create(
domain.Nodes()[definition.node_indices[0U]],
domain.Nodes()[definition.node_indices[1U]],
domain.Sections()[definition.section_index],
domain.Materials()[definition.material_index]);
if (!beam.HasValue()) {
localFailures[elementOrder] = beam.GetStatus();
return;
}
const Matrix stiffness = beam.Value().globalStiffness();
const Matrix stiffness = beam.Value().GlobalStiffness();
auto& buffer = localBuffers[elementOrder];
const auto& scatter = scatters[elementOrder];
for (std::size_t localRow = 0U;
+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
File diff suppressed because it is too large Load Diff
+16 -16
View File
@@ -37,16 +37,16 @@ bool tryPositiveInteger(const std::string& text, std::int64_t& value) {
std::vector<EntityIndex> expandBoundaryTarget(
const Domain& domain, const BoundaryCondition& boundary) {
for (const auto& set : domain.nodeSets()) {
for (const auto& set : domain.NodeSets()) {
if (equalName(set.name, boundary.target)) {
return set.nodeIndices;
return set.node_indices;
}
}
std::int64_t sourceLabel = 0;
if (tryPositiveInteger(boundary.target, sourceLabel)) {
for (std::size_t node = 0U; node < domain.nodes().size(); ++node) {
if (domain.nodes()[node].sourceId.source_label == sourceLabel) {
for (std::size_t node = 0U; node < domain.Nodes().size(); ++node) {
if (domain.Nodes()[node].source_id.source_label == sourceLabel) {
return {static_cast<EntityIndex>(node)};
}
}
@@ -96,15 +96,15 @@ SparsePattern buildSparsePattern(
Result<DofManager> DofManager::create(const AnalysisModel& model) {
const Domain& domain = model.domain();
const std::size_t fullCount = domain.nodes().size() * dofsPerNode;
const std::size_t fullCount = domain.Nodes().size() * dofsPerNode;
std::vector<std::optional<double>> prescribedByFullDof(fullCount);
for (const EntityIndex boundaryIndex : model.activeBoundaryConditions()) {
const auto& boundary = model.step().boundaries.at(boundaryIndex);
const auto target = expandBoundaryTarget(domain, boundary);
for (const EntityIndex node : target) {
for (int component = boundary.firstDof;
component <= boundary.lastDof;
for (int component = boundary.first_dof;
component <= boundary.last_dof;
++component) {
const std::size_t fullDof =
static_cast<std::size_t>(node) * dofsPerNode +
@@ -150,12 +150,12 @@ Result<DofManager> DofManager::create(const AnalysisModel& model) {
}
std::vector<std::array<std::size_t, 12>> elementScatters(
domain.elements().size());
domain.Elements().size());
for (const EntityIndex elementIndex : model.activeElements()) {
const auto& element = domain.elements().at(elementIndex);
const auto& element = domain.Elements().at(elementIndex);
auto& scatter = elementScatters.at(elementIndex);
for (std::size_t endpoint = 0U; endpoint < element.nodeIndices.size(); ++endpoint) {
const std::size_t node = element.nodeIndices[endpoint];
for (std::size_t endpoint = 0U; endpoint < element.node_indices.size(); ++endpoint) {
const std::size_t node = element.node_indices[endpoint];
for (std::size_t component = 0U; component < dofsPerNode; ++component) {
scatter[endpoint * dofsPerNode + component] =
node * dofsPerNode + component;
@@ -164,16 +164,16 @@ Result<DofManager> DofManager::create(const AnalysisModel& model) {
}
std::vector<std::array<std::size_t, 24>> shellElementScatters(
domain.shellElements().size());
domain.ShellElements().size());
for (std::size_t elementIndex = 0U;
elementIndex < domain.shellElements().size();
elementIndex < domain.ShellElements().size();
++elementIndex) {
const auto& element = domain.shellElements()[elementIndex];
const auto& element = domain.ShellElements()[elementIndex];
auto& scatter = shellElementScatters[elementIndex];
for (std::size_t nodePosition = 0U;
nodePosition < element.nodeIndices.size();
nodePosition < element.node_indices.size();
++nodePosition) {
const std::size_t node = element.nodeIndices[nodePosition];
const std::size_t node = element.node_indices[nodePosition];
for (std::size_t component = 0U;
component < dofsPerNode;
++component) {
+53 -53
View File
@@ -1,6 +1,6 @@
#include "fesa/io/abaqus/domain_mapper.hpp"
#include "fesa/model/shell_geometry.hpp"
#include "fesa/model/shell_geometry.h"
#include <algorithm>
#include <array>
@@ -151,7 +151,7 @@ public:
failure_->category, {std::move(failure_->diagnostic)}));
}
SortDiagnostics(definition_.warnings);
return Domain::create(std::move(definition_));
return Domain::Create(std::move(definition_));
}
private:
@@ -419,8 +419,8 @@ private:
}
void parseBlocks() {
definition_.sourcePath = input_.sourcePath;
definition_.sourceContentIdentity = input_.sourceContentIdentity;
definition_.source_path = input_.sourcePath;
definition_.source_content_identity = input_.sourceContentIdentity;
for (std::size_t index = 0U;
index < input_.blocks.size() && !failure_;
@@ -1776,11 +1776,11 @@ private:
if (failure_) {
return;
}
if (!definition_.shellElements.empty()) {
auto geometry = preprocessShellGeometry(
if (!definition_.shell_elements.empty()) {
auto geometry = PreprocessShellGeometry(
definition_.nodes,
definition_.shellElements,
definition_.shellSections);
definition_.shell_elements,
definition_.shell_sections);
if (!geometry.HasValue()) {
const auto& status = geometry.GetStatus();
failure_ = MappingFailure{
@@ -1788,8 +1788,8 @@ private:
status.Diagnostics().front()};
return;
}
definition_.shellNodeInitialFrames =
std::move(geometry.Value().nodalFrames);
definition_.shell_node_initial_frames =
std::move(geometry.Value().nodal_frames);
}
expandAssemblySets();
if (failure_) {
@@ -1863,10 +1863,10 @@ private:
partDefinition.name = part.name;
partDefinition.location = part.location;
for (const auto& node : part.nodes) {
partDefinition.nodeSourceLabels.push_back(node.label);
partDefinition.node_source_labels.push_back(node.label);
}
for (const auto& element : part.elements) {
partDefinition.elementSourceLabels.push_back(element.label);
partDefinition.element_source_labels.push_back(element.label);
for (const auto nodeLabel : element.nodeLabels) {
if (findNode(part, nodeLabel) == nullptr) {
inputFailure(
@@ -1880,7 +1880,7 @@ private:
}
}
for (const auto& set : part.nodeSets) {
partDefinition.nodeSetNames.push_back(set.name);
partDefinition.node_set_names.push_back(set.name);
for (const auto label : set.members) {
if (findNode(part, label) == nullptr) {
inputFailure(
@@ -1892,7 +1892,7 @@ private:
}
}
for (const auto& set : part.elementSets) {
partDefinition.elementSetNames.push_back(set.name);
partDefinition.element_set_names.push_back(set.name);
for (const auto label : set.members) {
if (findElement(part, label) == nullptr) {
inputFailure(
@@ -1919,8 +1919,8 @@ private:
return;
}
const EntityIndex sectionIndex =
static_cast<EntityIndex>(definition_.shellSections.size());
definition_.shellSections.push_back({
static_cast<EntityIndex>(definition_.shell_sections.size());
definition_.shell_sections.push_back({
section.elementSetName,
section.thickness,
*materialIndex,
@@ -2071,11 +2071,11 @@ private:
deltaScaled[2] / lengthRatio};
const double globalGuideScale =
std::max(1.0, maximumAbsolute(section.firstAxis));
std::max(1.0, maximumAbsolute(section.first_axis));
std::array<double, 3> guideScaled{};
for (std::size_t coordinate = 0U; coordinate < 3U; ++coordinate) {
guideScaled[coordinate] =
section.firstAxis[coordinate] / globalGuideScale;
section.first_axis[coordinate] / globalGuideScale;
}
const double projection =
guideScaled[0] * tangent[0] +
@@ -2130,7 +2130,7 @@ private:
rawNode.coordinates,
rawNode.location});
nodeIndices.emplace(rawNode.label, index);
instance.nodeMappings.push_back({rawNode.label, index});
instance.node_mappings.push_back({rawNode.label, index});
}
for (const auto& rawElement : part->elements) {
@@ -2199,7 +2199,7 @@ private:
"Expanded shell section assignment must resolve exactly once.");
return;
}
if (definition_.shellElements.size() >
if (definition_.shell_elements.size() >
std::numeric_limits<EntityIndex>::max()) {
inputFailure(
"entity-index-overflow", rawElement.location,
@@ -2208,19 +2208,19 @@ private:
return;
}
index =
static_cast<EntityIndex>(definition_.shellElements.size());
definition_.shellElements.push_back({
static_cast<EntityIndex>(definition_.shell_elements.size());
definition_.shell_elements.push_back({
{rawInstance.name, rawElement.label, rawElement.labelText},
rawElement.type == RawElement::Type::s4
? ShellSourceElementType::s4
: ShellSourceElementType::s4r,
? ShellSourceElementType::kS4
: ShellSourceElementType::kS4r,
connectedNodes,
assignment->second.second,
assignment->second.first,
rawElement.location});
}
elementIndices.emplace(rawElement.label, index);
instance.elementMappings.push_back({rawElement.label, index});
instance.element_mappings.push_back({rawElement.label, index});
}
for (const auto& rawSet : part->nodeSets) {
@@ -2234,9 +2234,9 @@ private:
"Part node-set expansion failed.");
return;
}
set.nodeIndices.push_back(found->second);
set.node_indices.push_back(found->second);
}
definition_.nodeSets.push_back(std::move(set));
definition_.node_sets.push_back(std::move(set));
}
for (const auto& rawSet : part->elementSets) {
ElementSet set{
@@ -2250,9 +2250,9 @@ private:
"Part element-set expansion failed.");
return;
}
set.elementIndices.push_back(found->second);
set.element_indices.push_back(found->second);
}
definition_.elementSets.push_back(std::move(set));
definition_.element_sets.push_back(std::move(set));
}
definition_.instances.push_back(std::move(instance));
}
@@ -2281,61 +2281,61 @@ private:
return;
}
if (rawSet.isNodeSet) {
definition_.nodeSets.erase(
definition_.node_sets.erase(
std::remove_if(
definition_.nodeSets.begin(),
definition_.nodeSets.end(),
definition_.node_sets.begin(),
definition_.node_sets.end(),
[&rawSet](const NodeSet& set) {
return equalName(set.name, rawSet.name);
}),
definition_.nodeSets.end());
definition_.node_sets.end());
NodeSet set{
rawSet.name, definitionInstance->name, {}, rawSet.location};
for (const auto member : rawSet.members) {
const auto mapping = std::find_if(
definitionInstance->nodeMappings.begin(),
definitionInstance->nodeMappings.end(),
definitionInstance->node_mappings.begin(),
definitionInstance->node_mappings.end(),
[member](const SourceIndexMapping& value) {
return value.sourceLabel == member;
return value.source_label == member;
});
if (mapping == definitionInstance->nodeMappings.end()) {
if (mapping == definitionInstance->node_mappings.end()) {
inputFailure(
"unresolved-reference", rawSet.location,
"NSET", rawSet.name,
"Assembly node-set member must resolve in its instance.");
return;
}
set.nodeIndices.push_back(mapping->internalIndex);
set.node_indices.push_back(mapping->internal_index);
}
definition_.nodeSets.push_back(std::move(set));
definition_.node_sets.push_back(std::move(set));
} else {
definition_.elementSets.erase(
definition_.element_sets.erase(
std::remove_if(
definition_.elementSets.begin(),
definition_.elementSets.end(),
definition_.element_sets.begin(),
definition_.element_sets.end(),
[&rawSet](const ElementSet& set) {
return equalName(set.name, rawSet.name);
}),
definition_.elementSets.end());
definition_.element_sets.end());
ElementSet set{
rawSet.name, definitionInstance->name, {}, rawSet.location};
for (const auto member : rawSet.members) {
const auto mapping = std::find_if(
definitionInstance->elementMappings.begin(),
definitionInstance->elementMappings.end(),
definitionInstance->element_mappings.begin(),
definitionInstance->element_mappings.end(),
[member](const SourceIndexMapping& value) {
return value.sourceLabel == member;
return value.source_label == member;
});
if (mapping == definitionInstance->elementMappings.end()) {
if (mapping == definitionInstance->element_mappings.end()) {
inputFailure(
"unresolved-reference", rawSet.location,
"ELSET", rawSet.name,
"Assembly element-set member must resolve in its instance.");
return;
}
set.elementIndices.push_back(mapping->internalIndex);
set.element_indices.push_back(mapping->internal_index);
}
definition_.elementSets.push_back(std::move(set));
definition_.element_sets.push_back(std::move(set));
}
}
}
@@ -2345,7 +2345,7 @@ private:
const SourceLocation& location,
const std::string& keyword) {
std::vector<const NodeSet*> matchingSets;
for (const auto& set : definition_.nodeSets) {
for (const auto& set : definition_.node_sets) {
if (equalName(set.name, target)) {
matchingSets.push_back(&set);
}
@@ -2357,7 +2357,7 @@ private:
for (std::size_t index = 0U;
index < definition_.nodes.size();
++index) {
if (definition_.nodes[index].sourceId.source_label == label) {
if (definition_.nodes[index].source_id.source_label == label) {
matchingNodes.push_back(static_cast<EntityIndex>(index));
}
}
@@ -2385,7 +2385,7 @@ private:
return std::nullopt;
}
if (matchingSets.size() == 1U) {
return matchingSets.front()->nodeIndices;
return matchingSets.front()->node_indices;
}
if (matchingNodes.size() == 1U) {
return matchingNodes;
@@ -2409,7 +2409,7 @@ private:
return;
}
for (const auto node : *target) {
for (int dof = boundary.firstDof; dof <= boundary.lastDof; ++dof) {
for (int dof = boundary.first_dof; dof <= boundary.last_dof; ++dof) {
const auto key = std::make_pair(node, dof);
const auto existing = prescribedValues.find(key);
if (existing != prescribedValues.end() &&
+123 -123
View File
@@ -231,11 +231,11 @@ struct WriterModelData {
};
bool isShellDomain(const Domain& domain) noexcept {
return !domain.shellElements().empty();
return !domain.ShellElements().empty();
}
const char* shellSourceTypeName(const ShellSourceElementType type) {
return type == ShellSourceElementType::s4 ? "S4" : "S4R";
return type == ShellSourceElementType::kS4 ? "S4" : "S4R";
}
bool isOrthonormalRightHanded(
@@ -266,14 +266,14 @@ bool isOrthonormalRightHanded(
bool computeLocalAxes(
const Domain& domain, const EulerBeam3DDefinition& element, AxisSet& axes) {
if (element.nodeIndices[0U] >= domain.nodes().size() ||
element.nodeIndices[1U] >= domain.nodes().size() ||
element.sectionIndex >= domain.sections().size()) {
if (element.node_indices[0U] >= domain.Nodes().size() ||
element.node_indices[1U] >= domain.Nodes().size() ||
element.section_index >= domain.Sections().size()) {
return false;
}
const auto& first = domain.nodes()[element.nodeIndices[0U]].coordinates;
const auto& second = domain.nodes()[element.nodeIndices[1U]].coordinates;
const auto& guide = domain.sections()[element.sectionIndex].firstAxis;
const auto& first = domain.Nodes()[element.node_indices[0U]].coordinates;
const auto& second = domain.Nodes()[element.node_indices[1U]].coordinates;
const auto& guide = domain.Sections()[element.section_index].first_axis;
const std::array<double, 3> delta = {
second[0U] - first[0U],
second[1U] - first[1U],
@@ -319,48 +319,48 @@ bool sizeProductFits(
Status validateShellWriterInput(
const Domain& domain, const AnalysisState& state) {
if (!domain.elements().empty()) {
if (!domain.Elements().empty()) {
return outputFailure(
"invalid-result-identity",
"Schema v0 does not combine B33 and FESA-MITC4 element inventories.");
}
for (const auto& material : domain.materials()) {
for (const auto& material : domain.Materials()) {
if (material.name.empty() || !isValidUtf8(material.name) ||
!std::isfinite(material.youngsModulus) ||
!std::isfinite(material.poissonRatio) ||
!(material.youngsModulus > 0.0)) {
!std::isfinite(material.youngs_modulus) ||
!std::isfinite(material.poisson_ratio) ||
!(material.youngs_modulus > 0.0)) {
return outputFailure(
"invalid-result-identity",
"Every shell material requires a UTF-8 name and finite constitutive data.");
}
}
for (const auto& section : domain.shellSections()) {
for (const auto& section : domain.ShellSections()) {
if (section.name.empty() || !isValidUtf8(section.name) ||
section.materialIndex >= domain.materials().size() ||
section.material_index >= domain.Materials().size() ||
!std::isfinite(section.thickness) || !(section.thickness > 0.0)) {
return outputFailure(
"invalid-result-identity",
"Every shell section requires stable material identity and positive finite thickness.");
}
}
for (const auto& element : domain.shellElements()) {
if ((element.sourceType != ShellSourceElementType::s4 &&
element.sourceType != ShellSourceElementType::s4r) ||
element.sourceId.source_label <= 0 ||
element.sourceId.source_label_text.empty() ||
!isValidUtf8(element.sourceId.instance_name) ||
!isValidUtf8(element.sourceId.source_label_text) ||
element.materialIndex >= domain.materials().size() ||
element.sectionIndex >= domain.shellSections().size() ||
domain.shellSections()[element.sectionIndex].materialIndex !=
element.materialIndex) {
for (const auto& element : domain.ShellElements()) {
if ((element.source_type != ShellSourceElementType::kS4 &&
element.source_type != ShellSourceElementType::kS4r) ||
element.source_id.source_label <= 0 ||
element.source_id.source_label_text.empty() ||
!isValidUtf8(element.source_id.instance_name) ||
!isValidUtf8(element.source_id.source_label_text) ||
element.material_index >= domain.Materials().size() ||
element.section_index >= domain.ShellSections().size() ||
domain.ShellSections()[element.section_index].material_index !=
element.material_index) {
return outputFailure(
"invalid-result-identity",
"Every shell element requires stable source, material, and section identity.");
}
std::array<EntityIndex, kShellNodeCount> sortedNodes = element.nodeIndices;
std::array<EntityIndex, kShellNodeCount> sortedNodes = element.node_indices;
std::sort(sortedNodes.begin(), sortedNodes.end());
if (sortedNodes.back() >= domain.nodes().size() ||
if (sortedNodes.back() >= domain.Nodes().size() ||
std::adjacent_find(sortedNodes.begin(), sortedNodes.end()) !=
sortedNodes.end()) {
return outputFailure(
@@ -368,18 +368,18 @@ Status validateShellWriterInput(
"Every shell element requires four distinct valid node identities.");
}
}
if (domain.shellNodeInitialFrames().size() != domain.nodes().size()) {
if (domain.ShellNodeInitialFrames().size() != domain.Nodes().size()) {
return outputFailure(
"invalid-result-identity",
"Shell output requires one initial director/frame per source-ordered node.");
}
for (std::size_t node = 0U;
node < domain.shellNodeInitialFrames().size();
node < domain.ShellNodeInitialFrames().size();
++node) {
const auto& source = domain.shellNodeInitialFrames()[node];
const auto& source = domain.ShellNodeInitialFrames()[node];
const std::array<std::array<double, 3>, 3> frame{
source.tangentA, source.tangentB, source.director};
if (source.nodeIndex != node || !isOrthonormalRightHanded(frame)) {
source.tangent_a, source.tangent_b, source.director};
if (source.node_index != node || !isOrthonormalRightHanded(frame)) {
return outputFailure(
"invalid-result-identity",
"Shell initial frames must be finite, orthonormal, right-handed, and node ordered.");
@@ -388,7 +388,7 @@ Status validateShellWriterInput(
std::size_t expectedRows = 0U;
if (!sizeProductFits(
domain.shellElements().size(), kShellLocationCount, expectedRows) ||
domain.ShellElements().size(), kShellLocationCount, expectedRows) ||
state.shellResults().size() != expectedRows) {
return outputFailure(
"invalid-result-rows",
@@ -477,7 +477,7 @@ Status validateWriterInput(
}
std::size_t fullDofCount = 0U;
if (!sizeProductFits(domain.nodes().size(), kDofsPerNode, fullDofCount)) {
if (!sizeProductFits(domain.Nodes().size(), kDofsPerNode, fullDofCount)) {
return outputFailure(
"invalid-result-state", "The nodal result shape overflows size_t.");
}
@@ -502,17 +502,17 @@ Status validateWriterInput(
}
}
if (domain.sourcePath().empty() || domain.sourceContentIdentity().empty() ||
!isValidUtf8(domain.sourceContentIdentity())) {
if (domain.SourcePath().empty() || domain.SourceContentIdentity().empty() ||
!isValidUtf8(domain.SourceContentIdentity())) {
return outputFailure(
"invalid-result-identity",
"Source path and UTF-8 content identity are required.");
}
for (const Node& node : domain.nodes()) {
if (node.sourceId.source_label <= 0 ||
node.sourceId.source_label_text.empty() ||
!isValidUtf8(node.sourceId.instance_name) ||
!isValidUtf8(node.sourceId.source_label_text) ||
for (const Node& node : domain.Nodes()) {
if (node.source_id.source_label <= 0 ||
node.source_id.source_label_text.empty() ||
!isValidUtf8(node.source_id.instance_name) ||
!isValidUtf8(node.source_id.source_label_text) ||
!isFinite(node.coordinates)) {
return outputFailure(
"invalid-result-identity",
@@ -521,15 +521,15 @@ Status validateWriterInput(
}
modelData.beamLocalAxes.clear();
modelData.beamLocalAxes.reserve(domain.elements().size());
for (const EulerBeam3DDefinition& element : domain.elements()) {
modelData.beamLocalAxes.reserve(domain.Elements().size());
for (const EulerBeam3DDefinition& element : domain.Elements()) {
AxisSet axes{};
if (element.sourceId.source_label <= 0 ||
element.sourceId.source_label_text.empty() ||
!isValidUtf8(element.sourceId.instance_name) ||
!isValidUtf8(element.sourceId.source_label_text) ||
element.nodeIndices[0U] == element.nodeIndices[1U] ||
element.materialIndex >= domain.materials().size() ||
if (element.source_id.source_label <= 0 ||
element.source_id.source_label_text.empty() ||
!isValidUtf8(element.source_id.instance_name) ||
!isValidUtf8(element.source_id.source_label_text) ||
element.node_indices[0U] == element.node_indices[1U] ||
element.material_index >= domain.Materials().size() ||
!computeLocalAxes(domain, element, axes)) {
return outputFailure(
"invalid-result-identity",
@@ -540,8 +540,8 @@ Status validateWriterInput(
std::size_t endpointCount = 0U;
std::size_t gaussCount = 0U;
if (!sizeProductFits(domain.elements().size(), kEndpointCount, endpointCount) ||
!sizeProductFits(domain.elements().size(), kGaussPointCount, gaussCount) ||
if (!sizeProductFits(domain.Elements().size(), kEndpointCount, endpointCount) ||
!sizeProductFits(domain.Elements().size(), kGaussPointCount, gaussCount) ||
state.endpointResults().size() != endpointCount ||
state.gaussResults().size() != gaussCount) {
return outputFailure(
@@ -555,11 +555,11 @@ Status validateWriterInput(
static_cast<EntityIndex>(rowIndex / kEndpointCount);
const int expectedEndpoint = static_cast<int>(rowIndex % kEndpointCount);
const EndpointResultRow& row = state.endpointResults()[rowIndex];
const auto& element = domain.elements()[expectedElement];
const auto& element = domain.Elements()[expectedElement];
const auto& expectedNode =
domain.nodes()[element.nodeIndices[static_cast<std::size_t>(expectedEndpoint)]];
domain.Nodes()[element.node_indices[static_cast<std::size_t>(expectedEndpoint)]];
if (row.element != expectedElement || row.endpoint != expectedEndpoint ||
!sameIdentity(row.node, expectedNode.sourceId) ||
!sameIdentity(row.node, expectedNode.source_id) ||
!isFinite(row.endAction) || !isFinite(row.sectionResultant)) {
return outputFailure(
"invalid-result-rows",
@@ -586,11 +586,11 @@ Status validateWriterInput(
std::size_t stressIndex = 0U;
for (std::size_t elementIndex = 0U;
elementIndex < domain.elements().size();
elementIndex < domain.Elements().size();
++elementIndex) {
const auto& element = domain.elements()[elementIndex];
const auto& element = domain.Elements()[elementIndex];
const auto& sectionPoints =
domain.sections()[element.sectionIndex].sectionPoints;
domain.Sections()[element.section_index].section_points;
for (std::size_t gauss = 0U; gauss < kGaussPointCount; ++gauss) {
const std::size_t count = sectionPoints.empty() ? 1U : sectionPoints.size();
for (std::size_t point = 0U; point < count; ++point) {
@@ -681,8 +681,8 @@ std::string normalizedPathString(const std::filesystem::path& path) {
}
std::string sourceInputIdentity(const Domain& domain) {
return "path=" + normalizedPathString(domain.sourcePath()) +
";content_identity=" + domain.sourceContentIdentity();
return "path=" + normalizedPathString(domain.SourcePath()) +
";content_identity=" + domain.SourceContentIdentity();
}
Hdf5Handle makeUtf8StringType() {
@@ -982,13 +982,13 @@ void writeMetadata(const hid_t file, const Domain& domain) {
void writeNodes(const hid_t file, const Domain& domain) {
std::vector<NodeWriteRow> rows;
rows.reserve(domain.nodes().size());
for (std::size_t index = 0U; index < domain.nodes().size(); ++index) {
const Node& node = domain.nodes()[index];
rows.reserve(domain.Nodes().size());
for (std::size_t index = 0U; index < domain.Nodes().size(); ++index) {
const Node& node = domain.Nodes()[index];
rows.push_back({
static_cast<std::uint64_t>(index),
node.sourceId.instance_name.c_str(),
node.sourceId.source_label_text.c_str(),
node.source_id.instance_name.c_str(),
node.source_id.source_label_text.c_str(),
{node.coordinates[0U], node.coordinates[1U], node.coordinates[2U]}});
}
@@ -1057,15 +1057,15 @@ void writeNodes(const hid_t file, const Domain& domain) {
void writeBeamElements(
const hid_t file, const Domain& domain, const std::vector<AxisSet>& axes) {
std::vector<ElementWriteRow> rows;
rows.reserve(domain.elements().size());
for (std::size_t index = 0U; index < domain.elements().size(); ++index) {
const auto& element = domain.elements()[index];
rows.reserve(domain.Elements().size());
for (std::size_t index = 0U; index < domain.Elements().size(); ++index) {
const auto& element = domain.Elements()[index];
ElementWriteRow row{
static_cast<std::uint64_t>(index),
element.sourceId.instance_name.c_str(),
element.sourceId.source_label_text.c_str(),
{static_cast<std::uint64_t>(element.nodeIndices[0U]),
static_cast<std::uint64_t>(element.nodeIndices[1U])},
element.source_id.instance_name.c_str(),
element.source_id.source_label_text.c_str(),
{static_cast<std::uint64_t>(element.node_indices[0U]),
static_cast<std::uint64_t>(element.node_indices[1U])},
{}};
std::copy(axes[index].begin(), axes[index].end(), row.localAxes);
rows.push_back(row);
@@ -1141,21 +1141,21 @@ void writeBeamElements(
void writeShellElements(const hid_t file, const Domain& domain) {
std::vector<ShellElementWriteRow> rows;
rows.reserve(domain.shellElements().size());
for (std::size_t index = 0U; index < domain.shellElements().size(); ++index) {
const auto& element = domain.shellElements()[index];
rows.reserve(domain.ShellElements().size());
for (std::size_t index = 0U; index < domain.ShellElements().size(); ++index) {
const auto& element = domain.ShellElements()[index];
rows.push_back({
static_cast<std::uint64_t>(index),
element.sourceId.instance_name.c_str(),
element.sourceId.source_label_text.c_str(),
shellSourceTypeName(element.sourceType),
element.source_id.instance_name.c_str(),
element.source_id.source_label_text.c_str(),
shellSourceTypeName(element.source_type),
kMitc4InternalFormulation.data(),
{static_cast<std::uint64_t>(element.nodeIndices[0U]),
static_cast<std::uint64_t>(element.nodeIndices[1U]),
static_cast<std::uint64_t>(element.nodeIndices[2U]),
static_cast<std::uint64_t>(element.nodeIndices[3U])},
static_cast<std::uint64_t>(element.sectionIndex),
static_cast<std::uint64_t>(element.materialIndex)});
{static_cast<std::uint64_t>(element.node_indices[0U]),
static_cast<std::uint64_t>(element.node_indices[1U]),
static_cast<std::uint64_t>(element.node_indices[2U]),
static_cast<std::uint64_t>(element.node_indices[3U])},
static_cast<std::uint64_t>(element.section_index),
static_cast<std::uint64_t>(element.material_index)});
}
auto stringType = makeUtf8StringType();
@@ -1226,14 +1226,14 @@ void writeShellElements(const hid_t file, const Domain& domain) {
void writeShellMaterials(const hid_t file, const Domain& domain) {
std::vector<ShellMaterialWriteRow> rows;
rows.reserve(domain.materials().size());
for (std::size_t index = 0U; index < domain.materials().size(); ++index) {
const auto& material = domain.materials()[index];
rows.reserve(domain.Materials().size());
for (std::size_t index = 0U; index < domain.Materials().size(); ++index) {
const auto& material = domain.Materials()[index];
rows.push_back({
static_cast<std::uint64_t>(index),
material.name.c_str(),
material.youngsModulus,
material.poissonRatio});
material.youngs_modulus,
material.poisson_ratio});
}
auto stringType = makeUtf8StringType();
Hdf5Handle fileType{
@@ -1276,20 +1276,20 @@ void writeShellMaterials(const hid_t file, const Domain& domain) {
void writeShellSections(const hid_t file, const Domain& domain) {
std::vector<std::string> sourceFiles;
sourceFiles.reserve(domain.shellSections().size());
for (const auto& section : domain.shellSections()) {
sourceFiles.reserve(domain.ShellSections().size());
for (const auto& section : domain.ShellSections()) {
sourceFiles.push_back(normalizedPathString(section.location.file));
}
std::vector<ShellSectionWriteRow> rows;
rows.reserve(domain.shellSections().size());
for (std::size_t index = 0U; index < domain.shellSections().size(); ++index) {
const auto& section = domain.shellSections()[index];
rows.reserve(domain.ShellSections().size());
for (std::size_t index = 0U; index < domain.ShellSections().size(); ++index) {
const auto& section = domain.ShellSections()[index];
rows.push_back({
static_cast<std::uint64_t>(index),
sourceFiles[index].c_str(),
static_cast<std::uint64_t>(section.location.line),
section.name.c_str(),
static_cast<std::uint64_t>(section.materialIndex),
static_cast<std::uint64_t>(section.material_index),
section.thickness});
}
auto stringType = makeUtf8StringType();
@@ -1343,24 +1343,24 @@ void writeShellSections(const hid_t file, const Domain& domain) {
void writeShellModelData(
const hid_t file, const Domain& domain, const WriterModelData& modelData) {
std::vector<double> directors;
directors.reserve(domain.nodes().size() * 3U);
directors.reserve(domain.Nodes().size() * 3U);
std::vector<double> frames;
frames.reserve(domain.nodes().size() * 9U);
for (const auto& frame : domain.shellNodeInitialFrames()) {
frames.reserve(domain.Nodes().size() * 9U);
for (const auto& frame : domain.ShellNodeInitialFrames()) {
directors.insert(
directors.end(), frame.director.begin(), frame.director.end());
frames.insert(frames.end(), frame.tangentA.begin(), frame.tangentA.end());
frames.insert(frames.end(), frame.tangentB.begin(), frame.tangentB.end());
frames.insert(frames.end(), frame.tangent_a.begin(), frame.tangent_a.end());
frames.insert(frames.end(), frame.tangent_b.begin(), frame.tangent_b.end());
frames.insert(frames.end(), frame.director.begin(), frame.director.end());
}
writeModelDoubleDataset(
file, "/model/shell/nodal_director",
{static_cast<hsize_t>(domain.nodes().size()), 3U},
{static_cast<hsize_t>(domain.Nodes().size()), 3U},
directors.data(), directors.size(), "D1,D2,D3", "1,1,1",
"global-cartesian", "nodal");
writeModelDoubleDataset(
file, "/model/shell/nodal_frame",
{static_cast<hsize_t>(domain.nodes().size()), 3U, 3U},
{static_cast<hsize_t>(domain.Nodes().size()), 3U, 3U},
frames.data(), frames.size(), "X,Y,Z", "1,1,1",
"global-cartesian", "nodal-frame");
{
@@ -1375,7 +1375,7 @@ void writeShellModelData(
writeShellMaterials(file, domain);
writeShellSections(file, domain);
const std::vector<hsize_t> nodalDimensions{
static_cast<hsize_t>(domain.nodes().size()), kDofsPerNode};
static_cast<hsize_t>(domain.Nodes().size()), kDofsPerNode};
writeUint8Dataset(
file, "/model/nodal_constraint_mask", nodalDimensions,
modelData.constraintMask.data(), modelData.constraintMask.size());
@@ -1573,7 +1573,7 @@ void writeShellResultDatasets(
}
const hsize_t elementCount =
static_cast<hsize_t>(domain.shellElements().size());
static_cast<hsize_t>(domain.ShellElements().size());
const std::string root = std::string{kStepRoot} + "/element/shell";
const std::string framePath = root + "/local_frame";
writeDoubleDataset(
@@ -1730,7 +1730,7 @@ void writeDiagnostics(
void writeResultDatasets(
const hid_t file, const Domain& domain, const AnalysisState& state) {
const std::vector<hsize_t> nodalDimensions = {
static_cast<hsize_t>(domain.nodes().size()), kDofsPerNode};
static_cast<hsize_t>(domain.Nodes().size()), kDofsPerNode};
writeDoubleDataset(
file,
std::string{kStepRoot} + "/nodal/displacement",
@@ -1758,11 +1758,11 @@ void writeResultDatasets(
}
const std::vector<hsize_t> endpointActionDimensions = {
static_cast<hsize_t>(domain.elements().size()),
static_cast<hsize_t>(domain.Elements().size()),
kEndpointCount,
kEndActionComponentCount};
const std::vector<hsize_t> generalizedDimensions = {
static_cast<hsize_t>(domain.elements().size()),
static_cast<hsize_t>(domain.Elements().size()),
kEndpointCount,
kGeneralizedComponentCount};
const auto endActions = flattenEndpointValues(state.endpointResults(), false);
@@ -2287,13 +2287,13 @@ void selfCheckFile(
requireCompoundDataset(
file.get(),
"/model/nodes",
static_cast<hsize_t>(domain.nodes().size()),
static_cast<hsize_t>(domain.Nodes().size()),
{"internal_node_id", "instance_name", "source_label", "coordinates"});
auto nodes = openDatasetForCheck(file.get(), "/model/nodes");
requireStringAttribute(nodes.get(), "coordinate_system", "global-cartesian");
requireStringAttribute(nodes.get(), "units_label", "length");
const std::vector<hsize_t> nodalDimensions = {
static_cast<hsize_t>(domain.nodes().size()), kDofsPerNode};
static_cast<hsize_t>(domain.Nodes().size()), kDofsPerNode};
requireDoubleDataset(
file.get(),
"/steps/Step-1/frames/0/nodal/displacement",
@@ -2315,7 +2315,7 @@ void selfCheckFile(
requireCompoundDataset(
file.get(),
"/model/elements",
static_cast<hsize_t>(domain.shellElements().size()),
static_cast<hsize_t>(domain.ShellElements().size()),
{"internal_element_id", "instance_name", "source_label",
"source_element_type", "internal_formulation", "node_internal_ids",
"shell_section_internal_id", "material_internal_id"});
@@ -2324,33 +2324,33 @@ void selfCheckFile(
requireCompoundDataset(
file.get(),
"/model/shell/materials",
static_cast<hsize_t>(domain.materials().size()),
static_cast<hsize_t>(domain.Materials().size()),
{"internal_material_id", "name", "E", "nu"});
requireCompoundDataset(
file.get(),
"/model/shell/sections",
static_cast<hsize_t>(domain.shellSections().size()),
static_cast<hsize_t>(domain.ShellSections().size()),
{"internal_section_id", "source_file", "source_line", "source_elset",
"material_internal_id", "thickness"});
std::vector<double> directors;
std::vector<double> frames;
directors.reserve(domain.nodes().size() * 3U);
frames.reserve(domain.nodes().size() * 9U);
for (const auto& frame : domain.shellNodeInitialFrames()) {
directors.reserve(domain.Nodes().size() * 3U);
frames.reserve(domain.Nodes().size() * 9U);
for (const auto& frame : domain.ShellNodeInitialFrames()) {
directors.insert(
directors.end(), frame.director.begin(), frame.director.end());
frames.insert(frames.end(), frame.tangentA.begin(), frame.tangentA.end());
frames.insert(frames.end(), frame.tangentB.begin(), frame.tangentB.end());
frames.insert(frames.end(), frame.tangent_a.begin(), frame.tangent_a.end());
frames.insert(frames.end(), frame.tangent_b.begin(), frame.tangent_b.end());
frames.insert(frames.end(), frame.director.begin(), frame.director.end());
}
requireModelDoubleDataset(
file.get(), "/model/shell/nodal_director",
{static_cast<hsize_t>(domain.nodes().size()), 3U},
{static_cast<hsize_t>(domain.Nodes().size()), 3U},
"D1,D2,D3", "1,1,1", "global-cartesian", "nodal", &directors);
requireModelDoubleDataset(
file.get(), "/model/shell/nodal_frame",
{static_cast<hsize_t>(domain.nodes().size()), 3U, 3U},
{static_cast<hsize_t>(domain.Nodes().size()), 3U, 3U},
"X,Y,Z", "1,1,1", "global-cartesian", "nodal-frame", &frames);
auto nodalFrame = openDatasetForCheck(
file.get(), "/model/shell/nodal_frame");
@@ -2383,7 +2383,7 @@ void selfCheckFile(
positions.get(), "position_names", "BOTTOM,MIDDLE,TOP");
const hsize_t elementCount =
static_cast<hsize_t>(domain.shellElements().size());
static_cast<hsize_t>(domain.ShellElements().size());
const std::string shellRoot = std::string{kStepRoot} + "/element/shell";
requireDoubleDataset(
file.get(), (shellRoot + "/local_frame").c_str(),
@@ -2459,18 +2459,18 @@ void selfCheckFile(
requireCompoundDataset(
file.get(),
"/model/elements",
static_cast<hsize_t>(domain.elements().size()),
static_cast<hsize_t>(domain.Elements().size()),
{"internal_element_id", "instance_name", "source_label",
"node_internal_ids", "local_axes"});
auto elements = openDatasetForCheck(file.get(), "/model/elements");
requireStringAttribute(
elements.get(), "formulation", "B33-3D-Euler-Bernoulli");
const std::vector<hsize_t> endDimensions = {
static_cast<hsize_t>(domain.elements().size()),
static_cast<hsize_t>(domain.Elements().size()),
kEndpointCount,
kEndActionComponentCount};
const std::vector<hsize_t> generalizedDimensions = {
static_cast<hsize_t>(domain.elements().size()),
static_cast<hsize_t>(domain.Elements().size()),
kGaussPointCount,
kGeneralizedComponentCount};
requireDoubleDataset(
+32 -30
View File
@@ -1,66 +1,68 @@
#include "fesa/model/domain.hpp"
#include "fesa/model/domain.h"
#include <utility>
namespace fesa {
Result<Domain> Domain::create(ModelDefinition definition) {
return Result<Domain>::Success(Domain{std::move(definition)});
Result<Domain> Domain::Create(ModelDefinition definition) {
return Result<Domain>::Success(Domain{std::move(definition)});
}
const std::vector<Node>& Domain::nodes() const noexcept {
return definition_.nodes;
const std::vector<Node>& Domain::Nodes() const noexcept {
return definition_.nodes;
}
const std::vector<EulerBeam3DDefinition>& Domain::elements() const noexcept {
return definition_.elements;
const std::vector<EulerBeam3DDefinition>& Domain::Elements() const noexcept {
return definition_.elements;
}
const std::vector<Mitc4ShellDefinition>& Domain::shellElements() const noexcept {
return definition_.shellElements;
const std::vector<Mitc4ShellDefinition>& Domain::ShellElements()
const noexcept {
return definition_.shell_elements;
}
const std::vector<LinearElasticMaterial>& Domain::materials() const noexcept {
return definition_.materials;
const std::vector<LinearElasticMaterial>& Domain::Materials() const noexcept {
return definition_.materials;
}
const std::vector<GeneralBeamSection>& Domain::sections() const noexcept {
return definition_.sections;
const std::vector<GeneralBeamSection>& Domain::Sections() const noexcept {
return definition_.sections;
}
const std::vector<ShellSection>& Domain::shellSections() const noexcept {
return definition_.shellSections;
const std::vector<ShellSection>& Domain::ShellSections() const noexcept {
return definition_.shell_sections;
}
const std::vector<ShellNodeInitialFrame>& Domain::shellNodeInitialFrames() const noexcept {
return definition_.shellNodeInitialFrames;
const std::vector<ShellNodeInitialFrame>& Domain::ShellNodeInitialFrames()
const noexcept {
return definition_.shell_node_initial_frames;
}
const std::vector<NodeSet>& Domain::nodeSets() const noexcept {
return definition_.nodeSets;
const std::vector<NodeSet>& Domain::NodeSets() const noexcept {
return definition_.node_sets;
}
const std::vector<ElementSet>& Domain::elementSets() const noexcept {
return definition_.elementSets;
const std::vector<ElementSet>& Domain::ElementSets() const noexcept {
return definition_.element_sets;
}
const std::vector<StaticStepDefinition>& Domain::steps() const noexcept {
return definition_.steps;
const std::vector<StaticStepDefinition>& Domain::Steps() const noexcept {
return definition_.steps;
}
const std::vector<Diagnostic>& Domain::warnings() const noexcept {
return definition_.warnings;
const std::vector<Diagnostic>& Domain::Warnings() const noexcept {
return definition_.warnings;
}
const std::filesystem::path& Domain::sourcePath() const noexcept {
return definition_.sourcePath;
const std::filesystem::path& Domain::SourcePath() const noexcept {
return definition_.source_path;
}
const std::string& Domain::sourceContentIdentity() const noexcept {
return definition_.sourceContentIdentity;
const std::string& Domain::SourceContentIdentity() const noexcept {
return definition_.source_content_identity;
}
Domain::Domain(ModelDefinition definition)
: definition_{std::move(definition)} {}
} // namespace fesa
} // namespace fesa
+379 -413
View File
@@ -1,4 +1,4 @@
#include "fesa/model/shell_geometry.hpp"
#include "fesa/model/shell_geometry.h"
#include <algorithm>
#include <cmath>
@@ -14,478 +14,444 @@ 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};
struct ShapeData {
std::array<double, 4> values;
std::array<double, 4> xiDerivatives;
std::array<double, 4> etaDerivatives;
std::array<double, 4> values;
std::array<double, 4> xi_derivatives;
std::array<double, 4> eta_derivatives;
};
struct ElementWork {
std::array<Vector3, 4> coordinates;
Vector3 normal;
double areaWeight;
std::array<Vector3, 4> coordinates;
Vector3 normal;
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 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 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]};
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];
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]};
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]);
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);
});
bool IsFinite(const Vector3& value) {
return std::all_of(value.begin(), value.end(),
[](double component) { return std::isfinite(component); });
}
ShapeData shapeData(double xi, double eta) {
ShapeData data{};
for (std::size_t node = 0U; node < 4U; ++node) {
data.values[node] =
0.25 * (1.0 + kXiSigns[node] * xi) *
(1.0 + kEtaSigns[node] * eta);
data.xiDerivatives[node] =
0.25 * kXiSigns[node] * (1.0 + kEtaSigns[node] * eta);
data.etaDerivatives[node] =
0.25 * kEtaSigns[node] * (1.0 + kXiSigns[node] * xi);
}
return data;
ShapeData ShapeDataAt(double xi, double eta) {
ShapeData data{};
for (std::size_t node = 0U; node < 4U; ++node) {
data.values[node] =
0.25 * (1.0 + kXiSigns[node] * xi) * (1.0 + kEtaSigns[node] * eta);
data.xi_derivatives[node] =
0.25 * kXiSigns[node] * (1.0 + kEtaSigns[node] * eta);
data.eta_derivatives[node] =
0.25 * kEtaSigns[node] * (1.0 + kXiSigns[node] * xi);
}
return data;
}
Vector3 weightedSum(
const std::array<double, 4>& weights,
const std::array<Vector3, 4>& values) {
Vector3 result{};
for (std::size_t node = 0U; node < values.size(); ++node) {
result = add(result, scale(weights[node], values[node]));
}
return result;
Vector3 WeightedSum(const std::array<double, 4>& weights,
const std::array<Vector3, 4>& values) {
Vector3 result{};
for (std::size_t node = 0U; node < values.size(); ++node) {
result = Add(result, Scale(weights[node], values[node]));
}
return result;
}
Vector3 derivativeSum(
const std::array<double, 4>& derivatives,
const std::array<Vector3, 4>& coordinates) {
// Shape derivatives sum to zero, so translating by node 1 improves the
// numerical cancellation without changing the covariant tangent.
std::array<Vector3, 4> relative{};
for (std::size_t node = 0U; node < coordinates.size(); ++node) {
relative[node] = subtract(coordinates[node], coordinates[0]);
}
return weightedSum(derivatives, relative);
/// @brief Forms a translation-invariant covariant tangent in node order.
Vector3 DerivativeSum(const std::array<double, 4>& derivatives,
const std::array<Vector3, 4>& coordinates) {
// Shape derivatives sum to zero, so translating by node 1 improves the
// numerical cancellation without changing the covariant tangent.
std::array<Vector3, 4> relative{};
for (std::size_t node = 0U; node < coordinates.size(); ++node) {
relative[node] = Subtract(coordinates[node], coordinates[0]);
}
return WeightedSum(derivatives, relative);
}
Result<ShellGeometry> geometryFailure(
std::string code,
const SourceLocation& location,
std::string keyword,
std::string identity,
std::string message) {
return Result<ShellGeometry>::Failure(Status::Failure(
FailureCategory::kModel,
{{Severity::kError,
std::move(code),
location,
std::move(keyword),
std::move(identity),
std::move(message)}}));
Result<ShellGeometry> GeometryFailure(std::string code,
const SourceLocation& location,
std::string keyword, std::string identity,
std::string message) {
return Result<ShellGeometry>::Failure(Status::Failure(
FailureCategory::kModel,
{{Severity::kError, std::move(code), location, std::move(keyword),
std::move(identity), std::move(message)}}));
}
bool sameCoordinates(const Vector3& left, const Vector3& right) {
return left == right;
bool SameCoordinates(const Vector3& left, const Vector3& right) {
return left == right;
}
double orientation(
const Vector3& first,
const Vector3& second,
const Vector3& third,
const Vector3& normal) {
return dot(cross(subtract(second, first), subtract(third, first)), normal);
double Orientation(const Vector3& first, const Vector3& second,
const Vector3& third, const Vector3& normal) {
return Dot(Cross(Subtract(second, first), Subtract(third, first)), normal);
}
bool hasOppositeSigns(double first, double second) {
return (first < 0.0 && second > 0.0) ||
(first > 0.0 && second < 0.0);
bool HasOppositeSigns(double first, double second) {
return (first < 0.0 && second > 0.0) || (first > 0.0 && second < 0.0);
}
bool segmentsProperlyIntersect(
const Vector3& firstStart,
const Vector3& firstEnd,
const Vector3& secondStart,
const Vector3& secondEnd,
const Vector3& normal) {
const double firstSideStart =
orientation(firstStart, firstEnd, secondStart, normal);
const double firstSideEnd =
orientation(firstStart, firstEnd, secondEnd, normal);
const double secondSideStart =
orientation(secondStart, secondEnd, firstStart, normal);
const double secondSideEnd =
orientation(secondStart, secondEnd, firstEnd, normal);
return hasOppositeSigns(firstSideStart, firstSideEnd) &&
hasOppositeSigns(secondSideStart, secondSideEnd);
/// @brief Detects a proper projected edge crossing without a tolerance clamp.
bool SegmentsProperlyIntersect(const Vector3& first_start,
const Vector3& first_end,
const Vector3& second_start,
const Vector3& second_end,
const Vector3& normal) {
const double first_side_start =
Orientation(first_start, first_end, second_start, normal);
const double first_side_end =
Orientation(first_start, first_end, second_end, normal);
const double second_side_start =
Orientation(second_start, second_end, first_start, normal);
const double second_side_end =
Orientation(second_start, second_end, first_end, normal);
return HasOppositeSigns(first_side_start, first_side_end) &&
HasOppositeSigns(second_side_start, second_side_end);
}
bool sourceIdentityLess(
const Mitc4ShellDefinition& left,
std::size_t leftIndex,
const Mitc4ShellDefinition& right,
std::size_t rightIndex) {
return std::tie(
left.sourceId.instance_name,
left.sourceId.source_label,
left.sourceId.source_label_text,
leftIndex) <
std::tie(
right.sourceId.instance_name,
right.sourceId.source_label,
right.sourceId.source_label_text,
rightIndex);
/// @brief Orders incident shell contributions by stable source identity.
bool SourceIdentityLess(const Mitc4ShellDefinition& left,
std::size_t left_index,
const Mitc4ShellDefinition& right,
std::size_t right_index) {
return std::tie(left.source_id.instance_name, left.source_id.source_label,
left.source_id.source_label_text, left_index) <
std::tie(right.source_id.instance_name, right.source_id.source_label,
right.source_id.source_label_text, right_index);
}
} // namespace
} // namespace
const std::array<ShellGeometryValidationPoint, 17>&
shellGeometryValidationPoints() noexcept {
static const std::array<ShellGeometryValidationPoint, 17> points = [] {
const double g = 1.0 / std::sqrt(3.0);
return std::array<ShellGeometryValidationPoint, 17>{
ShellGeometryValidationPoint{
ShellGeometryPointKind::center, 0U, {0.0, 0.0, 0.0}},
{ShellGeometryPointKind::stiffness, 0U, {-g, -g, -g}},
{ShellGeometryPointKind::stiffness, 1U, {-g, -g, g}},
{ShellGeometryPointKind::stiffness, 2U, {g, -g, -g}},
{ShellGeometryPointKind::stiffness, 3U, {g, -g, g}},
{ShellGeometryPointKind::stiffness, 4U, {g, g, -g}},
{ShellGeometryPointKind::stiffness, 5U, {g, g, g}},
{ShellGeometryPointKind::stiffness, 6U, {-g, g, -g}},
{ShellGeometryPointKind::stiffness, 7U, {-g, g, g}},
{ShellGeometryPointKind::tying, 0U, {0.0, -1.0, 0.0}},
{ShellGeometryPointKind::tying, 1U, {0.0, 1.0, 0.0}},
{ShellGeometryPointKind::tying, 2U, {-1.0, 0.0, 0.0}},
{ShellGeometryPointKind::tying, 3U, {1.0, 0.0, 0.0}},
{ShellGeometryPointKind::recovery, 0U, {-g, -g, 0.0}},
{ShellGeometryPointKind::recovery, 1U, {g, -g, 0.0}},
{ShellGeometryPointKind::recovery, 2U, {g, g, 0.0}},
{ShellGeometryPointKind::recovery, 3U, {-g, g, 0.0}}};
}();
return points;
ShellGeometryValidationPoints() noexcept {
static const std::array<ShellGeometryValidationPoint, 17> points = [] {
const double g = 1.0 / std::sqrt(3.0);
return std::array<ShellGeometryValidationPoint, 17>{
ShellGeometryValidationPoint{
ShellGeometryPointKind::kCenter, 0U, {0.0, 0.0, 0.0}},
{ShellGeometryPointKind::kStiffness, 0U, {-g, -g, -g}},
{ShellGeometryPointKind::kStiffness, 1U, {-g, -g, g}},
{ShellGeometryPointKind::kStiffness, 2U, {g, -g, -g}},
{ShellGeometryPointKind::kStiffness, 3U, {g, -g, g}},
{ShellGeometryPointKind::kStiffness, 4U, {g, g, -g}},
{ShellGeometryPointKind::kStiffness, 5U, {g, g, g}},
{ShellGeometryPointKind::kStiffness, 6U, {-g, g, -g}},
{ShellGeometryPointKind::kStiffness, 7U, {-g, g, g}},
{ShellGeometryPointKind::kTying, 0U, {0.0, -1.0, 0.0}},
{ShellGeometryPointKind::kTying, 1U, {0.0, 1.0, 0.0}},
{ShellGeometryPointKind::kTying, 2U, {-1.0, 0.0, 0.0}},
{ShellGeometryPointKind::kTying, 3U, {1.0, 0.0, 0.0}},
{ShellGeometryPointKind::kRecovery, 0U, {-g, -g, 0.0}},
{ShellGeometryPointKind::kRecovery, 1U, {g, -g, 0.0}},
{ShellGeometryPointKind::kRecovery, 2U, {g, g, 0.0}},
{ShellGeometryPointKind::kRecovery, 3U, {-g, g, 0.0}}};
}();
return points;
}
Result<ShellGeometry> preprocessShellGeometry(
Result<ShellGeometry> PreprocessShellGeometry(
const std::vector<Node>& nodes,
const std::vector<Mitc4ShellDefinition>& elements,
const std::vector<ShellSection>& sections) {
ShellGeometry geometry;
geometry.elementData.reserve(elements.size());
std::vector<ElementWork> work;
work.reserve(elements.size());
ShellGeometry geometry;
geometry.element_data.reserve(elements.size());
std::vector<ElementWork> work;
work.reserve(elements.size());
const double g = 1.0 / std::sqrt(3.0);
const std::array<Vector3, 4> surfaceGaussPoints{
Vector3{-g, -g, 0.0},
Vector3{g, -g, 0.0},
Vector3{g, g, 0.0},
Vector3{-g, g, 0.0}};
const double g = 1.0 / std::sqrt(3.0);
const std::array<Vector3, 4> surface_gauss_points{
Vector3{-g, -g, 0.0}, Vector3{g, -g, 0.0}, Vector3{g, g, 0.0},
Vector3{-g, g, 0.0}};
for (std::size_t elementIndex = 0U;
elementIndex < elements.size();
++elementIndex) {
const auto& element = elements[elementIndex];
ElementWork current{};
for (std::size_t localNode = 0U; localNode < 4U; ++localNode) {
if (element.nodeIndices[localNode] >= nodes.size()) {
return geometryFailure(
"invalid-shell-geometry", element.location, "ELEMENT",
element.sourceId.source_label_text,
"Shell geometry references an unavailable internal node.");
}
current.coordinates[localNode] =
nodes[element.nodeIndices[localNode]].coordinates;
if (!isFinite(current.coordinates[localNode])) {
return geometryFailure(
"invalid-shell-geometry", element.location, "ELEMENT",
element.sourceId.source_label_text,
"Shell geometry contains a nonfinite source coordinate.");
}
for (std::size_t element_index = 0U; element_index < elements.size();
++element_index) {
const auto& element = elements[element_index];
ElementWork current{};
for (std::size_t local_node = 0U; local_node < 4U; ++local_node) {
if (element.node_indices[local_node] >= nodes.size()) {
return GeometryFailure(
"invalid-shell-geometry", element.location, "ELEMENT",
element.source_id.source_label_text,
"Shell geometry references an unavailable internal node.");
}
current.coordinates[local_node] =
nodes[element.node_indices[local_node]].coordinates;
if (!IsFinite(current.coordinates[local_node])) {
return GeometryFailure(
"invalid-shell-geometry", element.location, "ELEMENT",
element.source_id.source_label_text,
"Shell geometry contains a nonfinite source coordinate.");
}
}
for (std::size_t first = 0U; first < 4U; ++first) {
for (std::size_t second = first + 1U; second < 4U; ++second) {
if (element.node_indices[first] == element.node_indices[second] ||
SameCoordinates(current.coordinates[first],
current.coordinates[second])) {
return GeometryFailure("invalid-shell-geometry", element.location,
"ELEMENT", element.source_id.source_label_text,
"Shell geometry contains duplicate nodes.");
}
for (std::size_t first = 0U; first < 4U; ++first) {
for (std::size_t second = first + 1U; second < 4U; ++second) {
if (element.nodeIndices[first] == element.nodeIndices[second] ||
sameCoordinates(
current.coordinates[first], current.coordinates[second])) {
return geometryFailure(
"invalid-shell-geometry", element.location, "ELEMENT",
element.sourceId.source_label_text,
"Shell geometry contains duplicate nodes.");
}
}
}
const ShapeData center = shapeData(0.0, 0.0);
const Vector3 centerXi =
derivativeSum(center.xiDerivatives, current.coordinates);
const Vector3 centerEta =
derivativeSum(center.etaDerivatives, current.coordinates);
const Vector3 centerCross = cross(centerXi, centerEta);
const double centerMeasure = norm(centerCross);
if (!isFinite(centerXi) || !isFinite(centerEta) ||
!isFinite(centerCross) || !std::isfinite(centerMeasure) ||
!(centerMeasure > 0.0)) {
return geometryFailure(
"invalid-shell-geometry", element.location, "ELEMENT",
element.sourceId.source_label_text,
"Shell center has no finite nonzero normal candidate.");
}
current.normal = scale(1.0 / centerMeasure, centerCross);
if (segmentsProperlyIntersect(
current.coordinates[0], current.coordinates[1],
current.coordinates[2], current.coordinates[3], current.normal) ||
segmentsProperlyIntersect(
current.coordinates[1], current.coordinates[2],
current.coordinates[3], current.coordinates[0], current.normal)) {
return geometryFailure(
"invalid-shell-geometry", element.location, "ELEMENT",
element.sourceId.source_label_text,
"Shell boundary is self-intersecting in the center-normal projection.");
}
double areaWeight = 0.0;
for (const auto& point : surfaceGaussPoints) {
const ShapeData shape = shapeData(point[0], point[1]);
const Vector3 tangentXi =
derivativeSum(shape.xiDerivatives, current.coordinates);
const Vector3 tangentEta =
derivativeSum(shape.etaDerivatives, current.coordinates);
const Vector3 areaVector = cross(tangentXi, tangentEta);
const double measure = norm(areaVector);
if (!isFinite(tangentXi) || !isFinite(tangentEta) ||
!isFinite(areaVector) || !std::isfinite(measure) ||
!(measure > 0.0) || !(dot(areaVector, current.normal) > 0.0)) {
return geometryFailure(
"invalid-shell-geometry", element.location, "ELEMENT",
element.sourceId.source_label_text,
"Shell surface is zero-area or locally reversed at a required point.");
}
areaWeight += measure;
}
if (!std::isfinite(areaWeight) || !(areaWeight > 0.0)) {
return geometryFailure(
"invalid-shell-geometry", element.location, "ELEMENT",
element.sourceId.source_label_text,
"Shell surface-area weight is nonfinite or zero.");
}
current.areaWeight = areaWeight;
work.push_back(current);
geometry.elementData.push_back({
static_cast<EntityIndex>(elementIndex), current.normal, areaWeight});
}
}
std::vector<std::vector<std::size_t>> incident(nodes.size());
for (std::size_t elementIndex = 0U;
elementIndex < elements.size();
++elementIndex) {
for (const EntityIndex nodeIndex : elements[elementIndex].nodeIndices) {
incident[nodeIndex].push_back(elementIndex);
}
const ShapeData center = ShapeDataAt(0.0, 0.0);
const Vector3 center_xi =
DerivativeSum(center.xi_derivatives, current.coordinates);
const Vector3 center_eta =
DerivativeSum(center.eta_derivatives, current.coordinates);
const Vector3 center_cross = Cross(center_xi, center_eta);
const double center_measure = Norm(center_cross);
if (!IsFinite(center_xi) || !IsFinite(center_eta) ||
!IsFinite(center_cross) || !std::isfinite(center_measure) ||
!(center_measure > 0.0)) {
return GeometryFailure(
"invalid-shell-geometry", element.location, "ELEMENT",
element.source_id.source_label_text,
"Shell center has no finite nonzero normal candidate.");
}
current.normal = Scale(1.0 / center_measure, center_cross);
if (SegmentsProperlyIntersect(
current.coordinates[0], current.coordinates[1],
current.coordinates[2], current.coordinates[3], current.normal) ||
SegmentsProperlyIntersect(
current.coordinates[1], current.coordinates[2],
current.coordinates[3], current.coordinates[0], current.normal)) {
return GeometryFailure("invalid-shell-geometry", element.location,
"ELEMENT", element.source_id.source_label_text,
"Shell boundary is self-intersecting in the "
"center-normal projection.");
}
geometry.nodalFrames.reserve(nodes.size());
std::vector<const ShellNodeInitialFrame*> frameByNode(nodes.size(), nullptr);
for (std::size_t nodeIndex = 0U; nodeIndex < incident.size(); ++nodeIndex) {
auto& nodeIncident = incident[nodeIndex];
if (nodeIncident.empty()) {
continue;
}
std::sort(
nodeIncident.begin(), nodeIncident.end(),
[&elements](std::size_t left, std::size_t right) {
return sourceIdentityLess(
elements[left], left, elements[right], right);
});
for (std::size_t first = 0U; first < nodeIncident.size(); ++first) {
for (std::size_t second = first + 1U;
second < nodeIncident.size();
++second) {
const double pairDot = dot(
work[nodeIncident[first]].normal,
work[nodeIncident[second]].normal);
if (!std::isfinite(pairDot) || !(pairDot > 0.0)) {
return geometryFailure(
"opposed-incident-normal", nodes[nodeIndex].location,
"NODE", nodes[nodeIndex].sourceId.source_label_text,
"Incident shell normal candidates do not share a positive orientation hemisphere.");
}
}
}
double area_weight = 0.0;
for (const auto& point : surface_gauss_points) {
const ShapeData shape = ShapeDataAt(point[0], point[1]);
const Vector3 tangent_xi =
DerivativeSum(shape.xi_derivatives, current.coordinates);
const Vector3 tangent_eta =
DerivativeSum(shape.eta_derivatives, current.coordinates);
const Vector3 area_vector = Cross(tangent_xi, tangent_eta);
const double measure = Norm(area_vector);
if (!IsFinite(tangent_xi) || !IsFinite(tangent_eta) ||
!IsFinite(area_vector) || !std::isfinite(measure) ||
!(measure > 0.0) || !(Dot(area_vector, current.normal) > 0.0)) {
return GeometryFailure("invalid-shell-geometry", element.location,
"ELEMENT", element.source_id.source_label_text,
"Shell surface is zero-area or locally reversed "
"at a required point.");
}
area_weight += measure;
}
if (!std::isfinite(area_weight) || !(area_weight > 0.0)) {
return GeometryFailure("invalid-shell-geometry", element.location,
"ELEMENT", element.source_id.source_label_text,
"Shell surface-area weight is nonfinite or zero.");
}
current.area_weight = area_weight;
work.push_back(current);
geometry.element_data.push_back(
{static_cast<EntityIndex>(element_index), current.normal, area_weight});
}
double maximumWeight = 0.0;
for (const std::size_t elementIndex : nodeIncident) {
maximumWeight = std::max(maximumWeight, work[elementIndex].areaWeight);
}
Vector3 directorSum{};
for (const std::size_t elementIndex : nodeIncident) {
directorSum = add(
directorSum,
scale(
work[elementIndex].areaWeight / maximumWeight,
work[elementIndex].normal));
}
const double directorNorm = norm(directorSum);
if (!isFinite(directorSum) || !std::isfinite(directorNorm) ||
!(directorNorm > 0.0)) {
return geometryFailure(
"invalid-shell-director", nodes[nodeIndex].location, "NODE",
nodes[nodeIndex].sourceId.source_label_text,
"Area-weighted shell director is nonfinite or zero.");
}
const Vector3 director = scale(1.0 / directorNorm, directorSum);
std::vector<std::vector<std::size_t>> incident(nodes.size());
for (std::size_t element_index = 0U; element_index < elements.size();
++element_index) {
for (const EntityIndex node_index : elements[element_index].node_indices) {
incident[node_index].push_back(element_index);
}
}
const std::array<Vector3, 3> globalAxes{
Vector3{1.0, 0.0, 0.0},
Vector3{0.0, 1.0, 0.0},
Vector3{0.0, 0.0, 1.0}};
std::size_t selectedAxis = 0U;
double selectedAlignment = std::abs(dot(globalAxes[0], director));
for (std::size_t axis = 1U; axis < globalAxes.size(); ++axis) {
const double alignment = std::abs(dot(globalAxes[axis], director));
if (alignment < selectedAlignment) {
selectedAlignment = alignment;
selectedAxis = axis;
}
geometry.nodal_frames.reserve(nodes.size());
std::vector<const ShellNodeInitialFrame*> frame_by_node(nodes.size(),
nullptr);
for (std::size_t node_index = 0U; node_index < incident.size();
++node_index) {
auto& node_incident = incident[node_index];
if (node_incident.empty()) {
continue;
}
std::sort(node_incident.begin(), node_incident.end(),
[&elements](std::size_t left, std::size_t right) {
return SourceIdentityLess(elements[left], left, elements[right],
right);
});
for (std::size_t first = 0U; first < node_incident.size(); ++first) {
for (std::size_t second = first + 1U; second < node_incident.size();
++second) {
const double pair_dot = Dot(work[node_incident[first]].normal,
work[node_incident[second]].normal);
if (!std::isfinite(pair_dot) || !(pair_dot > 0.0)) {
return GeometryFailure("opposed-incident-normal",
nodes[node_index].location, "NODE",
nodes[node_index].source_id.source_label_text,
"Incident shell normal candidates do not "
"share a positive orientation hemisphere.");
}
const Vector3 tangentCandidate = subtract(
globalAxes[selectedAxis],
scale(dot(globalAxes[selectedAxis], director), director));
const double tangentNorm = norm(tangentCandidate);
if (!isFinite(tangentCandidate) || !std::isfinite(tangentNorm) ||
!(tangentNorm > 0.0)) {
return geometryFailure(
"invalid-shell-director", nodes[nodeIndex].location, "NODE",
nodes[nodeIndex].sourceId.source_label_text,
"Least-aligned-axis tangent frame construction failed.");
}
const Vector3 tangentA = scale(1.0 / tangentNorm, tangentCandidate);
const Vector3 tangentB = cross(director, tangentA);
if (!isFinite(tangentB) || !(norm(tangentB) > 0.0)) {
return geometryFailure(
"invalid-shell-director", nodes[nodeIndex].location, "NODE",
nodes[nodeIndex].sourceId.source_label_text,
"Right-handed shell tangent frame construction failed.");
}
geometry.nodalFrames.push_back({
static_cast<EntityIndex>(nodeIndex), director, tangentA, tangentB});
}
}
// Build lookup only after frame storage is complete so later code never
// observes a pointer invalidated by vector growth.
for (const auto& frame : geometry.nodalFrames) {
frameByNode[frame.nodeIndex] = &frame;
double maximum_weight = 0.0;
for (const std::size_t element_index : node_incident) {
maximum_weight =
std::max(maximum_weight, work[element_index].area_weight);
}
Vector3 director_sum{};
for (const std::size_t element_index : node_incident) {
director_sum = Add(director_sum,
Scale(work[element_index].area_weight / maximum_weight,
work[element_index].normal));
}
const double director_norm = Norm(director_sum);
if (!IsFinite(director_sum) || !std::isfinite(director_norm) ||
!(director_norm > 0.0)) {
return GeometryFailure(
"invalid-shell-director", nodes[node_index].location, "NODE",
nodes[node_index].source_id.source_label_text,
"Area-weighted shell director is nonfinite or zero.");
}
const Vector3 director = Scale(1.0 / director_norm, director_sum);
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}};
std::size_t selected_axis = 0U;
double selected_alignment = std::abs(Dot(global_axes[0], director));
for (std::size_t axis = 1U; axis < global_axes.size(); ++axis) {
const double alignment = std::abs(Dot(global_axes[axis], director));
if (alignment < selected_alignment) {
selected_alignment = alignment;
selected_axis = axis;
}
}
const Vector3 tangent_candidate =
Subtract(global_axes[selected_axis],
Scale(Dot(global_axes[selected_axis], director), director));
const double tangent_norm = Norm(tangent_candidate);
if (!IsFinite(tangent_candidate) || !std::isfinite(tangent_norm) ||
!(tangent_norm > 0.0)) {
return GeometryFailure(
"invalid-shell-director", nodes[node_index].location, "NODE",
nodes[node_index].source_id.source_label_text,
"Least-aligned-axis tangent frame construction failed.");
}
const Vector3 tangent_a = Scale(1.0 / tangent_norm, tangent_candidate);
const Vector3 tangent_b = Cross(director, tangent_a);
if (!IsFinite(tangent_b) || !(Norm(tangent_b) > 0.0)) {
return GeometryFailure(
"invalid-shell-director", nodes[node_index].location, "NODE",
nodes[node_index].source_id.source_label_text,
"Right-handed shell tangent frame construction failed.");
}
geometry.nodal_frames.push_back(
{static_cast<EntityIndex>(node_index), director, tangent_a, tangent_b});
}
// Build lookup only after frame storage is complete so later code never
// observes a pointer invalidated by vector growth.
for (const auto& frame : geometry.nodal_frames) {
frame_by_node[frame.node_index] = &frame;
}
for (std::size_t element_index = 0U; element_index < elements.size();
++element_index) {
const auto& element = elements[element_index];
if (element.section_index >= sections.size()) {
return GeometryFailure("invalid-shell-jacobian", element.location,
"ELEMENT", element.source_id.source_label_text,
"Shell geometry cannot resolve its thickness for "
"Jacobian validation.");
}
const double thickness = sections[element.section_index].thickness;
std::array<Vector3, 4> directors{};
for (std::size_t local_node = 0U; local_node < 4U; ++local_node) {
const auto* frame = frame_by_node[element.node_indices[local_node]];
if (frame == nullptr) {
return GeometryFailure("invalid-shell-director", element.location,
"ELEMENT", element.source_id.source_label_text,
"Shell element is missing a nodal director.");
}
directors[local_node] = frame->director;
}
for (std::size_t elementIndex = 0U;
elementIndex < elements.size();
++elementIndex) {
const auto& element = elements[elementIndex];
if (element.sectionIndex >= sections.size()) {
return geometryFailure(
"invalid-shell-jacobian", element.location, "ELEMENT",
element.sourceId.source_label_text,
"Shell geometry cannot resolve its thickness for Jacobian validation.");
}
const double thickness = sections[element.sectionIndex].thickness;
std::array<Vector3, 4> directors{};
for (std::size_t localNode = 0U; localNode < 4U; ++localNode) {
const auto* frame = frameByNode[element.nodeIndices[localNode]];
if (frame == nullptr) {
return geometryFailure(
"invalid-shell-director", element.location, "ELEMENT",
element.sourceId.source_label_text,
"Shell element is missing a nodal director.");
}
directors[localNode] = frame->director;
}
for (const auto& point : ShellGeometryValidationPoints()) {
const double xi = point.natural_coordinates[0];
const double eta = point.natural_coordinates[1];
const double zeta = point.natural_coordinates[2];
const ShapeData shape = ShapeDataAt(xi, eta);
const Vector3 midsurface_xi =
DerivativeSum(shape.xi_derivatives, work[element_index].coordinates);
const Vector3 midsurface_eta =
DerivativeSum(shape.eta_derivatives, work[element_index].coordinates);
const Vector3 area_vector = Cross(midsurface_xi, midsurface_eta);
const double surface_measure = Norm(area_vector);
if (!IsFinite(midsurface_xi) || !IsFinite(midsurface_eta) ||
!IsFinite(area_vector) || !std::isfinite(surface_measure) ||
!(surface_measure > 0.0) ||
!(Dot(area_vector, work[element_index].normal) > 0.0)) {
return GeometryFailure("invalid-shell-geometry", element.location,
"ELEMENT", element.source_id.source_label_text,
"Shell surface basis is nonfinite, zero, or "
"reversed at a required point.");
}
for (const auto& point : shellGeometryValidationPoints()) {
const double xi = point.naturalCoordinates[0];
const double eta = point.naturalCoordinates[1];
const double zeta = point.naturalCoordinates[2];
const ShapeData shape = shapeData(xi, eta);
const Vector3 midsurfaceXi =
derivativeSum(shape.xiDerivatives, work[elementIndex].coordinates);
const Vector3 midsurfaceEta =
derivativeSum(shape.etaDerivatives, work[elementIndex].coordinates);
const Vector3 areaVector = cross(midsurfaceXi, midsurfaceEta);
const double surfaceMeasure = norm(areaVector);
if (!isFinite(midsurfaceXi) || !isFinite(midsurfaceEta) ||
!isFinite(areaVector) || !std::isfinite(surfaceMeasure) ||
!(surfaceMeasure > 0.0) ||
!(dot(areaVector, work[elementIndex].normal) > 0.0)) {
return geometryFailure(
"invalid-shell-geometry", element.location, "ELEMENT",
element.sourceId.source_label_text,
"Shell surface basis is nonfinite, zero, or reversed at a required point.");
}
const Vector3 directorXi =
weightedSum(shape.xiDerivatives, directors);
const Vector3 directorEta =
weightedSum(shape.etaDerivatives, directors);
const Vector3 directorValue = weightedSum(shape.values, directors);
const Vector3 covariantXi = add(
midsurfaceXi, scale(0.5 * thickness * zeta, directorXi));
const Vector3 covariantEta = add(
midsurfaceEta, scale(0.5 * thickness * zeta, directorEta));
const Vector3 covariantZeta = scale(0.5 * thickness, directorValue);
const double jacobian =
dot(covariantXi, cross(covariantEta, covariantZeta));
if (!isFinite(covariantXi) || !isFinite(covariantEta) ||
!isFinite(covariantZeta) || !std::isfinite(jacobian) ||
!(jacobian > 0.0)) {
return geometryFailure(
"invalid-shell-jacobian", element.location, "ELEMENT",
element.sourceId.source_label_text,
"Shell Jacobian is nonfinite or nonpositive at a required point.");
}
const Vector3 reciprocalXi =
scale(1.0 / jacobian, cross(covariantEta, covariantZeta));
const Vector3 reciprocalEta =
scale(1.0 / jacobian, cross(covariantZeta, covariantXi));
const Vector3 reciprocalZeta =
scale(1.0 / jacobian, cross(covariantXi, covariantEta));
if (!isFinite(reciprocalXi) || !isFinite(reciprocalEta) ||
!isFinite(reciprocalZeta)) {
return geometryFailure(
"invalid-shell-jacobian", element.location, "ELEMENT",
element.sourceId.source_label_text,
"Shell reciprocal basis is nonfinite at a required point.");
}
}
const Vector3 director_xi = WeightedSum(shape.xi_derivatives, directors);
const Vector3 director_eta =
WeightedSum(shape.eta_derivatives, directors);
const Vector3 director_value = WeightedSum(shape.values, directors);
const Vector3 covariant_xi =
Add(midsurface_xi, Scale(0.5 * thickness * zeta, director_xi));
const Vector3 covariant_eta =
Add(midsurface_eta, Scale(0.5 * thickness * zeta, director_eta));
const Vector3 covariant_zeta = Scale(0.5 * thickness, director_value);
const double jacobian =
Dot(covariant_xi, Cross(covariant_eta, covariant_zeta));
if (!IsFinite(covariant_xi) || !IsFinite(covariant_eta) ||
!IsFinite(covariant_zeta) || !std::isfinite(jacobian) ||
!(jacobian > 0.0)) {
return GeometryFailure(
"invalid-shell-jacobian", element.location, "ELEMENT",
element.source_id.source_label_text,
"Shell Jacobian is nonfinite or nonpositive at a required point.");
}
const Vector3 reciprocal_xi =
Scale(1.0 / jacobian, Cross(covariant_eta, covariant_zeta));
const Vector3 reciprocal_eta =
Scale(1.0 / jacobian, Cross(covariant_zeta, covariant_xi));
const Vector3 reciprocal_zeta =
Scale(1.0 / jacobian, Cross(covariant_xi, covariant_eta));
if (!IsFinite(reciprocal_xi) || !IsFinite(reciprocal_eta) ||
!IsFinite(reciprocal_zeta)) {
return GeometryFailure(
"invalid-shell-jacobian", element.location, "ELEMENT",
element.source_id.source_label_text,
"Shell reciprocal basis is nonfinite at a required point.");
}
}
}
return Result<ShellGeometry>::Success(std::move(geometry));
return Result<ShellGeometry>::Success(std::move(geometry));
}
} // namespace fesa
} // namespace fesa
+149 -149
View File
@@ -1,7 +1,7 @@
#include "fesa/results/result_recovery.hpp"
#include "fesa/elements/euler_beam_3d.hpp"
#include "fesa/elements/mitc4_shell.hpp"
#include "fesa/elements/euler_beam_3d.h"
#include "fesa/elements/mitc4_shell.h"
#include <algorithm>
#include <array>
@@ -131,15 +131,15 @@ Status validateRecoveryInputs(const AnalysisModel& model,
const SparseMatrix& fullStiffness,
const AnalysisState& state) {
const Domain& domain = model.domain();
if (domain.nodes().size() >
if (domain.Nodes().size() >
(std::numeric_limits<std::size_t>::max)() / kDofsPerNode) {
return recoveryFailure(
"invalid-recovery-dimensions",
{domain.sourcePath(), 0U},
domain.sourceContentIdentity(),
{domain.SourcePath(), 0U},
domain.SourceContentIdentity(),
"The semantic node count cannot be represented in full-DOF space.");
}
const std::size_t fullCount = domain.nodes().size() * kDofsPerNode;
const std::size_t fullCount = domain.Nodes().size() * kDofsPerNode;
if (dofs.fullDofCount() != fullCount ||
fullStiffness.Rows() != fullCount ||
fullStiffness.Columns() != fullCount ||
@@ -150,8 +150,8 @@ Status validateRecoveryInputs(const AnalysisModel& model,
state.reaction().Size() != fullCount) {
return recoveryFailure(
"invalid-recovery-dimensions",
{domain.sourcePath(), 0U},
domain.sourceContentIdentity(),
{domain.SourcePath(), 0U},
domain.SourceContentIdentity(),
"Model, DOF, stiffness, and AnalysisState full-space dimensions must agree.");
}
const Status matrixStatus = fullStiffness.Validate();
@@ -161,8 +161,8 @@ Status validateRecoveryInputs(const AnalysisModel& model,
if (!finite(state.displacement()) || !finite(state.externalForce())) {
return recoveryFailure(
"nonfinite-recovery-value",
{domain.sourcePath(), 0U},
domain.sourceContentIdentity(),
{domain.SourcePath(), 0U},
domain.SourceContentIdentity(),
"Displacement and external-force inputs must be finite.");
}
@@ -176,8 +176,8 @@ Status validateRecoveryInputs(const AnalysisModel& model,
!strictlyIncreasing(constrainedDofs)) {
return recoveryFailure(
"invalid-recovery-order",
{domain.sourcePath(), 0U},
domain.sourceContentIdentity(),
{domain.SourcePath(), 0U},
domain.SourceContentIdentity(),
"Free and constrained DOFs must form stable increasing full-space orders.");
}
std::vector<unsigned char> ownership(fullCount, 0U);
@@ -188,7 +188,7 @@ Status validateRecoveryInputs(const AnalysisModel& model,
dofs.freeEquation(fullDof) != equation) {
return recoveryFailure(
"invalid-recovery-order",
{domain.sourcePath(), 0U},
{domain.SourcePath(), 0U},
std::to_string(fullDof),
"Free equations must match stable full-DOF order.");
}
@@ -202,7 +202,7 @@ Status validateRecoveryInputs(const AnalysisModel& model,
dofs.freeEquation(fullDof).has_value()) {
return recoveryFailure(
"invalid-recovery-order",
{domain.sourcePath(), 0U},
{domain.SourcePath(), 0U},
std::to_string(fullDof),
"Constrained DOFs must be unique and absent from free equations.");
}
@@ -211,7 +211,7 @@ Status validateRecoveryInputs(const AnalysisModel& model,
dofs.prescribedValues()[constrained]) {
return recoveryFailure(
"invalid-recovery-state",
{domain.sourcePath(), 0U},
{domain.SourcePath(), 0U},
std::to_string(fullDof),
"Constrained displacement must equal its prescribed value before recovery.");
}
@@ -220,40 +220,40 @@ Status validateRecoveryInputs(const AnalysisModel& model,
} catch (const std::out_of_range&) {
return recoveryFailure(
"invalid-recovery-dimensions",
{domain.sourcePath(), 0U},
domain.sourceContentIdentity(),
{domain.SourcePath(), 0U},
domain.SourceContentIdentity(),
"DofManager equation storage must cover every full DOF.");
}
if (std::find(ownership.begin(), ownership.end(), 0U) != ownership.end()) {
return recoveryFailure(
"invalid-recovery-order",
{domain.sourcePath(), 0U},
domain.sourceContentIdentity(),
{domain.SourcePath(), 0U},
domain.SourceContentIdentity(),
"Free and constrained DOFs must partition the full range.");
}
EntityIndex previousElement = 0U;
bool firstElement = true;
for (const EntityIndex element : model.activeElements()) {
if (element >= domain.elements().size() ||
if (element >= domain.Elements().size() ||
(!firstElement && element <= previousElement)) {
return recoveryFailure(
"invalid-recovery-entity",
{domain.sourcePath(), 0U},
{domain.SourcePath(), 0U},
std::to_string(element),
"Active elements must be unique in stable internal-index order.");
}
firstElement = false;
previousElement = element;
const auto& definition = domain.elements()[element];
if (definition.nodeIndices[0U] >= domain.nodes().size() ||
definition.nodeIndices[1U] >= domain.nodes().size() ||
definition.materialIndex >= domain.materials().size() ||
definition.sectionIndex >= domain.sections().size()) {
const auto& definition = domain.Elements()[element];
if (definition.node_indices[0U] >= domain.Nodes().size() ||
definition.node_indices[1U] >= domain.Nodes().size() ||
definition.material_index >= domain.Materials().size() ||
definition.section_index >= domain.Sections().size()) {
return recoveryFailure(
"invalid-recovery-entity",
definition.location,
definition.sourceId.source_label_text,
definition.source_id.source_label_text,
"Active beam references must resolve before recovery.");
}
try {
@@ -263,7 +263,7 @@ Status validateRecoveryInputs(const AnalysisModel& model,
component < kDofsPerNode;
++component) {
const std::size_t expected =
static_cast<std::size_t>(definition.nodeIndices[endpoint]) *
static_cast<std::size_t>(definition.node_indices[endpoint]) *
kDofsPerNode +
component;
if (scatter[endpoint * kDofsPerNode + component] != expected ||
@@ -271,7 +271,7 @@ Status validateRecoveryInputs(const AnalysisModel& model,
return recoveryFailure(
"invalid-recovery-order",
definition.location,
definition.sourceId.source_label_text,
definition.source_id.source_label_text,
"Element scatter must preserve endpoint/component full-DOF order.");
}
}
@@ -280,51 +280,51 @@ Status validateRecoveryInputs(const AnalysisModel& model,
return recoveryFailure(
"invalid-recovery-entity",
definition.location,
definition.sourceId.source_label_text,
definition.source_id.source_label_text,
"Every active element requires one twelve-DOF scatter map.");
}
}
if (!model.activeElements().empty() && !domain.shellElements().empty()) {
if (!model.activeElements().empty() && !domain.ShellElements().empty()) {
return recoveryFailure(
"unsupported-mixed-element-model",
{domain.sourcePath(), 0U},
{domain.SourcePath(), 0U},
"B33:FESA-MITC4",
"Result recovery does not support mixed beam and shell models.");
}
if (domain.shellElements().size() >
if (domain.ShellElements().size() >
static_cast<std::size_t>(
(std::numeric_limits<EntityIndex>::max)())) {
return recoveryFailure(
"invalid-recovery-dimensions",
{domain.sourcePath(), 0U},
domain.sourceContentIdentity(),
{domain.SourcePath(), 0U},
domain.SourceContentIdentity(),
"The shell element count cannot be represented by stable element identities.");
}
for (std::size_t elementOrder = 0U;
elementOrder < domain.shellElements().size();
elementOrder < domain.ShellElements().size();
++elementOrder) {
const auto& definition = domain.shellElements()[elementOrder];
if (definition.materialIndex >= domain.materials().size() ||
definition.sectionIndex >= domain.shellSections().size()) {
const auto& definition = domain.ShellElements()[elementOrder];
if (definition.material_index >= domain.Materials().size() ||
definition.section_index >= domain.ShellSections().size()) {
return recoveryFailure(
"invalid-recovery-entity",
definition.location,
definition.sourceId.source_label_text,
definition.source_id.source_label_text,
"Active shell material and section references must resolve before recovery.");
}
try {
const auto& scatter = dofs.shellElementScatter(
static_cast<EntityIndex>(elementOrder));
for (std::size_t nodePosition = 0U;
nodePosition < definition.nodeIndices.size();
nodePosition < definition.node_indices.size();
++nodePosition) {
const EntityIndex node = definition.nodeIndices[nodePosition];
if (node >= domain.nodes().size()) {
const EntityIndex node = definition.node_indices[nodePosition];
if (node >= domain.Nodes().size()) {
return recoveryFailure(
"invalid-recovery-entity",
definition.location,
definition.sourceId.source_label_text,
definition.source_id.source_label_text,
"Active shell node references must resolve before recovery.");
}
for (std::size_t component = 0U;
@@ -339,7 +339,7 @@ Status validateRecoveryInputs(const AnalysisModel& model,
return recoveryFailure(
"invalid-recovery-order",
definition.location,
definition.sourceId.source_label_text,
definition.source_id.source_label_text,
"Shell scatter must preserve node/component full-DOF order.");
}
}
@@ -348,7 +348,7 @@ Status validateRecoveryInputs(const AnalysisModel& model,
return recoveryFailure(
"invalid-recovery-entity",
definition.location,
definition.sourceId.source_label_text,
definition.source_id.source_label_text,
"Every active shell requires one twenty-four-DOF scatter map.");
}
}
@@ -381,7 +381,7 @@ bool tryPositiveInteger(const std::string& text, std::int64_t& value) {
Result<std::vector<EntityIndex>> resolveLoadTarget(const Domain& domain,
const NodalLoad& load) {
std::vector<const NodeSet*> sets;
for (const auto& set : domain.nodeSets()) {
for (const auto& set : domain.NodeSets()) {
if (equalName(set.name, load.target)) {
sets.push_back(&set);
}
@@ -389,8 +389,8 @@ Result<std::vector<EntityIndex>> resolveLoadTarget(const Domain& domain,
std::vector<EntityIndex> nodes;
std::int64_t sourceLabel = 0;
if (tryPositiveInteger(load.target, sourceLabel)) {
for (std::size_t node = 0U; node < domain.nodes().size(); ++node) {
if (domain.nodes()[node].sourceId.source_label == sourceLabel) {
for (std::size_t node = 0U; node < domain.Nodes().size(); ++node) {
if (domain.Nodes()[node].source_id.source_label == sourceLabel) {
nodes.push_back(static_cast<EntityIndex>(node));
}
}
@@ -404,9 +404,9 @@ Result<std::vector<EntityIndex>> resolveLoadTarget(const Domain& domain,
"A station-eligibility load target must resolve unambiguously.");
}
if (!sets.empty()) {
std::vector<unsigned char> seen(domain.nodes().size(), 0U);
for (const EntityIndex node : sets.front()->nodeIndices) {
if (node >= domain.nodes().size() || seen[node] != 0U) {
std::vector<unsigned char> seen(domain.Nodes().size(), 0U);
for (const EntityIndex node : sets.front()->node_indices) {
if (node >= domain.Nodes().size() || seen[node] != 0U) {
return recoveryResultFailure<std::vector<EntityIndex>>(
"invalid-node-station-entity",
load.location,
@@ -416,7 +416,7 @@ Result<std::vector<EntityIndex>> resolveLoadTarget(const Domain& domain,
seen[node] = 1U;
}
return Result<std::vector<EntityIndex>>::Success(
sets.front()->nodeIndices);
sets.front()->node_indices);
}
if (!nodes.empty()) {
return Result<std::vector<EntityIndex>>::Success(std::move(nodes));
@@ -504,7 +504,7 @@ Status populateShellGlobalEvidence(
double reactionForceScale = 0.0;
double appliedMomentScale = 0.0;
double reactionMomentScale = 0.0;
for (std::size_t node = 0U; node < domain.nodes().size(); ++node) {
for (std::size_t node = 0U; node < domain.Nodes().size(); ++node) {
const std::size_t offset = node * kDofsPerNode;
std::array<double, 3> nodalAppliedForce{};
std::array<double, 3> nodalReactionForce{};
@@ -524,9 +524,9 @@ Status populateShellGlobalEvidence(
}
const auto appliedForceMoment =
cross(domain.nodes()[node].coordinates, nodalAppliedForce);
cross(domain.Nodes()[node].coordinates, nodalAppliedForce);
const auto reactionForceMoment =
cross(domain.nodes()[node].coordinates, nodalReactionForce);
cross(domain.Nodes()[node].coordinates, nodalReactionForce);
for (std::size_t component = 0U; component < 3U; ++component) {
nodalAppliedMoment[component] += appliedForceMoment[component];
nodalReactionMoment[component] += reactionForceMoment[component];
@@ -541,8 +541,8 @@ Status populateShellGlobalEvidence(
reactionMoment, reactionMomentScale, nodalReactionMoment)) {
return recoveryFailure(
"nonfinite-recovery-value",
domain.nodes()[node].location,
domain.nodes()[node].sourceId.source_label_text,
domain.Nodes()[node].location,
domain.Nodes()[node].source_id.source_label_text,
"Global force and moment evidence must remain finite in source-node order.");
}
}
@@ -569,7 +569,7 @@ Status populateShellGlobalEvidence(
!finite(candidate.verificationMetrics)) {
return recoveryFailure(
"nonfinite-recovery-value",
{domain.sourcePath(), 0U},
{domain.SourcePath(), 0U},
"global-equilibrium",
"Global equilibrium values and their physical normalization scales must be finite.");
}
@@ -577,7 +577,7 @@ Status populateShellGlobalEvidence(
momentMetric > kGlobalEquilibriumTolerance) {
return recoveryFailure(
"global-equilibrium-tolerance-failure",
{domain.sourcePath(), 0U},
{domain.SourcePath(), 0U},
"global-equilibrium",
"Normalized global force or moment balance exceeds 1e-10.");
}
@@ -586,8 +586,8 @@ Status populateShellGlobalEvidence(
std::optional<AxisSet> localAxes(const Domain& domain,
const EulerBeam3DDefinition& element) {
const auto& first = domain.nodes()[element.nodeIndices[0U]].coordinates;
const auto& second = domain.nodes()[element.nodeIndices[1U]].coordinates;
const auto& first = domain.Nodes()[element.node_indices[0U]].coordinates;
const auto& second = domain.Nodes()[element.node_indices[1U]].coordinates;
const std::array<double, 3> delta = {
second[0U] - first[0U],
second[1U] - first[1U],
@@ -598,7 +598,7 @@ std::optional<AxisSet> localAxes(const Domain& domain,
}
const std::array<double, 3> ex = {
delta[0U] / length, delta[1U] / length, delta[2U] / length};
const auto& guide = domain.sections()[element.sectionIndex].firstAxis;
const auto& guide = domain.Sections()[element.section_index].first_axis;
const double projection = dot(guide, ex);
const std::array<double, 3> eyTrial = {
guide[0U] - projection * ex[0U],
@@ -654,8 +654,8 @@ Status ResultRecovery::recover(const AnalysisModel& model,
if (!finite(internalForce)) {
return recoveryFailure(
"nonfinite-recovery-value",
{model.domain().sourcePath(), 0U},
model.domain().sourceContentIdentity(),
{model.domain().SourcePath(), 0U},
model.domain().SourceContentIdentity(),
"Full stiffness multiplication must produce finite internal force.");
}
Vector residual{dofs.fullDofCount()};
@@ -665,7 +665,7 @@ Status ResultRecovery::recover(const AnalysisModel& model,
if (!std::isfinite(residual[fullDof])) {
return recoveryFailure(
"nonfinite-recovery-value",
{model.domain().sourcePath(), 0U},
{model.domain().SourcePath(), 0U},
std::to_string(fullDof),
"Internal-minus-external residual must remain finite.");
}
@@ -684,7 +684,7 @@ Status ResultRecovery::recover(const AnalysisModel& model,
if (!std::isfinite(residualNorm) || !std::isfinite(denominator)) {
return recoveryFailure(
"nonfinite-recovery-value",
{model.domain().sourcePath(), 0U},
{model.domain().SourcePath(), 0U},
"free-residual",
"Free residual and its physical normalization scale must be finite.");
}
@@ -699,7 +699,7 @@ Status ResultRecovery::recover(const AnalysisModel& model,
normalizedResidual > kFreeResidualTolerance) {
return recoveryFailure(
"free-residual-tolerance-failure",
{model.domain().sourcePath(), 0U},
{model.domain().SourcePath(), 0U},
"free-residual",
"The normalized free residual exceeds 1e-10.");
}
@@ -716,12 +716,12 @@ Status ResultRecovery::recover(const AnalysisModel& model,
gaussRows.reserve(model.activeElements().size() * 2U);
const Domain& domain = model.domain();
for (const EntityIndex elementIndex : model.activeElements()) {
const auto& definition = domain.elements()[elementIndex];
auto beam = EulerBeam3D::create(
domain.nodes()[definition.nodeIndices[0U]],
domain.nodes()[definition.nodeIndices[1U]],
domain.sections()[definition.sectionIndex],
domain.materials()[definition.materialIndex]);
const auto& definition = domain.Elements()[elementIndex];
auto beam = EulerBeam3D::Create(
domain.Nodes()[definition.node_indices[0U]],
domain.Nodes()[definition.node_indices[1U]],
domain.Sections()[definition.section_index],
domain.Materials()[definition.material_index]);
if (!beam.HasValue()) {
return beam.GetStatus();
}
@@ -735,52 +735,52 @@ Status ResultRecovery::recover(const AnalysisModel& model,
state.displacement()[scatter[localDof]];
}
const BeamRecovery recovered =
beam.Value().recover(elementDisplacement);
beam.Value().Recover(elementDisplacement);
for (std::size_t endpoint = 0U; endpoint < 2U; ++endpoint) {
if (!finite(recovered.equilibriumEndActions[endpoint]) ||
!finite(recovered.endpointSectionResultants[endpoint])) {
if (!finite(recovered.equilibrium_end_actions[endpoint]) ||
!finite(recovered.endpoint_section_resultants[endpoint])) {
return recoveryFailure(
"nonfinite-recovery-value",
definition.location,
definition.sourceId.source_label_text,
definition.source_id.source_label_text,
"Endpoint recovery values must be finite.");
}
endpointRows.push_back({
elementIndex,
static_cast<int>(endpoint),
domain.nodes()[definition.nodeIndices[endpoint]].sourceId,
recovered.equilibriumEndActions[endpoint],
recovered.endpointSectionResultants[endpoint]});
domain.Nodes()[definition.node_indices[endpoint]].source_id,
recovered.equilibrium_end_actions[endpoint],
recovered.endpoint_section_resultants[endpoint]});
}
for (std::size_t gauss = 0U; gauss < 2U; ++gauss) {
if (!finite(recovered.gaussGeneralizedStrains[gauss]) ||
!finite(recovered.gaussGeneralizedResultants[gauss])) {
if (!finite(recovered.gauss_generalized_strains[gauss]) ||
!finite(recovered.gauss_generalized_resultants[gauss])) {
return recoveryFailure(
"nonfinite-recovery-value",
definition.location,
definition.sourceId.source_label_text,
definition.source_id.source_label_text,
"Gauss recovery values must be finite.");
}
gaussRows.push_back({
elementIndex,
static_cast<int>(gauss + 1U),
recovered.gaussGeneralizedStrains[gauss],
recovered.gaussGeneralizedResultants[gauss]});
recovered.gauss_generalized_strains[gauss],
recovered.gauss_generalized_resultants[gauss]});
}
for (const auto& point : recovered.stressPoints) {
if ((point.gaussPoint != 1 && point.gaussPoint != 2) ||
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 recoveryFailure(
"nonfinite-recovery-value",
definition.location,
definition.sourceId.source_label_text,
definition.source_id.source_label_text,
"Stress recovery identity and values must be finite and ordered.");
}
stressRows.push_back({
elementIndex,
point.gaussPoint,
point.sectionPoint,
point.gauss_point,
point.section_point,
point.x1,
point.x2,
point.s11,
@@ -790,33 +790,33 @@ Status ResultRecovery::recover(const AnalysisModel& model,
ShellStateCandidate shellCandidate{};
std::vector<EntityIndex> expectedShellElements;
if (!domain.shellElements().empty()) {
if (domain.shellElements().size() >
if (!domain.ShellElements().empty()) {
if (domain.ShellElements().size() >
(std::numeric_limits<std::size_t>::max)() /
kShellLocationCount) {
return recoveryFailure(
"invalid-recovery-dimensions",
{domain.sourcePath(), 0U},
domain.sourceContentIdentity(),
{domain.SourcePath(), 0U},
domain.SourceContentIdentity(),
"The shell result-row inventory exceeds the addressable range.");
}
std::vector<std::optional<std::array<double, 3>>> directorsByNode(
domain.nodes().size());
for (const auto& frame : domain.shellNodeInitialFrames()) {
if (frame.nodeIndex >= directorsByNode.size() ||
directorsByNode[frame.nodeIndex].has_value()) {
domain.Nodes().size());
for (const auto& frame : domain.ShellNodeInitialFrames()) {
if (frame.node_index >= directorsByNode.size() ||
directorsByNode[frame.node_index].has_value()) {
return recoveryFailure(
"invalid-recovery-entity",
{domain.sourcePath(), 0U},
std::to_string(frame.nodeIndex),
{domain.SourcePath(), 0U},
std::to_string(frame.node_index),
"Shell initial directors must map uniquely to model nodes.");
}
directorsByNode[frame.nodeIndex] = frame.director;
directorsByNode[frame.node_index] = frame.director;
}
shellCandidate.rows.reserve(
domain.shellElements().size() * kShellLocationCount);
expectedShellElements.reserve(domain.shellElements().size());
domain.ShellElements().size() * kShellLocationCount);
expectedShellElements.reserve(domain.ShellElements().size());
constexpr std::array<ShellMidsurfaceLocation, kShellLocationCount>
locations{
ShellMidsurfaceLocation::gp1,
@@ -829,33 +829,33 @@ Status ResultRecovery::recover(const AnalysisModel& model,
ShellSectionPosition::top};
constexpr std::array<double, 3> zeta{-1.0, 0.0, 1.0};
for (std::size_t elementOrder = 0U;
elementOrder < domain.shellElements().size();
elementOrder < domain.ShellElements().size();
++elementOrder) {
const EntityIndex elementIndex =
static_cast<EntityIndex>(elementOrder);
const auto& definition = domain.shellElements()[elementOrder];
const auto& definition = domain.ShellElements()[elementOrder];
std::array<const Node*, 4> nodes{};
std::array<std::array<double, 3>, 4> directors{};
for (std::size_t nodePosition = 0U;
nodePosition < definition.nodeIndices.size();
nodePosition < definition.node_indices.size();
++nodePosition) {
const EntityIndex node = definition.nodeIndices[nodePosition];
const EntityIndex node = definition.node_indices[nodePosition];
if (!directorsByNode[node].has_value()) {
return recoveryFailure(
"invalid-recovery-entity",
definition.location,
definition.sourceId.source_label_text,
definition.source_id.source_label_text,
"Shell recovery requires one initial director per element node.");
}
nodes[nodePosition] = &domain.nodes()[node];
nodes[nodePosition] = &domain.Nodes()[node];
directors[nodePosition] = *directorsByNode[node];
}
auto shell = Mitc4Shell::create(
auto shell = Mitc4Shell::Create(
nodes,
directors,
domain.shellSections()[definition.sectionIndex],
domain.materials()[definition.materialIndex]);
domain.ShellSections()[definition.section_index],
domain.Materials()[definition.material_index]);
if (!shell.HasValue()) {
return shell.GetStatus();
}
@@ -867,19 +867,19 @@ Status ResultRecovery::recover(const AnalysisModel& model,
elementDisplacement[localDof] =
state.displacement()[scatter[localDof]];
}
auto recovered = shell.Value().recoverPhysical(
auto recovered = shell.Value().RecoverPhysical(
elementDisplacement);
if (!recovered.HasValue()) {
return recovered.GetStatus();
}
const double accumulatedEnergy =
shellCandidate.physicalStrainEnergy +
recovered.Value().strainEnergy;
recovered.Value().strain_energy;
if (!std::isfinite(accumulatedEnergy)) {
return recoveryFailure(
"nonfinite-recovery-value",
definition.location,
definition.sourceId.source_label_text,
definition.source_id.source_label_text,
"Source-order physical shell energy reduction must remain finite.");
}
shellCandidate.physicalStrainEnergy = accumulatedEnergy;
@@ -892,20 +892,20 @@ Status ResultRecovery::recover(const AnalysisModel& model,
ShellResultRow row{};
row.element = elementIndex;
row.location = locations[point];
row.naturalCoordinates = physicalPoint.naturalCoordinates;
row.naturalCoordinates = physicalPoint.natural_coordinates;
row.localFrame = {
physicalPoint.localFrame.e1,
physicalPoint.localFrame.e2,
physicalPoint.localFrame.e3};
row.generalizedStrain = physicalPoint.generalizedStrain;
row.sectionResultant = physicalPoint.sectionResultant;
physicalPoint.local_frame.e1,
physicalPoint.local_frame.e2,
physicalPoint.local_frame.e3};
row.generalizedStrain = physicalPoint.generalized_strain;
row.sectionResultant = physicalPoint.section_resultant;
for (std::size_t position = 0U;
position < positions.size();
++position) {
row.stress[position] = {
positions[position],
zeta[position],
physicalPoint.inPlaneStress[position]};
physicalPoint.in_plane_stress[position]};
}
shellCandidate.rows.push_back(std::move(row));
}
@@ -951,7 +951,7 @@ ResultRecovery::normalizeSectionResultantsToNodeStations(
if (!std::isfinite(tolerance) || tolerance < 0.0) {
return recoveryResultFailure<std::vector<NodeStationResultRow>>(
"invalid-node-station-tolerance",
{domain.sourcePath(), 0U},
{domain.SourcePath(), 0U},
"component-tolerances",
"Node-station component tolerances must be finite and nonnegative.");
}
@@ -961,50 +961,50 @@ ResultRecovery::normalizeSectionResultantsToNodeStations(
endpointRows.size() != model.activeElements().size() * 2U) {
return recoveryResultFailure<std::vector<NodeStationResultRow>>(
"invalid-node-station-shape",
{domain.sourcePath(), 0U},
{domain.SourcePath(), 0U},
std::to_string(endpointRows.size()),
"Endpoint rows must contain exactly two rows per active element.");
}
std::vector<std::vector<const EndpointResultRow*>> rowsByNode(
domain.nodes().size());
domain.Nodes().size());
for (std::size_t order = 0U;
order < model.activeElements().size();
++order) {
const EntityIndex elementIndex = model.activeElements()[order];
if (elementIndex >= domain.elements().size()) {
if (elementIndex >= domain.Elements().size()) {
return recoveryResultFailure<std::vector<NodeStationResultRow>>(
"invalid-node-station-entity",
{domain.sourcePath(), 0U},
{domain.SourcePath(), 0U},
std::to_string(elementIndex),
"Every active station element must be a valid stable entity.");
}
const auto& definition = domain.elements()[elementIndex];
const auto& definition = domain.Elements()[elementIndex];
for (std::size_t endpoint = 0U; endpoint < 2U; ++endpoint) {
const auto& row = endpointRows[order * 2U + endpoint];
const EntityIndex nodeIndex = definition.nodeIndices[endpoint];
if (nodeIndex >= domain.nodes().size() ||
const EntityIndex nodeIndex = definition.node_indices[endpoint];
if (nodeIndex >= domain.Nodes().size() ||
row.element != elementIndex ||
row.endpoint != static_cast<int>(endpoint) ||
!sameSourceIdentity(row.node, domain.nodes()[nodeIndex].sourceId)) {
!sameSourceIdentity(row.node, domain.Nodes()[nodeIndex].source_id)) {
return recoveryResultFailure<std::vector<NodeStationResultRow>>(
"invalid-node-station-entity",
definition.location,
definition.sourceId.source_label_text,
definition.source_id.source_label_text,
"Endpoint rows must preserve active element, endpoint, and source-node order.");
}
if (!finite(row.sectionResultant)) {
return recoveryResultFailure<std::vector<NodeStationResultRow>>(
"nonfinite-node-station-value",
definition.location,
definition.sourceId.source_label_text,
definition.source_id.source_label_text,
"Node-station section resultants must be finite.");
}
rowsByNode[nodeIndex].push_back(&row);
}
}
std::vector<unsigned char> loadedNodes(domain.nodes().size(), 0U);
std::vector<unsigned char> loadedNodes(domain.Nodes().size(), 0U);
if (model.activeLoads().size() != model.step().loads.size()) {
return recoveryResultFailure<std::vector<NodeStationResultRow>>(
"invalid-node-station-entity",
@@ -1042,7 +1042,7 @@ ResultRecovery::normalizeSectionResultantsToNodeStations(
}
std::vector<NodeStationResultRow> stations;
stations.reserve(domain.nodes().size());
stations.reserve(domain.Nodes().size());
for (std::size_t nodeIndex = 0U;
nodeIndex < rowsByNode.size();
++nodeIndex) {
@@ -1052,7 +1052,7 @@ ResultRecovery::normalizeSectionResultantsToNodeStations(
}
if (incident.size() == 1U) {
stations.push_back({
domain.nodes()[nodeIndex].sourceId,
domain.Nodes()[nodeIndex].source_id,
incident.front()->element,
incident.front()->sectionResultant});
continue;
@@ -1060,13 +1060,13 @@ ResultRecovery::normalizeSectionResultantsToNodeStations(
if (incident.size() != 2U || loadedNodes[nodeIndex] != 0U) {
return recoveryResultFailure<std::vector<NodeStationResultRow>>(
"ineligible-node-station",
domain.nodes()[nodeIndex].location,
domain.nodes()[nodeIndex].sourceId.source_label_text,
domain.Nodes()[nodeIndex].location,
domain.Nodes()[nodeIndex].source_id.source_label_text,
"Interior station collapse requires exactly two unloaded endpoints.");
}
const auto& firstElement = domain.elements()[incident[0U]->element];
const auto& secondElement = domain.elements()[incident[1U]->element];
const auto& firstElement = domain.Elements()[incident[0U]->element];
const auto& secondElement = domain.Elements()[incident[1U]->element];
const bool chainOrientation =
incident[0U]->endpoint != incident[1U]->endpoint &&
((incident[0U]->endpoint == 1 && incident[1U]->endpoint == 0) ||
@@ -1074,13 +1074,13 @@ ResultRecovery::normalizeSectionResultantsToNodeStations(
const auto firstAxes = localAxes(domain, firstElement);
const auto secondAxes = localAxes(domain, secondElement);
if (!chainOrientation ||
firstElement.sectionIndex != secondElement.sectionIndex ||
firstElement.section_index != secondElement.section_index ||
!firstAxes.has_value() || !secondAxes.has_value() ||
!sameAxes(*firstAxes, *secondAxes)) {
return recoveryResultFailure<std::vector<NodeStationResultRow>>(
"ineligible-node-station",
domain.nodes()[nodeIndex].location,
domain.nodes()[nodeIndex].sourceId.source_label_text,
domain.Nodes()[nodeIndex].location,
domain.Nodes()[nodeIndex].source_id.source_label_text,
"Interior station endpoints require one consistent section and local-axis chain.");
}
@@ -1096,15 +1096,15 @@ ResultRecovery::normalizeSectionResultantsToNodeStations(
if (!std::isfinite(difference)) {
return recoveryResultFailure<std::vector<NodeStationResultRow>>(
"nonfinite-node-station-value",
domain.nodes()[nodeIndex].location,
domain.nodes()[nodeIndex].sourceId.source_label_text,
domain.Nodes()[nodeIndex].location,
domain.Nodes()[nodeIndex].source_id.source_label_text,
"Endpoint comparison must produce a finite difference.");
}
if (difference > componentTolerances[component]) {
return recoveryResultFailure<std::vector<NodeStationResultRow>>(
"node-station-tolerance-failure",
domain.nodes()[nodeIndex].location,
domain.nodes()[nodeIndex].sourceId.source_label_text,
domain.Nodes()[nodeIndex].location,
domain.Nodes()[nodeIndex].source_id.source_label_text,
"Interior endpoint resultants disagree beyond component tolerance.");
}
}
@@ -1114,7 +1114,7 @@ ResultRecovery::normalizeSectionResultantsToNodeStations(
? incident[0U]
: incident[1U];
stations.push_back({
domain.nodes()[nodeIndex].sourceId,
domain.Nodes()[nodeIndex].source_id,
representative->element,
representative->sectionResultant});
}