feat(cpp-object-oriented-modular-refactoring): step 4 - model-element-google-style
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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."));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
+793
-857
File diff suppressed because it is too large
Load Diff
@@ -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) {
|
||||
|
||||
@@ -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() &&
|
||||
|
||||
@@ -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
@@ -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
@@ -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
|
||||
|
||||
@@ -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});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user