feat(cpp-object-oriented-modular-refactoring): step 4 - model-element-google-style

This commit is contained in:
KOKO\Mimi
2026-08-16 05:37:40 +09:00
parent 34ab8b5bf1
commit 8bc0ea2f8e
46 changed files with 5184 additions and 5323 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
#pragma once
#include "fesa/model/domain.hpp"
#include "fesa/model/domain.h"
#include <vector>
@@ -7,7 +7,7 @@
#include "fesa/fem/dof_manager.hpp"
#include "fesa/math/sparse_matrix.h"
#include "fesa/math/vector.h"
#include "fesa/model/domain.hpp"
#include "fesa/model/domain.h"
#include <filesystem>
#include <memory>
+95
View File
@@ -0,0 +1,95 @@
#ifndef FESA_ELEMENTS_EULER_BEAM_3D_H_
#define FESA_ELEMENTS_EULER_BEAM_3D_H_
#include <array>
#include <cstddef>
#include <string>
#include <vector>
#include "fesa/core/status.h"
#include "fesa/math/matrix.h"
#include "fesa/math/vector.h"
#include "fesa/model/model_types.h"
namespace fesa {
/// @brief Stores constant line-load components in the beam local frame.
struct ConstantLocalLineLoad {
double px;
double py;
double pz;
double mx;
};
/// @brief Stores one axial stress at a Gauss and section-point identity.
struct BeamStressPoint {
int gauss_point;
std::size_t section_point;
double x1;
double x2;
double s11;
std::string source;
};
/// @brief Stores distinct beam end-action, section, Gauss, and stress results.
struct BeamRecovery {
std::array<std::array<double, 6>, 2> equilibrium_end_actions;
std::array<std::array<double, 4>, 2> endpoint_section_resultants;
std::array<std::array<double, 4>, 2> gauss_generalized_strains;
std::array<std::array<double, 4>, 2> gauss_generalized_resultants;
std::vector<BeamStressPoint> stress_points;
};
/// @brief Implements the approved two-node prismatic B33 Euler beam kernel.
/// @note Equation numbering and semantic element identity remain external.
class EulerBeam3D {
public:
/// @brief Creates a validated beam kernel and right-handed local frame.
/// @param first_node First source node in the element connectivity.
/// @param second_node Second source node in the element connectivity.
/// @param section Supported general beam section and local first axis.
/// @param material Supported isotropic elastic material.
/// @return A validated beam or a structured model failure.
static Result<EulerBeam3D> Create(const Node& first_node,
const Node& second_node,
const GeneralBeamSection& section,
const LinearElasticMaterial& material);
/// @brief Computes the 12-by-12 stiffness in local DOF order.
/// @note Uses the approved two-point Gauss operation order.
Matrix LocalStiffness() const;
/// @brief Computes the stiffness in stable global element DOF order.
Matrix GlobalStiffness() const;
/// @brief Computes the formulation-only constant local line-load vector.
/// @warning This kernel does not expose distributed loads through parser
/// input.
Vector LocalEquivalentLoad(const ConstantLocalLineLoad& load) const;
/// @brief Recovers signed physical quantities at their distinct locations.
/// @param global_element_displacement Twelve global element DOF values.
/// @return Beam recovery rows in deterministic location order.
BeamRecovery Recover(const Vector& global_element_displacement) const;
private:
/// @brief Stores already validated geometry, material, and section state.
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);
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_;
};
} // namespace fesa
#endif // FESA_ELEMENTS_EULER_BEAM_3D_H_
-74
View File
@@ -1,74 +0,0 @@
#pragma once
#include "fesa/core/status.h"
#include "fesa/math/matrix.h"
#include "fesa/math/vector.h"
#include "fesa/model/model_types.hpp"
#include <array>
#include <cstddef>
#include <string>
#include <vector>
namespace fesa {
struct ConstantLocalLineLoad {
double px;
double py;
double pz;
double mx;
};
struct BeamStressPoint {
int gaussPoint;
std::size_t sectionPoint;
double x1;
double x2;
double s11;
std::string source;
};
struct BeamRecovery {
std::array<std::array<double, 6>, 2> equilibriumEndActions;
std::array<std::array<double, 4>, 2> endpointSectionResultants;
std::array<std::array<double, 4>, 2> gaussGeneralizedStrains;
std::array<std::array<double, 4>, 2> gaussGeneralizedResultants;
std::vector<BeamStressPoint> stressPoints;
};
// Implements the approved two-node straight prismatic B33 Euler-Bernoulli
// kernel. Equation numbering and element identity remain outside this type.
class EulerBeam3D {
public:
static Result<EulerBeam3D> create(const Node& firstNode,
const Node& secondNode,
const GeneralBeamSection& section,
const LinearElasticMaterial& material);
Matrix localStiffness() const;
Matrix globalStiffness() const;
Vector localEquivalentLoad(const ConstantLocalLineLoad& load) const;
BeamRecovery recover(const Vector& globalElementDisplacement) const;
private:
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);
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_;
};
} // namespace fesa
+179
View File
@@ -0,0 +1,179 @@
#ifndef FESA_ELEMENTS_MITC4_SHELL_H_
#define FESA_ELEMENTS_MITC4_SHELL_H_
#include <array>
#include <string>
#include "fesa/core/status.h"
#include "fesa/math/matrix.h"
#include "fesa/math/vector.h"
#include "fesa/model/model_types.h"
namespace fesa {
/// @brief Stores bilinear shape values and natural-coordinate derivatives.
struct Mitc4ShapeFunctions {
std::array<double, 4> values;
std::array<double, 4> xi_derivatives;
std::array<double, 4> eta_derivatives;
};
/// @brief Stores a right-handed local shell frame at one location.
struct Mitc4LocalFrame {
std::array<double, 3> e1;
std::array<double, 3> e2;
std::array<double, 3> e3;
};
/// @brief Stores canonical MITC4 covariant shear interpolation weights.
struct Mitc4TyingWeights {
std::array<double, 2> xi_zeta;
std::array<double, 2> eta_zeta;
};
/// @brief Stores one fixed 2-by-2-by-2 integration point and weight.
struct Mitc4QuadraturePoint {
std::array<double, 3> natural_coordinates;
double weight;
};
/// @brief Separates physical, drilling, and stabilized stiffness matrices.
struct Mitc4Stiffness {
Matrix physical_local20;
Matrix physical_global24;
Matrix drilling_global24;
Matrix stabilized_global24;
double drilling_stiffness;
};
/// @brief Stores physical shell recovery at one midsurface Gauss location.
struct Mitc4PhysicalRecoveryPoint {
std::array<double, 2> natural_coordinates;
Mitc4LocalFrame local_frame;
std::array<double, 8> generalized_strain;
std::array<double, 8> section_resultant;
std::array<std::array<double, 3>, 3> in_plane_stress;
};
/// @brief Stores physical-only recovery rows and strain energy.
struct Mitc4PhysicalRecovery {
std::array<Mitc4PhysicalRecoveryPoint, 4> points;
double strain_energy;
};
/// @brief Implements the approved small-rotation FESA-MITC4 shell kernel.
/// @note Physical and numerical drilling contributions remain separate.
class Mitc4Shell {
public:
/// @brief Creates a validated shell kernel from four non-owning node
/// pointers.
/// @param nodes Node pointers valid for the duration of this call.
/// @param initial_directors Validated unit initial directors in node order.
/// @param section Centered constant-thickness shell section.
/// @param material Supported isotropic elastic material.
/// @return A validated shell or a structured model failure.
static Result<Mitc4Shell> Create(
std::array<const Node*, 4> nodes,
std::array<std::array<double, 3>, 4> initial_directors,
const ShellSection& section, const LinearElasticMaterial& material);
/// @brief Evaluates bilinear shape functions and derivatives.
static Mitc4ShapeFunctions ShapeFunctions(double xi, double eta) noexcept;
/// @brief Evaluates the canonical edge-midpoint tying weights.
static Mitc4TyingWeights TyingWeights(double xi, double eta) noexcept;
/// @brief Returns the fixed 2-by-2-by-2 quadrature inventory.
static const std::array<Mitc4QuadraturePoint, 8>& VolumeQuadrature() noexcept;
/// @brief Evaluates the right-handed local frame at a midsurface location.
[[nodiscard]] Mitc4LocalFrame LocalFrame(double xi, double eta) const;
/// @brief Returns the physical 24-to-20 transformation.
[[nodiscard]] Matrix PhysicalTransformation20() const;
/// @brief Returns the numerical drilling 24-to-4 transformation.
[[nodiscard]] Matrix DrillingTransformation4() const;
/// @brief Evaluates the direct five-component physical strain operator.
[[nodiscard]] Matrix DirectStrainDisplacement20(double xi, double eta,
double zeta) const;
/// @brief Evaluates all four canonical covariant tying shear samples.
[[nodiscard]] Matrix CovariantTyingShearSamples20() const;
/// @brief Evaluates the MITC-projected five-component strain operator.
[[nodiscard]] Matrix StrainDisplacement20(double xi, double eta,
double zeta) const;
/// @brief Returns the isotropic in-plane plane-stress matrix.
[[nodiscard]] Matrix PlaneStressConstitutive() const;
/// @brief Returns the five-component plane-stress and shear matrix.
[[nodiscard]] Matrix MaterialConstitutive5() const;
/// @brief Returns the centered membrane section matrix.
[[nodiscard]] Matrix MembraneSectionMatrix() const;
/// @brief Returns the centered bending section matrix.
[[nodiscard]] Matrix BendingSectionMatrix() const;
/// @brief Returns the corrected transverse-shear section matrix.
[[nodiscard]] Matrix TransverseShearSectionMatrix() const;
/// @brief Computes physical, drilling, and stabilized stiffness matrices.
/// @return Finite stiffness matrices or a structured model failure.
[[nodiscard]] Result<Mitc4Stiffness> Stiffness() const;
/// @brief Recovers physical shell quantities without drilling results.
/// @param global_element_displacement24 Global element DOFs in node order.
/// @return Physical recovery rows or a structured model failure.
[[nodiscard]] Result<Mitc4PhysicalRecovery> RecoverPhysical(
const Vector& global_element_displacement24) const;
private:
using Vector3 = std::array<double, 3>;
/// @brief Stores covariant, reciprocal, frame, and Jacobian data at one
/// point.
struct GeometryData {
std::array<Vector3, 3> covariant;
std::array<Vector3, 3> reciprocal;
Mitc4LocalFrame frame;
double jacobian;
};
/// @brief Stores validated shell geometry and constitutive state.
Mitc4Shell(std::array<Vector3, 4> coordinates,
std::array<Vector3, 4> directors, std::array<Vector3, 4> tangent_a,
std::array<Vector3, 4> tangent_b, Vector3 normal_candidate,
double thickness, double youngs_modulus, double poisson_ratio,
SourceLocation source_location, std::string identity);
/// @brief Evaluates a finite positive Jacobian and right-handed frame.
bool EvaluateGeometry(double xi, double eta, double zeta,
GeometryData& result) const noexcept;
/// @brief Evaluates displacement-basis derivatives in covariant directions.
std::array<std::array<Vector3, 3>, 20> BasisDerivatives(
double xi, double eta, double zeta) const noexcept;
/// @brief Builds direct or tied strain without changing projection order.
Matrix StrainDisplacement(double xi, double eta, double zeta,
const Matrix* tying_samples) const;
std::array<Vector3, 4> coordinates_;
std::array<Vector3, 4> directors_;
std::array<Vector3, 4> tangent_a_;
std::array<Vector3, 4> tangent_b_;
Vector3 normal_candidate_;
double thickness_;
double youngs_modulus_;
double poisson_ratio_;
SourceLocation source_location_;
std::string identity_;
};
} // namespace fesa
#endif // FESA_ELEMENTS_MITC4_SHELL_H_
-142
View File
@@ -1,142 +0,0 @@
#pragma once
#include "fesa/core/status.h"
#include "fesa/math/matrix.h"
#include "fesa/math/vector.h"
#include "fesa/model/model_types.hpp"
#include <array>
#include <string>
namespace fesa {
struct Mitc4ShapeFunctions {
std::array<double, 4> values;
std::array<double, 4> xiDerivatives;
std::array<double, 4> etaDerivatives;
};
struct Mitc4LocalFrame {
std::array<double, 3> e1;
std::array<double, 3> e2;
std::array<double, 3> e3;
};
struct Mitc4TyingWeights {
std::array<double, 2> xiZeta;
std::array<double, 2> etaZeta;
};
struct Mitc4QuadraturePoint {
std::array<double, 3> naturalCoordinates;
double weight;
};
struct Mitc4Stiffness {
Matrix physicalLocal20;
Matrix physicalGlobal24;
Matrix drillingGlobal24;
Matrix stabilizedGlobal24;
double drillingStiffness;
};
struct Mitc4PhysicalRecoveryPoint {
std::array<double, 2> naturalCoordinates;
Mitc4LocalFrame localFrame;
std::array<double, 8> generalizedStrain;
std::array<double, 8> sectionResultant;
std::array<std::array<double, 3>, 3> inPlaneStress;
};
struct Mitc4PhysicalRecovery {
std::array<Mitc4PhysicalRecoveryPoint, 4> points;
double strainEnergy;
};
// Concrete small-rotation MITC4 kinematics, constitutive, stiffness, and
// physical-only recovery kernel. Global equation/result ownership remains outside.
class Mitc4Shell {
public:
static Result<Mitc4Shell> create(
std::array<const Node*, 4> nodes,
std::array<std::array<double, 3>, 4> initialDirectors,
const ShellSection& section,
const LinearElasticMaterial& material);
static Mitc4ShapeFunctions shapeFunctions(double xi, double eta) noexcept;
static Mitc4TyingWeights tyingWeights(double xi, double eta) noexcept;
static const std::array<Mitc4QuadraturePoint, 8>&
volumeQuadrature() noexcept;
[[nodiscard]] Mitc4LocalFrame localFrame(double xi, double eta) const;
[[nodiscard]] Matrix physicalTransformation20() const;
[[nodiscard]] Matrix drillingTransformation4() const;
[[nodiscard]] Matrix directStrainDisplacement20(
double xi,
double eta,
double zeta) const;
[[nodiscard]] Matrix covariantTyingShearSamples20() const;
[[nodiscard]] Matrix strainDisplacement20(
double xi,
double eta,
double zeta) const;
[[nodiscard]] Matrix planeStressConstitutive() const;
[[nodiscard]] Matrix materialConstitutive5() const;
[[nodiscard]] Matrix membraneSectionMatrix() const;
[[nodiscard]] Matrix bendingSectionMatrix() const;
[[nodiscard]] Matrix transverseShearSectionMatrix() const;
[[nodiscard]] Result<Mitc4Stiffness> stiffness() const;
[[nodiscard]] Result<Mitc4PhysicalRecovery> recoverPhysical(
const Vector& globalElementDisplacement24) const;
private:
using Vector3 = std::array<double, 3>;
struct GeometryData {
std::array<Vector3, 3> covariant;
std::array<Vector3, 3> reciprocal;
Mitc4LocalFrame frame;
double jacobian;
};
Mitc4Shell(
std::array<Vector3, 4> coordinates,
std::array<Vector3, 4> directors,
std::array<Vector3, 4> tangentA,
std::array<Vector3, 4> tangentB,
Vector3 normalCandidate,
double thickness,
double youngsModulus,
double poissonRatio,
SourceLocation sourceLocation,
std::string identity);
bool evaluateGeometry(
double xi,
double eta,
double zeta,
GeometryData& result) const noexcept;
std::array<std::array<Vector3, 3>, 20> basisDerivatives(
double xi,
double eta,
double zeta) const noexcept;
Matrix strainDisplacement(
double xi,
double eta,
double zeta,
const Matrix* tyingSamples) const;
std::array<Vector3, 4> coordinates_;
std::array<Vector3, 4> directors_;
std::array<Vector3, 4> tangentA_;
std::array<Vector3, 4> tangentB_;
Vector3 normalCandidate_;
double thickness_;
double youngsModulus_;
double poissonRatio_;
SourceLocation sourceLocation_;
std::string identity_;
};
} // namespace fesa
+1 -1
View File
@@ -2,7 +2,7 @@
#include "fesa/core/status.h"
#include "fesa/io/abaqus/input_syntax.hpp"
#include "fesa/model/domain.hpp"
#include "fesa/model/domain.h"
namespace fesa {
+72
View File
@@ -0,0 +1,72 @@
#ifndef FESA_MODEL_DOMAIN_H_
#define FESA_MODEL_DOMAIN_H_
#include <filesystem>
#include <string>
#include <vector>
#include "fesa/core/status.h"
#include "fesa/model/model_types.h"
namespace fesa {
/// @brief Owns the complete immutable semantic model definition.
/// @note Collection positions remain stable internal indices after
/// construction.
class Domain {
public:
/// @brief Creates a Domain that owns a copy or moved model definition.
/// @param definition Complete parsed semantic records in declaration order.
/// @return A successful owning Domain.
static Result<Domain> Create(ModelDefinition definition);
/// @brief Returns nodes in stable declaration order.
const std::vector<Node>& Nodes() const noexcept;
/// @brief Returns Euler beam definitions in stable declaration order.
const std::vector<EulerBeam3DDefinition>& Elements() const noexcept;
/// @brief Returns MITC4 shell definitions in stable declaration order.
const std::vector<Mitc4ShellDefinition>& ShellElements() const noexcept;
/// @brief Returns materials in stable declaration order.
const std::vector<LinearElasticMaterial>& Materials() const noexcept;
/// @brief Returns beam sections in stable declaration order.
const std::vector<GeneralBeamSection>& Sections() const noexcept;
/// @brief Returns shell sections in stable declaration order.
const std::vector<ShellSection>& ShellSections() const noexcept;
/// @brief Returns preprocessed shell-node frames in stable node order.
const std::vector<ShellNodeInitialFrame>& ShellNodeInitialFrames()
const noexcept;
/// @brief Returns node sets in stable declaration order.
const std::vector<NodeSet>& NodeSets() const noexcept;
/// @brief Returns element sets in stable declaration order.
const std::vector<ElementSet>& ElementSets() const noexcept;
/// @brief Returns static steps in stable declaration order.
const std::vector<StaticStepDefinition>& Steps() const noexcept;
/// @brief Returns sorted nonfatal mapping diagnostics.
const std::vector<Diagnostic>& Warnings() const noexcept;
/// @brief Returns the source input path associated with this model.
const std::filesystem::path& SourcePath() const noexcept;
/// @brief Returns the deterministic source-content identity.
const std::string& SourceContentIdentity() const noexcept;
private:
/// @brief Takes ownership of an already constructed model definition.
explicit Domain(ModelDefinition definition);
ModelDefinition definition_;
};
} // namespace fesa
#endif // FESA_MODEL_DOMAIN_H_
-38
View File
@@ -1,38 +0,0 @@
#pragma once
#include "fesa/core/status.h"
#include "fesa/model/model_types.hpp"
#include <filesystem>
#include <string>
#include <vector>
namespace fesa {
// Owns the complete semantic definition. Public access remains const so a
// vector position can serve as a stable internal index after construction.
class Domain {
public:
static Result<Domain> create(ModelDefinition definition);
const std::vector<Node>& nodes() const noexcept;
const std::vector<EulerBeam3DDefinition>& elements() const noexcept;
const std::vector<Mitc4ShellDefinition>& shellElements() const noexcept;
const std::vector<LinearElasticMaterial>& materials() const noexcept;
const std::vector<GeneralBeamSection>& sections() const noexcept;
const std::vector<ShellSection>& shellSections() const noexcept;
const std::vector<ShellNodeInitialFrame>& shellNodeInitialFrames() const noexcept;
const std::vector<NodeSet>& nodeSets() const noexcept;
const std::vector<ElementSet>& elementSets() const noexcept;
const std::vector<StaticStepDefinition>& steps() const noexcept;
const std::vector<Diagnostic>& warnings() const noexcept;
const std::filesystem::path& sourcePath() const noexcept;
const std::string& sourceContentIdentity() const noexcept;
private:
explicit Domain(ModelDefinition definition);
ModelDefinition definition_;
};
} // namespace fesa
+185
View File
@@ -0,0 +1,185 @@
#ifndef FESA_MODEL_MODEL_TYPES_H_
#define FESA_MODEL_MODEL_TYPES_H_
#include <array>
#include <cstdint>
#include <filesystem>
#include <optional>
#include <string>
#include <string_view>
#include <vector>
#include "fesa/core/diagnostic.h"
#include "fesa/core/source_identity.h"
namespace fesa {
/// @brief Identifies a semantic entity by its stable collection position.
using EntityIndex = std::uint32_t;
/// @brief Stores one source node and its global coordinates.
struct Node {
SourceEntityId source_id;
std::array<double, 3> coordinates;
SourceLocation location;
};
/// @brief Stores the supported homogeneous isotropic elastic properties.
struct LinearElasticMaterial {
std::string name;
double youngs_modulus;
double poisson_ratio;
SourceLocation location;
};
/// @brief Stores the supported general Euler beam section properties.
struct GeneralBeamSection {
std::string name;
double area;
double i11;
double i12;
double i22;
double torsional_constant;
std::array<double, 3> first_axis;
std::vector<std::array<double, 2>> section_points;
SourceLocation location;
};
/// @brief Preserves the source shell element label independently of
/// formulation.
enum class ShellSourceElementType { kS4, kS4r };
/// @brief Names the single internal shell formulation selected by S4 and S4R.
inline constexpr std::string_view kMitc4InternalFormulation{"FESA-MITC4"};
/// @brief Stores a centered constant-thickness shell section assignment.
struct ShellSection {
std::string name;
double thickness;
EntityIndex material_index;
SourceLocation location;
};
/// @brief Defines one four-node shell with stable semantic references.
struct Mitc4ShellDefinition {
SourceEntityId source_id;
ShellSourceElementType source_type;
std::array<EntityIndex, 4> node_indices;
EntityIndex material_index;
EntityIndex section_index;
SourceLocation location;
};
/// @brief Stores the deterministic initial director and tangent frame at a
/// node.
struct ShellNodeInitialFrame {
EntityIndex node_index;
std::array<double, 3> director;
std::array<double, 3> tangent_a;
std::array<double, 3> tangent_b;
};
/// @brief Defines one two-node Euler beam with stable semantic references.
struct EulerBeam3DDefinition {
SourceEntityId source_id;
std::array<EntityIndex, 2> node_indices;
EntityIndex material_index;
EntityIndex section_index;
SourceLocation location;
};
/// @brief Stores one prescribed nodal degree-of-freedom range.
struct BoundaryCondition {
std::string target;
int first_dof;
int last_dof;
double value;
SourceLocation location;
};
/// @brief Stores one concentrated nodal load component.
struct NodalLoad {
std::string target;
int dof;
double magnitude;
SourceLocation location;
};
/// @brief Stores the approved single linear-static step definition.
struct StaticStepDefinition {
std::string name;
std::vector<BoundaryCondition> boundaries;
std::vector<NodalLoad> loads;
double initial_increment;
double time_period;
double minimum_increment;
double maximum_increment;
SourceLocation location;
};
/// @brief Stores a stable resolved node-set membership list.
struct NodeSet {
std::string name;
std::optional<std::string> instance_name;
std::vector<EntityIndex> node_indices;
SourceLocation location;
};
/// @brief Stores a stable resolved element-set membership list.
struct ElementSet {
std::string name;
std::optional<std::string> instance_name;
std::vector<EntityIndex> element_indices;
SourceLocation location;
};
/// @brief Preserves source identities declared inside one part.
struct PartDefinition {
std::string name;
std::vector<std::int64_t> node_source_labels;
std::vector<std::int64_t> element_source_labels;
std::vector<std::string> node_set_names;
std::vector<std::string> element_set_names;
SourceLocation location;
};
/// @brief Maps one source label to a stable internal entity index.
struct SourceIndexMapping {
std::int64_t source_label;
EntityIndex internal_index;
};
/// @brief Preserves one identity instance and its deterministic source
/// mappings.
struct InstanceDefinition {
std::string name;
std::string part_name;
std::vector<SourceIndexMapping> node_mappings;
std::vector<SourceIndexMapping> element_mappings;
SourceLocation location;
};
/// @brief Owns every parsed semantic record before immutable Domain
/// construction.
struct ModelDefinition {
std::filesystem::path source_path;
std::string source_content_identity;
std::string heading;
std::vector<Node> nodes;
std::vector<EulerBeam3DDefinition> elements;
std::vector<Mitc4ShellDefinition> shell_elements;
std::vector<LinearElasticMaterial> materials;
std::vector<GeneralBeamSection> sections;
std::vector<ShellSection> shell_sections;
std::vector<ShellNodeInitialFrame> shell_node_initial_frames;
std::vector<NodeSet> node_sets;
std::vector<ElementSet> element_sets;
std::vector<PartDefinition> parts;
std::vector<InstanceDefinition> instances;
std::vector<StaticStepDefinition> steps;
std::vector<Diagnostic> warnings;
};
} // namespace fesa
#endif // FESA_MODEL_MODEL_TYPES_H_
-165
View File
@@ -1,165 +0,0 @@
#pragma once
#include "fesa/core/diagnostic.h"
#include "fesa/core/source_identity.h"
#include <array>
#include <cstdint>
#include <filesystem>
#include <optional>
#include <string>
#include <string_view>
#include <vector>
namespace fesa {
// Stable internal identities are vector positions assigned in declaration order.
using EntityIndex = std::uint32_t;
struct Node {
SourceEntityId sourceId;
std::array<double, 3> coordinates;
SourceLocation location;
};
struct LinearElasticMaterial {
std::string name;
double youngsModulus;
double poissonRatio;
SourceLocation location;
};
struct GeneralBeamSection {
std::string name;
double area;
double i11;
double i12;
double i22;
double torsionalConstant;
std::array<double, 3> firstAxis;
std::vector<std::array<double, 2>> sectionPoints;
SourceLocation location;
};
enum class ShellSourceElementType {
s4,
s4r
};
inline constexpr std::string_view kMitc4InternalFormulation{"FESA-MITC4"};
struct ShellSection {
std::string name;
double thickness;
EntityIndex materialIndex;
SourceLocation location;
};
struct Mitc4ShellDefinition {
SourceEntityId sourceId;
ShellSourceElementType sourceType;
std::array<EntityIndex, 4> nodeIndices;
EntityIndex materialIndex;
EntityIndex sectionIndex;
SourceLocation location;
};
struct ShellNodeInitialFrame {
EntityIndex nodeIndex;
std::array<double, 3> director;
std::array<double, 3> tangentA;
std::array<double, 3> tangentB;
};
struct EulerBeam3DDefinition {
SourceEntityId sourceId;
std::array<EntityIndex, 2> nodeIndices;
EntityIndex materialIndex;
EntityIndex sectionIndex;
SourceLocation location;
};
struct BoundaryCondition {
std::string target;
int firstDof;
int lastDof;
double value;
SourceLocation location;
};
struct NodalLoad {
std::string target;
int dof;
double magnitude;
SourceLocation location;
};
struct StaticStepDefinition {
std::string name;
std::vector<BoundaryCondition> boundaries;
std::vector<NodalLoad> loads;
double initialIncrement;
double timePeriod;
double minimumIncrement;
double maximumIncrement;
SourceLocation location;
};
struct NodeSet {
std::string name;
std::optional<std::string> instanceName;
std::vector<EntityIndex> nodeIndices;
SourceLocation location;
};
struct ElementSet {
std::string name;
std::optional<std::string> instanceName;
std::vector<EntityIndex> elementIndices;
SourceLocation location;
};
struct PartDefinition {
std::string name;
std::vector<std::int64_t> nodeSourceLabels;
std::vector<std::int64_t> elementSourceLabels;
std::vector<std::string> nodeSetNames;
std::vector<std::string> elementSetNames;
SourceLocation location;
};
struct SourceIndexMapping {
std::int64_t sourceLabel;
EntityIndex internalIndex;
};
struct InstanceDefinition {
std::string name;
std::string partName;
std::vector<SourceIndexMapping> nodeMappings;
std::vector<SourceIndexMapping> elementMappings;
SourceLocation location;
};
// This construction-boundary value owns every parsed semantic record before
// it is finalized into an immutable Domain.
struct ModelDefinition {
std::filesystem::path sourcePath;
std::string sourceContentIdentity;
std::string heading;
std::vector<Node> nodes;
std::vector<EulerBeam3DDefinition> elements;
std::vector<Mitc4ShellDefinition> shellElements;
std::vector<LinearElasticMaterial> materials;
std::vector<GeneralBeamSection> sections;
std::vector<ShellSection> shellSections;
std::vector<ShellNodeInitialFrame> shellNodeInitialFrames;
std::vector<NodeSet> nodeSets;
std::vector<ElementSet> elementSets;
std::vector<PartDefinition> parts;
std::vector<InstanceDefinition> instances;
std::vector<StaticStepDefinition> steps;
std::vector<Diagnostic> warnings;
};
} // namespace fesa
+53
View File
@@ -0,0 +1,53 @@
#ifndef FESA_MODEL_SHELL_GEOMETRY_H_
#define FESA_MODEL_SHELL_GEOMETRY_H_
#include <array>
#include <cstddef>
#include <vector>
#include "fesa/core/status.h"
#include "fesa/model/model_types.h"
namespace fesa {
/// @brief Classifies a mandatory shell-geometry validation location.
enum class ShellGeometryPointKind { kCenter, kStiffness, kTying, kRecovery };
/// @brief Identifies one deterministic shell-geometry validation point.
struct ShellGeometryValidationPoint {
ShellGeometryPointKind kind;
std::size_t location_index;
std::array<double, 3> natural_coordinates;
};
/// @brief Stores deterministic preprocessing data for one shell element.
struct ShellElementGeometryData {
EntityIndex element_index;
std::array<double, 3> normal_candidate;
double surface_area_weight;
};
/// @brief Owns preprocessed shell node frames and element geometry data.
struct ShellGeometry {
std::vector<ShellNodeInitialFrame> nodal_frames;
std::vector<ShellElementGeometryData> element_data;
};
/// @brief Returns the complete fixed validation-point inventory.
/// @note Ordering is center, stiffness, tying, then recovery identity.
const std::array<ShellGeometryValidationPoint, 17>&
ShellGeometryValidationPoints() noexcept;
/// @brief Builds deterministic nodal frames and validates shell geometry.
/// @param nodes Source nodes indexed by stable EntityIndex.
/// @param elements Shell definitions in stable source order.
/// @param sections Shell sections used for thickness validation.
/// @return Validated geometry or a structured model failure.
Result<ShellGeometry> PreprocessShellGeometry(
const std::vector<Node>& nodes,
const std::vector<Mitc4ShellDefinition>& elements,
const std::vector<ShellSection>& sections);
} // namespace fesa
#endif // FESA_MODEL_SHELL_GEOMETRY_H_
-44
View File
@@ -1,44 +0,0 @@
#pragma once
#include "fesa/core/status.h"
#include "fesa/model/model_types.hpp"
#include <array>
#include <cstddef>
#include <vector>
namespace fesa {
enum class ShellGeometryPointKind {
center,
stiffness,
tying,
recovery
};
struct ShellGeometryValidationPoint {
ShellGeometryPointKind kind;
std::size_t locationIndex;
std::array<double, 3> naturalCoordinates;
};
struct ShellElementGeometryData {
EntityIndex elementIndex;
std::array<double, 3> normalCandidate;
double surfaceAreaWeight;
};
struct ShellGeometry {
std::vector<ShellNodeInitialFrame> nodalFrames;
std::vector<ShellElementGeometryData> elementData;
};
const std::array<ShellGeometryValidationPoint, 17>&
shellGeometryValidationPoints() noexcept;
Result<ShellGeometry> preprocessShellGeometry(
const std::vector<Node>& nodes,
const std::vector<Mitc4ShellDefinition>& elements,
const std::vector<ShellSection>& sections);
} // namespace fesa
+1 -1
View File
@@ -1,6 +1,6 @@
#pragma once
#include "fesa/model/model_types.hpp"
#include "fesa/model/model_types.h"
#include <array>
#include <cstddef>
+1 -1
View File
@@ -3,7 +3,7 @@
#include "fesa/analysis/analysis_state.hpp"
#include "fesa/core/diagnostic.h"
#include "fesa/core/status.h"
#include "fesa/model/domain.hpp"
#include "fesa/model/domain.h"
#include <filesystem>
#include <vector>
+11 -11
View File
@@ -6,18 +6,18 @@
namespace fesa {
Result<AnalysisModel> AnalysisModel::create(const Domain& domain) {
if (domain.steps().empty()) {
if (domain.Steps().empty()) {
return Result<AnalysisModel>::Failure(Status::Failure(
FailureCategory::kInput,
{{Severity::kError,
"invalid-model-cardinality",
{domain.sourcePath(), 0U},
{domain.SourcePath(), 0U},
"STEP",
"0",
"AnalysisModel requires exactly one static step."}}));
}
if (domain.steps().size() > 1U) {
const auto& secondStep = domain.steps()[1];
if (domain.Steps().size() > 1U) {
const auto& secondStep = domain.Steps()[1];
return Result<AnalysisModel>::Failure(Status::Failure(
FailureCategory::kInput,
{{Severity::kError,
@@ -35,7 +35,7 @@ const Domain& AnalysisModel::domain() const noexcept {
}
const StaticStepDefinition& AnalysisModel::step() const noexcept {
return domain_->steps().front();
return domain_->Steps().front();
}
const std::vector<EntityIndex>& AnalysisModel::activeElements() const noexcept {
@@ -60,14 +60,14 @@ const std::vector<EntityIndex>& AnalysisModel::activeLoads() const noexcept {
}
AnalysisModel::AnalysisModel(const Domain& domain) : domain_{&domain} {
std::vector<bool> reachableMaterials(domain.materials().size(), false);
std::vector<bool> reachableSections(domain.sections().size(), false);
std::vector<bool> reachableMaterials(domain.Materials().size(), false);
std::vector<bool> reachableSections(domain.Sections().size(), false);
for (std::size_t index = 0U; index < domain.elements().size(); ++index) {
const auto& element = domain.elements()[index];
for (std::size_t index = 0U; index < domain.Elements().size(); ++index) {
const auto& element = domain.Elements()[index];
activeElements_.push_back(static_cast<EntityIndex>(index));
reachableMaterials[element.materialIndex] = true;
reachableSections[element.sectionIndex] = true;
reachableMaterials[element.material_index] = true;
reachableSections[element.section_index] = true;
}
// Ascending vector positions are the stable internal order, independent
+1 -1
View File
@@ -76,7 +76,7 @@ Status LinearStaticAnalysis::initialize(const AnalysisRequest& request) {
}
domain_ = std::make_unique<Domain>(std::move(domain.Value()));
diagnostics_ = domain_->warnings();
diagnostics_ = domain_->Warnings();
SortDiagnostics(diagnostics_);
return Status::Ok();
}
+29 -29
View File
@@ -146,7 +146,7 @@ Result<std::vector<EntityIndex>> resolveTarget(
const Domain& domain,
const NodalLoad& load) {
std::vector<const NodeSet*> matchingSets;
for (const auto& set : domain.nodeSets()) {
for (const auto& set : domain.NodeSets()) {
if (equalName(set.name, load.target)) {
matchingSets.push_back(&set);
}
@@ -155,8 +155,8 @@ Result<std::vector<EntityIndex>> resolveTarget(
std::vector<EntityIndex> matchingNodes;
std::int64_t label = 0;
if (tryPositiveInteger(load.target, label)) {
for (std::size_t index = 0U; index < domain.nodes().size(); ++index) {
if (domain.nodes()[index].sourceId.source_label == label) {
for (std::size_t index = 0U; index < domain.Nodes().size(); ++index) {
if (domain.Nodes()[index].source_id.source_label == label) {
matchingNodes.push_back(static_cast<EntityIndex>(index));
}
}
@@ -172,10 +172,10 @@ Result<std::vector<EntityIndex>> resolveTarget(
"The load target must resolve unambiguously to one node or one expanded node set."));
}
if (!matchingSets.empty()) {
const auto& nodes = matchingSets.front()->nodeIndices;
std::vector<unsigned char> seen(domain.nodes().size(), 0U);
const auto& nodes = matchingSets.front()->node_indices;
std::vector<unsigned char> seen(domain.Nodes().size(), 0U);
for (const EntityIndex node : nodes) {
if (node >= domain.nodes().size() || seen[node] != 0U) {
if (node >= domain.Nodes().size() || seen[node] != 0U) {
return Result<std::vector<EntityIndex>>::Failure(loadFailure(
"invalid-load-target",
load.location,
@@ -219,26 +219,26 @@ Status validateFiniteVector(
Status validateShellMoments(
const Domain& domain,
const Vector& fullLoad) {
if (domain.shellElements().empty()) {
if (domain.ShellElements().empty()) {
return Status::Ok();
}
std::vector<const ShellNodeInitialFrame*> frameByNode(
domain.nodes().size(), nullptr);
for (const auto& frame : domain.shellNodeInitialFrames()) {
if (frame.nodeIndex >= frameByNode.size() ||
frameByNode[frame.nodeIndex] != nullptr) {
domain.Nodes().size(), nullptr);
for (const auto& frame : domain.ShellNodeInitialFrames()) {
if (frame.node_index >= frameByNode.size() ||
frameByNode[frame.node_index] != nullptr) {
return loadFailure(
"invalid-shell-director",
{domain.sourcePath(), 0U},
{domain.SourcePath(), 0U},
"NODE",
std::to_string(frame.nodeIndex),
std::to_string(frame.node_index),
"Shell nodal directors must have unique in-range node identities.");
}
frameByNode[frame.nodeIndex] = &frame;
frameByNode[frame.node_index] = &frame;
}
for (std::size_t node = 0U; node < domain.nodes().size(); ++node) {
for (std::size_t node = 0U; node < domain.Nodes().size(); ++node) {
const double momentX = fullLoad[node * dofsPerNode + 3U];
const double momentY = fullLoad[node * dofsPerNode + 4U];
const double momentZ = fullLoad[node * dofsPerNode + 5U];
@@ -250,9 +250,9 @@ Status validateShellMoments(
if (frame == nullptr) {
return loadFailure(
"invalid-shell-director",
domain.nodes()[node].location,
domain.Nodes()[node].location,
"NODE",
domain.nodes()[node].sourceId.source_label_text,
domain.Nodes()[node].source_id.source_label_text,
"A loaded shell node must have an approved initial director.");
}
@@ -271,9 +271,9 @@ Status validateShellMoments(
if (!(projectionRatio <= shellMomentProjectionTolerance)) {
return loadFailure(
"unsupported-drilling-load",
domain.nodes()[node].location,
domain.Nodes()[node].location,
"CLOAD",
domain.nodes()[node].sourceId.source_label_text,
domain.Nodes()[node].source_id.source_label_text,
"The aggregate nodal moment has an unsupported director-parallel component.");
}
}
@@ -286,23 +286,23 @@ Result<Vector> LoadAssembler::assembleFullNodalLoad(
const AnalysisModel& model,
const DofManager& dofs) {
const Domain& domain = model.domain();
if (domain.nodes().size() >
if (domain.Nodes().size() >
(std::numeric_limits<std::size_t>::max)() / dofsPerNode) {
return Result<Vector>::Failure(loadFailure(
"invalid-load-dimensions",
{domain.sourcePath(), 0U},
{domain.SourcePath(), 0U},
"LOAD_ASSEMBLER",
domain.sourceContentIdentity(),
domain.SourceContentIdentity(),
"The semantic node count cannot be represented in full-DOF order."));
}
const std::size_t expectedFullCount =
domain.nodes().size() * dofsPerNode;
domain.Nodes().size() * dofsPerNode;
const Status dofStatus = validateDofOrder(
dofs, expectedFullCount, {domain.sourcePath(), 0U});
dofs, expectedFullCount, {domain.SourcePath(), 0U});
if (!dofStatus.IsOk()) {
return Result<Vector>::Failure(dofStatus);
}
for (std::size_t node = 0U; node < domain.nodes().size(); ++node) {
for (std::size_t node = 0U; node < domain.Nodes().size(); ++node) {
for (std::size_t component = 0U;
component < dofsPerNode;
++component) {
@@ -313,17 +313,17 @@ Result<Vector> LoadAssembler::assembleFullNodalLoad(
node * dofsPerNode + component) {
return Result<Vector>::Failure(loadFailure(
"invalid-load-order",
domain.nodes()[node].location,
domain.Nodes()[node].location,
"LOAD_ASSEMBLER",
domain.nodes()[node].sourceId.source_label_text,
domain.Nodes()[node].source_id.source_label_text,
"DofManager node/component identity must match full-DOF order."));
}
} catch (const std::out_of_range&) {
return Result<Vector>::Failure(loadFailure(
"invalid-load-dimensions",
domain.nodes()[node].location,
domain.Nodes()[node].location,
"LOAD_ASSEMBLER",
domain.nodes()[node].sourceId.source_label_text,
domain.Nodes()[node].source_id.source_label_text,
"DofManager must provide all six DOFs for every semantic node."));
}
}
+55 -55
View File
@@ -2,8 +2,8 @@
#include "fesa/analysis/analysis_model.hpp"
#include "fesa/assembly/parallel_for.hpp"
#include "fesa/elements/euler_beam_3d.hpp"
#include "fesa/elements/mitc4_shell.hpp"
#include "fesa/elements/euler_beam_3d.h"
#include "fesa/elements/mitc4_shell.h"
#include "fesa/fem/dof_manager.hpp"
#include <array>
@@ -52,46 +52,46 @@ Result<SparseMatrix> SparseAssembler::assembleStiffness(
const DofManager& dofs,
const ParallelFor& parallelFor) {
const Domain& domain = model.domain();
if (domain.nodes().size() >
if (domain.Nodes().size() >
(std::numeric_limits<std::size_t>::max)() / kDofsPerNode ||
dofs.fullDofCount() != domain.nodes().size() * kDofsPerNode) {
dofs.fullDofCount() != domain.Nodes().size() * kDofsPerNode) {
return assemblyFailure(
"invalid-assembly-dimensions",
{domain.sourcePath(), 0U},
{domain.SourcePath(), 0U},
std::to_string(dofs.fullDofCount()),
"DofManager dimensions do not match the active model nodes.");
}
if (!model.activeElements().empty() && !domain.shellElements().empty()) {
if (!model.activeElements().empty() && !domain.ShellElements().empty()) {
return assemblyFailure(
"unsupported-mixed-element-model",
{domain.sourcePath(), 0U},
{domain.SourcePath(), 0U},
"B33:FESA-MITC4",
"Sparse assembly does not support mixed beam and shell models.");
}
if (!domain.shellElements().empty()) {
if (domain.shellElements().size() >
if (!domain.ShellElements().empty()) {
if (domain.ShellElements().size() >
(std::numeric_limits<std::size_t>::max)() /
kShellContributionCount) {
return assemblyFailure(
"invalid-assembly-dimensions",
{domain.sourcePath(), 0U},
std::to_string(domain.shellElements().size()),
{domain.SourcePath(), 0U},
std::to_string(domain.ShellElements().size()),
"Shell contribution storage exceeds the addressable range.");
}
std::vector<std::optional<std::array<double, 3>>> directorsByNode(
domain.nodes().size());
for (const auto& frame : domain.shellNodeInitialFrames()) {
if (frame.nodeIndex >= directorsByNode.size() ||
directorsByNode[frame.nodeIndex]) {
domain.Nodes().size());
for (const auto& frame : domain.ShellNodeInitialFrames()) {
if (frame.node_index >= directorsByNode.size() ||
directorsByNode[frame.node_index]) {
return assemblyFailure(
"invalid-assembly-element",
{domain.sourcePath(), 0U},
std::to_string(frame.nodeIndex),
{domain.SourcePath(), 0U},
std::to_string(frame.node_index),
"Shell initial frames must map uniquely to model nodes.");
}
directorsByNode[frame.nodeIndex] = frame.director;
directorsByNode[frame.node_index] = frame.director;
}
struct ShellInput {
@@ -102,23 +102,23 @@ Result<SparseMatrix> SparseAssembler::assembleStiffness(
std::array<std::size_t, kShellElementDofCount> scatter;
};
std::vector<ShellInput> inputs;
inputs.reserve(domain.shellElements().size());
inputs.reserve(domain.ShellElements().size());
for (std::size_t elementOrder = 0U;
elementOrder < domain.shellElements().size();
elementOrder < domain.ShellElements().size();
++elementOrder) {
const auto& element = domain.shellElements()[elementOrder];
if (element.materialIndex >= domain.materials().size() ||
element.sectionIndex >= domain.shellSections().size()) {
const auto& element = domain.ShellElements()[elementOrder];
if (element.material_index >= domain.Materials().size() ||
element.section_index >= domain.ShellSections().size()) {
return assemblyFailure(
"invalid-assembly-element",
element.location,
element.sourceId.source_label_text,
element.source_id.source_label_text,
"Shell element references an entity outside the Domain.");
}
ShellInput input{};
input.section = &domain.shellSections()[element.sectionIndex];
input.material = &domain.materials()[element.materialIndex];
input.section = &domain.ShellSections()[element.section_index];
input.material = &domain.Materials()[element.material_index];
try {
input.scatter = dofs.shellElementScatter(
static_cast<EntityIndex>(elementOrder));
@@ -126,22 +126,22 @@ Result<SparseMatrix> SparseAssembler::assembleStiffness(
return assemblyFailure(
"invalid-assembly-scatter",
element.location,
element.sourceId.source_label_text,
element.source_id.source_label_text,
"DofManager does not contain the active shell scatter.");
}
for (std::size_t nodePosition = 0U;
nodePosition < element.nodeIndices.size();
nodePosition < element.node_indices.size();
++nodePosition) {
const EntityIndex nodeIndex = element.nodeIndices[nodePosition];
if (nodeIndex >= domain.nodes().size() ||
const EntityIndex nodeIndex = element.node_indices[nodePosition];
if (nodeIndex >= domain.Nodes().size() ||
!directorsByNode[nodeIndex]) {
return assemblyFailure(
"invalid-assembly-element",
element.location,
element.sourceId.source_label_text,
element.source_id.source_label_text,
"Shell element requires a valid node and initial director.");
}
input.nodes[nodePosition] = &domain.nodes()[nodeIndex];
input.nodes[nodePosition] = &domain.Nodes()[nodeIndex];
input.directors[nodePosition] = *directorsByNode[nodeIndex];
for (std::size_t component = 0U;
component < kDofsPerNode;
@@ -156,7 +156,7 @@ Result<SparseMatrix> SparseAssembler::assembleStiffness(
return assemblyFailure(
"invalid-assembly-scatter",
element.location,
element.sourceId.source_label_text,
element.source_id.source_label_text,
"Shell scatter does not match the active model topology.");
}
}
@@ -170,7 +170,7 @@ Result<SparseMatrix> SparseAssembler::assembleStiffness(
inputs.size(),
[&](const std::size_t elementOrder) {
const auto& input = inputs[elementOrder];
const auto shell = Mitc4Shell::create(
const auto shell = Mitc4Shell::Create(
input.nodes,
input.directors,
*input.section,
@@ -179,7 +179,7 @@ Result<SparseMatrix> SparseAssembler::assembleStiffness(
localFailures[elementOrder] = shell.GetStatus();
return;
}
const auto stiffness = shell.Value().stiffness();
const auto stiffness = shell.Value().Stiffness();
if (!stiffness.HasValue()) {
localFailures[elementOrder] = stiffness.GetStatus();
return;
@@ -197,7 +197,7 @@ Result<SparseMatrix> SparseAssembler::assembleStiffness(
buffer[localOrder] = {
input.scatter[localRow],
input.scatter[localColumn],
stiffness.Value().stabilizedGlobal24(
stiffness.Value().stabilized_global24(
localRow, localColumn),
elementOrder,
localOrder};
@@ -235,7 +235,7 @@ Result<SparseMatrix> SparseAssembler::assembleStiffness(
kBeamContributionCount) {
return assemblyFailure(
"invalid-assembly-dimensions",
{domain.sourcePath(), 0U},
{domain.SourcePath(), 0U},
std::to_string(model.activeElements().size()),
"Element contribution storage exceeds the addressable range.");
}
@@ -243,22 +243,22 @@ Result<SparseMatrix> SparseAssembler::assembleStiffness(
std::vector<std::array<std::size_t, kBeamElementDofCount>> scatters;
scatters.reserve(model.activeElements().size());
for (const EntityIndex elementIndex : model.activeElements()) {
if (elementIndex >= domain.elements().size()) {
if (elementIndex >= domain.Elements().size()) {
return assemblyFailure(
"invalid-assembly-element",
{domain.sourcePath(), 0U},
{domain.SourcePath(), 0U},
std::to_string(elementIndex),
"Active element index is outside the Domain.");
}
const auto& element = domain.elements()[elementIndex];
if (element.nodeIndices[0U] >= domain.nodes().size() ||
element.nodeIndices[1U] >= domain.nodes().size() ||
element.materialIndex >= domain.materials().size() ||
element.sectionIndex >= domain.sections().size()) {
const auto& element = domain.Elements()[elementIndex];
if (element.node_indices[0U] >= domain.Nodes().size() ||
element.node_indices[1U] >= domain.Nodes().size() ||
element.material_index >= domain.Materials().size() ||
element.section_index >= domain.Sections().size()) {
return assemblyFailure(
"invalid-assembly-element",
element.location,
element.sourceId.source_label_text,
element.source_id.source_label_text,
"Element references an entity outside the Domain.");
}
@@ -269,7 +269,7 @@ Result<SparseMatrix> SparseAssembler::assembleStiffness(
return assemblyFailure(
"invalid-assembly-scatter",
element.location,
element.sourceId.source_label_text,
element.source_id.source_label_text,
"DofManager does not contain the active element scatter.");
}
for (std::size_t endpoint = 0U; endpoint < 2U; ++endpoint) {
@@ -278,7 +278,7 @@ Result<SparseMatrix> SparseAssembler::assembleStiffness(
++component) {
const std::size_t local = endpoint * kDofsPerNode + component;
const std::size_t expected =
static_cast<std::size_t>(element.nodeIndices[endpoint]) *
static_cast<std::size_t>(element.node_indices[endpoint]) *
kDofsPerNode +
component;
if (scatter[local] != expected ||
@@ -286,7 +286,7 @@ Result<SparseMatrix> SparseAssembler::assembleStiffness(
return assemblyFailure(
"invalid-assembly-scatter",
element.location,
element.sourceId.source_label_text,
element.source_id.source_label_text,
"Element scatter does not match the active model topology.");
}
}
@@ -301,18 +301,18 @@ Result<SparseMatrix> SparseAssembler::assembleStiffness(
model.activeElements().size(),
[&](const std::size_t elementOrder) {
const EntityIndex elementIndex = model.activeElements()[elementOrder];
const auto& definition = domain.elements()[elementIndex];
const auto beam = EulerBeam3D::create(
domain.nodes()[definition.nodeIndices[0U]],
domain.nodes()[definition.nodeIndices[1U]],
domain.sections()[definition.sectionIndex],
domain.materials()[definition.materialIndex]);
const auto& definition = domain.Elements()[elementIndex];
const auto beam = EulerBeam3D::Create(
domain.Nodes()[definition.node_indices[0U]],
domain.Nodes()[definition.node_indices[1U]],
domain.Sections()[definition.section_index],
domain.Materials()[definition.material_index]);
if (!beam.HasValue()) {
localFailures[elementOrder] = beam.GetStatus();
return;
}
const Matrix stiffness = beam.Value().globalStiffness();
const Matrix stiffness = beam.Value().GlobalStiffness();
auto& buffer = localBuffers[elementOrder];
const auto& scatter = scatters[elementOrder];
for (std::size_t localRow = 0U;
+390 -425
View File
@@ -1,4 +1,4 @@
#include "fesa/elements/euler_beam_3d.hpp"
#include "fesa/elements/euler_beam_3d.h"
#include <algorithm>
#include <array>
@@ -19,497 +19,462 @@ constexpr double kStiffnessInvariantTolerance = 1.0e-12;
using Vector3 = std::array<double, 3>;
double norm(const Vector3& value) {
return std::hypot(value[0], value[1], value[2]);
double Norm(const Vector3& value) {
return std::hypot(value[0], value[1], value[2]);
}
double dot(const Vector3& lhs, const Vector3& rhs) {
return lhs[0] * rhs[0] + lhs[1] * rhs[1] + lhs[2] * rhs[2];
double Dot(const Vector3& lhs, const Vector3& rhs) {
return lhs[0] * rhs[0] + lhs[1] * rhs[1] + lhs[2] * rhs[2];
}
Vector3 cross(const Vector3& lhs, const Vector3& rhs) {
return {
lhs[1] * rhs[2] - lhs[2] * rhs[1],
lhs[2] * rhs[0] - lhs[0] * rhs[2],
lhs[0] * rhs[1] - lhs[1] * rhs[0]};
Vector3 Cross(const Vector3& lhs, const Vector3& rhs) {
return {lhs[1] * rhs[2] - lhs[2] * rhs[1], lhs[2] * rhs[0] - lhs[0] * rhs[2],
lhs[0] * rhs[1] - lhs[1] * rhs[0]};
}
bool isFinite(const Vector3& value) {
return std::isfinite(value[0]) && std::isfinite(value[1]) &&
std::isfinite(value[2]);
bool IsFinite(const Vector3& value) {
return std::isfinite(value[0]) && std::isfinite(value[1]) &&
std::isfinite(value[2]);
}
std::string elementIdentity(const Node& firstNode, const Node& secondNode) {
return firstNode.sourceId.instance_name + ":" +
firstNode.sourceId.source_label_text + "-" +
secondNode.sourceId.source_label_text;
std::string ElementIdentity(const Node& first_node, const Node& second_node) {
return first_node.source_id.instance_name + ":" +
first_node.source_id.source_label_text + "-" +
second_node.source_id.source_label_text;
}
Result<EulerBeam3D> modelFailure(const std::string& code,
Result<EulerBeam3D> ModelFailure(const std::string& code,
const SourceLocation& location,
const std::string& identity,
const std::string& message) {
return Result<EulerBeam3D>::Failure(Status::Failure(
FailureCategory::kModel,
{{Severity::kError, code, location, "*ELEMENT", identity, message}}));
return Result<EulerBeam3D>::Failure(Status::Failure(
FailureCategory::kModel,
{{Severity::kError, code, location, "*ELEMENT", identity, message}}));
}
Matrix transformation(const std::array<double, 9>& rotation) {
Matrix result{kElementDofCount, kElementDofCount};
// Blocks preserve [translation, rotation] at node 1 then node 2.
for (std::size_t block = 0; block < 4U; ++block) {
for (std::size_t row = 0; row < 3U; ++row) {
for (std::size_t column = 0; column < 3U; ++column) {
result(block * 3U + row, block * 3U + column) =
rotation[row * 3U + column];
}
}
/// @brief Repeats the local-axis rotation in stable nodal translation and
/// rotation blocks.
Matrix Transformation(const std::array<double, 9>& rotation) {
Matrix result{kElementDofCount, kElementDofCount};
// Blocks preserve [translation, rotation] at node 1 then node 2.
for (std::size_t block = 0; block < 4U; ++block) {
for (std::size_t row = 0; row < 3U; ++row) {
for (std::size_t column = 0; column < 3U; ++column) {
result(block * 3U + row, block * 3U + column) =
rotation[row * 3U + column];
}
}
return result;
}
return result;
}
Matrix strainDisplacement(double xi, double length) {
Matrix b{kGeneralizedComponentCount, kElementDofCount};
const double r = 0.5 * (1.0 + xi);
const double inverseLength = 1.0 / length;
const double inverseLengthSquared = inverseLength * inverseLength;
/// @brief Builds the reviewed axial, twist, and signed curvature operator.
/// @warning The theta-y and theta-z signs are formulation contracts.
Matrix StrainDisplacement(double xi, double length) {
Matrix b{kGeneralizedComponentCount, kElementDofCount};
const double r = 0.5 * (1.0 + xi);
const double inverse_length = 1.0 / length;
const double inverse_length_squared = inverse_length * inverse_length;
b(0U, 0U) = -inverseLength;
b(0U, 6U) = inverseLength;
b(1U, 3U) = -inverseLength;
b(1U, 9U) = inverseLength;
b(0U, 0U) = -inverse_length;
b(0U, 6U) = inverse_length;
b(1U, 3U) = -inverse_length;
b(1U, 9U) = inverse_length;
// theta_y=-w' makes kappa_y=-w''; theta_z=v' makes kappa_z=v''.
b(2U, 2U) = (6.0 - 12.0 * r) * inverseLengthSquared;
b(2U, 4U) = (-4.0 + 6.0 * r) * inverseLength;
b(2U, 8U) = (-6.0 + 12.0 * r) * inverseLengthSquared;
b(2U, 10U) = (-2.0 + 6.0 * r) * inverseLength;
// theta_y=-w' makes kappa_y=-w''; theta_z=v' makes kappa_z=v''.
b(2U, 2U) = (6.0 - 12.0 * r) * inverse_length_squared;
b(2U, 4U) = (-4.0 + 6.0 * r) * inverse_length;
b(2U, 8U) = (-6.0 + 12.0 * r) * inverse_length_squared;
b(2U, 10U) = (-2.0 + 6.0 * r) * inverse_length;
b(3U, 1U) = (-6.0 + 12.0 * r) * inverseLengthSquared;
b(3U, 5U) = (-4.0 + 6.0 * r) * inverseLength;
b(3U, 7U) = (6.0 - 12.0 * r) * inverseLengthSquared;
b(3U, 11U) = (-2.0 + 6.0 * r) * inverseLength;
return b;
b(3U, 1U) = (-6.0 + 12.0 * r) * inverse_length_squared;
b(3U, 5U) = (-4.0 + 6.0 * r) * inverse_length;
b(3U, 7U) = (6.0 - 12.0 * r) * inverse_length_squared;
b(3U, 11U) = (-2.0 + 6.0 * r) * inverse_length;
return b;
}
std::array<double, kGeneralizedComponentCount> constitutiveDiagonal(
double youngsModulus,
double shearModulus,
double area,
double iy,
double iz,
double torsionalConstant) {
return {
youngsModulus * area,
shearModulus * torsionalConstant,
youngsModulus * iy,
youngsModulus * iz};
std::array<double, kGeneralizedComponentCount> ConstitutiveDiagonal(
double youngs_modulus, double shear_modulus, double area, double iy,
double iz, double torsional_constant) {
return {youngs_modulus * area, shear_modulus * torsional_constant,
youngs_modulus * iy, youngs_modulus * iz};
}
Matrix closedStiffness(double length,
const std::array<double, kGeneralizedComponentCount>& diagonal) {
Matrix closed{kElementDofCount, kElementDofCount};
const auto addBlock = [&closed](const std::array<std::size_t, 2>& indices,
double coefficient) {
closed(indices[0], indices[0]) = coefficient;
closed(indices[0], indices[1]) = -coefficient;
closed(indices[1], indices[0]) = -coefficient;
closed(indices[1], indices[1]) = coefficient;
};
addBlock({0U, 6U}, diagonal[0U] / length);
addBlock({3U, 9U}, diagonal[1U] / length);
/// @brief Builds the independent closed-form stiffness used as a Gauss-rule
/// invariant.
Matrix ClosedStiffness(
double length,
const std::array<double, kGeneralizedComponentCount>& diagonal) {
Matrix closed{kElementDofCount, kElementDofCount};
const auto add_block = [&closed](const std::array<std::size_t, 2>& indices,
double coefficient) {
closed(indices[0], indices[0]) = coefficient;
closed(indices[0], indices[1]) = -coefficient;
closed(indices[1], indices[0]) = -coefficient;
closed(indices[1], indices[1]) = coefficient;
};
add_block({0U, 6U}, diagonal[0U] / length);
add_block({3U, 9U}, diagonal[1U] / length);
const auto addBendingBlock = [&closed, length](
const auto add_bending_block = [&closed, length](
const std::array<std::size_t, 4>& indices,
double flexuralRigidity,
double rotationSign) {
const double value = 12.0 * flexuralRigidity /
(length * length * length);
const double coupling = rotationSign * 6.0 * flexuralRigidity /
(length * length);
const double diagonalRotation = 4.0 * flexuralRigidity / length;
const double offDiagonalRotation = 2.0 * flexuralRigidity / length;
const std::array<double, 16> block = {
value, coupling, -value, coupling,
coupling, diagonalRotation, -coupling, offDiagonalRotation,
-value, -coupling, value, -coupling,
coupling, offDiagonalRotation, -coupling, diagonalRotation};
for (std::size_t row = 0; row < indices.size(); ++row) {
for (std::size_t column = 0; column < indices.size(); ++column) {
closed(indices[row], indices[column]) = block[row * indices.size() + column];
}
}
};
addBendingBlock({1U, 5U, 7U, 11U}, diagonal[3U], 1.0);
addBendingBlock({2U, 4U, 8U, 10U}, diagonal[2U], -1.0);
return closed;
double flexural_rigidity,
double rotation_sign) {
const double value = 12.0 * flexural_rigidity / (length * length * length);
const double coupling =
rotation_sign * 6.0 * flexural_rigidity / (length * length);
const double diagonal_rotation = 4.0 * flexural_rigidity / length;
const double off_diagonal_rotation = 2.0 * flexural_rigidity / length;
const std::array<double, 16> block = {value, coupling,
-value, coupling,
coupling, diagonal_rotation,
-coupling, off_diagonal_rotation,
-value, -coupling,
value, -coupling,
coupling, off_diagonal_rotation,
-coupling, diagonal_rotation};
for (std::size_t row = 0; row < indices.size(); ++row) {
for (std::size_t column = 0; column < indices.size(); ++column) {
closed(indices[row], indices[column]) =
block[row * indices.size() + column];
}
}
};
add_bending_block({1U, 5U, 7U, 11U}, diagonal[3U], 1.0);
add_bending_block({2U, 4U, 8U, 10U}, diagonal[2U], -1.0);
return closed;
}
double normalizedMatrixError(const Matrix& lhs, const Matrix& rhs) {
double maximumDifference = 0.0;
double scale = 1.0;
for (std::size_t row = 0; row < lhs.Rows(); ++row) {
for (std::size_t column = 0; column < lhs.Columns(); ++column) {
const double lhsValue = lhs(row, column);
const double rhsValue = rhs(row, column);
if (!std::isfinite(lhsValue) || !std::isfinite(rhsValue)) {
return std::numeric_limits<double>::infinity();
}
const double difference = std::abs(lhsValue - rhsValue);
if (!std::isfinite(difference)) {
return std::numeric_limits<double>::infinity();
}
maximumDifference = (std::max)(
maximumDifference,
difference);
scale = (std::max)(scale, std::abs(lhsValue));
scale = (std::max)(scale, std::abs(rhsValue));
}
}
if (!std::isfinite(maximumDifference) || !std::isfinite(scale) ||
!(scale > 0.0)) {
double NormalizedMatrixError(const Matrix& lhs, const Matrix& rhs) {
double maximum_difference = 0.0;
double scale_value = 1.0;
for (std::size_t row = 0; row < lhs.Rows(); ++row) {
for (std::size_t column = 0; column < lhs.Columns(); ++column) {
const double lhs_value = lhs(row, column);
const double rhs_value = rhs(row, column);
if (!std::isfinite(lhs_value) || !std::isfinite(rhs_value)) {
return std::numeric_limits<double>::infinity();
}
const double difference = std::abs(lhs_value - rhs_value);
if (!std::isfinite(difference)) {
return std::numeric_limits<double>::infinity();
}
maximum_difference = (std::max)(maximum_difference, difference);
scale_value = (std::max)(scale_value, std::abs(lhs_value));
scale_value = (std::max)(scale_value, std::abs(rhs_value));
}
const double normalizedError = maximumDifference / scale;
return std::isfinite(normalizedError)
? normalizedError
: std::numeric_limits<double>::infinity();
}
if (!std::isfinite(maximum_difference) || !std::isfinite(scale_value) ||
!(scale_value > 0.0)) {
return std::numeric_limits<double>::infinity();
}
const double normalized_error = maximum_difference / scale_value;
return std::isfinite(normalized_error)
? normalized_error
: std::numeric_limits<double>::infinity();
}
std::array<double, kGeneralizedComponentCount> generalizedStrain(
const Matrix& b,
const Vector& localDisplacement) {
std::array<double, kGeneralizedComponentCount> strain{};
for (std::size_t component = 0; component < strain.size(); ++component) {
for (std::size_t dof = 0; dof < localDisplacement.Size(); ++dof) {
strain[component] += b(component, dof) * localDisplacement[dof];
}
/// @brief Recovers generalized strain in fixed component and DOF order.
std::array<double, kGeneralizedComponentCount> GeneralizedStrain(
const Matrix& b, const Vector& local_displacement) {
std::array<double, kGeneralizedComponentCount> strain{};
for (std::size_t component = 0; component < strain.size(); ++component) {
for (std::size_t dof = 0; dof < local_displacement.Size(); ++dof) {
strain[component] += b(component, dof) * local_displacement[dof];
}
return strain;
}
return strain;
}
std::array<double, kGeneralizedComponentCount> generalizedResultant(
/// @brief Applies the diagonal section law without changing component order.
std::array<double, kGeneralizedComponentCount> GeneralizedResultant(
const std::array<double, kGeneralizedComponentCount>& strain,
const std::array<double, kGeneralizedComponentCount>& diagonal) {
std::array<double, kGeneralizedComponentCount> resultant{};
for (std::size_t component = 0; component < resultant.size(); ++component) {
resultant[component] = diagonal[component] * strain[component];
}
return resultant;
std::array<double, kGeneralizedComponentCount> resultant{};
for (std::size_t component = 0; component < resultant.size(); ++component) {
resultant[component] = diagonal[component] * strain[component];
}
return resultant;
}
Matrix kinematicInterpolation(double xi, double length) {
Matrix interpolation{4U, kElementDofCount};
const double r = 0.5 * (1.0 + xi);
const double rSquared = r * r;
const double rCubed = rSquared * r;
const double n1 = 1.0 - r;
const double n2 = r;
const double h1 = 1.0 - 3.0 * rSquared + 2.0 * rCubed;
const double h2 = length * (r - 2.0 * rSquared + rCubed);
const double h3 = 3.0 * rSquared - 2.0 * rCubed;
const double h4 = length * (-rSquared + rCubed);
/// @brief Builds signed Hermite interpolation for endpoint recovery.
Matrix KinematicInterpolation(double xi, double length) {
Matrix interpolation{4U, kElementDofCount};
const double r = 0.5 * (1.0 + xi);
const double r_squared = r * r;
const double r_cubed = r_squared * r;
const double n1 = 1.0 - r;
const double n2 = r;
const double h1 = 1.0 - 3.0 * r_squared + 2.0 * r_cubed;
const double h2 = length * (r - 2.0 * r_squared + r_cubed);
const double h3 = 3.0 * r_squared - 2.0 * r_cubed;
const double h4 = length * (-r_squared + r_cubed);
interpolation(0U, 0U) = n1;
interpolation(0U, 6U) = n2;
interpolation(1U, 1U) = h1;
interpolation(1U, 5U) = h2;
interpolation(1U, 7U) = h3;
interpolation(1U, 11U) = h4;
interpolation(2U, 2U) = h1;
interpolation(2U, 4U) = -h2;
interpolation(2U, 8U) = h3;
interpolation(2U, 10U) = -h4;
interpolation(3U, 3U) = n1;
interpolation(3U, 9U) = n2;
return interpolation;
interpolation(0U, 0U) = n1;
interpolation(0U, 6U) = n2;
interpolation(1U, 1U) = h1;
interpolation(1U, 5U) = h2;
interpolation(1U, 7U) = h3;
interpolation(1U, 11U) = h4;
interpolation(2U, 2U) = h1;
interpolation(2U, 4U) = -h2;
interpolation(2U, 8U) = h3;
interpolation(2U, 10U) = -h4;
interpolation(3U, 3U) = n1;
interpolation(3U, 9U) = n2;
return interpolation;
}
} // namespace
} // namespace
Result<EulerBeam3D> EulerBeam3D::create(
const Node& firstNode,
const Node& secondNode,
const GeneralBeamSection& section,
const LinearElasticMaterial& material) {
const std::string identity = elementIdentity(firstNode, secondNode);
const Vector3& first = firstNode.coordinates;
const Vector3& second = secondNode.coordinates;
const Vector3 delta = {
second[0] - first[0], second[1] - first[1], second[2] - first[2]};
const double length = norm(delta);
const double coordinateScale =
(std::max)({1.0, norm(first), norm(second)});
if (!isFinite(first) || !isFinite(second) || !isFinite(delta) ||
!std::isfinite(length) || !std::isfinite(coordinateScale) ||
!(length > kGeometryTolerance * coordinateScale)) {
return modelFailure(
"invalid-beam-length",
firstNode.location,
identity,
"Beam length must exceed the scale-aware geometry threshold.");
}
Result<EulerBeam3D> EulerBeam3D::Create(const Node& first_node,
const Node& second_node,
const GeneralBeamSection& section,
const LinearElasticMaterial& material) {
const std::string identity = ElementIdentity(first_node, second_node);
const Vector3& first = first_node.coordinates;
const Vector3& second = second_node.coordinates;
const Vector3 delta = {second[0] - first[0], second[1] - first[1],
second[2] - first[2]};
const double length = Norm(delta);
const double coordinate_scale = (std::max)({1.0, Norm(first), Norm(second)});
if (!IsFinite(first) || !IsFinite(second) || !IsFinite(delta) ||
!std::isfinite(length) || !std::isfinite(coordinate_scale) ||
!(length > kGeometryTolerance * coordinate_scale)) {
return ModelFailure(
"invalid-beam-length", first_node.location, identity,
"Beam length must exceed the scale-aware geometry threshold.");
}
const Vector3 ex = {delta[0] / length, delta[1] / length, delta[2] / length};
const Vector3& guide = section.firstAxis;
const double guideNorm = norm(guide);
const double guideProjection = dot(guide, ex);
const Vector3 eyTrial = {
guide[0] - guideProjection * ex[0],
guide[1] - guideProjection * ex[1],
guide[2] - guideProjection * ex[2]};
const double eyTrialNorm = norm(eyTrial);
if (!isFinite(guide) || !std::isfinite(guideNorm) || !isFinite(eyTrial) ||
!std::isfinite(eyTrialNorm) ||
!(eyTrialNorm > kGeometryTolerance * (std::max)(1.0, guideNorm))) {
return modelFailure(
"invalid-beam-guide-vector",
section.location,
identity,
"Beam guide vector must define a scale-aware transverse direction.");
}
const Vector3 ex = {delta[0] / length, delta[1] / length, delta[2] / length};
const Vector3& guide = section.first_axis;
const double guide_norm = Norm(guide);
const double guide_projection = Dot(guide, ex);
const Vector3 ey_trial = {guide[0] - guide_projection * ex[0],
guide[1] - guide_projection * ex[1],
guide[2] - guide_projection * ex[2]};
const double ey_trial_norm = Norm(ey_trial);
if (!IsFinite(guide) || !std::isfinite(guide_norm) || !IsFinite(ey_trial) ||
!std::isfinite(ey_trial_norm) ||
!(ey_trial_norm > kGeometryTolerance * (std::max)(1.0, guide_norm))) {
return ModelFailure(
"invalid-beam-guide-vector", section.location, identity,
"Beam guide vector must define a scale-aware transverse direction.");
}
if (!std::isfinite(section.i12)) {
return modelFailure(
"invalid-beam-property",
section.location,
identity,
"Beam section properties must be finite and positive.");
}
if (section.i12 != 0.0) {
return modelFailure(
"unsupported-coupled-section",
section.location,
identity,
"The Euler beam kernel requires exact I12=0.");
}
if (!std::isfinite(section.i12)) {
return ModelFailure("invalid-beam-property", section.location, identity,
"Beam section properties must be finite and positive.");
}
if (section.i12 != 0.0) {
return ModelFailure("unsupported-coupled-section", section.location,
identity,
"The Euler beam kernel requires exact I12=0.");
}
const double shearModulus =
material.youngsModulus / (2.0 * (1.0 + material.poissonRatio));
const std::array<double, 6> positiveProperties = {
material.youngsModulus,
shearModulus,
section.area,
section.i11,
section.i22,
section.torsionalConstant};
if (!std::isfinite(material.poissonRatio) ||
std::any_of(
positiveProperties.begin(),
positiveProperties.end(),
[](double property) { return !std::isfinite(property) || !(property > 0.0); })) {
return modelFailure(
"invalid-beam-property",
section.location,
identity,
"E, G, A, Iy, Iz, and J must be finite and positive.");
}
const double shear_modulus =
material.youngs_modulus / (2.0 * (1.0 + material.poisson_ratio));
const std::array<double, 6> positive_properties = {
material.youngs_modulus,
shear_modulus,
section.area,
section.i11,
section.i22,
section.torsional_constant};
if (!std::isfinite(material.poisson_ratio) ||
std::any_of(positive_properties.begin(), positive_properties.end(),
[](double property) {
return !std::isfinite(property) || !(property > 0.0);
})) {
return ModelFailure("invalid-beam-property", section.location, identity,
"E, G, A, Iy, Iz, and J must be finite and positive.");
}
const auto derivedRigidity = constitutiveDiagonal(
material.youngsModulus,
shearModulus,
section.area,
section.i11,
section.i22,
section.torsionalConstant);
const double lengthSquared = length * length;
const double lengthCubed = lengthSquared * length;
// These are every distinct positive magnitude used by the exact axial,
// torsion, and two bending closed-form blocks. Reject arithmetic
// overflow/underflow without introducing a conditioning threshold.
const std::array<double, 14> requiredStiffnessMagnitudes = {
derivedRigidity[0U],
derivedRigidity[1U],
derivedRigidity[2U],
derivedRigidity[3U],
derivedRigidity[0U] / length,
derivedRigidity[1U] / length,
12.0 * derivedRigidity[2U] / lengthCubed,
6.0 * derivedRigidity[2U] / lengthSquared,
4.0 * derivedRigidity[2U] / length,
2.0 * derivedRigidity[2U] / length,
12.0 * derivedRigidity[3U] / lengthCubed,
6.0 * derivedRigidity[3U] / lengthSquared,
4.0 * derivedRigidity[3U] / length,
2.0 * derivedRigidity[3U] / length};
if (std::any_of(
requiredStiffnessMagnitudes.begin(),
requiredStiffnessMagnitudes.end(),
[](double magnitude) {
return !std::isfinite(magnitude) || !(magnitude > 0.0);
})) {
return modelFailure(
"invalid-beam-property",
section.location,
identity,
"Derived beam stiffness coefficients must be finite and positive.");
}
const auto derived_rigidity = ConstitutiveDiagonal(
material.youngs_modulus, shear_modulus, section.area, section.i11,
section.i22, section.torsional_constant);
const double length_squared = length * length;
const double length_cubed = length_squared * length;
// These are every distinct positive magnitude used by the exact axial,
// torsion, and two bending closed-form blocks. Reject arithmetic
// overflow/underflow without introducing a conditioning threshold.
const std::array<double, 14> required_stiffness_magnitudes = {
derived_rigidity[0U],
derived_rigidity[1U],
derived_rigidity[2U],
derived_rigidity[3U],
derived_rigidity[0U] / length,
derived_rigidity[1U] / length,
12.0 * derived_rigidity[2U] / length_cubed,
6.0 * derived_rigidity[2U] / length_squared,
4.0 * derived_rigidity[2U] / length,
2.0 * derived_rigidity[2U] / length,
12.0 * derived_rigidity[3U] / length_cubed,
6.0 * derived_rigidity[3U] / length_squared,
4.0 * derived_rigidity[3U] / length,
2.0 * derived_rigidity[3U] / length};
if (std::any_of(required_stiffness_magnitudes.begin(),
required_stiffness_magnitudes.end(), [](double magnitude) {
return !std::isfinite(magnitude) || !(magnitude > 0.0);
})) {
return ModelFailure(
"invalid-beam-property", section.location, identity,
"Derived beam stiffness coefficients must be finite and positive.");
}
const Vector3 ey = {
eyTrial[0] / eyTrialNorm,
eyTrial[1] / eyTrialNorm,
eyTrial[2] / eyTrialNorm};
const Vector3 ez = cross(ex, ey);
// Rows map global vectors to the approved right-handed local (ex,ey,ez) basis.
const std::array<double, 9> rotation = {
ex[0], ex[1], ex[2],
ey[0], ey[1], ey[2],
ez[0], ez[1], ez[2]};
const Vector3 ey = {ey_trial[0] / ey_trial_norm, ey_trial[1] / ey_trial_norm,
ey_trial[2] / ey_trial_norm};
const Vector3 ez = Cross(ex, ey);
// Rows map global vectors to the approved right-handed local (ex,ey,ez)
// basis.
const std::array<double, 9> rotation = {ex[0], ex[1], ex[2], ey[0], ey[1],
ey[2], ez[0], ez[1], ez[2]};
return Result<EulerBeam3D>::Success(EulerBeam3D{
length,
material.youngsModulus,
shearModulus,
section.area,
section.i11,
section.i22,
section.torsionalConstant,
rotation,
section.sectionPoints});
return Result<EulerBeam3D>::Success(
EulerBeam3D{length, material.youngs_modulus, shear_modulus, section.area,
section.i11, section.i22, section.torsional_constant,
rotation, section.section_points});
}
Matrix EulerBeam3D::localStiffness() const {
const auto diagonal = constitutiveDiagonal(
youngsModulus_, shearModulus_, area_, iy_, iz_, torsionalConstant_);
Matrix stiffness{kElementDofCount, kElementDofCount};
const double inverseSqrtThree = 1.0 / std::sqrt(3.0);
const std::array<double, 2> gaussPoints = {-inverseSqrtThree, inverseSqrtThree};
const double jacobian = 0.5 * length_;
Matrix EulerBeam3D::LocalStiffness() const {
const auto diagonal = ConstitutiveDiagonal(
youngs_modulus_, shear_modulus_, area_, iy_, iz_, torsional_constant_);
Matrix stiffness{kElementDofCount, kElementDofCount};
const double inverse_sqrt_three = 1.0 / std::sqrt(3.0);
const std::array<double, 2> gauss_points = {-inverse_sqrt_three,
inverse_sqrt_three};
const double jacobian = 0.5 * length_;
// Both Gauss points are required: a one-point bending rule loses two ranks.
for (const double xi : gaussPoints) {
const Matrix b = strainDisplacement(xi, length_);
for (std::size_t row = 0; row < kElementDofCount; ++row) {
for (std::size_t column = 0; column < kElementDofCount; ++column) {
for (std::size_t component = 0; component < diagonal.size(); ++component) {
stiffness(row, column) +=
b(component, row) * diagonal[component] *
b(component, column) * jacobian;
}
}
}
}
const Matrix closed = closedStiffness(length_, diagonal);
if (normalizedMatrixError(stiffness, closed) > kStiffnessInvariantTolerance) {
throw std::logic_error{
"Two-point Euler beam stiffness violated the closed-form invariant."};
}
return stiffness;
}
Matrix EulerBeam3D::globalStiffness() const {
const Matrix local = localStiffness();
const Matrix transform = transformation(rotation_);
const Matrix localTimesTransform = local.Multiply(transform);
Matrix global{kElementDofCount, kElementDofCount};
// Kg=T^T*Kl*T while dl=T*dg.
// Both Gauss points are required: a one-point bending rule loses two ranks.
for (const double xi : gauss_points) {
const Matrix b = StrainDisplacement(xi, length_);
for (std::size_t row = 0; row < kElementDofCount; ++row) {
for (std::size_t column = 0; column < kElementDofCount; ++column) {
for (std::size_t inner = 0; inner < kElementDofCount; ++inner) {
global(row, column) +=
transform(inner, row) * localTimesTransform(inner, column);
}
for (std::size_t column = 0; column < kElementDofCount; ++column) {
for (std::size_t component = 0; component < diagonal.size();
++component) {
stiffness(row, column) += b(component, row) * diagonal[component] *
b(component, column) * jacobian;
}
}
}
return global;
}
const Matrix closed = ClosedStiffness(length_, diagonal);
if (NormalizedMatrixError(stiffness, closed) > kStiffnessInvariantTolerance) {
throw std::logic_error{
"Two-point Euler beam stiffness violated the closed-form invariant."};
}
return stiffness;
}
Vector EulerBeam3D::localEquivalentLoad(const ConstantLocalLineLoad& load) const {
const std::array<double, 4> components = {load.px, load.py, load.pz, load.mx};
Vector equivalent{kElementDofCount};
const double inverseSqrtThree = 1.0 / std::sqrt(3.0);
const std::array<double, 2> gaussPoints = {-inverseSqrtThree, inverseSqrtThree};
const double jacobian = 0.5 * length_;
for (const double xi : gaussPoints) {
const Matrix interpolation = kinematicInterpolation(xi, length_);
for (std::size_t dof = 0; dof < equivalent.Size(); ++dof) {
for (std::size_t component = 0; component < components.size(); ++component) {
equivalent[dof] +=
interpolation(component, dof) * components[component] * jacobian;
}
}
Matrix EulerBeam3D::GlobalStiffness() const {
const Matrix local = LocalStiffness();
const Matrix transform = Transformation(rotation_);
const Matrix local_times_transform = local.Multiply(transform);
Matrix global{kElementDofCount, kElementDofCount};
// Kg=T^T*Kl*T while dl=T*dg.
for (std::size_t row = 0; row < kElementDofCount; ++row) {
for (std::size_t column = 0; column < kElementDofCount; ++column) {
for (std::size_t inner = 0; inner < kElementDofCount; ++inner) {
global(row, column) +=
transform(inner, row) * local_times_transform(inner, column);
}
}
return equivalent;
}
return global;
}
BeamRecovery EulerBeam3D::recover(const Vector& globalElementDisplacement) const {
const Matrix transform = transformation(rotation_);
const Vector localDisplacement = transform.Multiply(globalElementDisplacement);
const auto diagonal = constitutiveDiagonal(
youngsModulus_, shearModulus_, area_, iy_, iz_, torsionalConstant_);
BeamRecovery recovery{};
// With parser/CLI distributed loading excluded, Kl*dl is the local outward end action.
const Vector endAction = localStiffness().Multiply(localDisplacement);
for (std::size_t endpoint = 0; endpoint < 2U; ++endpoint) {
for (std::size_t component = 0; component < 6U; ++component) {
recovery.equilibriumEndActions[endpoint][component] =
endAction[endpoint * 6U + component];
}
const double xi = endpoint == 0U ? -1.0 : 1.0;
recovery.endpointSectionResultants[endpoint] = generalizedResultant(
generalizedStrain(strainDisplacement(xi, length_), localDisplacement),
diagonal);
Vector EulerBeam3D::LocalEquivalentLoad(
const ConstantLocalLineLoad& load) const {
const std::array<double, 4> components = {load.px, load.py, load.pz, load.mx};
Vector equivalent{kElementDofCount};
const double inverse_sqrt_three = 1.0 / std::sqrt(3.0);
const std::array<double, 2> gauss_points = {-inverse_sqrt_three,
inverse_sqrt_three};
const double jacobian = 0.5 * length_;
for (const double xi : gauss_points) {
const Matrix interpolation = KinematicInterpolation(xi, length_);
for (std::size_t dof = 0; dof < equivalent.Size(); ++dof) {
for (std::size_t component = 0; component < components.size();
++component) {
equivalent[dof] +=
interpolation(component, dof) * components[component] * jacobian;
}
}
const double inverseSqrtThree = 1.0 / std::sqrt(3.0);
const std::array<double, 2> gaussPoints = {-inverseSqrtThree, inverseSqrtThree};
for (std::size_t point = 0; point < gaussPoints.size(); ++point) {
recovery.gaussGeneralizedStrains[point] = generalizedStrain(
strainDisplacement(gaussPoints[point], length_), localDisplacement);
recovery.gaussGeneralizedResultants[point] = generalizedResultant(
recovery.gaussGeneralizedStrains[point], diagonal);
if (sectionPoints_.empty()) {
recovery.stressPoints.push_back({
static_cast<int>(point + 1U),
0U,
0.0,
0.0,
youngsModulus_ * recovery.gaussGeneralizedStrains[point][0U],
"fesa-default"});
continue;
}
for (std::size_t sectionPoint = 0; sectionPoint < sectionPoints_.size();
++sectionPoint) {
const double x1 = sectionPoints_[sectionPoint][0U];
const double x2 = sectionPoints_[sectionPoint][1U];
const auto& strain = recovery.gaussGeneralizedStrains[point];
// x1=y and x2=z: S11=E(epsilon0+x2*kappa_y-x1*kappa_z).
recovery.stressPoints.push_back({
static_cast<int>(point + 1U),
sectionPoint + 1U,
x1,
x2,
youngsModulus_ *
(strain[0U] + x2 * strain[2U] - x1 * strain[3U]),
"input"});
}
}
return recovery;
}
return equivalent;
}
EulerBeam3D::EulerBeam3D(
double length,
double youngsModulus,
double shearModulus,
double area,
double iy,
double iz,
double torsionalConstant,
std::array<double, 9> rotation,
std::vector<std::array<double, 2>> sectionPoints)
BeamRecovery EulerBeam3D::Recover(
const Vector& global_element_displacement) const {
const Matrix transform = Transformation(rotation_);
const Vector local_displacement =
transform.Multiply(global_element_displacement);
const auto diagonal = ConstitutiveDiagonal(
youngs_modulus_, shear_modulus_, area_, iy_, iz_, torsional_constant_);
BeamRecovery recovery{};
// With parser/CLI distributed loading excluded, Kl*dl is the local outward
// end action.
const Vector end_action = LocalStiffness().Multiply(local_displacement);
for (std::size_t endpoint = 0; endpoint < 2U; ++endpoint) {
for (std::size_t component = 0; component < 6U; ++component) {
recovery.equilibrium_end_actions[endpoint][component] =
end_action[endpoint * 6U + component];
}
const double xi = endpoint == 0U ? -1.0 : 1.0;
recovery.endpoint_section_resultants[endpoint] = GeneralizedResultant(
GeneralizedStrain(StrainDisplacement(xi, length_), local_displacement),
diagonal);
}
const double inverse_sqrt_three = 1.0 / std::sqrt(3.0);
const std::array<double, 2> gauss_points = {-inverse_sqrt_three,
inverse_sqrt_three};
for (std::size_t point = 0; point < gauss_points.size(); ++point) {
recovery.gauss_generalized_strains[point] = GeneralizedStrain(
StrainDisplacement(gauss_points[point], length_), local_displacement);
recovery.gauss_generalized_resultants[point] = GeneralizedResultant(
recovery.gauss_generalized_strains[point], diagonal);
if (section_points_.empty()) {
recovery.stress_points.push_back(
{static_cast<int>(point + 1U), 0U, 0.0, 0.0,
youngs_modulus_ * recovery.gauss_generalized_strains[point][0U],
"fesa-default"});
continue;
}
for (std::size_t section_point = 0; section_point < section_points_.size();
++section_point) {
const double x1 = section_points_[section_point][0U];
const double x2 = section_points_[section_point][1U];
const auto& strain = recovery.gauss_generalized_strains[point];
// x1=y and x2=z: S11=E(epsilon0+x2*kappa_y-x1*kappa_z).
recovery.stress_points.push_back(
{static_cast<int>(point + 1U), section_point + 1U, x1, x2,
youngs_modulus_ * (strain[0U] + x2 * strain[2U] - x1 * strain[3U]),
"input"});
}
}
return recovery;
}
EulerBeam3D::EulerBeam3D(double length, double youngs_modulus,
double shear_modulus, double area, double iy,
double iz, double torsional_constant,
std::array<double, 9> rotation,
std::vector<std::array<double, 2>> section_points)
: length_{length},
youngsModulus_{youngsModulus},
shearModulus_{shearModulus},
youngs_modulus_{youngs_modulus},
shear_modulus_{shear_modulus},
area_{area},
iy_{iy},
iz_{iz},
torsionalConstant_{torsionalConstant},
torsional_constant_{torsional_constant},
rotation_{rotation},
sectionPoints_{std::move(sectionPoints)} {}
section_points_{std::move(section_points)} {}
} // namespace fesa
} // namespace fesa
File diff suppressed because it is too large Load Diff
+16 -16
View File
@@ -37,16 +37,16 @@ bool tryPositiveInteger(const std::string& text, std::int64_t& value) {
std::vector<EntityIndex> expandBoundaryTarget(
const Domain& domain, const BoundaryCondition& boundary) {
for (const auto& set : domain.nodeSets()) {
for (const auto& set : domain.NodeSets()) {
if (equalName(set.name, boundary.target)) {
return set.nodeIndices;
return set.node_indices;
}
}
std::int64_t sourceLabel = 0;
if (tryPositiveInteger(boundary.target, sourceLabel)) {
for (std::size_t node = 0U; node < domain.nodes().size(); ++node) {
if (domain.nodes()[node].sourceId.source_label == sourceLabel) {
for (std::size_t node = 0U; node < domain.Nodes().size(); ++node) {
if (domain.Nodes()[node].source_id.source_label == sourceLabel) {
return {static_cast<EntityIndex>(node)};
}
}
@@ -96,15 +96,15 @@ SparsePattern buildSparsePattern(
Result<DofManager> DofManager::create(const AnalysisModel& model) {
const Domain& domain = model.domain();
const std::size_t fullCount = domain.nodes().size() * dofsPerNode;
const std::size_t fullCount = domain.Nodes().size() * dofsPerNode;
std::vector<std::optional<double>> prescribedByFullDof(fullCount);
for (const EntityIndex boundaryIndex : model.activeBoundaryConditions()) {
const auto& boundary = model.step().boundaries.at(boundaryIndex);
const auto target = expandBoundaryTarget(domain, boundary);
for (const EntityIndex node : target) {
for (int component = boundary.firstDof;
component <= boundary.lastDof;
for (int component = boundary.first_dof;
component <= boundary.last_dof;
++component) {
const std::size_t fullDof =
static_cast<std::size_t>(node) * dofsPerNode +
@@ -150,12 +150,12 @@ Result<DofManager> DofManager::create(const AnalysisModel& model) {
}
std::vector<std::array<std::size_t, 12>> elementScatters(
domain.elements().size());
domain.Elements().size());
for (const EntityIndex elementIndex : model.activeElements()) {
const auto& element = domain.elements().at(elementIndex);
const auto& element = domain.Elements().at(elementIndex);
auto& scatter = elementScatters.at(elementIndex);
for (std::size_t endpoint = 0U; endpoint < element.nodeIndices.size(); ++endpoint) {
const std::size_t node = element.nodeIndices[endpoint];
for (std::size_t endpoint = 0U; endpoint < element.node_indices.size(); ++endpoint) {
const std::size_t node = element.node_indices[endpoint];
for (std::size_t component = 0U; component < dofsPerNode; ++component) {
scatter[endpoint * dofsPerNode + component] =
node * dofsPerNode + component;
@@ -164,16 +164,16 @@ Result<DofManager> DofManager::create(const AnalysisModel& model) {
}
std::vector<std::array<std::size_t, 24>> shellElementScatters(
domain.shellElements().size());
domain.ShellElements().size());
for (std::size_t elementIndex = 0U;
elementIndex < domain.shellElements().size();
elementIndex < domain.ShellElements().size();
++elementIndex) {
const auto& element = domain.shellElements()[elementIndex];
const auto& element = domain.ShellElements()[elementIndex];
auto& scatter = shellElementScatters[elementIndex];
for (std::size_t nodePosition = 0U;
nodePosition < element.nodeIndices.size();
nodePosition < element.node_indices.size();
++nodePosition) {
const std::size_t node = element.nodeIndices[nodePosition];
const std::size_t node = element.node_indices[nodePosition];
for (std::size_t component = 0U;
component < dofsPerNode;
++component) {
+53 -53
View File
@@ -1,6 +1,6 @@
#include "fesa/io/abaqus/domain_mapper.hpp"
#include "fesa/model/shell_geometry.hpp"
#include "fesa/model/shell_geometry.h"
#include <algorithm>
#include <array>
@@ -151,7 +151,7 @@ public:
failure_->category, {std::move(failure_->diagnostic)}));
}
SortDiagnostics(definition_.warnings);
return Domain::create(std::move(definition_));
return Domain::Create(std::move(definition_));
}
private:
@@ -419,8 +419,8 @@ private:
}
void parseBlocks() {
definition_.sourcePath = input_.sourcePath;
definition_.sourceContentIdentity = input_.sourceContentIdentity;
definition_.source_path = input_.sourcePath;
definition_.source_content_identity = input_.sourceContentIdentity;
for (std::size_t index = 0U;
index < input_.blocks.size() && !failure_;
@@ -1776,11 +1776,11 @@ private:
if (failure_) {
return;
}
if (!definition_.shellElements.empty()) {
auto geometry = preprocessShellGeometry(
if (!definition_.shell_elements.empty()) {
auto geometry = PreprocessShellGeometry(
definition_.nodes,
definition_.shellElements,
definition_.shellSections);
definition_.shell_elements,
definition_.shell_sections);
if (!geometry.HasValue()) {
const auto& status = geometry.GetStatus();
failure_ = MappingFailure{
@@ -1788,8 +1788,8 @@ private:
status.Diagnostics().front()};
return;
}
definition_.shellNodeInitialFrames =
std::move(geometry.Value().nodalFrames);
definition_.shell_node_initial_frames =
std::move(geometry.Value().nodal_frames);
}
expandAssemblySets();
if (failure_) {
@@ -1863,10 +1863,10 @@ private:
partDefinition.name = part.name;
partDefinition.location = part.location;
for (const auto& node : part.nodes) {
partDefinition.nodeSourceLabels.push_back(node.label);
partDefinition.node_source_labels.push_back(node.label);
}
for (const auto& element : part.elements) {
partDefinition.elementSourceLabels.push_back(element.label);
partDefinition.element_source_labels.push_back(element.label);
for (const auto nodeLabel : element.nodeLabels) {
if (findNode(part, nodeLabel) == nullptr) {
inputFailure(
@@ -1880,7 +1880,7 @@ private:
}
}
for (const auto& set : part.nodeSets) {
partDefinition.nodeSetNames.push_back(set.name);
partDefinition.node_set_names.push_back(set.name);
for (const auto label : set.members) {
if (findNode(part, label) == nullptr) {
inputFailure(
@@ -1892,7 +1892,7 @@ private:
}
}
for (const auto& set : part.elementSets) {
partDefinition.elementSetNames.push_back(set.name);
partDefinition.element_set_names.push_back(set.name);
for (const auto label : set.members) {
if (findElement(part, label) == nullptr) {
inputFailure(
@@ -1919,8 +1919,8 @@ private:
return;
}
const EntityIndex sectionIndex =
static_cast<EntityIndex>(definition_.shellSections.size());
definition_.shellSections.push_back({
static_cast<EntityIndex>(definition_.shell_sections.size());
definition_.shell_sections.push_back({
section.elementSetName,
section.thickness,
*materialIndex,
@@ -2071,11 +2071,11 @@ private:
deltaScaled[2] / lengthRatio};
const double globalGuideScale =
std::max(1.0, maximumAbsolute(section.firstAxis));
std::max(1.0, maximumAbsolute(section.first_axis));
std::array<double, 3> guideScaled{};
for (std::size_t coordinate = 0U; coordinate < 3U; ++coordinate) {
guideScaled[coordinate] =
section.firstAxis[coordinate] / globalGuideScale;
section.first_axis[coordinate] / globalGuideScale;
}
const double projection =
guideScaled[0] * tangent[0] +
@@ -2130,7 +2130,7 @@ private:
rawNode.coordinates,
rawNode.location});
nodeIndices.emplace(rawNode.label, index);
instance.nodeMappings.push_back({rawNode.label, index});
instance.node_mappings.push_back({rawNode.label, index});
}
for (const auto& rawElement : part->elements) {
@@ -2199,7 +2199,7 @@ private:
"Expanded shell section assignment must resolve exactly once.");
return;
}
if (definition_.shellElements.size() >
if (definition_.shell_elements.size() >
std::numeric_limits<EntityIndex>::max()) {
inputFailure(
"entity-index-overflow", rawElement.location,
@@ -2208,19 +2208,19 @@ private:
return;
}
index =
static_cast<EntityIndex>(definition_.shellElements.size());
definition_.shellElements.push_back({
static_cast<EntityIndex>(definition_.shell_elements.size());
definition_.shell_elements.push_back({
{rawInstance.name, rawElement.label, rawElement.labelText},
rawElement.type == RawElement::Type::s4
? ShellSourceElementType::s4
: ShellSourceElementType::s4r,
? ShellSourceElementType::kS4
: ShellSourceElementType::kS4r,
connectedNodes,
assignment->second.second,
assignment->second.first,
rawElement.location});
}
elementIndices.emplace(rawElement.label, index);
instance.elementMappings.push_back({rawElement.label, index});
instance.element_mappings.push_back({rawElement.label, index});
}
for (const auto& rawSet : part->nodeSets) {
@@ -2234,9 +2234,9 @@ private:
"Part node-set expansion failed.");
return;
}
set.nodeIndices.push_back(found->second);
set.node_indices.push_back(found->second);
}
definition_.nodeSets.push_back(std::move(set));
definition_.node_sets.push_back(std::move(set));
}
for (const auto& rawSet : part->elementSets) {
ElementSet set{
@@ -2250,9 +2250,9 @@ private:
"Part element-set expansion failed.");
return;
}
set.elementIndices.push_back(found->second);
set.element_indices.push_back(found->second);
}
definition_.elementSets.push_back(std::move(set));
definition_.element_sets.push_back(std::move(set));
}
definition_.instances.push_back(std::move(instance));
}
@@ -2281,61 +2281,61 @@ private:
return;
}
if (rawSet.isNodeSet) {
definition_.nodeSets.erase(
definition_.node_sets.erase(
std::remove_if(
definition_.nodeSets.begin(),
definition_.nodeSets.end(),
definition_.node_sets.begin(),
definition_.node_sets.end(),
[&rawSet](const NodeSet& set) {
return equalName(set.name, rawSet.name);
}),
definition_.nodeSets.end());
definition_.node_sets.end());
NodeSet set{
rawSet.name, definitionInstance->name, {}, rawSet.location};
for (const auto member : rawSet.members) {
const auto mapping = std::find_if(
definitionInstance->nodeMappings.begin(),
definitionInstance->nodeMappings.end(),
definitionInstance->node_mappings.begin(),
definitionInstance->node_mappings.end(),
[member](const SourceIndexMapping& value) {
return value.sourceLabel == member;
return value.source_label == member;
});
if (mapping == definitionInstance->nodeMappings.end()) {
if (mapping == definitionInstance->node_mappings.end()) {
inputFailure(
"unresolved-reference", rawSet.location,
"NSET", rawSet.name,
"Assembly node-set member must resolve in its instance.");
return;
}
set.nodeIndices.push_back(mapping->internalIndex);
set.node_indices.push_back(mapping->internal_index);
}
definition_.nodeSets.push_back(std::move(set));
definition_.node_sets.push_back(std::move(set));
} else {
definition_.elementSets.erase(
definition_.element_sets.erase(
std::remove_if(
definition_.elementSets.begin(),
definition_.elementSets.end(),
definition_.element_sets.begin(),
definition_.element_sets.end(),
[&rawSet](const ElementSet& set) {
return equalName(set.name, rawSet.name);
}),
definition_.elementSets.end());
definition_.element_sets.end());
ElementSet set{
rawSet.name, definitionInstance->name, {}, rawSet.location};
for (const auto member : rawSet.members) {
const auto mapping = std::find_if(
definitionInstance->elementMappings.begin(),
definitionInstance->elementMappings.end(),
definitionInstance->element_mappings.begin(),
definitionInstance->element_mappings.end(),
[member](const SourceIndexMapping& value) {
return value.sourceLabel == member;
return value.source_label == member;
});
if (mapping == definitionInstance->elementMappings.end()) {
if (mapping == definitionInstance->element_mappings.end()) {
inputFailure(
"unresolved-reference", rawSet.location,
"ELSET", rawSet.name,
"Assembly element-set member must resolve in its instance.");
return;
}
set.elementIndices.push_back(mapping->internalIndex);
set.element_indices.push_back(mapping->internal_index);
}
definition_.elementSets.push_back(std::move(set));
definition_.element_sets.push_back(std::move(set));
}
}
}
@@ -2345,7 +2345,7 @@ private:
const SourceLocation& location,
const std::string& keyword) {
std::vector<const NodeSet*> matchingSets;
for (const auto& set : definition_.nodeSets) {
for (const auto& set : definition_.node_sets) {
if (equalName(set.name, target)) {
matchingSets.push_back(&set);
}
@@ -2357,7 +2357,7 @@ private:
for (std::size_t index = 0U;
index < definition_.nodes.size();
++index) {
if (definition_.nodes[index].sourceId.source_label == label) {
if (definition_.nodes[index].source_id.source_label == label) {
matchingNodes.push_back(static_cast<EntityIndex>(index));
}
}
@@ -2385,7 +2385,7 @@ private:
return std::nullopt;
}
if (matchingSets.size() == 1U) {
return matchingSets.front()->nodeIndices;
return matchingSets.front()->node_indices;
}
if (matchingNodes.size() == 1U) {
return matchingNodes;
@@ -2409,7 +2409,7 @@ private:
return;
}
for (const auto node : *target) {
for (int dof = boundary.firstDof; dof <= boundary.lastDof; ++dof) {
for (int dof = boundary.first_dof; dof <= boundary.last_dof; ++dof) {
const auto key = std::make_pair(node, dof);
const auto existing = prescribedValues.find(key);
if (existing != prescribedValues.end() &&
+123 -123
View File
@@ -231,11 +231,11 @@ struct WriterModelData {
};
bool isShellDomain(const Domain& domain) noexcept {
return !domain.shellElements().empty();
return !domain.ShellElements().empty();
}
const char* shellSourceTypeName(const ShellSourceElementType type) {
return type == ShellSourceElementType::s4 ? "S4" : "S4R";
return type == ShellSourceElementType::kS4 ? "S4" : "S4R";
}
bool isOrthonormalRightHanded(
@@ -266,14 +266,14 @@ bool isOrthonormalRightHanded(
bool computeLocalAxes(
const Domain& domain, const EulerBeam3DDefinition& element, AxisSet& axes) {
if (element.nodeIndices[0U] >= domain.nodes().size() ||
element.nodeIndices[1U] >= domain.nodes().size() ||
element.sectionIndex >= domain.sections().size()) {
if (element.node_indices[0U] >= domain.Nodes().size() ||
element.node_indices[1U] >= domain.Nodes().size() ||
element.section_index >= domain.Sections().size()) {
return false;
}
const auto& first = domain.nodes()[element.nodeIndices[0U]].coordinates;
const auto& second = domain.nodes()[element.nodeIndices[1U]].coordinates;
const auto& guide = domain.sections()[element.sectionIndex].firstAxis;
const auto& first = domain.Nodes()[element.node_indices[0U]].coordinates;
const auto& second = domain.Nodes()[element.node_indices[1U]].coordinates;
const auto& guide = domain.Sections()[element.section_index].first_axis;
const std::array<double, 3> delta = {
second[0U] - first[0U],
second[1U] - first[1U],
@@ -319,48 +319,48 @@ bool sizeProductFits(
Status validateShellWriterInput(
const Domain& domain, const AnalysisState& state) {
if (!domain.elements().empty()) {
if (!domain.Elements().empty()) {
return outputFailure(
"invalid-result-identity",
"Schema v0 does not combine B33 and FESA-MITC4 element inventories.");
}
for (const auto& material : domain.materials()) {
for (const auto& material : domain.Materials()) {
if (material.name.empty() || !isValidUtf8(material.name) ||
!std::isfinite(material.youngsModulus) ||
!std::isfinite(material.poissonRatio) ||
!(material.youngsModulus > 0.0)) {
!std::isfinite(material.youngs_modulus) ||
!std::isfinite(material.poisson_ratio) ||
!(material.youngs_modulus > 0.0)) {
return outputFailure(
"invalid-result-identity",
"Every shell material requires a UTF-8 name and finite constitutive data.");
}
}
for (const auto& section : domain.shellSections()) {
for (const auto& section : domain.ShellSections()) {
if (section.name.empty() || !isValidUtf8(section.name) ||
section.materialIndex >= domain.materials().size() ||
section.material_index >= domain.Materials().size() ||
!std::isfinite(section.thickness) || !(section.thickness > 0.0)) {
return outputFailure(
"invalid-result-identity",
"Every shell section requires stable material identity and positive finite thickness.");
}
}
for (const auto& element : domain.shellElements()) {
if ((element.sourceType != ShellSourceElementType::s4 &&
element.sourceType != ShellSourceElementType::s4r) ||
element.sourceId.source_label <= 0 ||
element.sourceId.source_label_text.empty() ||
!isValidUtf8(element.sourceId.instance_name) ||
!isValidUtf8(element.sourceId.source_label_text) ||
element.materialIndex >= domain.materials().size() ||
element.sectionIndex >= domain.shellSections().size() ||
domain.shellSections()[element.sectionIndex].materialIndex !=
element.materialIndex) {
for (const auto& element : domain.ShellElements()) {
if ((element.source_type != ShellSourceElementType::kS4 &&
element.source_type != ShellSourceElementType::kS4r) ||
element.source_id.source_label <= 0 ||
element.source_id.source_label_text.empty() ||
!isValidUtf8(element.source_id.instance_name) ||
!isValidUtf8(element.source_id.source_label_text) ||
element.material_index >= domain.Materials().size() ||
element.section_index >= domain.ShellSections().size() ||
domain.ShellSections()[element.section_index].material_index !=
element.material_index) {
return outputFailure(
"invalid-result-identity",
"Every shell element requires stable source, material, and section identity.");
}
std::array<EntityIndex, kShellNodeCount> sortedNodes = element.nodeIndices;
std::array<EntityIndex, kShellNodeCount> sortedNodes = element.node_indices;
std::sort(sortedNodes.begin(), sortedNodes.end());
if (sortedNodes.back() >= domain.nodes().size() ||
if (sortedNodes.back() >= domain.Nodes().size() ||
std::adjacent_find(sortedNodes.begin(), sortedNodes.end()) !=
sortedNodes.end()) {
return outputFailure(
@@ -368,18 +368,18 @@ Status validateShellWriterInput(
"Every shell element requires four distinct valid node identities.");
}
}
if (domain.shellNodeInitialFrames().size() != domain.nodes().size()) {
if (domain.ShellNodeInitialFrames().size() != domain.Nodes().size()) {
return outputFailure(
"invalid-result-identity",
"Shell output requires one initial director/frame per source-ordered node.");
}
for (std::size_t node = 0U;
node < domain.shellNodeInitialFrames().size();
node < domain.ShellNodeInitialFrames().size();
++node) {
const auto& source = domain.shellNodeInitialFrames()[node];
const auto& source = domain.ShellNodeInitialFrames()[node];
const std::array<std::array<double, 3>, 3> frame{
source.tangentA, source.tangentB, source.director};
if (source.nodeIndex != node || !isOrthonormalRightHanded(frame)) {
source.tangent_a, source.tangent_b, source.director};
if (source.node_index != node || !isOrthonormalRightHanded(frame)) {
return outputFailure(
"invalid-result-identity",
"Shell initial frames must be finite, orthonormal, right-handed, and node ordered.");
@@ -388,7 +388,7 @@ Status validateShellWriterInput(
std::size_t expectedRows = 0U;
if (!sizeProductFits(
domain.shellElements().size(), kShellLocationCount, expectedRows) ||
domain.ShellElements().size(), kShellLocationCount, expectedRows) ||
state.shellResults().size() != expectedRows) {
return outputFailure(
"invalid-result-rows",
@@ -477,7 +477,7 @@ Status validateWriterInput(
}
std::size_t fullDofCount = 0U;
if (!sizeProductFits(domain.nodes().size(), kDofsPerNode, fullDofCount)) {
if (!sizeProductFits(domain.Nodes().size(), kDofsPerNode, fullDofCount)) {
return outputFailure(
"invalid-result-state", "The nodal result shape overflows size_t.");
}
@@ -502,17 +502,17 @@ Status validateWriterInput(
}
}
if (domain.sourcePath().empty() || domain.sourceContentIdentity().empty() ||
!isValidUtf8(domain.sourceContentIdentity())) {
if (domain.SourcePath().empty() || domain.SourceContentIdentity().empty() ||
!isValidUtf8(domain.SourceContentIdentity())) {
return outputFailure(
"invalid-result-identity",
"Source path and UTF-8 content identity are required.");
}
for (const Node& node : domain.nodes()) {
if (node.sourceId.source_label <= 0 ||
node.sourceId.source_label_text.empty() ||
!isValidUtf8(node.sourceId.instance_name) ||
!isValidUtf8(node.sourceId.source_label_text) ||
for (const Node& node : domain.Nodes()) {
if (node.source_id.source_label <= 0 ||
node.source_id.source_label_text.empty() ||
!isValidUtf8(node.source_id.instance_name) ||
!isValidUtf8(node.source_id.source_label_text) ||
!isFinite(node.coordinates)) {
return outputFailure(
"invalid-result-identity",
@@ -521,15 +521,15 @@ Status validateWriterInput(
}
modelData.beamLocalAxes.clear();
modelData.beamLocalAxes.reserve(domain.elements().size());
for (const EulerBeam3DDefinition& element : domain.elements()) {
modelData.beamLocalAxes.reserve(domain.Elements().size());
for (const EulerBeam3DDefinition& element : domain.Elements()) {
AxisSet axes{};
if (element.sourceId.source_label <= 0 ||
element.sourceId.source_label_text.empty() ||
!isValidUtf8(element.sourceId.instance_name) ||
!isValidUtf8(element.sourceId.source_label_text) ||
element.nodeIndices[0U] == element.nodeIndices[1U] ||
element.materialIndex >= domain.materials().size() ||
if (element.source_id.source_label <= 0 ||
element.source_id.source_label_text.empty() ||
!isValidUtf8(element.source_id.instance_name) ||
!isValidUtf8(element.source_id.source_label_text) ||
element.node_indices[0U] == element.node_indices[1U] ||
element.material_index >= domain.Materials().size() ||
!computeLocalAxes(domain, element, axes)) {
return outputFailure(
"invalid-result-identity",
@@ -540,8 +540,8 @@ Status validateWriterInput(
std::size_t endpointCount = 0U;
std::size_t gaussCount = 0U;
if (!sizeProductFits(domain.elements().size(), kEndpointCount, endpointCount) ||
!sizeProductFits(domain.elements().size(), kGaussPointCount, gaussCount) ||
if (!sizeProductFits(domain.Elements().size(), kEndpointCount, endpointCount) ||
!sizeProductFits(domain.Elements().size(), kGaussPointCount, gaussCount) ||
state.endpointResults().size() != endpointCount ||
state.gaussResults().size() != gaussCount) {
return outputFailure(
@@ -555,11 +555,11 @@ Status validateWriterInput(
static_cast<EntityIndex>(rowIndex / kEndpointCount);
const int expectedEndpoint = static_cast<int>(rowIndex % kEndpointCount);
const EndpointResultRow& row = state.endpointResults()[rowIndex];
const auto& element = domain.elements()[expectedElement];
const auto& element = domain.Elements()[expectedElement];
const auto& expectedNode =
domain.nodes()[element.nodeIndices[static_cast<std::size_t>(expectedEndpoint)]];
domain.Nodes()[element.node_indices[static_cast<std::size_t>(expectedEndpoint)]];
if (row.element != expectedElement || row.endpoint != expectedEndpoint ||
!sameIdentity(row.node, expectedNode.sourceId) ||
!sameIdentity(row.node, expectedNode.source_id) ||
!isFinite(row.endAction) || !isFinite(row.sectionResultant)) {
return outputFailure(
"invalid-result-rows",
@@ -586,11 +586,11 @@ Status validateWriterInput(
std::size_t stressIndex = 0U;
for (std::size_t elementIndex = 0U;
elementIndex < domain.elements().size();
elementIndex < domain.Elements().size();
++elementIndex) {
const auto& element = domain.elements()[elementIndex];
const auto& element = domain.Elements()[elementIndex];
const auto& sectionPoints =
domain.sections()[element.sectionIndex].sectionPoints;
domain.Sections()[element.section_index].section_points;
for (std::size_t gauss = 0U; gauss < kGaussPointCount; ++gauss) {
const std::size_t count = sectionPoints.empty() ? 1U : sectionPoints.size();
for (std::size_t point = 0U; point < count; ++point) {
@@ -681,8 +681,8 @@ std::string normalizedPathString(const std::filesystem::path& path) {
}
std::string sourceInputIdentity(const Domain& domain) {
return "path=" + normalizedPathString(domain.sourcePath()) +
";content_identity=" + domain.sourceContentIdentity();
return "path=" + normalizedPathString(domain.SourcePath()) +
";content_identity=" + domain.SourceContentIdentity();
}
Hdf5Handle makeUtf8StringType() {
@@ -982,13 +982,13 @@ void writeMetadata(const hid_t file, const Domain& domain) {
void writeNodes(const hid_t file, const Domain& domain) {
std::vector<NodeWriteRow> rows;
rows.reserve(domain.nodes().size());
for (std::size_t index = 0U; index < domain.nodes().size(); ++index) {
const Node& node = domain.nodes()[index];
rows.reserve(domain.Nodes().size());
for (std::size_t index = 0U; index < domain.Nodes().size(); ++index) {
const Node& node = domain.Nodes()[index];
rows.push_back({
static_cast<std::uint64_t>(index),
node.sourceId.instance_name.c_str(),
node.sourceId.source_label_text.c_str(),
node.source_id.instance_name.c_str(),
node.source_id.source_label_text.c_str(),
{node.coordinates[0U], node.coordinates[1U], node.coordinates[2U]}});
}
@@ -1057,15 +1057,15 @@ void writeNodes(const hid_t file, const Domain& domain) {
void writeBeamElements(
const hid_t file, const Domain& domain, const std::vector<AxisSet>& axes) {
std::vector<ElementWriteRow> rows;
rows.reserve(domain.elements().size());
for (std::size_t index = 0U; index < domain.elements().size(); ++index) {
const auto& element = domain.elements()[index];
rows.reserve(domain.Elements().size());
for (std::size_t index = 0U; index < domain.Elements().size(); ++index) {
const auto& element = domain.Elements()[index];
ElementWriteRow row{
static_cast<std::uint64_t>(index),
element.sourceId.instance_name.c_str(),
element.sourceId.source_label_text.c_str(),
{static_cast<std::uint64_t>(element.nodeIndices[0U]),
static_cast<std::uint64_t>(element.nodeIndices[1U])},
element.source_id.instance_name.c_str(),
element.source_id.source_label_text.c_str(),
{static_cast<std::uint64_t>(element.node_indices[0U]),
static_cast<std::uint64_t>(element.node_indices[1U])},
{}};
std::copy(axes[index].begin(), axes[index].end(), row.localAxes);
rows.push_back(row);
@@ -1141,21 +1141,21 @@ void writeBeamElements(
void writeShellElements(const hid_t file, const Domain& domain) {
std::vector<ShellElementWriteRow> rows;
rows.reserve(domain.shellElements().size());
for (std::size_t index = 0U; index < domain.shellElements().size(); ++index) {
const auto& element = domain.shellElements()[index];
rows.reserve(domain.ShellElements().size());
for (std::size_t index = 0U; index < domain.ShellElements().size(); ++index) {
const auto& element = domain.ShellElements()[index];
rows.push_back({
static_cast<std::uint64_t>(index),
element.sourceId.instance_name.c_str(),
element.sourceId.source_label_text.c_str(),
shellSourceTypeName(element.sourceType),
element.source_id.instance_name.c_str(),
element.source_id.source_label_text.c_str(),
shellSourceTypeName(element.source_type),
kMitc4InternalFormulation.data(),
{static_cast<std::uint64_t>(element.nodeIndices[0U]),
static_cast<std::uint64_t>(element.nodeIndices[1U]),
static_cast<std::uint64_t>(element.nodeIndices[2U]),
static_cast<std::uint64_t>(element.nodeIndices[3U])},
static_cast<std::uint64_t>(element.sectionIndex),
static_cast<std::uint64_t>(element.materialIndex)});
{static_cast<std::uint64_t>(element.node_indices[0U]),
static_cast<std::uint64_t>(element.node_indices[1U]),
static_cast<std::uint64_t>(element.node_indices[2U]),
static_cast<std::uint64_t>(element.node_indices[3U])},
static_cast<std::uint64_t>(element.section_index),
static_cast<std::uint64_t>(element.material_index)});
}
auto stringType = makeUtf8StringType();
@@ -1226,14 +1226,14 @@ void writeShellElements(const hid_t file, const Domain& domain) {
void writeShellMaterials(const hid_t file, const Domain& domain) {
std::vector<ShellMaterialWriteRow> rows;
rows.reserve(domain.materials().size());
for (std::size_t index = 0U; index < domain.materials().size(); ++index) {
const auto& material = domain.materials()[index];
rows.reserve(domain.Materials().size());
for (std::size_t index = 0U; index < domain.Materials().size(); ++index) {
const auto& material = domain.Materials()[index];
rows.push_back({
static_cast<std::uint64_t>(index),
material.name.c_str(),
material.youngsModulus,
material.poissonRatio});
material.youngs_modulus,
material.poisson_ratio});
}
auto stringType = makeUtf8StringType();
Hdf5Handle fileType{
@@ -1276,20 +1276,20 @@ void writeShellMaterials(const hid_t file, const Domain& domain) {
void writeShellSections(const hid_t file, const Domain& domain) {
std::vector<std::string> sourceFiles;
sourceFiles.reserve(domain.shellSections().size());
for (const auto& section : domain.shellSections()) {
sourceFiles.reserve(domain.ShellSections().size());
for (const auto& section : domain.ShellSections()) {
sourceFiles.push_back(normalizedPathString(section.location.file));
}
std::vector<ShellSectionWriteRow> rows;
rows.reserve(domain.shellSections().size());
for (std::size_t index = 0U; index < domain.shellSections().size(); ++index) {
const auto& section = domain.shellSections()[index];
rows.reserve(domain.ShellSections().size());
for (std::size_t index = 0U; index < domain.ShellSections().size(); ++index) {
const auto& section = domain.ShellSections()[index];
rows.push_back({
static_cast<std::uint64_t>(index),
sourceFiles[index].c_str(),
static_cast<std::uint64_t>(section.location.line),
section.name.c_str(),
static_cast<std::uint64_t>(section.materialIndex),
static_cast<std::uint64_t>(section.material_index),
section.thickness});
}
auto stringType = makeUtf8StringType();
@@ -1343,24 +1343,24 @@ void writeShellSections(const hid_t file, const Domain& domain) {
void writeShellModelData(
const hid_t file, const Domain& domain, const WriterModelData& modelData) {
std::vector<double> directors;
directors.reserve(domain.nodes().size() * 3U);
directors.reserve(domain.Nodes().size() * 3U);
std::vector<double> frames;
frames.reserve(domain.nodes().size() * 9U);
for (const auto& frame : domain.shellNodeInitialFrames()) {
frames.reserve(domain.Nodes().size() * 9U);
for (const auto& frame : domain.ShellNodeInitialFrames()) {
directors.insert(
directors.end(), frame.director.begin(), frame.director.end());
frames.insert(frames.end(), frame.tangentA.begin(), frame.tangentA.end());
frames.insert(frames.end(), frame.tangentB.begin(), frame.tangentB.end());
frames.insert(frames.end(), frame.tangent_a.begin(), frame.tangent_a.end());
frames.insert(frames.end(), frame.tangent_b.begin(), frame.tangent_b.end());
frames.insert(frames.end(), frame.director.begin(), frame.director.end());
}
writeModelDoubleDataset(
file, "/model/shell/nodal_director",
{static_cast<hsize_t>(domain.nodes().size()), 3U},
{static_cast<hsize_t>(domain.Nodes().size()), 3U},
directors.data(), directors.size(), "D1,D2,D3", "1,1,1",
"global-cartesian", "nodal");
writeModelDoubleDataset(
file, "/model/shell/nodal_frame",
{static_cast<hsize_t>(domain.nodes().size()), 3U, 3U},
{static_cast<hsize_t>(domain.Nodes().size()), 3U, 3U},
frames.data(), frames.size(), "X,Y,Z", "1,1,1",
"global-cartesian", "nodal-frame");
{
@@ -1375,7 +1375,7 @@ void writeShellModelData(
writeShellMaterials(file, domain);
writeShellSections(file, domain);
const std::vector<hsize_t> nodalDimensions{
static_cast<hsize_t>(domain.nodes().size()), kDofsPerNode};
static_cast<hsize_t>(domain.Nodes().size()), kDofsPerNode};
writeUint8Dataset(
file, "/model/nodal_constraint_mask", nodalDimensions,
modelData.constraintMask.data(), modelData.constraintMask.size());
@@ -1573,7 +1573,7 @@ void writeShellResultDatasets(
}
const hsize_t elementCount =
static_cast<hsize_t>(domain.shellElements().size());
static_cast<hsize_t>(domain.ShellElements().size());
const std::string root = std::string{kStepRoot} + "/element/shell";
const std::string framePath = root + "/local_frame";
writeDoubleDataset(
@@ -1730,7 +1730,7 @@ void writeDiagnostics(
void writeResultDatasets(
const hid_t file, const Domain& domain, const AnalysisState& state) {
const std::vector<hsize_t> nodalDimensions = {
static_cast<hsize_t>(domain.nodes().size()), kDofsPerNode};
static_cast<hsize_t>(domain.Nodes().size()), kDofsPerNode};
writeDoubleDataset(
file,
std::string{kStepRoot} + "/nodal/displacement",
@@ -1758,11 +1758,11 @@ void writeResultDatasets(
}
const std::vector<hsize_t> endpointActionDimensions = {
static_cast<hsize_t>(domain.elements().size()),
static_cast<hsize_t>(domain.Elements().size()),
kEndpointCount,
kEndActionComponentCount};
const std::vector<hsize_t> generalizedDimensions = {
static_cast<hsize_t>(domain.elements().size()),
static_cast<hsize_t>(domain.Elements().size()),
kEndpointCount,
kGeneralizedComponentCount};
const auto endActions = flattenEndpointValues(state.endpointResults(), false);
@@ -2287,13 +2287,13 @@ void selfCheckFile(
requireCompoundDataset(
file.get(),
"/model/nodes",
static_cast<hsize_t>(domain.nodes().size()),
static_cast<hsize_t>(domain.Nodes().size()),
{"internal_node_id", "instance_name", "source_label", "coordinates"});
auto nodes = openDatasetForCheck(file.get(), "/model/nodes");
requireStringAttribute(nodes.get(), "coordinate_system", "global-cartesian");
requireStringAttribute(nodes.get(), "units_label", "length");
const std::vector<hsize_t> nodalDimensions = {
static_cast<hsize_t>(domain.nodes().size()), kDofsPerNode};
static_cast<hsize_t>(domain.Nodes().size()), kDofsPerNode};
requireDoubleDataset(
file.get(),
"/steps/Step-1/frames/0/nodal/displacement",
@@ -2315,7 +2315,7 @@ void selfCheckFile(
requireCompoundDataset(
file.get(),
"/model/elements",
static_cast<hsize_t>(domain.shellElements().size()),
static_cast<hsize_t>(domain.ShellElements().size()),
{"internal_element_id", "instance_name", "source_label",
"source_element_type", "internal_formulation", "node_internal_ids",
"shell_section_internal_id", "material_internal_id"});
@@ -2324,33 +2324,33 @@ void selfCheckFile(
requireCompoundDataset(
file.get(),
"/model/shell/materials",
static_cast<hsize_t>(domain.materials().size()),
static_cast<hsize_t>(domain.Materials().size()),
{"internal_material_id", "name", "E", "nu"});
requireCompoundDataset(
file.get(),
"/model/shell/sections",
static_cast<hsize_t>(domain.shellSections().size()),
static_cast<hsize_t>(domain.ShellSections().size()),
{"internal_section_id", "source_file", "source_line", "source_elset",
"material_internal_id", "thickness"});
std::vector<double> directors;
std::vector<double> frames;
directors.reserve(domain.nodes().size() * 3U);
frames.reserve(domain.nodes().size() * 9U);
for (const auto& frame : domain.shellNodeInitialFrames()) {
directors.reserve(domain.Nodes().size() * 3U);
frames.reserve(domain.Nodes().size() * 9U);
for (const auto& frame : domain.ShellNodeInitialFrames()) {
directors.insert(
directors.end(), frame.director.begin(), frame.director.end());
frames.insert(frames.end(), frame.tangentA.begin(), frame.tangentA.end());
frames.insert(frames.end(), frame.tangentB.begin(), frame.tangentB.end());
frames.insert(frames.end(), frame.tangent_a.begin(), frame.tangent_a.end());
frames.insert(frames.end(), frame.tangent_b.begin(), frame.tangent_b.end());
frames.insert(frames.end(), frame.director.begin(), frame.director.end());
}
requireModelDoubleDataset(
file.get(), "/model/shell/nodal_director",
{static_cast<hsize_t>(domain.nodes().size()), 3U},
{static_cast<hsize_t>(domain.Nodes().size()), 3U},
"D1,D2,D3", "1,1,1", "global-cartesian", "nodal", &directors);
requireModelDoubleDataset(
file.get(), "/model/shell/nodal_frame",
{static_cast<hsize_t>(domain.nodes().size()), 3U, 3U},
{static_cast<hsize_t>(domain.Nodes().size()), 3U, 3U},
"X,Y,Z", "1,1,1", "global-cartesian", "nodal-frame", &frames);
auto nodalFrame = openDatasetForCheck(
file.get(), "/model/shell/nodal_frame");
@@ -2383,7 +2383,7 @@ void selfCheckFile(
positions.get(), "position_names", "BOTTOM,MIDDLE,TOP");
const hsize_t elementCount =
static_cast<hsize_t>(domain.shellElements().size());
static_cast<hsize_t>(domain.ShellElements().size());
const std::string shellRoot = std::string{kStepRoot} + "/element/shell";
requireDoubleDataset(
file.get(), (shellRoot + "/local_frame").c_str(),
@@ -2459,18 +2459,18 @@ void selfCheckFile(
requireCompoundDataset(
file.get(),
"/model/elements",
static_cast<hsize_t>(domain.elements().size()),
static_cast<hsize_t>(domain.Elements().size()),
{"internal_element_id", "instance_name", "source_label",
"node_internal_ids", "local_axes"});
auto elements = openDatasetForCheck(file.get(), "/model/elements");
requireStringAttribute(
elements.get(), "formulation", "B33-3D-Euler-Bernoulli");
const std::vector<hsize_t> endDimensions = {
static_cast<hsize_t>(domain.elements().size()),
static_cast<hsize_t>(domain.Elements().size()),
kEndpointCount,
kEndActionComponentCount};
const std::vector<hsize_t> generalizedDimensions = {
static_cast<hsize_t>(domain.elements().size()),
static_cast<hsize_t>(domain.Elements().size()),
kGaussPointCount,
kGeneralizedComponentCount};
requireDoubleDataset(
+32 -30
View File
@@ -1,66 +1,68 @@
#include "fesa/model/domain.hpp"
#include "fesa/model/domain.h"
#include <utility>
namespace fesa {
Result<Domain> Domain::create(ModelDefinition definition) {
return Result<Domain>::Success(Domain{std::move(definition)});
Result<Domain> Domain::Create(ModelDefinition definition) {
return Result<Domain>::Success(Domain{std::move(definition)});
}
const std::vector<Node>& Domain::nodes() const noexcept {
return definition_.nodes;
const std::vector<Node>& Domain::Nodes() const noexcept {
return definition_.nodes;
}
const std::vector<EulerBeam3DDefinition>& Domain::elements() const noexcept {
return definition_.elements;
const std::vector<EulerBeam3DDefinition>& Domain::Elements() const noexcept {
return definition_.elements;
}
const std::vector<Mitc4ShellDefinition>& Domain::shellElements() const noexcept {
return definition_.shellElements;
const std::vector<Mitc4ShellDefinition>& Domain::ShellElements()
const noexcept {
return definition_.shell_elements;
}
const std::vector<LinearElasticMaterial>& Domain::materials() const noexcept {
return definition_.materials;
const std::vector<LinearElasticMaterial>& Domain::Materials() const noexcept {
return definition_.materials;
}
const std::vector<GeneralBeamSection>& Domain::sections() const noexcept {
return definition_.sections;
const std::vector<GeneralBeamSection>& Domain::Sections() const noexcept {
return definition_.sections;
}
const std::vector<ShellSection>& Domain::shellSections() const noexcept {
return definition_.shellSections;
const std::vector<ShellSection>& Domain::ShellSections() const noexcept {
return definition_.shell_sections;
}
const std::vector<ShellNodeInitialFrame>& Domain::shellNodeInitialFrames() const noexcept {
return definition_.shellNodeInitialFrames;
const std::vector<ShellNodeInitialFrame>& Domain::ShellNodeInitialFrames()
const noexcept {
return definition_.shell_node_initial_frames;
}
const std::vector<NodeSet>& Domain::nodeSets() const noexcept {
return definition_.nodeSets;
const std::vector<NodeSet>& Domain::NodeSets() const noexcept {
return definition_.node_sets;
}
const std::vector<ElementSet>& Domain::elementSets() const noexcept {
return definition_.elementSets;
const std::vector<ElementSet>& Domain::ElementSets() const noexcept {
return definition_.element_sets;
}
const std::vector<StaticStepDefinition>& Domain::steps() const noexcept {
return definition_.steps;
const std::vector<StaticStepDefinition>& Domain::Steps() const noexcept {
return definition_.steps;
}
const std::vector<Diagnostic>& Domain::warnings() const noexcept {
return definition_.warnings;
const std::vector<Diagnostic>& Domain::Warnings() const noexcept {
return definition_.warnings;
}
const std::filesystem::path& Domain::sourcePath() const noexcept {
return definition_.sourcePath;
const std::filesystem::path& Domain::SourcePath() const noexcept {
return definition_.source_path;
}
const std::string& Domain::sourceContentIdentity() const noexcept {
return definition_.sourceContentIdentity;
const std::string& Domain::SourceContentIdentity() const noexcept {
return definition_.source_content_identity;
}
Domain::Domain(ModelDefinition definition)
: definition_{std::move(definition)} {}
} // namespace fesa
} // namespace fesa
+379 -413
View File
@@ -1,4 +1,4 @@
#include "fesa/model/shell_geometry.hpp"
#include "fesa/model/shell_geometry.h"
#include <algorithm>
#include <cmath>
@@ -14,478 +14,444 @@ constexpr std::array<double, 4> kXiSigns{-1.0, 1.0, 1.0, -1.0};
constexpr std::array<double, 4> kEtaSigns{-1.0, -1.0, 1.0, 1.0};
struct ShapeData {
std::array<double, 4> values;
std::array<double, 4> xiDerivatives;
std::array<double, 4> etaDerivatives;
std::array<double, 4> values;
std::array<double, 4> xi_derivatives;
std::array<double, 4> eta_derivatives;
};
struct ElementWork {
std::array<Vector3, 4> coordinates;
Vector3 normal;
double areaWeight;
std::array<Vector3, 4> coordinates;
Vector3 normal;
double area_weight;
};
Vector3 add(const Vector3& left, const Vector3& right) {
return {
left[0] + right[0], left[1] + right[1], left[2] + right[2]};
Vector3 Add(const Vector3& left, const Vector3& right) {
return {left[0] + right[0], left[1] + right[1], left[2] + right[2]};
}
Vector3 subtract(const Vector3& left, const Vector3& right) {
return {
left[0] - right[0], left[1] - right[1], left[2] - right[2]};
Vector3 Subtract(const Vector3& left, const Vector3& right) {
return {left[0] - right[0], left[1] - right[1], left[2] - right[2]};
}
Vector3 scale(double factor, const Vector3& value) {
return {factor * value[0], factor * value[1], factor * value[2]};
Vector3 Scale(double factor, const Vector3& value) {
return {factor * value[0], factor * value[1], factor * value[2]};
}
double dot(const Vector3& left, const Vector3& right) {
return left[0] * right[0] + left[1] * right[1] + left[2] * right[2];
double Dot(const Vector3& left, const Vector3& right) {
return left[0] * right[0] + left[1] * right[1] + left[2] * right[2];
}
Vector3 cross(const Vector3& left, const Vector3& right) {
return {
left[1] * right[2] - left[2] * right[1],
left[2] * right[0] - left[0] * right[2],
left[0] * right[1] - left[1] * right[0]};
Vector3 Cross(const Vector3& left, const Vector3& right) {
return {left[1] * right[2] - left[2] * right[1],
left[2] * right[0] - left[0] * right[2],
left[0] * right[1] - left[1] * right[0]};
}
double norm(const Vector3& value) {
return std::hypot(value[0], value[1], value[2]);
double Norm(const Vector3& value) {
return std::hypot(value[0], value[1], value[2]);
}
bool isFinite(const Vector3& value) {
return std::all_of(value.begin(), value.end(), [](double component) {
return std::isfinite(component);
});
bool IsFinite(const Vector3& value) {
return std::all_of(value.begin(), value.end(),
[](double component) { return std::isfinite(component); });
}
ShapeData shapeData(double xi, double eta) {
ShapeData data{};
for (std::size_t node = 0U; node < 4U; ++node) {
data.values[node] =
0.25 * (1.0 + kXiSigns[node] * xi) *
(1.0 + kEtaSigns[node] * eta);
data.xiDerivatives[node] =
0.25 * kXiSigns[node] * (1.0 + kEtaSigns[node] * eta);
data.etaDerivatives[node] =
0.25 * kEtaSigns[node] * (1.0 + kXiSigns[node] * xi);
}
return data;
ShapeData ShapeDataAt(double xi, double eta) {
ShapeData data{};
for (std::size_t node = 0U; node < 4U; ++node) {
data.values[node] =
0.25 * (1.0 + kXiSigns[node] * xi) * (1.0 + kEtaSigns[node] * eta);
data.xi_derivatives[node] =
0.25 * kXiSigns[node] * (1.0 + kEtaSigns[node] * eta);
data.eta_derivatives[node] =
0.25 * kEtaSigns[node] * (1.0 + kXiSigns[node] * xi);
}
return data;
}
Vector3 weightedSum(
const std::array<double, 4>& weights,
const std::array<Vector3, 4>& values) {
Vector3 result{};
for (std::size_t node = 0U; node < values.size(); ++node) {
result = add(result, scale(weights[node], values[node]));
}
return result;
Vector3 WeightedSum(const std::array<double, 4>& weights,
const std::array<Vector3, 4>& values) {
Vector3 result{};
for (std::size_t node = 0U; node < values.size(); ++node) {
result = Add(result, Scale(weights[node], values[node]));
}
return result;
}
Vector3 derivativeSum(
const std::array<double, 4>& derivatives,
const std::array<Vector3, 4>& coordinates) {
// Shape derivatives sum to zero, so translating by node 1 improves the
// numerical cancellation without changing the covariant tangent.
std::array<Vector3, 4> relative{};
for (std::size_t node = 0U; node < coordinates.size(); ++node) {
relative[node] = subtract(coordinates[node], coordinates[0]);
}
return weightedSum(derivatives, relative);
/// @brief Forms a translation-invariant covariant tangent in node order.
Vector3 DerivativeSum(const std::array<double, 4>& derivatives,
const std::array<Vector3, 4>& coordinates) {
// Shape derivatives sum to zero, so translating by node 1 improves the
// numerical cancellation without changing the covariant tangent.
std::array<Vector3, 4> relative{};
for (std::size_t node = 0U; node < coordinates.size(); ++node) {
relative[node] = Subtract(coordinates[node], coordinates[0]);
}
return WeightedSum(derivatives, relative);
}
Result<ShellGeometry> geometryFailure(
std::string code,
const SourceLocation& location,
std::string keyword,
std::string identity,
std::string message) {
return Result<ShellGeometry>::Failure(Status::Failure(
FailureCategory::kModel,
{{Severity::kError,
std::move(code),
location,
std::move(keyword),
std::move(identity),
std::move(message)}}));
Result<ShellGeometry> GeometryFailure(std::string code,
const SourceLocation& location,
std::string keyword, std::string identity,
std::string message) {
return Result<ShellGeometry>::Failure(Status::Failure(
FailureCategory::kModel,
{{Severity::kError, std::move(code), location, std::move(keyword),
std::move(identity), std::move(message)}}));
}
bool sameCoordinates(const Vector3& left, const Vector3& right) {
return left == right;
bool SameCoordinates(const Vector3& left, const Vector3& right) {
return left == right;
}
double orientation(
const Vector3& first,
const Vector3& second,
const Vector3& third,
const Vector3& normal) {
return dot(cross(subtract(second, first), subtract(third, first)), normal);
double Orientation(const Vector3& first, const Vector3& second,
const Vector3& third, const Vector3& normal) {
return Dot(Cross(Subtract(second, first), Subtract(third, first)), normal);
}
bool hasOppositeSigns(double first, double second) {
return (first < 0.0 && second > 0.0) ||
(first > 0.0 && second < 0.0);
bool HasOppositeSigns(double first, double second) {
return (first < 0.0 && second > 0.0) || (first > 0.0 && second < 0.0);
}
bool segmentsProperlyIntersect(
const Vector3& firstStart,
const Vector3& firstEnd,
const Vector3& secondStart,
const Vector3& secondEnd,
const Vector3& normal) {
const double firstSideStart =
orientation(firstStart, firstEnd, secondStart, normal);
const double firstSideEnd =
orientation(firstStart, firstEnd, secondEnd, normal);
const double secondSideStart =
orientation(secondStart, secondEnd, firstStart, normal);
const double secondSideEnd =
orientation(secondStart, secondEnd, firstEnd, normal);
return hasOppositeSigns(firstSideStart, firstSideEnd) &&
hasOppositeSigns(secondSideStart, secondSideEnd);
/// @brief Detects a proper projected edge crossing without a tolerance clamp.
bool SegmentsProperlyIntersect(const Vector3& first_start,
const Vector3& first_end,
const Vector3& second_start,
const Vector3& second_end,
const Vector3& normal) {
const double first_side_start =
Orientation(first_start, first_end, second_start, normal);
const double first_side_end =
Orientation(first_start, first_end, second_end, normal);
const double second_side_start =
Orientation(second_start, second_end, first_start, normal);
const double second_side_end =
Orientation(second_start, second_end, first_end, normal);
return HasOppositeSigns(first_side_start, first_side_end) &&
HasOppositeSigns(second_side_start, second_side_end);
}
bool sourceIdentityLess(
const Mitc4ShellDefinition& left,
std::size_t leftIndex,
const Mitc4ShellDefinition& right,
std::size_t rightIndex) {
return std::tie(
left.sourceId.instance_name,
left.sourceId.source_label,
left.sourceId.source_label_text,
leftIndex) <
std::tie(
right.sourceId.instance_name,
right.sourceId.source_label,
right.sourceId.source_label_text,
rightIndex);
/// @brief Orders incident shell contributions by stable source identity.
bool SourceIdentityLess(const Mitc4ShellDefinition& left,
std::size_t left_index,
const Mitc4ShellDefinition& right,
std::size_t right_index) {
return std::tie(left.source_id.instance_name, left.source_id.source_label,
left.source_id.source_label_text, left_index) <
std::tie(right.source_id.instance_name, right.source_id.source_label,
right.source_id.source_label_text, right_index);
}
} // namespace
} // namespace
const std::array<ShellGeometryValidationPoint, 17>&
shellGeometryValidationPoints() noexcept {
static const std::array<ShellGeometryValidationPoint, 17> points = [] {
const double g = 1.0 / std::sqrt(3.0);
return std::array<ShellGeometryValidationPoint, 17>{
ShellGeometryValidationPoint{
ShellGeometryPointKind::center, 0U, {0.0, 0.0, 0.0}},
{ShellGeometryPointKind::stiffness, 0U, {-g, -g, -g}},
{ShellGeometryPointKind::stiffness, 1U, {-g, -g, g}},
{ShellGeometryPointKind::stiffness, 2U, {g, -g, -g}},
{ShellGeometryPointKind::stiffness, 3U, {g, -g, g}},
{ShellGeometryPointKind::stiffness, 4U, {g, g, -g}},
{ShellGeometryPointKind::stiffness, 5U, {g, g, g}},
{ShellGeometryPointKind::stiffness, 6U, {-g, g, -g}},
{ShellGeometryPointKind::stiffness, 7U, {-g, g, g}},
{ShellGeometryPointKind::tying, 0U, {0.0, -1.0, 0.0}},
{ShellGeometryPointKind::tying, 1U, {0.0, 1.0, 0.0}},
{ShellGeometryPointKind::tying, 2U, {-1.0, 0.0, 0.0}},
{ShellGeometryPointKind::tying, 3U, {1.0, 0.0, 0.0}},
{ShellGeometryPointKind::recovery, 0U, {-g, -g, 0.0}},
{ShellGeometryPointKind::recovery, 1U, {g, -g, 0.0}},
{ShellGeometryPointKind::recovery, 2U, {g, g, 0.0}},
{ShellGeometryPointKind::recovery, 3U, {-g, g, 0.0}}};
}();
return points;
ShellGeometryValidationPoints() noexcept {
static const std::array<ShellGeometryValidationPoint, 17> points = [] {
const double g = 1.0 / std::sqrt(3.0);
return std::array<ShellGeometryValidationPoint, 17>{
ShellGeometryValidationPoint{
ShellGeometryPointKind::kCenter, 0U, {0.0, 0.0, 0.0}},
{ShellGeometryPointKind::kStiffness, 0U, {-g, -g, -g}},
{ShellGeometryPointKind::kStiffness, 1U, {-g, -g, g}},
{ShellGeometryPointKind::kStiffness, 2U, {g, -g, -g}},
{ShellGeometryPointKind::kStiffness, 3U, {g, -g, g}},
{ShellGeometryPointKind::kStiffness, 4U, {g, g, -g}},
{ShellGeometryPointKind::kStiffness, 5U, {g, g, g}},
{ShellGeometryPointKind::kStiffness, 6U, {-g, g, -g}},
{ShellGeometryPointKind::kStiffness, 7U, {-g, g, g}},
{ShellGeometryPointKind::kTying, 0U, {0.0, -1.0, 0.0}},
{ShellGeometryPointKind::kTying, 1U, {0.0, 1.0, 0.0}},
{ShellGeometryPointKind::kTying, 2U, {-1.0, 0.0, 0.0}},
{ShellGeometryPointKind::kTying, 3U, {1.0, 0.0, 0.0}},
{ShellGeometryPointKind::kRecovery, 0U, {-g, -g, 0.0}},
{ShellGeometryPointKind::kRecovery, 1U, {g, -g, 0.0}},
{ShellGeometryPointKind::kRecovery, 2U, {g, g, 0.0}},
{ShellGeometryPointKind::kRecovery, 3U, {-g, g, 0.0}}};
}();
return points;
}
Result<ShellGeometry> preprocessShellGeometry(
Result<ShellGeometry> PreprocessShellGeometry(
const std::vector<Node>& nodes,
const std::vector<Mitc4ShellDefinition>& elements,
const std::vector<ShellSection>& sections) {
ShellGeometry geometry;
geometry.elementData.reserve(elements.size());
std::vector<ElementWork> work;
work.reserve(elements.size());
ShellGeometry geometry;
geometry.element_data.reserve(elements.size());
std::vector<ElementWork> work;
work.reserve(elements.size());
const double g = 1.0 / std::sqrt(3.0);
const std::array<Vector3, 4> surfaceGaussPoints{
Vector3{-g, -g, 0.0},
Vector3{g, -g, 0.0},
Vector3{g, g, 0.0},
Vector3{-g, g, 0.0}};
const double g = 1.0 / std::sqrt(3.0);
const std::array<Vector3, 4> surface_gauss_points{
Vector3{-g, -g, 0.0}, Vector3{g, -g, 0.0}, Vector3{g, g, 0.0},
Vector3{-g, g, 0.0}};
for (std::size_t elementIndex = 0U;
elementIndex < elements.size();
++elementIndex) {
const auto& element = elements[elementIndex];
ElementWork current{};
for (std::size_t localNode = 0U; localNode < 4U; ++localNode) {
if (element.nodeIndices[localNode] >= nodes.size()) {
return geometryFailure(
"invalid-shell-geometry", element.location, "ELEMENT",
element.sourceId.source_label_text,
"Shell geometry references an unavailable internal node.");
}
current.coordinates[localNode] =
nodes[element.nodeIndices[localNode]].coordinates;
if (!isFinite(current.coordinates[localNode])) {
return geometryFailure(
"invalid-shell-geometry", element.location, "ELEMENT",
element.sourceId.source_label_text,
"Shell geometry contains a nonfinite source coordinate.");
}
for (std::size_t element_index = 0U; element_index < elements.size();
++element_index) {
const auto& element = elements[element_index];
ElementWork current{};
for (std::size_t local_node = 0U; local_node < 4U; ++local_node) {
if (element.node_indices[local_node] >= nodes.size()) {
return GeometryFailure(
"invalid-shell-geometry", element.location, "ELEMENT",
element.source_id.source_label_text,
"Shell geometry references an unavailable internal node.");
}
current.coordinates[local_node] =
nodes[element.node_indices[local_node]].coordinates;
if (!IsFinite(current.coordinates[local_node])) {
return GeometryFailure(
"invalid-shell-geometry", element.location, "ELEMENT",
element.source_id.source_label_text,
"Shell geometry contains a nonfinite source coordinate.");
}
}
for (std::size_t first = 0U; first < 4U; ++first) {
for (std::size_t second = first + 1U; second < 4U; ++second) {
if (element.node_indices[first] == element.node_indices[second] ||
SameCoordinates(current.coordinates[first],
current.coordinates[second])) {
return GeometryFailure("invalid-shell-geometry", element.location,
"ELEMENT", element.source_id.source_label_text,
"Shell geometry contains duplicate nodes.");
}
for (std::size_t first = 0U; first < 4U; ++first) {
for (std::size_t second = first + 1U; second < 4U; ++second) {
if (element.nodeIndices[first] == element.nodeIndices[second] ||
sameCoordinates(
current.coordinates[first], current.coordinates[second])) {
return geometryFailure(
"invalid-shell-geometry", element.location, "ELEMENT",
element.sourceId.source_label_text,
"Shell geometry contains duplicate nodes.");
}
}
}
const ShapeData center = shapeData(0.0, 0.0);
const Vector3 centerXi =
derivativeSum(center.xiDerivatives, current.coordinates);
const Vector3 centerEta =
derivativeSum(center.etaDerivatives, current.coordinates);
const Vector3 centerCross = cross(centerXi, centerEta);
const double centerMeasure = norm(centerCross);
if (!isFinite(centerXi) || !isFinite(centerEta) ||
!isFinite(centerCross) || !std::isfinite(centerMeasure) ||
!(centerMeasure > 0.0)) {
return geometryFailure(
"invalid-shell-geometry", element.location, "ELEMENT",
element.sourceId.source_label_text,
"Shell center has no finite nonzero normal candidate.");
}
current.normal = scale(1.0 / centerMeasure, centerCross);
if (segmentsProperlyIntersect(
current.coordinates[0], current.coordinates[1],
current.coordinates[2], current.coordinates[3], current.normal) ||
segmentsProperlyIntersect(
current.coordinates[1], current.coordinates[2],
current.coordinates[3], current.coordinates[0], current.normal)) {
return geometryFailure(
"invalid-shell-geometry", element.location, "ELEMENT",
element.sourceId.source_label_text,
"Shell boundary is self-intersecting in the center-normal projection.");
}
double areaWeight = 0.0;
for (const auto& point : surfaceGaussPoints) {
const ShapeData shape = shapeData(point[0], point[1]);
const Vector3 tangentXi =
derivativeSum(shape.xiDerivatives, current.coordinates);
const Vector3 tangentEta =
derivativeSum(shape.etaDerivatives, current.coordinates);
const Vector3 areaVector = cross(tangentXi, tangentEta);
const double measure = norm(areaVector);
if (!isFinite(tangentXi) || !isFinite(tangentEta) ||
!isFinite(areaVector) || !std::isfinite(measure) ||
!(measure > 0.0) || !(dot(areaVector, current.normal) > 0.0)) {
return geometryFailure(
"invalid-shell-geometry", element.location, "ELEMENT",
element.sourceId.source_label_text,
"Shell surface is zero-area or locally reversed at a required point.");
}
areaWeight += measure;
}
if (!std::isfinite(areaWeight) || !(areaWeight > 0.0)) {
return geometryFailure(
"invalid-shell-geometry", element.location, "ELEMENT",
element.sourceId.source_label_text,
"Shell surface-area weight is nonfinite or zero.");
}
current.areaWeight = areaWeight;
work.push_back(current);
geometry.elementData.push_back({
static_cast<EntityIndex>(elementIndex), current.normal, areaWeight});
}
}
std::vector<std::vector<std::size_t>> incident(nodes.size());
for (std::size_t elementIndex = 0U;
elementIndex < elements.size();
++elementIndex) {
for (const EntityIndex nodeIndex : elements[elementIndex].nodeIndices) {
incident[nodeIndex].push_back(elementIndex);
}
const ShapeData center = ShapeDataAt(0.0, 0.0);
const Vector3 center_xi =
DerivativeSum(center.xi_derivatives, current.coordinates);
const Vector3 center_eta =
DerivativeSum(center.eta_derivatives, current.coordinates);
const Vector3 center_cross = Cross(center_xi, center_eta);
const double center_measure = Norm(center_cross);
if (!IsFinite(center_xi) || !IsFinite(center_eta) ||
!IsFinite(center_cross) || !std::isfinite(center_measure) ||
!(center_measure > 0.0)) {
return GeometryFailure(
"invalid-shell-geometry", element.location, "ELEMENT",
element.source_id.source_label_text,
"Shell center has no finite nonzero normal candidate.");
}
current.normal = Scale(1.0 / center_measure, center_cross);
if (SegmentsProperlyIntersect(
current.coordinates[0], current.coordinates[1],
current.coordinates[2], current.coordinates[3], current.normal) ||
SegmentsProperlyIntersect(
current.coordinates[1], current.coordinates[2],
current.coordinates[3], current.coordinates[0], current.normal)) {
return GeometryFailure("invalid-shell-geometry", element.location,
"ELEMENT", element.source_id.source_label_text,
"Shell boundary is self-intersecting in the "
"center-normal projection.");
}
geometry.nodalFrames.reserve(nodes.size());
std::vector<const ShellNodeInitialFrame*> frameByNode(nodes.size(), nullptr);
for (std::size_t nodeIndex = 0U; nodeIndex < incident.size(); ++nodeIndex) {
auto& nodeIncident = incident[nodeIndex];
if (nodeIncident.empty()) {
continue;
}
std::sort(
nodeIncident.begin(), nodeIncident.end(),
[&elements](std::size_t left, std::size_t right) {
return sourceIdentityLess(
elements[left], left, elements[right], right);
});
for (std::size_t first = 0U; first < nodeIncident.size(); ++first) {
for (std::size_t second = first + 1U;
second < nodeIncident.size();
++second) {
const double pairDot = dot(
work[nodeIncident[first]].normal,
work[nodeIncident[second]].normal);
if (!std::isfinite(pairDot) || !(pairDot > 0.0)) {
return geometryFailure(
"opposed-incident-normal", nodes[nodeIndex].location,
"NODE", nodes[nodeIndex].sourceId.source_label_text,
"Incident shell normal candidates do not share a positive orientation hemisphere.");
}
}
}
double area_weight = 0.0;
for (const auto& point : surface_gauss_points) {
const ShapeData shape = ShapeDataAt(point[0], point[1]);
const Vector3 tangent_xi =
DerivativeSum(shape.xi_derivatives, current.coordinates);
const Vector3 tangent_eta =
DerivativeSum(shape.eta_derivatives, current.coordinates);
const Vector3 area_vector = Cross(tangent_xi, tangent_eta);
const double measure = Norm(area_vector);
if (!IsFinite(tangent_xi) || !IsFinite(tangent_eta) ||
!IsFinite(area_vector) || !std::isfinite(measure) ||
!(measure > 0.0) || !(Dot(area_vector, current.normal) > 0.0)) {
return GeometryFailure("invalid-shell-geometry", element.location,
"ELEMENT", element.source_id.source_label_text,
"Shell surface is zero-area or locally reversed "
"at a required point.");
}
area_weight += measure;
}
if (!std::isfinite(area_weight) || !(area_weight > 0.0)) {
return GeometryFailure("invalid-shell-geometry", element.location,
"ELEMENT", element.source_id.source_label_text,
"Shell surface-area weight is nonfinite or zero.");
}
current.area_weight = area_weight;
work.push_back(current);
geometry.element_data.push_back(
{static_cast<EntityIndex>(element_index), current.normal, area_weight});
}
double maximumWeight = 0.0;
for (const std::size_t elementIndex : nodeIncident) {
maximumWeight = std::max(maximumWeight, work[elementIndex].areaWeight);
}
Vector3 directorSum{};
for (const std::size_t elementIndex : nodeIncident) {
directorSum = add(
directorSum,
scale(
work[elementIndex].areaWeight / maximumWeight,
work[elementIndex].normal));
}
const double directorNorm = norm(directorSum);
if (!isFinite(directorSum) || !std::isfinite(directorNorm) ||
!(directorNorm > 0.0)) {
return geometryFailure(
"invalid-shell-director", nodes[nodeIndex].location, "NODE",
nodes[nodeIndex].sourceId.source_label_text,
"Area-weighted shell director is nonfinite or zero.");
}
const Vector3 director = scale(1.0 / directorNorm, directorSum);
std::vector<std::vector<std::size_t>> incident(nodes.size());
for (std::size_t element_index = 0U; element_index < elements.size();
++element_index) {
for (const EntityIndex node_index : elements[element_index].node_indices) {
incident[node_index].push_back(element_index);
}
}
const std::array<Vector3, 3> globalAxes{
Vector3{1.0, 0.0, 0.0},
Vector3{0.0, 1.0, 0.0},
Vector3{0.0, 0.0, 1.0}};
std::size_t selectedAxis = 0U;
double selectedAlignment = std::abs(dot(globalAxes[0], director));
for (std::size_t axis = 1U; axis < globalAxes.size(); ++axis) {
const double alignment = std::abs(dot(globalAxes[axis], director));
if (alignment < selectedAlignment) {
selectedAlignment = alignment;
selectedAxis = axis;
}
geometry.nodal_frames.reserve(nodes.size());
std::vector<const ShellNodeInitialFrame*> frame_by_node(nodes.size(),
nullptr);
for (std::size_t node_index = 0U; node_index < incident.size();
++node_index) {
auto& node_incident = incident[node_index];
if (node_incident.empty()) {
continue;
}
std::sort(node_incident.begin(), node_incident.end(),
[&elements](std::size_t left, std::size_t right) {
return SourceIdentityLess(elements[left], left, elements[right],
right);
});
for (std::size_t first = 0U; first < node_incident.size(); ++first) {
for (std::size_t second = first + 1U; second < node_incident.size();
++second) {
const double pair_dot = Dot(work[node_incident[first]].normal,
work[node_incident[second]].normal);
if (!std::isfinite(pair_dot) || !(pair_dot > 0.0)) {
return GeometryFailure("opposed-incident-normal",
nodes[node_index].location, "NODE",
nodes[node_index].source_id.source_label_text,
"Incident shell normal candidates do not "
"share a positive orientation hemisphere.");
}
const Vector3 tangentCandidate = subtract(
globalAxes[selectedAxis],
scale(dot(globalAxes[selectedAxis], director), director));
const double tangentNorm = norm(tangentCandidate);
if (!isFinite(tangentCandidate) || !std::isfinite(tangentNorm) ||
!(tangentNorm > 0.0)) {
return geometryFailure(
"invalid-shell-director", nodes[nodeIndex].location, "NODE",
nodes[nodeIndex].sourceId.source_label_text,
"Least-aligned-axis tangent frame construction failed.");
}
const Vector3 tangentA = scale(1.0 / tangentNorm, tangentCandidate);
const Vector3 tangentB = cross(director, tangentA);
if (!isFinite(tangentB) || !(norm(tangentB) > 0.0)) {
return geometryFailure(
"invalid-shell-director", nodes[nodeIndex].location, "NODE",
nodes[nodeIndex].sourceId.source_label_text,
"Right-handed shell tangent frame construction failed.");
}
geometry.nodalFrames.push_back({
static_cast<EntityIndex>(nodeIndex), director, tangentA, tangentB});
}
}
// Build lookup only after frame storage is complete so later code never
// observes a pointer invalidated by vector growth.
for (const auto& frame : geometry.nodalFrames) {
frameByNode[frame.nodeIndex] = &frame;
double maximum_weight = 0.0;
for (const std::size_t element_index : node_incident) {
maximum_weight =
std::max(maximum_weight, work[element_index].area_weight);
}
Vector3 director_sum{};
for (const std::size_t element_index : node_incident) {
director_sum = Add(director_sum,
Scale(work[element_index].area_weight / maximum_weight,
work[element_index].normal));
}
const double director_norm = Norm(director_sum);
if (!IsFinite(director_sum) || !std::isfinite(director_norm) ||
!(director_norm > 0.0)) {
return GeometryFailure(
"invalid-shell-director", nodes[node_index].location, "NODE",
nodes[node_index].source_id.source_label_text,
"Area-weighted shell director is nonfinite or zero.");
}
const Vector3 director = Scale(1.0 / director_norm, director_sum);
const std::array<Vector3, 3> global_axes{
Vector3{1.0, 0.0, 0.0}, Vector3{0.0, 1.0, 0.0}, Vector3{0.0, 0.0, 1.0}};
std::size_t selected_axis = 0U;
double selected_alignment = std::abs(Dot(global_axes[0], director));
for (std::size_t axis = 1U; axis < global_axes.size(); ++axis) {
const double alignment = std::abs(Dot(global_axes[axis], director));
if (alignment < selected_alignment) {
selected_alignment = alignment;
selected_axis = axis;
}
}
const Vector3 tangent_candidate =
Subtract(global_axes[selected_axis],
Scale(Dot(global_axes[selected_axis], director), director));
const double tangent_norm = Norm(tangent_candidate);
if (!IsFinite(tangent_candidate) || !std::isfinite(tangent_norm) ||
!(tangent_norm > 0.0)) {
return GeometryFailure(
"invalid-shell-director", nodes[node_index].location, "NODE",
nodes[node_index].source_id.source_label_text,
"Least-aligned-axis tangent frame construction failed.");
}
const Vector3 tangent_a = Scale(1.0 / tangent_norm, tangent_candidate);
const Vector3 tangent_b = Cross(director, tangent_a);
if (!IsFinite(tangent_b) || !(Norm(tangent_b) > 0.0)) {
return GeometryFailure(
"invalid-shell-director", nodes[node_index].location, "NODE",
nodes[node_index].source_id.source_label_text,
"Right-handed shell tangent frame construction failed.");
}
geometry.nodal_frames.push_back(
{static_cast<EntityIndex>(node_index), director, tangent_a, tangent_b});
}
// Build lookup only after frame storage is complete so later code never
// observes a pointer invalidated by vector growth.
for (const auto& frame : geometry.nodal_frames) {
frame_by_node[frame.node_index] = &frame;
}
for (std::size_t element_index = 0U; element_index < elements.size();
++element_index) {
const auto& element = elements[element_index];
if (element.section_index >= sections.size()) {
return GeometryFailure("invalid-shell-jacobian", element.location,
"ELEMENT", element.source_id.source_label_text,
"Shell geometry cannot resolve its thickness for "
"Jacobian validation.");
}
const double thickness = sections[element.section_index].thickness;
std::array<Vector3, 4> directors{};
for (std::size_t local_node = 0U; local_node < 4U; ++local_node) {
const auto* frame = frame_by_node[element.node_indices[local_node]];
if (frame == nullptr) {
return GeometryFailure("invalid-shell-director", element.location,
"ELEMENT", element.source_id.source_label_text,
"Shell element is missing a nodal director.");
}
directors[local_node] = frame->director;
}
for (std::size_t elementIndex = 0U;
elementIndex < elements.size();
++elementIndex) {
const auto& element = elements[elementIndex];
if (element.sectionIndex >= sections.size()) {
return geometryFailure(
"invalid-shell-jacobian", element.location, "ELEMENT",
element.sourceId.source_label_text,
"Shell geometry cannot resolve its thickness for Jacobian validation.");
}
const double thickness = sections[element.sectionIndex].thickness;
std::array<Vector3, 4> directors{};
for (std::size_t localNode = 0U; localNode < 4U; ++localNode) {
const auto* frame = frameByNode[element.nodeIndices[localNode]];
if (frame == nullptr) {
return geometryFailure(
"invalid-shell-director", element.location, "ELEMENT",
element.sourceId.source_label_text,
"Shell element is missing a nodal director.");
}
directors[localNode] = frame->director;
}
for (const auto& point : ShellGeometryValidationPoints()) {
const double xi = point.natural_coordinates[0];
const double eta = point.natural_coordinates[1];
const double zeta = point.natural_coordinates[2];
const ShapeData shape = ShapeDataAt(xi, eta);
const Vector3 midsurface_xi =
DerivativeSum(shape.xi_derivatives, work[element_index].coordinates);
const Vector3 midsurface_eta =
DerivativeSum(shape.eta_derivatives, work[element_index].coordinates);
const Vector3 area_vector = Cross(midsurface_xi, midsurface_eta);
const double surface_measure = Norm(area_vector);
if (!IsFinite(midsurface_xi) || !IsFinite(midsurface_eta) ||
!IsFinite(area_vector) || !std::isfinite(surface_measure) ||
!(surface_measure > 0.0) ||
!(Dot(area_vector, work[element_index].normal) > 0.0)) {
return GeometryFailure("invalid-shell-geometry", element.location,
"ELEMENT", element.source_id.source_label_text,
"Shell surface basis is nonfinite, zero, or "
"reversed at a required point.");
}
for (const auto& point : shellGeometryValidationPoints()) {
const double xi = point.naturalCoordinates[0];
const double eta = point.naturalCoordinates[1];
const double zeta = point.naturalCoordinates[2];
const ShapeData shape = shapeData(xi, eta);
const Vector3 midsurfaceXi =
derivativeSum(shape.xiDerivatives, work[elementIndex].coordinates);
const Vector3 midsurfaceEta =
derivativeSum(shape.etaDerivatives, work[elementIndex].coordinates);
const Vector3 areaVector = cross(midsurfaceXi, midsurfaceEta);
const double surfaceMeasure = norm(areaVector);
if (!isFinite(midsurfaceXi) || !isFinite(midsurfaceEta) ||
!isFinite(areaVector) || !std::isfinite(surfaceMeasure) ||
!(surfaceMeasure > 0.0) ||
!(dot(areaVector, work[elementIndex].normal) > 0.0)) {
return geometryFailure(
"invalid-shell-geometry", element.location, "ELEMENT",
element.sourceId.source_label_text,
"Shell surface basis is nonfinite, zero, or reversed at a required point.");
}
const Vector3 directorXi =
weightedSum(shape.xiDerivatives, directors);
const Vector3 directorEta =
weightedSum(shape.etaDerivatives, directors);
const Vector3 directorValue = weightedSum(shape.values, directors);
const Vector3 covariantXi = add(
midsurfaceXi, scale(0.5 * thickness * zeta, directorXi));
const Vector3 covariantEta = add(
midsurfaceEta, scale(0.5 * thickness * zeta, directorEta));
const Vector3 covariantZeta = scale(0.5 * thickness, directorValue);
const double jacobian =
dot(covariantXi, cross(covariantEta, covariantZeta));
if (!isFinite(covariantXi) || !isFinite(covariantEta) ||
!isFinite(covariantZeta) || !std::isfinite(jacobian) ||
!(jacobian > 0.0)) {
return geometryFailure(
"invalid-shell-jacobian", element.location, "ELEMENT",
element.sourceId.source_label_text,
"Shell Jacobian is nonfinite or nonpositive at a required point.");
}
const Vector3 reciprocalXi =
scale(1.0 / jacobian, cross(covariantEta, covariantZeta));
const Vector3 reciprocalEta =
scale(1.0 / jacobian, cross(covariantZeta, covariantXi));
const Vector3 reciprocalZeta =
scale(1.0 / jacobian, cross(covariantXi, covariantEta));
if (!isFinite(reciprocalXi) || !isFinite(reciprocalEta) ||
!isFinite(reciprocalZeta)) {
return geometryFailure(
"invalid-shell-jacobian", element.location, "ELEMENT",
element.sourceId.source_label_text,
"Shell reciprocal basis is nonfinite at a required point.");
}
}
const Vector3 director_xi = WeightedSum(shape.xi_derivatives, directors);
const Vector3 director_eta =
WeightedSum(shape.eta_derivatives, directors);
const Vector3 director_value = WeightedSum(shape.values, directors);
const Vector3 covariant_xi =
Add(midsurface_xi, Scale(0.5 * thickness * zeta, director_xi));
const Vector3 covariant_eta =
Add(midsurface_eta, Scale(0.5 * thickness * zeta, director_eta));
const Vector3 covariant_zeta = Scale(0.5 * thickness, director_value);
const double jacobian =
Dot(covariant_xi, Cross(covariant_eta, covariant_zeta));
if (!IsFinite(covariant_xi) || !IsFinite(covariant_eta) ||
!IsFinite(covariant_zeta) || !std::isfinite(jacobian) ||
!(jacobian > 0.0)) {
return GeometryFailure(
"invalid-shell-jacobian", element.location, "ELEMENT",
element.source_id.source_label_text,
"Shell Jacobian is nonfinite or nonpositive at a required point.");
}
const Vector3 reciprocal_xi =
Scale(1.0 / jacobian, Cross(covariant_eta, covariant_zeta));
const Vector3 reciprocal_eta =
Scale(1.0 / jacobian, Cross(covariant_zeta, covariant_xi));
const Vector3 reciprocal_zeta =
Scale(1.0 / jacobian, Cross(covariant_xi, covariant_eta));
if (!IsFinite(reciprocal_xi) || !IsFinite(reciprocal_eta) ||
!IsFinite(reciprocal_zeta)) {
return GeometryFailure(
"invalid-shell-jacobian", element.location, "ELEMENT",
element.source_id.source_label_text,
"Shell reciprocal basis is nonfinite at a required point.");
}
}
}
return Result<ShellGeometry>::Success(std::move(geometry));
return Result<ShellGeometry>::Success(std::move(geometry));
}
} // namespace fesa
} // namespace fesa
+149 -149
View File
@@ -1,7 +1,7 @@
#include "fesa/results/result_recovery.hpp"
#include "fesa/elements/euler_beam_3d.hpp"
#include "fesa/elements/mitc4_shell.hpp"
#include "fesa/elements/euler_beam_3d.h"
#include "fesa/elements/mitc4_shell.h"
#include <algorithm>
#include <array>
@@ -131,15 +131,15 @@ Status validateRecoveryInputs(const AnalysisModel& model,
const SparseMatrix& fullStiffness,
const AnalysisState& state) {
const Domain& domain = model.domain();
if (domain.nodes().size() >
if (domain.Nodes().size() >
(std::numeric_limits<std::size_t>::max)() / kDofsPerNode) {
return recoveryFailure(
"invalid-recovery-dimensions",
{domain.sourcePath(), 0U},
domain.sourceContentIdentity(),
{domain.SourcePath(), 0U},
domain.SourceContentIdentity(),
"The semantic node count cannot be represented in full-DOF space.");
}
const std::size_t fullCount = domain.nodes().size() * kDofsPerNode;
const std::size_t fullCount = domain.Nodes().size() * kDofsPerNode;
if (dofs.fullDofCount() != fullCount ||
fullStiffness.Rows() != fullCount ||
fullStiffness.Columns() != fullCount ||
@@ -150,8 +150,8 @@ Status validateRecoveryInputs(const AnalysisModel& model,
state.reaction().Size() != fullCount) {
return recoveryFailure(
"invalid-recovery-dimensions",
{domain.sourcePath(), 0U},
domain.sourceContentIdentity(),
{domain.SourcePath(), 0U},
domain.SourceContentIdentity(),
"Model, DOF, stiffness, and AnalysisState full-space dimensions must agree.");
}
const Status matrixStatus = fullStiffness.Validate();
@@ -161,8 +161,8 @@ Status validateRecoveryInputs(const AnalysisModel& model,
if (!finite(state.displacement()) || !finite(state.externalForce())) {
return recoveryFailure(
"nonfinite-recovery-value",
{domain.sourcePath(), 0U},
domain.sourceContentIdentity(),
{domain.SourcePath(), 0U},
domain.SourceContentIdentity(),
"Displacement and external-force inputs must be finite.");
}
@@ -176,8 +176,8 @@ Status validateRecoveryInputs(const AnalysisModel& model,
!strictlyIncreasing(constrainedDofs)) {
return recoveryFailure(
"invalid-recovery-order",
{domain.sourcePath(), 0U},
domain.sourceContentIdentity(),
{domain.SourcePath(), 0U},
domain.SourceContentIdentity(),
"Free and constrained DOFs must form stable increasing full-space orders.");
}
std::vector<unsigned char> ownership(fullCount, 0U);
@@ -188,7 +188,7 @@ Status validateRecoveryInputs(const AnalysisModel& model,
dofs.freeEquation(fullDof) != equation) {
return recoveryFailure(
"invalid-recovery-order",
{domain.sourcePath(), 0U},
{domain.SourcePath(), 0U},
std::to_string(fullDof),
"Free equations must match stable full-DOF order.");
}
@@ -202,7 +202,7 @@ Status validateRecoveryInputs(const AnalysisModel& model,
dofs.freeEquation(fullDof).has_value()) {
return recoveryFailure(
"invalid-recovery-order",
{domain.sourcePath(), 0U},
{domain.SourcePath(), 0U},
std::to_string(fullDof),
"Constrained DOFs must be unique and absent from free equations.");
}
@@ -211,7 +211,7 @@ Status validateRecoveryInputs(const AnalysisModel& model,
dofs.prescribedValues()[constrained]) {
return recoveryFailure(
"invalid-recovery-state",
{domain.sourcePath(), 0U},
{domain.SourcePath(), 0U},
std::to_string(fullDof),
"Constrained displacement must equal its prescribed value before recovery.");
}
@@ -220,40 +220,40 @@ Status validateRecoveryInputs(const AnalysisModel& model,
} catch (const std::out_of_range&) {
return recoveryFailure(
"invalid-recovery-dimensions",
{domain.sourcePath(), 0U},
domain.sourceContentIdentity(),
{domain.SourcePath(), 0U},
domain.SourceContentIdentity(),
"DofManager equation storage must cover every full DOF.");
}
if (std::find(ownership.begin(), ownership.end(), 0U) != ownership.end()) {
return recoveryFailure(
"invalid-recovery-order",
{domain.sourcePath(), 0U},
domain.sourceContentIdentity(),
{domain.SourcePath(), 0U},
domain.SourceContentIdentity(),
"Free and constrained DOFs must partition the full range.");
}
EntityIndex previousElement = 0U;
bool firstElement = true;
for (const EntityIndex element : model.activeElements()) {
if (element >= domain.elements().size() ||
if (element >= domain.Elements().size() ||
(!firstElement && element <= previousElement)) {
return recoveryFailure(
"invalid-recovery-entity",
{domain.sourcePath(), 0U},
{domain.SourcePath(), 0U},
std::to_string(element),
"Active elements must be unique in stable internal-index order.");
}
firstElement = false;
previousElement = element;
const auto& definition = domain.elements()[element];
if (definition.nodeIndices[0U] >= domain.nodes().size() ||
definition.nodeIndices[1U] >= domain.nodes().size() ||
definition.materialIndex >= domain.materials().size() ||
definition.sectionIndex >= domain.sections().size()) {
const auto& definition = domain.Elements()[element];
if (definition.node_indices[0U] >= domain.Nodes().size() ||
definition.node_indices[1U] >= domain.Nodes().size() ||
definition.material_index >= domain.Materials().size() ||
definition.section_index >= domain.Sections().size()) {
return recoveryFailure(
"invalid-recovery-entity",
definition.location,
definition.sourceId.source_label_text,
definition.source_id.source_label_text,
"Active beam references must resolve before recovery.");
}
try {
@@ -263,7 +263,7 @@ Status validateRecoveryInputs(const AnalysisModel& model,
component < kDofsPerNode;
++component) {
const std::size_t expected =
static_cast<std::size_t>(definition.nodeIndices[endpoint]) *
static_cast<std::size_t>(definition.node_indices[endpoint]) *
kDofsPerNode +
component;
if (scatter[endpoint * kDofsPerNode + component] != expected ||
@@ -271,7 +271,7 @@ Status validateRecoveryInputs(const AnalysisModel& model,
return recoveryFailure(
"invalid-recovery-order",
definition.location,
definition.sourceId.source_label_text,
definition.source_id.source_label_text,
"Element scatter must preserve endpoint/component full-DOF order.");
}
}
@@ -280,51 +280,51 @@ Status validateRecoveryInputs(const AnalysisModel& model,
return recoveryFailure(
"invalid-recovery-entity",
definition.location,
definition.sourceId.source_label_text,
definition.source_id.source_label_text,
"Every active element requires one twelve-DOF scatter map.");
}
}
if (!model.activeElements().empty() && !domain.shellElements().empty()) {
if (!model.activeElements().empty() && !domain.ShellElements().empty()) {
return recoveryFailure(
"unsupported-mixed-element-model",
{domain.sourcePath(), 0U},
{domain.SourcePath(), 0U},
"B33:FESA-MITC4",
"Result recovery does not support mixed beam and shell models.");
}
if (domain.shellElements().size() >
if (domain.ShellElements().size() >
static_cast<std::size_t>(
(std::numeric_limits<EntityIndex>::max)())) {
return recoveryFailure(
"invalid-recovery-dimensions",
{domain.sourcePath(), 0U},
domain.sourceContentIdentity(),
{domain.SourcePath(), 0U},
domain.SourceContentIdentity(),
"The shell element count cannot be represented by stable element identities.");
}
for (std::size_t elementOrder = 0U;
elementOrder < domain.shellElements().size();
elementOrder < domain.ShellElements().size();
++elementOrder) {
const auto& definition = domain.shellElements()[elementOrder];
if (definition.materialIndex >= domain.materials().size() ||
definition.sectionIndex >= domain.shellSections().size()) {
const auto& definition = domain.ShellElements()[elementOrder];
if (definition.material_index >= domain.Materials().size() ||
definition.section_index >= domain.ShellSections().size()) {
return recoveryFailure(
"invalid-recovery-entity",
definition.location,
definition.sourceId.source_label_text,
definition.source_id.source_label_text,
"Active shell material and section references must resolve before recovery.");
}
try {
const auto& scatter = dofs.shellElementScatter(
static_cast<EntityIndex>(elementOrder));
for (std::size_t nodePosition = 0U;
nodePosition < definition.nodeIndices.size();
nodePosition < definition.node_indices.size();
++nodePosition) {
const EntityIndex node = definition.nodeIndices[nodePosition];
if (node >= domain.nodes().size()) {
const EntityIndex node = definition.node_indices[nodePosition];
if (node >= domain.Nodes().size()) {
return recoveryFailure(
"invalid-recovery-entity",
definition.location,
definition.sourceId.source_label_text,
definition.source_id.source_label_text,
"Active shell node references must resolve before recovery.");
}
for (std::size_t component = 0U;
@@ -339,7 +339,7 @@ Status validateRecoveryInputs(const AnalysisModel& model,
return recoveryFailure(
"invalid-recovery-order",
definition.location,
definition.sourceId.source_label_text,
definition.source_id.source_label_text,
"Shell scatter must preserve node/component full-DOF order.");
}
}
@@ -348,7 +348,7 @@ Status validateRecoveryInputs(const AnalysisModel& model,
return recoveryFailure(
"invalid-recovery-entity",
definition.location,
definition.sourceId.source_label_text,
definition.source_id.source_label_text,
"Every active shell requires one twenty-four-DOF scatter map.");
}
}
@@ -381,7 +381,7 @@ bool tryPositiveInteger(const std::string& text, std::int64_t& value) {
Result<std::vector<EntityIndex>> resolveLoadTarget(const Domain& domain,
const NodalLoad& load) {
std::vector<const NodeSet*> sets;
for (const auto& set : domain.nodeSets()) {
for (const auto& set : domain.NodeSets()) {
if (equalName(set.name, load.target)) {
sets.push_back(&set);
}
@@ -389,8 +389,8 @@ Result<std::vector<EntityIndex>> resolveLoadTarget(const Domain& domain,
std::vector<EntityIndex> nodes;
std::int64_t sourceLabel = 0;
if (tryPositiveInteger(load.target, sourceLabel)) {
for (std::size_t node = 0U; node < domain.nodes().size(); ++node) {
if (domain.nodes()[node].sourceId.source_label == sourceLabel) {
for (std::size_t node = 0U; node < domain.Nodes().size(); ++node) {
if (domain.Nodes()[node].source_id.source_label == sourceLabel) {
nodes.push_back(static_cast<EntityIndex>(node));
}
}
@@ -404,9 +404,9 @@ Result<std::vector<EntityIndex>> resolveLoadTarget(const Domain& domain,
"A station-eligibility load target must resolve unambiguously.");
}
if (!sets.empty()) {
std::vector<unsigned char> seen(domain.nodes().size(), 0U);
for (const EntityIndex node : sets.front()->nodeIndices) {
if (node >= domain.nodes().size() || seen[node] != 0U) {
std::vector<unsigned char> seen(domain.Nodes().size(), 0U);
for (const EntityIndex node : sets.front()->node_indices) {
if (node >= domain.Nodes().size() || seen[node] != 0U) {
return recoveryResultFailure<std::vector<EntityIndex>>(
"invalid-node-station-entity",
load.location,
@@ -416,7 +416,7 @@ Result<std::vector<EntityIndex>> resolveLoadTarget(const Domain& domain,
seen[node] = 1U;
}
return Result<std::vector<EntityIndex>>::Success(
sets.front()->nodeIndices);
sets.front()->node_indices);
}
if (!nodes.empty()) {
return Result<std::vector<EntityIndex>>::Success(std::move(nodes));
@@ -504,7 +504,7 @@ Status populateShellGlobalEvidence(
double reactionForceScale = 0.0;
double appliedMomentScale = 0.0;
double reactionMomentScale = 0.0;
for (std::size_t node = 0U; node < domain.nodes().size(); ++node) {
for (std::size_t node = 0U; node < domain.Nodes().size(); ++node) {
const std::size_t offset = node * kDofsPerNode;
std::array<double, 3> nodalAppliedForce{};
std::array<double, 3> nodalReactionForce{};
@@ -524,9 +524,9 @@ Status populateShellGlobalEvidence(
}
const auto appliedForceMoment =
cross(domain.nodes()[node].coordinates, nodalAppliedForce);
cross(domain.Nodes()[node].coordinates, nodalAppliedForce);
const auto reactionForceMoment =
cross(domain.nodes()[node].coordinates, nodalReactionForce);
cross(domain.Nodes()[node].coordinates, nodalReactionForce);
for (std::size_t component = 0U; component < 3U; ++component) {
nodalAppliedMoment[component] += appliedForceMoment[component];
nodalReactionMoment[component] += reactionForceMoment[component];
@@ -541,8 +541,8 @@ Status populateShellGlobalEvidence(
reactionMoment, reactionMomentScale, nodalReactionMoment)) {
return recoveryFailure(
"nonfinite-recovery-value",
domain.nodes()[node].location,
domain.nodes()[node].sourceId.source_label_text,
domain.Nodes()[node].location,
domain.Nodes()[node].source_id.source_label_text,
"Global force and moment evidence must remain finite in source-node order.");
}
}
@@ -569,7 +569,7 @@ Status populateShellGlobalEvidence(
!finite(candidate.verificationMetrics)) {
return recoveryFailure(
"nonfinite-recovery-value",
{domain.sourcePath(), 0U},
{domain.SourcePath(), 0U},
"global-equilibrium",
"Global equilibrium values and their physical normalization scales must be finite.");
}
@@ -577,7 +577,7 @@ Status populateShellGlobalEvidence(
momentMetric > kGlobalEquilibriumTolerance) {
return recoveryFailure(
"global-equilibrium-tolerance-failure",
{domain.sourcePath(), 0U},
{domain.SourcePath(), 0U},
"global-equilibrium",
"Normalized global force or moment balance exceeds 1e-10.");
}
@@ -586,8 +586,8 @@ Status populateShellGlobalEvidence(
std::optional<AxisSet> localAxes(const Domain& domain,
const EulerBeam3DDefinition& element) {
const auto& first = domain.nodes()[element.nodeIndices[0U]].coordinates;
const auto& second = domain.nodes()[element.nodeIndices[1U]].coordinates;
const auto& first = domain.Nodes()[element.node_indices[0U]].coordinates;
const auto& second = domain.Nodes()[element.node_indices[1U]].coordinates;
const std::array<double, 3> delta = {
second[0U] - first[0U],
second[1U] - first[1U],
@@ -598,7 +598,7 @@ std::optional<AxisSet> localAxes(const Domain& domain,
}
const std::array<double, 3> ex = {
delta[0U] / length, delta[1U] / length, delta[2U] / length};
const auto& guide = domain.sections()[element.sectionIndex].firstAxis;
const auto& guide = domain.Sections()[element.section_index].first_axis;
const double projection = dot(guide, ex);
const std::array<double, 3> eyTrial = {
guide[0U] - projection * ex[0U],
@@ -654,8 +654,8 @@ Status ResultRecovery::recover(const AnalysisModel& model,
if (!finite(internalForce)) {
return recoveryFailure(
"nonfinite-recovery-value",
{model.domain().sourcePath(), 0U},
model.domain().sourceContentIdentity(),
{model.domain().SourcePath(), 0U},
model.domain().SourceContentIdentity(),
"Full stiffness multiplication must produce finite internal force.");
}
Vector residual{dofs.fullDofCount()};
@@ -665,7 +665,7 @@ Status ResultRecovery::recover(const AnalysisModel& model,
if (!std::isfinite(residual[fullDof])) {
return recoveryFailure(
"nonfinite-recovery-value",
{model.domain().sourcePath(), 0U},
{model.domain().SourcePath(), 0U},
std::to_string(fullDof),
"Internal-minus-external residual must remain finite.");
}
@@ -684,7 +684,7 @@ Status ResultRecovery::recover(const AnalysisModel& model,
if (!std::isfinite(residualNorm) || !std::isfinite(denominator)) {
return recoveryFailure(
"nonfinite-recovery-value",
{model.domain().sourcePath(), 0U},
{model.domain().SourcePath(), 0U},
"free-residual",
"Free residual and its physical normalization scale must be finite.");
}
@@ -699,7 +699,7 @@ Status ResultRecovery::recover(const AnalysisModel& model,
normalizedResidual > kFreeResidualTolerance) {
return recoveryFailure(
"free-residual-tolerance-failure",
{model.domain().sourcePath(), 0U},
{model.domain().SourcePath(), 0U},
"free-residual",
"The normalized free residual exceeds 1e-10.");
}
@@ -716,12 +716,12 @@ Status ResultRecovery::recover(const AnalysisModel& model,
gaussRows.reserve(model.activeElements().size() * 2U);
const Domain& domain = model.domain();
for (const EntityIndex elementIndex : model.activeElements()) {
const auto& definition = domain.elements()[elementIndex];
auto beam = EulerBeam3D::create(
domain.nodes()[definition.nodeIndices[0U]],
domain.nodes()[definition.nodeIndices[1U]],
domain.sections()[definition.sectionIndex],
domain.materials()[definition.materialIndex]);
const auto& definition = domain.Elements()[elementIndex];
auto beam = EulerBeam3D::Create(
domain.Nodes()[definition.node_indices[0U]],
domain.Nodes()[definition.node_indices[1U]],
domain.Sections()[definition.section_index],
domain.Materials()[definition.material_index]);
if (!beam.HasValue()) {
return beam.GetStatus();
}
@@ -735,52 +735,52 @@ Status ResultRecovery::recover(const AnalysisModel& model,
state.displacement()[scatter[localDof]];
}
const BeamRecovery recovered =
beam.Value().recover(elementDisplacement);
beam.Value().Recover(elementDisplacement);
for (std::size_t endpoint = 0U; endpoint < 2U; ++endpoint) {
if (!finite(recovered.equilibriumEndActions[endpoint]) ||
!finite(recovered.endpointSectionResultants[endpoint])) {
if (!finite(recovered.equilibrium_end_actions[endpoint]) ||
!finite(recovered.endpoint_section_resultants[endpoint])) {
return recoveryFailure(
"nonfinite-recovery-value",
definition.location,
definition.sourceId.source_label_text,
definition.source_id.source_label_text,
"Endpoint recovery values must be finite.");
}
endpointRows.push_back({
elementIndex,
static_cast<int>(endpoint),
domain.nodes()[definition.nodeIndices[endpoint]].sourceId,
recovered.equilibriumEndActions[endpoint],
recovered.endpointSectionResultants[endpoint]});
domain.Nodes()[definition.node_indices[endpoint]].source_id,
recovered.equilibrium_end_actions[endpoint],
recovered.endpoint_section_resultants[endpoint]});
}
for (std::size_t gauss = 0U; gauss < 2U; ++gauss) {
if (!finite(recovered.gaussGeneralizedStrains[gauss]) ||
!finite(recovered.gaussGeneralizedResultants[gauss])) {
if (!finite(recovered.gauss_generalized_strains[gauss]) ||
!finite(recovered.gauss_generalized_resultants[gauss])) {
return recoveryFailure(
"nonfinite-recovery-value",
definition.location,
definition.sourceId.source_label_text,
definition.source_id.source_label_text,
"Gauss recovery values must be finite.");
}
gaussRows.push_back({
elementIndex,
static_cast<int>(gauss + 1U),
recovered.gaussGeneralizedStrains[gauss],
recovered.gaussGeneralizedResultants[gauss]});
recovered.gauss_generalized_strains[gauss],
recovered.gauss_generalized_resultants[gauss]});
}
for (const auto& point : recovered.stressPoints) {
if ((point.gaussPoint != 1 && point.gaussPoint != 2) ||
for (const auto& point : recovered.stress_points) {
if ((point.gauss_point != 1 && point.gauss_point != 2) ||
!std::isfinite(point.x1) || !std::isfinite(point.x2) ||
!std::isfinite(point.s11)) {
return recoveryFailure(
"nonfinite-recovery-value",
definition.location,
definition.sourceId.source_label_text,
definition.source_id.source_label_text,
"Stress recovery identity and values must be finite and ordered.");
}
stressRows.push_back({
elementIndex,
point.gaussPoint,
point.sectionPoint,
point.gauss_point,
point.section_point,
point.x1,
point.x2,
point.s11,
@@ -790,33 +790,33 @@ Status ResultRecovery::recover(const AnalysisModel& model,
ShellStateCandidate shellCandidate{};
std::vector<EntityIndex> expectedShellElements;
if (!domain.shellElements().empty()) {
if (domain.shellElements().size() >
if (!domain.ShellElements().empty()) {
if (domain.ShellElements().size() >
(std::numeric_limits<std::size_t>::max)() /
kShellLocationCount) {
return recoveryFailure(
"invalid-recovery-dimensions",
{domain.sourcePath(), 0U},
domain.sourceContentIdentity(),
{domain.SourcePath(), 0U},
domain.SourceContentIdentity(),
"The shell result-row inventory exceeds the addressable range.");
}
std::vector<std::optional<std::array<double, 3>>> directorsByNode(
domain.nodes().size());
for (const auto& frame : domain.shellNodeInitialFrames()) {
if (frame.nodeIndex >= directorsByNode.size() ||
directorsByNode[frame.nodeIndex].has_value()) {
domain.Nodes().size());
for (const auto& frame : domain.ShellNodeInitialFrames()) {
if (frame.node_index >= directorsByNode.size() ||
directorsByNode[frame.node_index].has_value()) {
return recoveryFailure(
"invalid-recovery-entity",
{domain.sourcePath(), 0U},
std::to_string(frame.nodeIndex),
{domain.SourcePath(), 0U},
std::to_string(frame.node_index),
"Shell initial directors must map uniquely to model nodes.");
}
directorsByNode[frame.nodeIndex] = frame.director;
directorsByNode[frame.node_index] = frame.director;
}
shellCandidate.rows.reserve(
domain.shellElements().size() * kShellLocationCount);
expectedShellElements.reserve(domain.shellElements().size());
domain.ShellElements().size() * kShellLocationCount);
expectedShellElements.reserve(domain.ShellElements().size());
constexpr std::array<ShellMidsurfaceLocation, kShellLocationCount>
locations{
ShellMidsurfaceLocation::gp1,
@@ -829,33 +829,33 @@ Status ResultRecovery::recover(const AnalysisModel& model,
ShellSectionPosition::top};
constexpr std::array<double, 3> zeta{-1.0, 0.0, 1.0};
for (std::size_t elementOrder = 0U;
elementOrder < domain.shellElements().size();
elementOrder < domain.ShellElements().size();
++elementOrder) {
const EntityIndex elementIndex =
static_cast<EntityIndex>(elementOrder);
const auto& definition = domain.shellElements()[elementOrder];
const auto& definition = domain.ShellElements()[elementOrder];
std::array<const Node*, 4> nodes{};
std::array<std::array<double, 3>, 4> directors{};
for (std::size_t nodePosition = 0U;
nodePosition < definition.nodeIndices.size();
nodePosition < definition.node_indices.size();
++nodePosition) {
const EntityIndex node = definition.nodeIndices[nodePosition];
const EntityIndex node = definition.node_indices[nodePosition];
if (!directorsByNode[node].has_value()) {
return recoveryFailure(
"invalid-recovery-entity",
definition.location,
definition.sourceId.source_label_text,
definition.source_id.source_label_text,
"Shell recovery requires one initial director per element node.");
}
nodes[nodePosition] = &domain.nodes()[node];
nodes[nodePosition] = &domain.Nodes()[node];
directors[nodePosition] = *directorsByNode[node];
}
auto shell = Mitc4Shell::create(
auto shell = Mitc4Shell::Create(
nodes,
directors,
domain.shellSections()[definition.sectionIndex],
domain.materials()[definition.materialIndex]);
domain.ShellSections()[definition.section_index],
domain.Materials()[definition.material_index]);
if (!shell.HasValue()) {
return shell.GetStatus();
}
@@ -867,19 +867,19 @@ Status ResultRecovery::recover(const AnalysisModel& model,
elementDisplacement[localDof] =
state.displacement()[scatter[localDof]];
}
auto recovered = shell.Value().recoverPhysical(
auto recovered = shell.Value().RecoverPhysical(
elementDisplacement);
if (!recovered.HasValue()) {
return recovered.GetStatus();
}
const double accumulatedEnergy =
shellCandidate.physicalStrainEnergy +
recovered.Value().strainEnergy;
recovered.Value().strain_energy;
if (!std::isfinite(accumulatedEnergy)) {
return recoveryFailure(
"nonfinite-recovery-value",
definition.location,
definition.sourceId.source_label_text,
definition.source_id.source_label_text,
"Source-order physical shell energy reduction must remain finite.");
}
shellCandidate.physicalStrainEnergy = accumulatedEnergy;
@@ -892,20 +892,20 @@ Status ResultRecovery::recover(const AnalysisModel& model,
ShellResultRow row{};
row.element = elementIndex;
row.location = locations[point];
row.naturalCoordinates = physicalPoint.naturalCoordinates;
row.naturalCoordinates = physicalPoint.natural_coordinates;
row.localFrame = {
physicalPoint.localFrame.e1,
physicalPoint.localFrame.e2,
physicalPoint.localFrame.e3};
row.generalizedStrain = physicalPoint.generalizedStrain;
row.sectionResultant = physicalPoint.sectionResultant;
physicalPoint.local_frame.e1,
physicalPoint.local_frame.e2,
physicalPoint.local_frame.e3};
row.generalizedStrain = physicalPoint.generalized_strain;
row.sectionResultant = physicalPoint.section_resultant;
for (std::size_t position = 0U;
position < positions.size();
++position) {
row.stress[position] = {
positions[position],
zeta[position],
physicalPoint.inPlaneStress[position]};
physicalPoint.in_plane_stress[position]};
}
shellCandidate.rows.push_back(std::move(row));
}
@@ -951,7 +951,7 @@ ResultRecovery::normalizeSectionResultantsToNodeStations(
if (!std::isfinite(tolerance) || tolerance < 0.0) {
return recoveryResultFailure<std::vector<NodeStationResultRow>>(
"invalid-node-station-tolerance",
{domain.sourcePath(), 0U},
{domain.SourcePath(), 0U},
"component-tolerances",
"Node-station component tolerances must be finite and nonnegative.");
}
@@ -961,50 +961,50 @@ ResultRecovery::normalizeSectionResultantsToNodeStations(
endpointRows.size() != model.activeElements().size() * 2U) {
return recoveryResultFailure<std::vector<NodeStationResultRow>>(
"invalid-node-station-shape",
{domain.sourcePath(), 0U},
{domain.SourcePath(), 0U},
std::to_string(endpointRows.size()),
"Endpoint rows must contain exactly two rows per active element.");
}
std::vector<std::vector<const EndpointResultRow*>> rowsByNode(
domain.nodes().size());
domain.Nodes().size());
for (std::size_t order = 0U;
order < model.activeElements().size();
++order) {
const EntityIndex elementIndex = model.activeElements()[order];
if (elementIndex >= domain.elements().size()) {
if (elementIndex >= domain.Elements().size()) {
return recoveryResultFailure<std::vector<NodeStationResultRow>>(
"invalid-node-station-entity",
{domain.sourcePath(), 0U},
{domain.SourcePath(), 0U},
std::to_string(elementIndex),
"Every active station element must be a valid stable entity.");
}
const auto& definition = domain.elements()[elementIndex];
const auto& definition = domain.Elements()[elementIndex];
for (std::size_t endpoint = 0U; endpoint < 2U; ++endpoint) {
const auto& row = endpointRows[order * 2U + endpoint];
const EntityIndex nodeIndex = definition.nodeIndices[endpoint];
if (nodeIndex >= domain.nodes().size() ||
const EntityIndex nodeIndex = definition.node_indices[endpoint];
if (nodeIndex >= domain.Nodes().size() ||
row.element != elementIndex ||
row.endpoint != static_cast<int>(endpoint) ||
!sameSourceIdentity(row.node, domain.nodes()[nodeIndex].sourceId)) {
!sameSourceIdentity(row.node, domain.Nodes()[nodeIndex].source_id)) {
return recoveryResultFailure<std::vector<NodeStationResultRow>>(
"invalid-node-station-entity",
definition.location,
definition.sourceId.source_label_text,
definition.source_id.source_label_text,
"Endpoint rows must preserve active element, endpoint, and source-node order.");
}
if (!finite(row.sectionResultant)) {
return recoveryResultFailure<std::vector<NodeStationResultRow>>(
"nonfinite-node-station-value",
definition.location,
definition.sourceId.source_label_text,
definition.source_id.source_label_text,
"Node-station section resultants must be finite.");
}
rowsByNode[nodeIndex].push_back(&row);
}
}
std::vector<unsigned char> loadedNodes(domain.nodes().size(), 0U);
std::vector<unsigned char> loadedNodes(domain.Nodes().size(), 0U);
if (model.activeLoads().size() != model.step().loads.size()) {
return recoveryResultFailure<std::vector<NodeStationResultRow>>(
"invalid-node-station-entity",
@@ -1042,7 +1042,7 @@ ResultRecovery::normalizeSectionResultantsToNodeStations(
}
std::vector<NodeStationResultRow> stations;
stations.reserve(domain.nodes().size());
stations.reserve(domain.Nodes().size());
for (std::size_t nodeIndex = 0U;
nodeIndex < rowsByNode.size();
++nodeIndex) {
@@ -1052,7 +1052,7 @@ ResultRecovery::normalizeSectionResultantsToNodeStations(
}
if (incident.size() == 1U) {
stations.push_back({
domain.nodes()[nodeIndex].sourceId,
domain.Nodes()[nodeIndex].source_id,
incident.front()->element,
incident.front()->sectionResultant});
continue;
@@ -1060,13 +1060,13 @@ ResultRecovery::normalizeSectionResultantsToNodeStations(
if (incident.size() != 2U || loadedNodes[nodeIndex] != 0U) {
return recoveryResultFailure<std::vector<NodeStationResultRow>>(
"ineligible-node-station",
domain.nodes()[nodeIndex].location,
domain.nodes()[nodeIndex].sourceId.source_label_text,
domain.Nodes()[nodeIndex].location,
domain.Nodes()[nodeIndex].source_id.source_label_text,
"Interior station collapse requires exactly two unloaded endpoints.");
}
const auto& firstElement = domain.elements()[incident[0U]->element];
const auto& secondElement = domain.elements()[incident[1U]->element];
const auto& firstElement = domain.Elements()[incident[0U]->element];
const auto& secondElement = domain.Elements()[incident[1U]->element];
const bool chainOrientation =
incident[0U]->endpoint != incident[1U]->endpoint &&
((incident[0U]->endpoint == 1 && incident[1U]->endpoint == 0) ||
@@ -1074,13 +1074,13 @@ ResultRecovery::normalizeSectionResultantsToNodeStations(
const auto firstAxes = localAxes(domain, firstElement);
const auto secondAxes = localAxes(domain, secondElement);
if (!chainOrientation ||
firstElement.sectionIndex != secondElement.sectionIndex ||
firstElement.section_index != secondElement.section_index ||
!firstAxes.has_value() || !secondAxes.has_value() ||
!sameAxes(*firstAxes, *secondAxes)) {
return recoveryResultFailure<std::vector<NodeStationResultRow>>(
"ineligible-node-station",
domain.nodes()[nodeIndex].location,
domain.nodes()[nodeIndex].sourceId.source_label_text,
domain.Nodes()[nodeIndex].location,
domain.Nodes()[nodeIndex].source_id.source_label_text,
"Interior station endpoints require one consistent section and local-axis chain.");
}
@@ -1096,15 +1096,15 @@ ResultRecovery::normalizeSectionResultantsToNodeStations(
if (!std::isfinite(difference)) {
return recoveryResultFailure<std::vector<NodeStationResultRow>>(
"nonfinite-node-station-value",
domain.nodes()[nodeIndex].location,
domain.nodes()[nodeIndex].sourceId.source_label_text,
domain.Nodes()[nodeIndex].location,
domain.Nodes()[nodeIndex].source_id.source_label_text,
"Endpoint comparison must produce a finite difference.");
}
if (difference > componentTolerances[component]) {
return recoveryResultFailure<std::vector<NodeStationResultRow>>(
"node-station-tolerance-failure",
domain.nodes()[nodeIndex].location,
domain.nodes()[nodeIndex].sourceId.source_label_text,
domain.Nodes()[nodeIndex].location,
domain.Nodes()[nodeIndex].source_id.source_label_text,
"Interior endpoint resultants disagree beyond component tolerance.");
}
}
@@ -1114,7 +1114,7 @@ ResultRecovery::normalizeSectionResultantsToNodeStations(
? incident[0U]
: incident[1U];
stations.push_back({
domain.nodes()[nodeIndex].sourceId,
domain.Nodes()[nodeIndex].source_id,
representative->element,
representative->sectionResultant});
}
@@ -307,8 +307,8 @@ public:
const fesa::AnalysisState& state,
const std::vector<fesa::Diagnostic>& diagnostics) override {
outputPath_ = outputPath;
nodeCount_ = domain.nodes().size();
shellElementCount_ = domain.shellElements().size();
nodeCount_ = domain.Nodes().size();
shellElementCount_ = domain.ShellElements().size();
state_ = std::make_unique<fesa::AnalysisState>(state);
diagnostics_ = diagnostics;
return fesa::Status::Ok();
+19 -19
View File
@@ -785,9 +785,9 @@ void requireFiniteStress(const hid_t file) {
std::array<double, 9> expectedLocalAxes(
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& 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],
@@ -862,7 +862,7 @@ HdfProjection readHdfProjection(
std::filesystem::absolute(input).lexically_normal().generic_u8string();
const std::string expectedIdentity =
"path=" + normalizedInput +
";content_identity=" + domain.sourceContentIdentity();
";content_identity=" + domain.SourceContentIdentity();
if (sourceIdentity != expectedIdentity) {
fail("schema-mismatch", "The HDF5 source-input identity is inconsistent.");
}
@@ -870,30 +870,30 @@ HdfProjection readHdfProjection(
HdfProjection projection{};
projection.nodes = readNodeRows(file.get());
projection.elements = readElementRows(file.get());
if (projection.nodes.size() != domain.nodes().size() ||
projection.elements.size() != domain.elements().size()) {
if (projection.nodes.size() != domain.Nodes().size() ||
projection.elements.size() != domain.Elements().size()) {
fail("schema-mismatch", "HDF5 model identity counts do not match the input.");
}
for (std::size_t node = 0U; node < projection.nodes.size(); ++node) {
const auto& actual = projection.nodes[node];
const auto& expected = domain.nodes()[node];
const auto& expected = domain.Nodes()[node];
if (actual.internalNodeId != node ||
actual.instanceName != expected.sourceId.instance_name ||
actual.sourceNodeLabel != expected.sourceId.source_label ||
actual.sourceNodeLabelText != expected.sourceId.source_label_text ||
actual.instanceName != expected.source_id.instance_name ||
actual.sourceNodeLabel != expected.source_id.source_label ||
actual.sourceNodeLabelText != expected.source_id.source_label_text ||
actual.coordinates != expected.coordinates) {
fail("schema-mismatch", "An HDF5 node identity does not match the input.");
}
}
for (std::size_t element = 0U; element < projection.elements.size(); ++element) {
const auto& actual = projection.elements[element];
const auto& expected = domain.elements()[element];
const auto& expected = domain.Elements()[element];
if (actual.internalElementId != element ||
actual.instanceName != expected.sourceId.instance_name ||
actual.sourceElementLabel != expected.sourceId.source_label ||
actual.sourceElementLabelText != expected.sourceId.source_label_text ||
actual.nodeInternalIds[0U] != expected.nodeIndices[0U] ||
actual.nodeInternalIds[1U] != expected.nodeIndices[1U]) {
actual.instanceName != expected.source_id.instance_name ||
actual.sourceElementLabel != expected.source_id.source_label ||
actual.sourceElementLabelText != expected.source_id.source_label_text ||
actual.nodeInternalIds[0U] != expected.node_indices[0U] ||
actual.nodeInternalIds[1U] != expected.node_indices[1U]) {
fail("schema-mismatch", "An HDF5 element identity does not match the input.");
}
const auto axes = expectedLocalAxes(domain, expected);
@@ -986,7 +986,7 @@ std::vector<NodeStationResultRow> normalizeStations(
endpoints.push_back({
static_cast<EntityIndex>(element),
static_cast<int>(endpoint),
domain.nodes()[node].sourceId,
domain.Nodes()[node].source_id,
{},
values});
}
@@ -1192,8 +1192,8 @@ PhysicsEvidence makePhysicsEvidence(
}
evidence.freeResidualNorm =
std::sqrt(static_cast<double>(residualSquared));
for (std::size_t node = 0U; node < domain.nodes().size(); ++node) {
const auto& coordinates = domain.nodes()[node].coordinates;
for (std::size_t node = 0U; node < domain.Nodes().size(); ++node) {
const auto& coordinates = domain.Nodes()[node].coordinates;
const std::array<double, 3> applied = {
load[node * 6U + 0U],
load[node * 6U + 1U],
@@ -5,7 +5,7 @@
#include "fesa/fem/dof_manager.hpp"
#include "fesa/io/abaqus/input_reader.hpp"
#include "fesa/io/hdf5/hdf5_results_writer.hpp"
#include "fesa/model/domain.hpp"
#include "fesa/model/domain.h"
#include <gtest/gtest.h>
@@ -173,8 +173,8 @@ fesa::ModelDefinition makeDefinition(
const std::filesystem::path& input,
std::string sourceContentIdentity) {
fesa::ModelDefinition definition{};
definition.sourcePath = input;
definition.sourceContentIdentity = std::move(sourceContentIdentity);
definition.source_path = input;
definition.source_content_identity = std::move(sourceContentIdentity);
for (std::size_t node = 0U; node < kNodeCount; ++node) {
const auto label = static_cast<std::int64_t>(node + 1U);
definition.nodes.push_back({
@@ -217,7 +217,7 @@ void writeResultsFixture(
if (!parsedInput.HasValue()) {
throw std::runtime_error{"Reference fixture input identity read failed."};
}
auto domainResult = fesa::Domain::create(
auto domainResult = fesa::Domain::Create(
makeDefinition(input, parsedInput.Value().sourceContentIdentity));
if (!domainResult.HasValue()) {
throw std::runtime_error{"Reference fixture Domain construction failed."};
@@ -250,7 +250,7 @@ void writeResultsFixture(
state.endpointResults().push_back({
static_cast<fesa::EntityIndex>(element),
static_cast<int>(endpoint),
domain.nodes()[node].sourceId,
domain.Nodes()[node].source_id,
{},
values.sectionResultants[element][endpoint]});
}
+23 -23
View File
@@ -12,8 +12,8 @@ namespace {
fesa::ModelDefinition makeDefinition() {
const std::filesystem::path source{"models/analysis-model.inp"};
fesa::ModelDefinition definition{};
definition.sourcePath = source;
definition.sourceContentIdentity = "fnv1a64:0123456789abcdef";
definition.source_path = source;
definition.source_content_identity = "fnv1a64:0123456789abcdef";
definition.nodes = {
{{"Beam-1", 1, "1"}, {0.0, 0.0, 0.0}, {source, 10U}},
{{"Beam-1", 2, "2"}, {1.0, 0.0, 0.0}, {source, 11U}},
@@ -57,7 +57,7 @@ fesa::ModelDefinition makeDefinition() {
} // namespace
TEST(AnalysisModel, ClassifiesActiveEntitiesInStableOrder) {
auto domainResult = fesa::Domain::create(makeDefinition());
auto domainResult = fesa::Domain::Create(makeDefinition());
ASSERT_TRUE(domainResult.HasValue());
auto modelResult = fesa::AnalysisModel::create(domainResult.Value());
@@ -82,41 +82,41 @@ TEST(AnalysisModel, ClassifiesActiveEntitiesInStableOrder) {
}
TEST(AnalysisModel, ReferencesWithoutCopyingOrMutatingDomain) {
auto domainResult = fesa::Domain::create(makeDefinition());
auto domainResult = fesa::Domain::Create(makeDefinition());
ASSERT_TRUE(domainResult.HasValue());
const fesa::Domain& domain = domainResult.Value();
const auto* const elementAddress = domain.elements().data();
const auto* const materialAddress = domain.materials().data();
const auto* const sectionAddress = domain.sections().data();
const std::string stepName = domain.steps()[0].name;
const double firstLoadMagnitude = domain.steps()[0].loads[0].magnitude;
const auto* const elementAddress = domain.Elements().data();
const auto* const materialAddress = domain.Materials().data();
const auto* const sectionAddress = domain.Sections().data();
const std::string stepName = domain.Steps()[0].name;
const double firstLoadMagnitude = domain.Steps()[0].loads[0].magnitude;
auto modelResult = fesa::AnalysisModel::create(domain);
ASSERT_TRUE(modelResult.HasValue());
const auto& model = modelResult.Value();
EXPECT_EQ(&model.domain(), &domain);
EXPECT_EQ(&model.step(), &domain.steps()[0]);
EXPECT_EQ(model.domain().elements().data(), elementAddress);
EXPECT_EQ(model.domain().materials().data(), materialAddress);
EXPECT_EQ(model.domain().sections().data(), sectionAddress);
EXPECT_EQ(&model.step(), &domain.Steps()[0]);
EXPECT_EQ(model.domain().Elements().data(), elementAddress);
EXPECT_EQ(model.domain().Materials().data(), materialAddress);
EXPECT_EQ(model.domain().Sections().data(), sectionAddress);
EXPECT_EQ(
&model.domain().elements()[model.activeElements()[1]],
&domain.elements()[1]);
&model.domain().Elements()[model.activeElements()[1]],
&domain.Elements()[1]);
EXPECT_EQ(
&model.domain().materials()[model.activeMaterials()[2]],
&domain.materials()[2]);
&model.domain().Materials()[model.activeMaterials()[2]],
&domain.Materials()[2]);
EXPECT_EQ(
&model.domain().sections()[model.activeSections()[1]],
&domain.sections()[1]);
EXPECT_EQ(domain.steps()[0].name, stepName);
EXPECT_DOUBLE_EQ(domain.steps()[0].loads[0].magnitude, firstLoadMagnitude);
&model.domain().Sections()[model.activeSections()[1]],
&domain.Sections()[1]);
EXPECT_EQ(domain.Steps()[0].name, stepName);
EXPECT_DOUBLE_EQ(domain.Steps()[0].loads[0].magnitude, firstLoadMagnitude);
}
TEST(AnalysisModel, RejectsMissingOrMultipleStep) {
auto missingDefinition = makeDefinition();
missingDefinition.steps.clear();
auto missingDomain = fesa::Domain::create(std::move(missingDefinition));
auto missingDomain = fesa::Domain::Create(std::move(missingDefinition));
ASSERT_TRUE(missingDomain.HasValue());
auto missing = fesa::AnalysisModel::create(missingDomain.Value());
@@ -136,7 +136,7 @@ TEST(AnalysisModel, RejectsMissingOrMultipleStep) {
secondStep.name = "Step-2";
secondStep.location.line = 60U;
multipleDefinition.steps.push_back(std::move(secondStep));
auto multipleDomain = fesa::Domain::create(std::move(multipleDefinition));
auto multipleDomain = fesa::Domain::Create(std::move(multipleDefinition));
ASSERT_TRUE(multipleDomain.HasValue());
auto multiple = fesa::AnalysisModel::create(multipleDomain.Value());
+7 -7
View File
@@ -49,22 +49,22 @@ struct HasNonlinearState<
fesa::DofManager makeDofs() {
fesa::ModelDefinition definition{};
definition.sourcePath = "models/analysis-state.inp";
definition.sourceContentIdentity = "fnv1a64:0123456789abcdef";
definition.source_path = "models/analysis-state.inp";
definition.source_content_identity = "fnv1a64:0123456789abcdef";
definition.nodes = {
{{"Beam-1", 1, "1"}, {0.0, 0.0, 0.0}, {definition.sourcePath, 10U}},
{{"Beam-1", 2, "2"}, {1.0, 0.0, 0.0}, {definition.sourcePath, 11U}}};
{{"Beam-1", 1, "1"}, {0.0, 0.0, 0.0}, {definition.source_path, 10U}},
{{"Beam-1", 2, "2"}, {1.0, 0.0, 0.0}, {definition.source_path, 11U}}};
definition.steps = {{
"Step-1",
{{"1", 1, 6, 0.0, {definition.sourcePath, 20U}}},
{{"1", 1, 6, 0.0, {definition.source_path, 20U}}},
{},
0.1,
1.0,
0.01,
1.0,
{definition.sourcePath, 19U}}};
{definition.source_path, 19U}}};
auto domain = fesa::Domain::create(std::move(definition));
auto domain = fesa::Domain::Create(std::move(definition));
EXPECT_TRUE(domain.HasValue());
auto model = fesa::AnalysisModel::create(domain.Value());
EXPECT_TRUE(model.HasValue());
+12 -12
View File
@@ -2,7 +2,7 @@
#include "fesa/analysis/analysis_model.hpp"
#include "fesa/fem/dof_manager.hpp"
#include "fesa/model/domain.hpp"
#include "fesa/model/domain.h"
#include <gtest/gtest.h>
@@ -33,8 +33,8 @@ LoadFixture makeFixture(
std::vector<fesa::NodalLoad> loads) {
const std::filesystem::path source{"models/load-assembly.inp"};
fesa::ModelDefinition definition{};
definition.sourcePath = source;
definition.sourceContentIdentity = "fnv1a64:abcdef0123456789";
definition.source_path = source;
definition.source_content_identity = "fnv1a64:abcdef0123456789";
for (std::size_t index = 0U; index < nodeCount; ++index) {
const auto label = static_cast<std::int64_t>((index + 1U) * 10U);
definition.nodes.push_back({
@@ -42,7 +42,7 @@ LoadFixture makeFixture(
{static_cast<double>(index), 0.0, 0.0},
{source, index + 2U}});
}
definition.nodeSets = std::move(nodeSets);
definition.node_sets = std::move(nodeSets);
definition.steps = {{
"Step-1",
std::move(boundaries),
@@ -53,7 +53,7 @@ LoadFixture makeFixture(
1.0,
{source, 20U}}};
auto domainResult = fesa::Domain::create(std::move(definition));
auto domainResult = fesa::Domain::Create(std::move(definition));
if (!domainResult.HasValue()) {
throw std::runtime_error{"Load fixture Domain construction failed."};
}
@@ -81,8 +81,8 @@ LoadFixture makeShellFixture(
std::vector<fesa::NodalLoad> loads) {
const std::filesystem::path source{"models/shell-load-assembly.inp"};
fesa::ModelDefinition definition{};
definition.sourcePath = source;
definition.sourceContentIdentity = "fnv1a64:1122334455667788";
definition.source_path = source;
definition.source_content_identity = "fnv1a64:1122334455667788";
definition.nodes = {
{{"Shell-1", 10, "10"}, {0.0, 0.0, 0.0}, {source, 2U}},
{{"Shell-1", 20, "20"}, {1.0, 0.0, 0.0}, {source, 3U}},
@@ -90,17 +90,17 @@ LoadFixture makeShellFixture(
{{"Shell-1", 40, "40"}, {0.0, 1.0, 0.0}, {source, 5U}}};
definition.materials = {
{"Material", 1000.0, 0.25, {source, 6U}}};
definition.shellSections = {
definition.shell_sections = {
{"ShellSection", 0.1, 0U, {source, 7U}}};
definition.shellElements = {{
definition.shell_elements = {{
{"Shell-1", 1, "1"},
fesa::ShellSourceElementType::s4,
fesa::ShellSourceElementType::kS4,
{0U, 1U, 2U, 3U},
0U,
0U,
{source, 8U}}};
for (std::size_t node = 0U; node < definition.nodes.size(); ++node) {
definition.shellNodeInitialFrames.push_back({
definition.shell_node_initial_frames.push_back({
static_cast<fesa::EntityIndex>(node),
{0.0, 0.0, 1.0},
{1.0, 0.0, 0.0},
@@ -116,7 +116,7 @@ LoadFixture makeShellFixture(
1.0,
{source, 20U}}};
auto domainResult = fesa::Domain::create(std::move(definition));
auto domainResult = fesa::Domain::Create(std::move(definition));
if (!domainResult.HasValue()) {
throw std::runtime_error{"Shell load fixture Domain construction failed."};
}
+31 -31
View File
@@ -1,9 +1,9 @@
#include "fesa/analysis/analysis_model.hpp"
#include "fesa/assembly/parallel_for.hpp"
#include "fesa/assembly/sparse_assembler.hpp"
#include "fesa/elements/mitc4_shell.hpp"
#include "fesa/elements/mitc4_shell.h"
#include "fesa/fem/dof_manager.hpp"
#include "fesa/model/domain.hpp"
#include "fesa/model/domain.h"
#include <gtest/gtest.h>
@@ -19,8 +19,8 @@ namespace {
fesa::ModelDefinition makeDefinition() {
const std::filesystem::path source{"models/sparse-assembly.inp"};
fesa::ModelDefinition definition{};
definition.sourcePath = source;
definition.sourceContentIdentity = "fnv1a64:0123456789abcdef";
definition.source_path = source;
definition.source_content_identity = "fnv1a64:0123456789abcdef";
definition.nodes = {
{{"Beam-1", 1, "1"}, {0.0, 0.0, 0.0}, {source, 10U}},
{{"Beam-1", 2, "2"}, {2.0, 0.0, 0.0}, {source, 11U}},
@@ -50,8 +50,8 @@ fesa::ModelDefinition makeShellDefinition(
const bool twoElements = false) {
const std::filesystem::path source{"models/shell-sparse-assembly.inp"};
fesa::ModelDefinition definition{};
definition.sourcePath = source;
definition.sourceContentIdentity = "fnv1a64:fedcba9876543210";
definition.source_path = source;
definition.source_content_identity = "fnv1a64:fedcba9876543210";
if (twoElements) {
definition.nodes = {
{{"Shell-1", 1, "1"}, {0.0, 0.0, 0.0}, {source, 10U}},
@@ -61,13 +61,13 @@ fesa::ModelDefinition makeShellDefinition(
{{"Shell-1", 5, "5"}, {1.0, 1.0, 0.0}, {source, 14U}},
{{"Shell-1", 6, "6"}, {2.0, 1.0, 0.0}, {source, 15U}}};
for (std::size_t node = 0U; node < definition.nodes.size(); ++node) {
definition.shellNodeInitialFrames.push_back({
definition.shell_node_initial_frames.push_back({
static_cast<fesa::EntityIndex>(node),
{0.0, 0.0, 1.0},
{1.0, 0.0, 0.0},
{0.0, 1.0, 0.0}});
}
definition.shellElements = {
definition.shell_elements = {
{{"Shell-1", 10, "10"}, sourceType, {0U, 1U, 4U, 3U},
0U, 0U, {source, 40U}},
{{"Shell-1", 20, "20"}, sourceType, {1U, 2U, 5U, 4U},
@@ -80,19 +80,19 @@ fesa::ModelDefinition makeShellDefinition(
{{"Shell-1", 3, "3"}, {0.0, 1.0, 1.0}, {source, 12U}},
{{"Shell-1", 4, "4"}, {0.0, 0.0, 1.0}, {source, 13U}}};
for (std::size_t node = 0U; node < definition.nodes.size(); ++node) {
definition.shellNodeInitialFrames.push_back({
definition.shell_node_initial_frames.push_back({
static_cast<fesa::EntityIndex>(node),
{1.0, 0.0, 0.0},
{0.0, 1.0, 0.0},
{0.0, 0.0, 1.0}});
}
definition.shellElements = {{
definition.shell_elements = {{
{"Shell-1", 10, "10"}, sourceType, {0U, 1U, 2U, 3U},
0U, 0U, {source, 40U}}};
}
definition.materials = {
{"Material", 120.0, 0.25, {source, 20U}}};
definition.shellSections = {
definition.shell_sections = {
{"ShellSection", 0.2, 0U, {source, 30U}}};
definition.steps = {{
"Step-1", {}, {}, 0.1, 1.0, 0.01, 1.0, {source, 50U}}};
@@ -102,30 +102,30 @@ fesa::ModelDefinition makeShellDefinition(
fesa::Result<fesa::Mitc4Stiffness> directShellStiffness(
const fesa::Domain& domain,
const fesa::EntityIndex elementIndex) {
const auto& definition = domain.shellElements().at(elementIndex);
const auto& definition = domain.ShellElements().at(elementIndex);
std::array<const fesa::Node*, 4> nodes{};
std::array<std::array<double, 3>, 4> directors{};
for (std::size_t node = 0U; node < definition.nodeIndices.size(); ++node) {
const fesa::EntityIndex nodeIndex = definition.nodeIndices[node];
nodes[node] = &domain.nodes().at(nodeIndex);
directors[node] = domain.shellNodeInitialFrames().at(nodeIndex).director;
for (std::size_t node = 0U; node < definition.node_indices.size(); ++node) {
const fesa::EntityIndex nodeIndex = definition.node_indices[node];
nodes[node] = &domain.Nodes().at(nodeIndex);
directors[node] = domain.ShellNodeInitialFrames().at(nodeIndex).director;
}
auto shell = fesa::Mitc4Shell::create(
auto shell = fesa::Mitc4Shell::Create(
nodes,
directors,
domain.shellSections().at(definition.sectionIndex),
domain.materials().at(definition.materialIndex));
domain.ShellSections().at(definition.section_index),
domain.Materials().at(definition.material_index));
if (!shell.HasValue()) {
return fesa::Result<fesa::Mitc4Stiffness>::Failure(shell.GetStatus());
}
return shell.Value().stiffness();
return shell.Value().Stiffness();
}
fesa::Result<fesa::SparseMatrix> assembleShell(
const fesa::ShellSourceElementType sourceType,
const fesa::ParallelFor& parallelFor,
const bool twoElements = false) {
auto domain = fesa::Domain::create(
auto domain = fesa::Domain::Create(
makeShellDefinition(sourceType, twoElements));
if (!domain.HasValue()) {
return fesa::Result<fesa::SparseMatrix>::Failure(domain.GetStatus());
@@ -198,7 +198,7 @@ void expectByteIdentical(
}
TEST(SparseAssembly, SerialTbbAndRepeatedRunsAreByteIdentical) {
auto domainResult = fesa::Domain::create(makeDefinition());
auto domainResult = fesa::Domain::Create(makeDefinition());
ASSERT_TRUE(domainResult.HasValue());
auto modelResult = fesa::AnalysisModel::create(domainResult.Value());
ASSERT_TRUE(modelResult.HasValue());
@@ -257,8 +257,8 @@ TEST(SparseAssembly, SerialTbbAndRepeatedRunsAreByteIdentical) {
TEST(
SparseAssembly,
AssemblesFourNodeTwentyFourDofKernelAndPreservesDiagonalSlots) {
auto domain = fesa::Domain::create(
makeShellDefinition(fesa::ShellSourceElementType::s4));
auto domain = fesa::Domain::Create(
makeShellDefinition(fesa::ShellSourceElementType::kS4));
ASSERT_TRUE(domain.HasValue());
auto model = fesa::AnalysisModel::create(domain.Value());
ASSERT_TRUE(model.HasValue());
@@ -290,7 +290,7 @@ TEST(
for (std::size_t column = 0U; column < 24U; ++column) {
EXPECT_DOUBLE_EQ(
entry(assembled.Value(), row, column),
expected.Value().stabilizedGlobal24(row, column));
expected.Value().stabilized_global24(row, column));
}
}
}
@@ -300,11 +300,11 @@ TEST(SparseAssembly, ShellSerialTbbReverseAndRepeatedRunsAreByteIdentical) {
fesa::TbbParallelFor tbbExecutor;
ReverseParallelFor reverseExecutor;
auto serial = assembleShell(
fesa::ShellSourceElementType::s4, serialExecutor, true);
fesa::ShellSourceElementType::kS4, serialExecutor, true);
auto tbb = assembleShell(
fesa::ShellSourceElementType::s4, tbbExecutor, true);
fesa::ShellSourceElementType::kS4, tbbExecutor, true);
auto reversed = assembleShell(
fesa::ShellSourceElementType::s4, reverseExecutor, true);
fesa::ShellSourceElementType::kS4, reverseExecutor, true);
ASSERT_TRUE(serial.HasValue());
ASSERT_TRUE(tbb.HasValue());
ASSERT_TRUE(reversed.HasValue());
@@ -315,7 +315,7 @@ TEST(SparseAssembly, ShellSerialTbbReverseAndRepeatedRunsAreByteIdentical) {
expectByteIdentical(reversed.Value(), serial.Value());
for (std::size_t repetition = 0U; repetition < 8U; ++repetition) {
auto repeated = assembleShell(
fesa::ShellSourceElementType::s4, tbbExecutor, true);
fesa::ShellSourceElementType::kS4, tbbExecutor, true);
ASSERT_TRUE(repeated.HasValue());
expectByteIdentical(repeated.Value(), serial.Value());
}
@@ -324,9 +324,9 @@ TEST(SparseAssembly, ShellSerialTbbReverseAndRepeatedRunsAreByteIdentical) {
TEST(SparseAssembly, S4AndS4rSemanticFixturesAssembleIdenticalStiffness) {
fesa::SerialParallelFor serialExecutor;
auto s4 = assembleShell(
fesa::ShellSourceElementType::s4, serialExecutor);
fesa::ShellSourceElementType::kS4, serialExecutor);
auto s4r = assembleShell(
fesa::ShellSourceElementType::s4r, serialExecutor);
fesa::ShellSourceElementType::kS4r, serialExecutor);
ASSERT_TRUE(s4.HasValue());
ASSERT_TRUE(s4r.HasValue());
EXPECT_TRUE(std::any_of(
@@ -2,7 +2,7 @@
#include "fesa/analysis/analysis_model.hpp"
#include "fesa/fem/dof_manager.hpp"
#include "fesa/model/domain.hpp"
#include "fesa/model/domain.h"
#include <gtest/gtest.h>
@@ -19,8 +19,8 @@ fesa::DofManager makeDofs(
std::vector<fesa::BoundaryCondition> boundaries) {
const std::filesystem::path source{"models/essential-constraints.inp"};
fesa::ModelDefinition definition{};
definition.sourcePath = source;
definition.sourceContentIdentity = "fnv1a64:1234567890abcdef";
definition.source_path = source;
definition.source_content_identity = "fnv1a64:1234567890abcdef";
definition.nodes = {
{{"Beam-1", 1, "1"}, {0.0, 0.0, 0.0}, {source, 2U}}};
definition.steps = {{
@@ -33,7 +33,7 @@ fesa::DofManager makeDofs(
1.0,
{source, 10U}}};
auto domain = fesa::Domain::create(std::move(definition));
auto domain = fesa::Domain::Create(std::move(definition));
EXPECT_TRUE(domain.HasValue());
auto model = fesa::AnalysisModel::create(domain.Value());
EXPECT_TRUE(model.HasValue());
@@ -46,8 +46,8 @@ fesa::DofManager makeShellSizedDofs(
std::vector<fesa::BoundaryCondition> boundaries) {
const std::filesystem::path source{"models/shell-essential-constraints.inp"};
fesa::ModelDefinition definition{};
definition.sourcePath = source;
definition.sourceContentIdentity = "fnv1a64:8877665544332211";
definition.source_path = source;
definition.source_content_identity = "fnv1a64:8877665544332211";
definition.nodes = {
{{"Shell-1", 1, "1"}, {0.0, 0.0, 0.0}, {source, 2U}},
{{"Shell-1", 2, "2"}, {1.0, 0.0, 0.0}, {source, 3U}},
@@ -55,11 +55,11 @@ fesa::DofManager makeShellSizedDofs(
{{"Shell-1", 4, "4"}, {0.0, 1.0, 0.0}, {source, 5U}}};
definition.materials = {
{"Material", 1000.0, 0.25, {source, 6U}}};
definition.shellSections = {
definition.shell_sections = {
{"ShellSection", 0.1, 0U, {source, 7U}}};
definition.shellElements = {{
definition.shell_elements = {{
{"Shell-1", 1, "1"},
fesa::ShellSourceElementType::s4,
fesa::ShellSourceElementType::kS4,
{0U, 1U, 2U, 3U},
0U,
0U,
@@ -74,7 +74,7 @@ fesa::DofManager makeShellSizedDofs(
1.0,
{source, 10U}}};
auto domain = fesa::Domain::create(std::move(definition));
auto domain = fesa::Domain::Create(std::move(definition));
EXPECT_TRUE(domain.HasValue());
auto model = fesa::AnalysisModel::create(domain.Value());
EXPECT_TRUE(model.HasValue());
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+11 -11
View File
@@ -14,8 +14,8 @@ namespace {
fesa::ModelDefinition makeDefinition() {
const std::filesystem::path source{"models/dof-manager.inp"};
fesa::ModelDefinition definition{};
definition.sourcePath = source;
definition.sourceContentIdentity = "fnv1a64:0123456789abcdef";
definition.source_path = source;
definition.source_content_identity = "fnv1a64:0123456789abcdef";
definition.nodes = {
{{"Beam-1", 10, "10"}, {0.0, 0.0, 0.0}, {source, 10U}},
{{"Beam-1", 20, "20"}, {1.0, 0.0, 0.0}, {source, 11U}},
@@ -28,7 +28,7 @@ fesa::ModelDefinition makeDefinition() {
definition.elements = {
{{"Beam-1", 100, "100"}, {0U, 1U}, 0U, 0U, {source, 40U}},
{{"Beam-1", 200, "200"}, {1U, 2U}, 0U, 0U, {source, 41U}}};
definition.nodeSets = {
definition.node_sets = {
{"Root", std::optional<std::string>{"Beam-1"}, {0U}, {source, 50U}},
{"Ends", std::optional<std::string>{"Beam-1"}, {0U, 2U}, {source, 51U}}};
definition.steps = {{
@@ -49,8 +49,8 @@ fesa::ModelDefinition makeDefinition() {
fesa::ModelDefinition makeShellDefinition() {
const std::filesystem::path source{"models/shell-dof-manager.inp"};
fesa::ModelDefinition definition{};
definition.sourcePath = source;
definition.sourceContentIdentity = "fnv1a64:1122334455667788";
definition.source_path = source;
definition.source_content_identity = "fnv1a64:1122334455667788";
definition.nodes = {
{{"Shell-1", 10, "10"}, {0.0, 0.0, 0.0}, {source, 10U}},
{{"Shell-1", 20, "20"}, {1.0, 0.0, 0.0}, {source, 11U}},
@@ -58,11 +58,11 @@ fesa::ModelDefinition makeShellDefinition() {
{{"Shell-1", 40, "40"}, {0.0, 1.0, 0.0}, {source, 13U}}};
definition.materials = {
{"Material", 1000.0, 0.25, {source, 20U}}};
definition.shellSections = {
definition.shell_sections = {
{"ShellSection", 0.1, 0U, {source, 30U}}};
definition.shellElements = {{
definition.shell_elements = {{
{"Shell-1", 100, "100"},
fesa::ShellSourceElementType::s4,
fesa::ShellSourceElementType::kS4,
{2U, 0U, 3U, 1U},
0U,
0U,
@@ -77,7 +77,7 @@ struct DofFixture {
};
DofFixture makeDofFixture(fesa::ModelDefinition definition = makeDefinition()) {
auto domain = fesa::Domain::create(std::move(definition));
auto domain = fesa::Domain::Create(std::move(definition));
EXPECT_TRUE(domain.HasValue());
auto model = fesa::AnalysisModel::create(domain.Value());
EXPECT_TRUE(model.HasValue());
@@ -131,8 +131,8 @@ TEST(DofManager, ExpandsAndValidatesPrescribedValues) {
auto conflictingDefinition = makeDefinition();
conflictingDefinition.steps[0].boundaries.push_back(
{"root", 1, 1, 1.0, {conflictingDefinition.sourcePath, 77U}});
auto domain = fesa::Domain::create(std::move(conflictingDefinition));
{"root", 1, 1, 1.0, {conflictingDefinition.source_path, 77U}});
auto domain = fesa::Domain::Create(std::move(conflictingDefinition));
ASSERT_TRUE(domain.HasValue());
auto model = fesa::AnalysisModel::create(domain.Value());
ASSERT_TRUE(model.HasValue());
+139 -139
View File
@@ -249,55 +249,55 @@ TEST(InpDomainMapping, MapsEverySupportedKeywordAndLegacyDeck) {
ASSERT_TRUE(result.HasValue());
const fesa::Domain& domain = result.Value();
ASSERT_EQ(domain.nodes().size(), 2U);
EXPECT_EQ(domain.nodes()[0].sourceId.instance_name, "Beam-1");
EXPECT_EQ(domain.nodes()[0].sourceId.source_label, 1);
EXPECT_EQ(domain.nodes()[0].sourceId.source_label_text, "0001");
EXPECT_EQ(domain.nodes()[1].sourceId.source_label_text, "0002");
EXPECT_DOUBLE_EQ(domain.nodes()[1].coordinates[0], 2.0);
ASSERT_EQ(domain.Nodes().size(), 2U);
EXPECT_EQ(domain.Nodes()[0].source_id.instance_name, "Beam-1");
EXPECT_EQ(domain.Nodes()[0].source_id.source_label, 1);
EXPECT_EQ(domain.Nodes()[0].source_id.source_label_text, "0001");
EXPECT_EQ(domain.Nodes()[1].source_id.source_label_text, "0002");
EXPECT_DOUBLE_EQ(domain.Nodes()[1].coordinates[0], 2.0);
ASSERT_EQ(domain.elements().size(), 1U);
EXPECT_EQ(domain.elements()[0].sourceId.source_label_text, "0007");
EXPECT_EQ(domain.elements()[0].nodeIndices[0], 0U);
EXPECT_EQ(domain.elements()[0].nodeIndices[1], 1U);
ASSERT_EQ(domain.Elements().size(), 1U);
EXPECT_EQ(domain.Elements()[0].source_id.source_label_text, "0007");
EXPECT_EQ(domain.Elements()[0].node_indices[0], 0U);
EXPECT_EQ(domain.Elements()[0].node_indices[1], 1U);
ASSERT_EQ(domain.materials().size(), 1U);
EXPECT_EQ(domain.materials()[0].name, "Steel");
EXPECT_DOUBLE_EQ(domain.materials()[0].youngsModulus, 210000.0);
EXPECT_DOUBLE_EQ(domain.materials()[0].poissonRatio, 0.75);
ASSERT_EQ(domain.Materials().size(), 1U);
EXPECT_EQ(domain.Materials()[0].name, "Steel");
EXPECT_DOUBLE_EQ(domain.Materials()[0].youngs_modulus, 210000.0);
EXPECT_DOUBLE_EQ(domain.Materials()[0].poisson_ratio, 0.75);
ASSERT_EQ(domain.sections().size(), 1U);
const auto& section = domain.sections()[0];
ASSERT_EQ(domain.Sections().size(), 1U);
const auto& section = domain.Sections()[0];
EXPECT_DOUBLE_EQ(section.area, 2.0);
EXPECT_DOUBLE_EQ(section.i11, 3.0);
EXPECT_DOUBLE_EQ(section.i12, 0.0);
EXPECT_DOUBLE_EQ(section.i22, 4.0);
EXPECT_DOUBLE_EQ(section.torsionalConstant, 5.0);
EXPECT_EQ(section.firstAxis, (std::array<double, 3>{0.0, 1.0, 0.0}));
EXPECT_DOUBLE_EQ(section.torsional_constant, 5.0);
EXPECT_EQ(section.first_axis, (std::array<double, 3>{0.0, 1.0, 0.0}));
EXPECT_EQ(
section.sectionPoints,
section.section_points,
(std::vector<std::array<double, 2>>{{-0.5, 0.25}, {0.5, -0.25}}));
EXPECT_EQ(domain.elements()[0].materialIndex, 0U);
EXPECT_EQ(domain.elements()[0].sectionIndex, 0U);
EXPECT_EQ(domain.Elements()[0].material_index, 0U);
EXPECT_EQ(domain.Elements()[0].section_index, 0U);
ASSERT_EQ(domain.steps().size(), 1U);
const auto& step = domain.steps()[0];
ASSERT_EQ(domain.Steps().size(), 1U);
const auto& step = domain.Steps()[0];
EXPECT_EQ(step.name, "Step-1");
EXPECT_DOUBLE_EQ(step.initialIncrement, 0.25);
EXPECT_DOUBLE_EQ(step.timePeriod, 1.5);
EXPECT_DOUBLE_EQ(step.minimumIncrement, 0.01);
EXPECT_DOUBLE_EQ(step.maximumIncrement, 1.5);
EXPECT_DOUBLE_EQ(step.initial_increment, 0.25);
EXPECT_DOUBLE_EQ(step.time_period, 1.5);
EXPECT_DOUBLE_EQ(step.minimum_increment, 0.01);
EXPECT_DOUBLE_EQ(step.maximum_increment, 1.5);
ASSERT_EQ(step.boundaries.size(), 2U);
EXPECT_EQ(step.boundaries[0].target, "rootassembly");
EXPECT_EQ(step.boundaries[0].firstDof, 1);
EXPECT_EQ(step.boundaries[0].first_dof, 1);
EXPECT_DOUBLE_EQ(step.boundaries[0].value, 0.125);
EXPECT_EQ(step.boundaries[1].lastDof, 2);
EXPECT_EQ(step.boundaries[1].last_dof, 2);
EXPECT_DOUBLE_EQ(step.boundaries[1].value, 0.0);
ASSERT_EQ(step.loads.size(), 1U);
EXPECT_EQ(step.loads[0].target, "RootAssembly");
EXPECT_EQ(step.loads[0].dof, 6);
EXPECT_DOUBLE_EQ(step.loads[0].magnitude, -12.5);
EXPECT_EQ(domain.warnings().size(), 8U);
EXPECT_EQ(domain.Warnings().size(), 8U);
const auto legacyPath = repositoryRoot() / "reference" /
"cantilever beam" / "cantilever beam.inp";
@@ -307,12 +307,12 @@ TEST(InpDomainMapping, MapsEverySupportedKeywordAndLegacyDeck) {
ASSERT_TRUE(parsedLegacy.HasValue());
auto legacy = fesa::AbaqusDomainMapper{}.map(parsedLegacy.Value());
ASSERT_TRUE(legacy.HasValue());
EXPECT_EQ(legacy.Value().nodes().size(), 11U);
EXPECT_EQ(legacy.Value().elements().size(), 10U);
EXPECT_EQ(legacy.Value().materials().size(), 1U);
EXPECT_EQ(legacy.Value().sections().size(), 1U);
EXPECT_EQ(legacy.Value().steps().size(), 1U);
EXPECT_EQ(legacy.Value().warnings().size(), 7U);
EXPECT_EQ(legacy.Value().Nodes().size(), 11U);
EXPECT_EQ(legacy.Value().Elements().size(), 10U);
EXPECT_EQ(legacy.Value().Materials().size(), 1U);
EXPECT_EQ(legacy.Value().Sections().size(), 1U);
EXPECT_EQ(legacy.Value().Steps().size(), 1U);
EXPECT_EQ(legacy.Value().Warnings().size(), 7U);
EXPECT_EQ(readExactBytes(legacyPath), bytesBefore);
EXPECT_EQ(std::filesystem::last_write_time(legacyPath), timestampBefore);
}
@@ -359,52 +359,52 @@ OnlySecond, 2, 5.
ASSERT_TRUE(result.HasValue());
const fesa::Domain& domain = result.Value();
ASSERT_EQ(domain.nodes().size(), 4U);
EXPECT_EQ(domain.nodes()[0].sourceId.instance_name, "First");
EXPECT_EQ(domain.nodes()[0].sourceId.source_label, 10);
EXPECT_EQ(domain.nodes()[1].sourceId.instance_name, "First");
EXPECT_EQ(domain.nodes()[1].sourceId.source_label, 20);
EXPECT_EQ(domain.nodes()[2].sourceId.instance_name, "Second");
EXPECT_EQ(domain.nodes()[2].sourceId.source_label, 10);
EXPECT_EQ(domain.nodes()[3].sourceId.instance_name, "Second");
EXPECT_EQ(domain.nodes()[3].sourceId.source_label, 20);
ASSERT_EQ(domain.Nodes().size(), 4U);
EXPECT_EQ(domain.Nodes()[0].source_id.instance_name, "First");
EXPECT_EQ(domain.Nodes()[0].source_id.source_label, 10);
EXPECT_EQ(domain.Nodes()[1].source_id.instance_name, "First");
EXPECT_EQ(domain.Nodes()[1].source_id.source_label, 20);
EXPECT_EQ(domain.Nodes()[2].source_id.instance_name, "Second");
EXPECT_EQ(domain.Nodes()[2].source_id.source_label, 10);
EXPECT_EQ(domain.Nodes()[3].source_id.instance_name, "Second");
EXPECT_EQ(domain.Nodes()[3].source_id.source_label, 20);
ASSERT_EQ(domain.elements().size(), 2U);
EXPECT_EQ(domain.elements()[0].sourceId.instance_name, "First");
EXPECT_EQ(domain.elements()[0].nodeIndices,
ASSERT_EQ(domain.Elements().size(), 2U);
EXPECT_EQ(domain.Elements()[0].source_id.instance_name, "First");
EXPECT_EQ(domain.Elements()[0].node_indices,
(std::array<fesa::EntityIndex, 2>{0U, 1U}));
EXPECT_EQ(domain.elements()[1].sourceId.instance_name, "Second");
EXPECT_EQ(domain.elements()[1].nodeIndices,
EXPECT_EQ(domain.Elements()[1].source_id.instance_name, "Second");
EXPECT_EQ(domain.Elements()[1].node_indices,
(std::array<fesa::EntityIndex, 2>{2U, 3U}));
ASSERT_EQ(domain.nodeSets().size(), 3U);
EXPECT_EQ(domain.nodeSets()[0].name, "Ends");
EXPECT_EQ(domain.nodeSets()[0].instanceName, std::optional<std::string>{"First"});
EXPECT_EQ(domain.nodeSets()[0].nodeIndices,
ASSERT_EQ(domain.NodeSets().size(), 3U);
EXPECT_EQ(domain.NodeSets()[0].name, "Ends");
EXPECT_EQ(domain.NodeSets()[0].instance_name, std::optional<std::string>{"First"});
EXPECT_EQ(domain.NodeSets()[0].node_indices,
(std::vector<fesa::EntityIndex>{0U, 1U}));
EXPECT_EQ(domain.nodeSets()[1].instanceName, std::optional<std::string>{"Second"});
EXPECT_EQ(domain.nodeSets()[1].nodeIndices,
EXPECT_EQ(domain.NodeSets()[1].instance_name, std::optional<std::string>{"Second"});
EXPECT_EQ(domain.NodeSets()[1].node_indices,
(std::vector<fesa::EntityIndex>{2U, 3U}));
EXPECT_EQ(domain.nodeSets()[2].name, "OnlySecond");
EXPECT_EQ(domain.nodeSets()[2].nodeIndices,
EXPECT_EQ(domain.NodeSets()[2].name, "OnlySecond");
EXPECT_EQ(domain.NodeSets()[2].node_indices,
(std::vector<fesa::EntityIndex>{3U}));
ASSERT_EQ(domain.elementSets().size(), 3U);
EXPECT_EQ(domain.elementSets()[0].instanceName,
ASSERT_EQ(domain.ElementSets().size(), 3U);
EXPECT_EQ(domain.ElementSets()[0].instance_name,
std::optional<std::string>{"First"});
EXPECT_EQ(domain.elementSets()[0].elementIndices,
EXPECT_EQ(domain.ElementSets()[0].element_indices,
(std::vector<fesa::EntityIndex>{0U}));
EXPECT_EQ(domain.elementSets()[1].instanceName,
EXPECT_EQ(domain.ElementSets()[1].instance_name,
std::optional<std::string>{"Second"});
EXPECT_EQ(domain.elementSets()[1].elementIndices,
EXPECT_EQ(domain.ElementSets()[1].element_indices,
(std::vector<fesa::EntityIndex>{1U}));
EXPECT_EQ(domain.elementSets()[2].name, "OnlySecondBeam");
EXPECT_EQ(domain.elementSets()[2].elementIndices,
EXPECT_EQ(domain.ElementSets()[2].name, "OnlySecondBeam");
EXPECT_EQ(domain.ElementSets()[2].element_indices,
(std::vector<fesa::EntityIndex>{1U}));
ASSERT_EQ(domain.steps().size(), 1U);
EXPECT_EQ(domain.steps()[0].boundaries[0].target, "OnlySecond");
EXPECT_EQ(domain.steps()[0].loads[0].target, "OnlySecond");
ASSERT_EQ(domain.Steps().size(), 1U);
EXPECT_EQ(domain.Steps()[0].boundaries[0].target, "OnlySecond");
EXPECT_EQ(domain.Steps()[0].loads[0].target, "OnlySecond");
auto direct = mapText(
"direct-node-labels",
@@ -413,8 +413,8 @@ OnlySecond, 2, 5.
"Tip, 2, -1.",
"2, 2, -1."));
ASSERT_TRUE(direct.HasValue());
EXPECT_EQ(direct.Value().steps()[0].boundaries[0].target, "1");
EXPECT_EQ(direct.Value().steps()[0].loads[0].target, "2");
EXPECT_EQ(direct.Value().Steps()[0].boundaries[0].target, "1");
EXPECT_EQ(direct.Value().Steps()[0].loads[0].target, "2");
auto aboveThresholds = mapText(
"above-geometry-thresholds",
@@ -447,10 +447,10 @@ TEST(InpDomainMapping, KeepsNodeAndElementSetNamesInSeparateNamespaces) {
ASSERT_TRUE(sharedName.HasValue());
const auto& domain = sharedName.Value();
EXPECT_TRUE(std::any_of(
domain.nodeSets().begin(), domain.nodeSets().end(),
domain.NodeSets().begin(), domain.NodeSets().end(),
[](const fesa::NodeSet& set) { return set.name == "ShellS4"; }));
EXPECT_TRUE(std::any_of(
domain.elementSets().begin(), domain.elementSets().end(),
domain.ElementSets().begin(), domain.ElementSets().end(),
[](const fesa::ElementSet& set) { return set.name == "ShellS4"; }));
auto duplicateNodeSet = mapText(
@@ -486,16 +486,16 @@ TEST(InpDomainMapping, KeepsNodeAndElementSetNamesInSeparateNamespaces) {
ASSERT_TRUE(assemblySharedName.HasValue());
const auto& assemblyDomain = assemblySharedName.Value();
const auto rootNodeSetCount = std::count_if(
assemblyDomain.nodeSets().begin(), assemblyDomain.nodeSets().end(),
assemblyDomain.NodeSets().begin(), assemblyDomain.NodeSets().end(),
[](const fesa::NodeSet& set) { return set.name == "Root"; });
EXPECT_EQ(rootNodeSetCount, 1);
const auto rootNodeSet = std::find_if(
assemblyDomain.nodeSets().begin(), assemblyDomain.nodeSets().end(),
assemblyDomain.NodeSets().begin(), assemblyDomain.NodeSets().end(),
[](const fesa::NodeSet& set) { return set.name == "Root"; });
ASSERT_NE(rootNodeSet, assemblyDomain.nodeSets().end());
EXPECT_EQ(rootNodeSet->nodeIndices, (std::vector<fesa::EntityIndex>{0U}));
ASSERT_NE(rootNodeSet, assemblyDomain.NodeSets().end());
EXPECT_EQ(rootNodeSet->node_indices, (std::vector<fesa::EntityIndex>{0U}));
EXPECT_EQ(std::count_if(
assemblyDomain.elementSets().begin(), assemblyDomain.elementSets().end(),
assemblyDomain.ElementSets().begin(), assemblyDomain.ElementSets().end(),
[](const fesa::ElementSet& set) { return set.name == "Root"; }), 1);
}
@@ -505,33 +505,33 @@ TEST(InpDomainMapping, NoOpAllowlistWarnsWithoutSemanticEffect) {
ASSERT_TRUE(plain.HasValue());
ASSERT_TRUE(withNoOps.HasValue());
EXPECT_TRUE(plain.Value().warnings().empty());
ASSERT_EQ(withNoOps.Value().warnings().size(), 8U);
EXPECT_EQ(withNoOps.Value().warnings()[0].code, "ignored-input-keyword");
EXPECT_EQ(withNoOps.Value().warnings()[0].keyword, "PREPRINT");
EXPECT_EQ(withNoOps.Value().warnings()[1].keyword,
EXPECT_TRUE(plain.Value().Warnings().empty());
ASSERT_EQ(withNoOps.Value().Warnings().size(), 8U);
EXPECT_EQ(withNoOps.Value().Warnings()[0].code, "ignored-input-keyword");
EXPECT_EQ(withNoOps.Value().Warnings()[0].keyword, "PREPRINT");
EXPECT_EQ(withNoOps.Value().Warnings()[1].keyword,
"TRANSVERSE SHEAR STIFFNESS");
EXPECT_EQ(withNoOps.Value().warnings()[2].keyword, "RESTART");
EXPECT_EQ(withNoOps.Value().warnings()[3].keyword, "OUTPUT");
EXPECT_EQ(withNoOps.Value().warnings()[4].keyword, "NODE OUTPUT");
EXPECT_EQ(withNoOps.Value().warnings()[5].keyword, "ELEMENT OUTPUT");
EXPECT_EQ(withNoOps.Value().warnings()[6].keyword, "CONTACT OUTPUT");
EXPECT_EQ(withNoOps.Value().warnings()[7].keyword, "OUTPUT");
for (const auto& warning : withNoOps.Value().warnings()) {
EXPECT_EQ(withNoOps.Value().Warnings()[2].keyword, "RESTART");
EXPECT_EQ(withNoOps.Value().Warnings()[3].keyword, "OUTPUT");
EXPECT_EQ(withNoOps.Value().Warnings()[4].keyword, "NODE OUTPUT");
EXPECT_EQ(withNoOps.Value().Warnings()[5].keyword, "ELEMENT OUTPUT");
EXPECT_EQ(withNoOps.Value().Warnings()[6].keyword, "CONTACT OUTPUT");
EXPECT_EQ(withNoOps.Value().Warnings()[7].keyword, "OUTPUT");
for (const auto& warning : withNoOps.Value().Warnings()) {
EXPECT_EQ(warning.severity, fesa::Severity::kWarning);
}
EXPECT_EQ(withNoOps.Value().nodes().size(), plain.Value().nodes().size());
EXPECT_EQ(withNoOps.Value().elements().size(), plain.Value().elements().size());
EXPECT_EQ(withNoOps.Value().materials().size(), plain.Value().materials().size());
EXPECT_EQ(withNoOps.Value().sections().size(), plain.Value().sections().size());
EXPECT_EQ(withNoOps.Value().nodeSets().size(), plain.Value().nodeSets().size());
EXPECT_EQ(withNoOps.Value().elementSets().size(), plain.Value().elementSets().size());
EXPECT_EQ(withNoOps.Value().steps().size(), plain.Value().steps().size());
EXPECT_EQ(withNoOps.Value().steps()[0].boundaries.size(),
plain.Value().steps()[0].boundaries.size());
EXPECT_EQ(withNoOps.Value().steps()[0].loads.size(),
plain.Value().steps()[0].loads.size());
EXPECT_EQ(withNoOps.Value().Nodes().size(), plain.Value().Nodes().size());
EXPECT_EQ(withNoOps.Value().Elements().size(), plain.Value().Elements().size());
EXPECT_EQ(withNoOps.Value().Materials().size(), plain.Value().Materials().size());
EXPECT_EQ(withNoOps.Value().Sections().size(), plain.Value().Sections().size());
EXPECT_EQ(withNoOps.Value().NodeSets().size(), plain.Value().NodeSets().size());
EXPECT_EQ(withNoOps.Value().ElementSets().size(), plain.Value().ElementSets().size());
EXPECT_EQ(withNoOps.Value().Steps().size(), plain.Value().Steps().size());
EXPECT_EQ(withNoOps.Value().Steps()[0].boundaries.size(),
plain.Value().Steps()[0].boundaries.size());
EXPECT_EQ(withNoOps.Value().Steps()[0].loads.size(),
plain.Value().Steps()[0].loads.size());
}
// MITC4-MAP-001
@@ -540,63 +540,63 @@ TEST(InpDomainMapping, MapsS4AndS4rThroughOneMitc4Identity) {
ASSERT_TRUE(result.HasValue());
const fesa::Domain& domain = result.Value();
EXPECT_TRUE(domain.elements().empty());
ASSERT_EQ(domain.shellElements().size(), 4U);
EXPECT_EQ(domain.shellElements()[0].sourceId.instance_name, "First");
EXPECT_EQ(domain.shellElements()[0].sourceId.source_label_text, "0010");
EXPECT_TRUE(domain.Elements().empty());
ASSERT_EQ(domain.ShellElements().size(), 4U);
EXPECT_EQ(domain.ShellElements()[0].source_id.instance_name, "First");
EXPECT_EQ(domain.ShellElements()[0].source_id.source_label_text, "0010");
EXPECT_EQ(
domain.shellElements()[0].sourceType,
fesa::ShellSourceElementType::s4);
domain.ShellElements()[0].source_type,
fesa::ShellSourceElementType::kS4);
EXPECT_EQ(
domain.shellElements()[0].nodeIndices,
domain.ShellElements()[0].node_indices,
(std::array<fesa::EntityIndex, 4>{0U, 1U, 2U, 3U}));
EXPECT_EQ(domain.shellElements()[0].sectionIndex, 0U);
EXPECT_EQ(domain.shellElements()[0].materialIndex, 0U);
EXPECT_EQ(domain.ShellElements()[0].section_index, 0U);
EXPECT_EQ(domain.ShellElements()[0].material_index, 0U);
EXPECT_EQ(domain.shellElements()[1].sourceId.instance_name, "First");
EXPECT_EQ(domain.ShellElements()[1].source_id.instance_name, "First");
EXPECT_EQ(
domain.shellElements()[1].sourceType,
fesa::ShellSourceElementType::s4r);
domain.ShellElements()[1].source_type,
fesa::ShellSourceElementType::kS4r);
EXPECT_EQ(
domain.shellElements()[1].nodeIndices,
domain.ShellElements()[1].node_indices,
(std::array<fesa::EntityIndex, 4>{1U, 4U, 5U, 2U}));
EXPECT_EQ(domain.shellElements()[1].sectionIndex, 1U);
EXPECT_EQ(domain.shellElements()[1].materialIndex, 1U);
EXPECT_EQ(domain.ShellElements()[1].section_index, 1U);
EXPECT_EQ(domain.ShellElements()[1].material_index, 1U);
EXPECT_EQ(domain.shellElements()[2].sourceId.instance_name, "Second");
EXPECT_EQ(domain.ShellElements()[2].source_id.instance_name, "Second");
EXPECT_EQ(
domain.shellElements()[2].nodeIndices,
domain.ShellElements()[2].node_indices,
(std::array<fesa::EntityIndex, 4>{6U, 7U, 8U, 9U}));
EXPECT_EQ(domain.shellElements()[3].sourceId.instance_name, "Second");
EXPECT_EQ(domain.ShellElements()[3].source_id.instance_name, "Second");
EXPECT_EQ(
fesa::kMitc4InternalFormulation,
std::string_view{"FESA-MITC4"});
ASSERT_EQ(domain.shellSections().size(), 2U);
EXPECT_EQ(domain.shellSections()[0].name, "ShellS4");
EXPECT_DOUBLE_EQ(domain.shellSections()[0].thickness, 0.1);
EXPECT_EQ(domain.shellSections()[0].materialIndex, 0U);
EXPECT_EQ(domain.shellSections()[1].name, "ShellS4R");
EXPECT_DOUBLE_EQ(domain.shellSections()[1].thickness, 0.2);
EXPECT_EQ(domain.shellSections()[1].materialIndex, 1U);
ASSERT_EQ(domain.ShellSections().size(), 2U);
EXPECT_EQ(domain.ShellSections()[0].name, "ShellS4");
EXPECT_DOUBLE_EQ(domain.ShellSections()[0].thickness, 0.1);
EXPECT_EQ(domain.ShellSections()[0].material_index, 0U);
EXPECT_EQ(domain.ShellSections()[1].name, "ShellS4R");
EXPECT_DOUBLE_EQ(domain.ShellSections()[1].thickness, 0.2);
EXPECT_EQ(domain.ShellSections()[1].material_index, 1U);
ASSERT_EQ(domain.shellNodeInitialFrames().size(), domain.nodes().size());
EXPECT_EQ(domain.shellNodeInitialFrames()[0].nodeIndex, 0U);
ASSERT_EQ(domain.ShellNodeInitialFrames().size(), domain.Nodes().size());
EXPECT_EQ(domain.ShellNodeInitialFrames()[0].node_index, 0U);
EXPECT_EQ(
domain.shellNodeInitialFrames()[0].director,
domain.ShellNodeInitialFrames()[0].director,
(std::array<double, 3>{0.0, 0.0, 1.0}));
EXPECT_EQ(
domain.shellNodeInitialFrames()[0].tangentA,
domain.ShellNodeInitialFrames()[0].tangent_a,
(std::array<double, 3>{1.0, 0.0, 0.0}));
EXPECT_EQ(
domain.shellNodeInitialFrames()[0].tangentB,
domain.ShellNodeInitialFrames()[0].tangent_b,
(std::array<double, 3>{0.0, 1.0, 0.0}));
ASSERT_EQ(domain.steps().size(), 1U);
ASSERT_EQ(domain.steps()[0].boundaries.size(), 1U);
EXPECT_EQ(domain.steps()[0].boundaries[0].lastDof, 6);
ASSERT_EQ(domain.steps()[0].loads.size(), 1U);
EXPECT_EQ(domain.steps()[0].loads[0].dof, 6);
ASSERT_EQ(domain.Steps().size(), 1U);
ASSERT_EQ(domain.Steps()[0].boundaries.size(), 1U);
EXPECT_EQ(domain.Steps()[0].boundaries[0].last_dof, 6);
ASSERT_EQ(domain.Steps()[0].loads.size(), 1U);
EXPECT_EQ(domain.Steps()[0].loads[0].dof, 6);
}
// MITC4-MAP-002
@@ -690,10 +690,10 @@ TEST(InpDomainMapping, PreservesProcedureLoadAndOutputRequestBoundariesForShells
"*Output, field\n*Node Output\nU, RF\n*Element Output\nS\n*End Step\n");
auto valid = mapText("mitc4-map-004-no-ops", withNoOps);
ASSERT_TRUE(valid.HasValue());
ASSERT_EQ(valid.Value().warnings().size(), 3U);
EXPECT_EQ(valid.Value().warnings()[0].keyword, "OUTPUT");
EXPECT_EQ(valid.Value().warnings()[1].keyword, "NODE OUTPUT");
EXPECT_EQ(valid.Value().warnings()[2].keyword, "ELEMENT OUTPUT");
ASSERT_EQ(valid.Value().Warnings().size(), 3U);
EXPECT_EQ(valid.Value().Warnings()[0].keyword, "OUTPUT");
EXPECT_EQ(valid.Value().Warnings()[1].keyword, "NODE OUTPUT");
EXPECT_EQ(valid.Value().Warnings()[2].keyword, "ELEMENT OUTPUT");
struct InvalidCase {
std::string name;
+20 -20
View File
@@ -7,7 +7,7 @@
#include "fesa/analysis/analysis_state.hpp"
#include "fesa/build_info.h"
#include "fesa/fem/dof_manager.hpp"
#include "fesa/model/domain.hpp"
#include "fesa/model/domain.h"
#include <hdf5.h>
@@ -123,8 +123,8 @@ fesa::ModelDefinition makeDefinition(
const std::filesystem::path& source,
const bool useDefaultCentroid) {
fesa::ModelDefinition definition{};
definition.sourcePath = source;
definition.sourceContentIdentity = "fnv1a64:0123456789abcdef";
definition.source_path = source;
definition.source_content_identity = "fnv1a64:0123456789abcdef";
definition.nodes = {
{{u8"Beam-\u03b1", 101, "101"}, {0.0, 0.0, 0.0}, {source, 10U}},
{{u8"Beam-\u03b1", 202, "202"}, {3.0, 4.0, 0.0}, {source, 11U}}};
@@ -156,7 +156,7 @@ fesa::ModelDefinition makeDefinition(
WriterFixture makeFixture(
const std::filesystem::path& source,
const bool useDefaultCentroid = false) {
auto domainResult = fesa::Domain::create(
auto domainResult = fesa::Domain::Create(
makeDefinition(source, useDefaultCentroid));
if (!domainResult.HasValue()) {
throw std::runtime_error{"Writer fixture Domain construction failed."};
@@ -186,16 +186,16 @@ WriterFixture makeFixture(
state->reaction()[index] = 100.0 + static_cast<double>(index);
}
const auto& nodes = domain->nodes();
const auto& nodes = domain->Nodes();
state->endpointResults() = {
{0U,
0,
nodes[0U].sourceId,
nodes[0U].source_id,
{1.0, 2.0, 3.0, 4.0, 5.0, 6.0},
{11.0, 12.0, 13.0, 14.0}},
{0U,
1,
nodes[1U].sourceId,
nodes[1U].source_id,
{7.0, 8.0, 9.0, 10.0, 11.0, 12.0},
{15.0, 16.0, 17.0, 18.0}}};
state->gaussResults() = {
@@ -217,8 +217,8 @@ WriterFixture makeFixture(
fesa::ModelDefinition makeShellDefinition(const std::filesystem::path& source) {
fesa::ModelDefinition definition{};
definition.sourcePath = source;
definition.sourceContentIdentity = "fnv1a64:fedcba9876543210";
definition.source_path = source;
definition.source_content_identity = "fnv1a64:fedcba9876543210";
definition.nodes = {
{{"Shell-1", 11, "11"}, {-1.0, -1.0, 0.0}, {source, 10U}},
{{"Shell-1", 12, "12"}, {1.0, -1.0, 0.0}, {source, 11U}},
@@ -226,23 +226,23 @@ fesa::ModelDefinition makeShellDefinition(const std::filesystem::path& source) {
{{"Shell-1", 14, "14"}, {-1.0, 1.0, 0.0}, {source, 13U}}};
definition.materials = {
{"ShellSteel", 210.0e9, 0.3, {source, 20U}}};
definition.shellSections = {
definition.shell_sections = {
{"PlateSet", 0.02, 0U, {source, 30U}}};
definition.shellElements = {{
definition.shell_elements = {{
{"Shell-1", 401, "401"},
fesa::ShellSourceElementType::s4r,
fesa::ShellSourceElementType::kS4r,
{0U, 1U, 2U, 3U},
0U,
0U,
{source, 40U}}};
for (std::size_t node = 0U; node < definition.nodes.size(); ++node) {
definition.shellNodeInitialFrames.push_back({
definition.shell_node_initial_frames.push_back({
static_cast<fesa::EntityIndex>(node),
{0.0, 0.0, 1.0},
{1.0, 0.0, 0.0},
{0.0, 1.0, 0.0}});
}
definition.nodeSets = {
definition.node_sets = {
{"Fixed", {}, {0U}, {source, 50U}}};
definition.steps = {{
"Step-1",
@@ -258,7 +258,7 @@ fesa::ModelDefinition makeShellDefinition(const std::filesystem::path& source) {
}
WriterFixture makeShellFixture(const std::filesystem::path& source) {
auto domainResult = fesa::Domain::create(makeShellDefinition(source));
auto domainResult = fesa::Domain::Create(makeShellDefinition(source));
if (!domainResult.HasValue()) {
throw std::runtime_error{"Shell writer fixture Domain construction failed."};
}
@@ -1033,7 +1033,7 @@ TEST(Hdf5ResultsWriter, WritesMandatoryOutputsDespiteOutputRequests) {
const fesa::Diagnostic ignoredRequest{
fesa::Severity::kWarning,
"ignored-output-request",
{fixture.domain->sourcePath(), 70U},
{fixture.domain->SourcePath(), 70U},
"*OUTPUT",
"FIELD",
"Abaqus output requests do not filter FESA mandatory results."};
@@ -1065,13 +1065,13 @@ TEST(Hdf5ResultsWriter, WritesWarningsAndDefaultCentroid) {
std::vector<fesa::Diagnostic> diagnostics = {
{fesa::Severity::kWarning,
"ignored-output-request",
{fixture.domain->sourcePath(), 80U},
{fixture.domain->SourcePath(), 80U},
"*OUTPUT",
"FIELD",
"Ignored output request."},
{fesa::Severity::kWarning,
"ignored-keyword",
{fixture.domain->sourcePath(), 20U},
{fixture.domain->SourcePath(), 20U},
"*PREPRINT",
"",
"Ignored generator control."}};
@@ -1098,7 +1098,7 @@ TEST(Hdf5ResultsWriter, WritesWarningsAndDefaultCentroid) {
EXPECT_STREQ(rows[0U].code, "ignored-keyword");
EXPECT_STREQ(
rows[0U].file,
std::filesystem::absolute(fixture.domain->sourcePath())
std::filesystem::absolute(fixture.domain->SourcePath())
.lexically_normal()
.generic_u8string()
.c_str());
@@ -1321,7 +1321,7 @@ TEST(Hdf5ResultsWriter, WritesShellInventoryDespiteRequestsAndOmitsForbiddenPath
const fesa::Diagnostic ignoredRequest{
fesa::Severity::kWarning,
"ignored-output-request",
{fixture.domain->sourcePath(), 80U},
{fixture.domain->SourcePath(), 80U},
"*ELEMENT OUTPUT",
"S",
"Output requests cannot filter mandatory shell results."};
+238 -224
View File
@@ -1,4 +1,4 @@
#include "fesa/model/domain.hpp"
#include "fesa/model/domain.h"
#include <gtest/gtest.h>
@@ -10,254 +10,268 @@
namespace {
fesa::ModelDefinition makeOwnedDefinition() {
fesa::ModelDefinition definition{};
definition.sourcePath = "models/owned.inp";
definition.sourceContentIdentity = "fnv1a64:fedcba9876543210";
definition.heading = "Stable declaration order";
definition.nodes = {
{{"Instance-A", 20, "0020"}, {2.0, 0.0, 0.0}, {"models/owned.inp", 12U}},
{{"Instance-A", 10, "0010"}, {1.0, 0.0, 0.0}, {"models/owned.inp", 11U}}};
definition.elements = {{
{"Instance-A", 5, "0005"},
{fesa::EntityIndex{1}, fesa::EntityIndex{0}},
fesa::EntityIndex{0},
fesa::EntityIndex{0},
{"models/owned.inp", 20U}}};
definition.materials = {{
"Steel", 210.0e9, 0.3, {"models/owned.inp", 30U}}};
definition.sections = {{
"Section-1",
0.02,
1.0e-5,
0.0,
2.0e-5,
5.0e-6,
{0.0, 1.0, 0.0},
{{-0.1, 0.0}, {0.1, 0.0}},
{"models/owned.inp", 40U}}};
definition.nodeSets = {{
"Tip", std::string{"Instance-A"}, {0U}, {"models/owned.inp", 45U}}};
definition.elementSets = {{
"Beam", std::string{"Instance-A"}, {0U}, {"models/owned.inp", 46U}}};
definition.parts = {{
"BeamPart", {20, 10}, {5}, {"Tip"}, {"Beam"},
{"models/owned.inp", 2U}}};
definition.instances = {{
"Instance-A",
"BeamPart",
{{20, 0U}, {10, 1U}},
{{5, 0U}},
{"models/owned.inp", 50U}}};
definition.steps = {{
"Step-1",
{{"Root", 1, 6, 0.0, {"models/owned.inp", 60U}}},
{{"Tip", 3, -1000.0, {"models/owned.inp", 61U}}},
0.1,
1.0,
1.0e-5,
1.0,
{"models/owned.inp", 55U}}};
definition.warnings = {{
fesa::Severity::kWarning,
"ignored-output-request",
{"models/owned.inp", 70U},
"*OUTPUT",
"",
"Ignored output request."}};
return definition;
fesa::ModelDefinition MakeOwnedDefinition() {
fesa::ModelDefinition definition{};
definition.source_path = "models/owned.inp";
definition.source_content_identity = "fnv1a64:fedcba9876543210";
definition.heading = "Stable declaration order";
definition.nodes = {
{{"Instance-A", 20, "0020"}, {2.0, 0.0, 0.0}, {"models/owned.inp", 12U}},
{{"Instance-A", 10, "0010"}, {1.0, 0.0, 0.0}, {"models/owned.inp", 11U}}};
definition.elements = {{{"Instance-A", 5, "0005"},
{fesa::EntityIndex{1}, fesa::EntityIndex{0}},
fesa::EntityIndex{0},
fesa::EntityIndex{0},
{"models/owned.inp", 20U}}};
definition.materials = {{"Steel", 210.0e9, 0.3, {"models/owned.inp", 30U}}};
definition.sections = {{"Section-1",
0.02,
1.0e-5,
0.0,
2.0e-5,
5.0e-6,
{0.0, 1.0, 0.0},
{{-0.1, 0.0}, {0.1, 0.0}},
{"models/owned.inp", 40U}}};
definition.node_sets = {
{"Tip", std::string{"Instance-A"}, {0U}, {"models/owned.inp", 45U}}};
definition.element_sets = {
{"Beam", std::string{"Instance-A"}, {0U}, {"models/owned.inp", 46U}}};
definition.parts = {
{"BeamPart", {20, 10}, {5}, {"Tip"}, {"Beam"}, {"models/owned.inp", 2U}}};
definition.instances = {{"Instance-A",
"BeamPart",
{{20, 0U}, {10, 1U}},
{{5, 0U}},
{"models/owned.inp", 50U}}};
definition.steps = {{"Step-1",
{{"Root", 1, 6, 0.0, {"models/owned.inp", 60U}}},
{{"Tip", 3, -1000.0, {"models/owned.inp", 61U}}},
0.1,
1.0,
1.0e-5,
1.0,
{"models/owned.inp", 55U}}};
definition.warnings = {{fesa::Severity::kWarning,
"ignored-output-request",
{"models/owned.inp", 70U},
"*OUTPUT",
"",
"Ignored output request."}};
return definition;
}
} // namespace
} // namespace
TEST(DomainModel, ImmutableOwnershipPreservesStableOrder) {
auto definition = makeOwnedDefinition();
auto result = fesa::Domain::create(definition);
ASSERT_TRUE(result.HasValue());
auto definition = MakeOwnedDefinition();
auto result = fesa::Domain::Create(definition);
ASSERT_TRUE(result.HasValue());
definition.sourcePath = "mutated.inp";
definition.sourceContentIdentity = "mutated";
definition.nodes[0].sourceId.source_label_text = "mutated";
definition.nodes[0].coordinates[0] = -99.0;
definition.sections[0].sectionPoints[0][0] = -99.0;
definition.steps[0].loads[0].magnitude = 99.0;
definition.warnings[0].code = "mutated";
definition.source_path = "mutated.inp";
definition.source_content_identity = "mutated";
definition.nodes[0].source_id.source_label_text = "mutated";
definition.nodes[0].coordinates[0] = -99.0;
definition.sections[0].section_points[0][0] = -99.0;
definition.steps[0].loads[0].magnitude = 99.0;
definition.warnings[0].code = "mutated";
const fesa::Domain& domain = result.Value();
const fesa::Node* const firstNodeAddress = domain.nodes().data();
static_assert(std::is_same_v<
decltype(std::declval<const fesa::Domain&>().nodes()),
const std::vector<fesa::Node>&>);
static_assert(std::is_same_v<
decltype(std::declval<const fesa::Domain&>().elements()),
const std::vector<fesa::EulerBeam3DDefinition>&>);
const fesa::Domain& domain = result.Value();
const fesa::Node* const first_node_address = domain.Nodes().data();
static_assert(
std::is_same_v<decltype(std::declval<const fesa::Domain&>().Nodes()),
const std::vector<fesa::Node>&>);
static_assert(
std::is_same_v<decltype(std::declval<const fesa::Domain&>().Elements()),
const std::vector<fesa::EulerBeam3DDefinition>&>);
EXPECT_EQ(domain.sourcePath(), std::filesystem::path{"models/owned.inp"});
EXPECT_EQ(domain.sourceContentIdentity(), "fnv1a64:fedcba9876543210");
ASSERT_EQ(domain.nodes().size(), 2U);
EXPECT_EQ(domain.nodes()[0].sourceId.source_label, 20);
EXPECT_EQ(domain.nodes()[0].sourceId.source_label_text, "0020");
EXPECT_DOUBLE_EQ(domain.nodes()[0].coordinates[0], 2.0);
EXPECT_EQ(domain.nodes()[1].sourceId.source_label, 10);
EXPECT_EQ(domain.nodes().data(), firstNodeAddress);
EXPECT_EQ(domain.SourcePath(), std::filesystem::path{"models/owned.inp"});
EXPECT_EQ(domain.SourceContentIdentity(), "fnv1a64:fedcba9876543210");
ASSERT_EQ(domain.Nodes().size(), 2U);
EXPECT_EQ(domain.Nodes()[0].source_id.source_label, 20);
EXPECT_EQ(domain.Nodes()[0].source_id.source_label_text, "0020");
EXPECT_DOUBLE_EQ(domain.Nodes()[0].coordinates[0], 2.0);
EXPECT_EQ(domain.Nodes()[1].source_id.source_label, 10);
EXPECT_EQ(domain.Nodes().data(), first_node_address);
ASSERT_EQ(domain.elements().size(), 1U);
EXPECT_EQ(domain.elements()[0].nodeIndices[0], 1U);
EXPECT_EQ(domain.elements()[0].nodeIndices[1], 0U);
ASSERT_EQ(domain.materials().size(), 1U);
ASSERT_EQ(domain.sections().size(), 1U);
EXPECT_DOUBLE_EQ(domain.sections()[0].sectionPoints[0][0], -0.1);
ASSERT_EQ(domain.nodeSets().size(), 1U);
EXPECT_EQ(domain.nodeSets()[0].nodeIndices[0], 0U);
ASSERT_EQ(domain.elementSets().size(), 1U);
EXPECT_EQ(domain.elementSets()[0].elementIndices[0], 0U);
ASSERT_EQ(domain.Elements().size(), 1U);
EXPECT_EQ(domain.Elements()[0].node_indices[0], 1U);
EXPECT_EQ(domain.Elements()[0].node_indices[1], 0U);
ASSERT_EQ(domain.Materials().size(), 1U);
ASSERT_EQ(domain.Sections().size(), 1U);
EXPECT_DOUBLE_EQ(domain.Sections()[0].section_points[0][0], -0.1);
ASSERT_EQ(domain.NodeSets().size(), 1U);
EXPECT_EQ(domain.NodeSets()[0].node_indices[0], 0U);
ASSERT_EQ(domain.ElementSets().size(), 1U);
EXPECT_EQ(domain.ElementSets()[0].element_indices[0], 0U);
ASSERT_EQ(domain.steps().size(), 1U);
EXPECT_EQ(domain.steps()[0].name, "Step-1");
EXPECT_DOUBLE_EQ(domain.steps()[0].initialIncrement, 0.1);
EXPECT_DOUBLE_EQ(domain.steps()[0].timePeriod, 1.0);
EXPECT_DOUBLE_EQ(domain.steps()[0].minimumIncrement, 1.0e-5);
EXPECT_DOUBLE_EQ(domain.steps()[0].maximumIncrement, 1.0);
ASSERT_EQ(domain.steps()[0].boundaries.size(), 1U);
ASSERT_EQ(domain.steps()[0].loads.size(), 1U);
EXPECT_DOUBLE_EQ(domain.steps()[0].loads[0].magnitude, -1000.0);
ASSERT_EQ(domain.Steps().size(), 1U);
EXPECT_EQ(domain.Steps()[0].name, "Step-1");
EXPECT_DOUBLE_EQ(domain.Steps()[0].initial_increment, 0.1);
EXPECT_DOUBLE_EQ(domain.Steps()[0].time_period, 1.0);
EXPECT_DOUBLE_EQ(domain.Steps()[0].minimum_increment, 1.0e-5);
EXPECT_DOUBLE_EQ(domain.Steps()[0].maximum_increment, 1.0);
ASSERT_EQ(domain.Steps()[0].boundaries.size(), 1U);
ASSERT_EQ(domain.Steps()[0].loads.size(), 1U);
EXPECT_DOUBLE_EQ(domain.Steps()[0].loads[0].magnitude, -1000.0);
ASSERT_EQ(domain.warnings().size(), 1U);
EXPECT_EQ(domain.warnings()[0].code, "ignored-output-request");
ASSERT_EQ(domain.Warnings().size(), 1U);
EXPECT_EQ(domain.Warnings()[0].code, "ignored-output-request");
}
TEST(DomainModel, MultipleIdentityInstancesDoNotMerge) {
fesa::ModelDefinition definition{};
definition.sourcePath = "models/two-instances.inp";
definition.sourceContentIdentity = "fnv1a64:0011223344556677";
definition.nodes = {
{{"Instance-A", 1, "1"}, {0.0, 0.0, 0.0}, {"models/two-instances.inp", 10U}},
{{"Instance-A", 2, "2"}, {1.0, 0.0, 0.0}, {"models/two-instances.inp", 11U}},
{{"Instance-B", 1, "1"}, {0.0, 0.0, 0.0}, {"models/two-instances.inp", 10U}},
{{"Instance-B", 2, "2"}, {1.0, 0.0, 0.0}, {"models/two-instances.inp", 11U}}};
definition.elements = {
{{"Instance-A", 1, "1"}, {0U, 1U}, 0U, 0U, {"models/two-instances.inp", 20U}},
{{"Instance-B", 1, "1"}, {2U, 3U}, 0U, 0U, {"models/two-instances.inp", 20U}}};
definition.materials = {{
"Steel", 210.0e9, 0.3, {"models/two-instances.inp", 30U}}};
definition.sections = {{
"Section-1",
0.02,
1.0e-5,
0.0,
2.0e-5,
5.0e-6,
{0.0, 1.0, 0.0},
{},
{"models/two-instances.inp", 40U}}};
definition.parts = {{
"BeamPart", {1, 2}, {1}, {}, {}, {"models/two-instances.inp", 2U}}};
definition.instances = {
{"Instance-A", "BeamPart", {{1, 0U}, {2, 1U}}, {{1, 0U}},
{"models/two-instances.inp", 50U}},
{"Instance-B", "BeamPart", {{1, 2U}, {2, 3U}}, {{1, 1U}},
{"models/two-instances.inp", 51U}}};
definition.steps = {{
"Step-1", {}, {}, 0.1, 1.0, 1.0e-5, 1.0,
{"models/two-instances.inp", 60U}}};
fesa::ModelDefinition definition{};
definition.source_path = "models/two-instances.inp";
definition.source_content_identity = "fnv1a64:0011223344556677";
definition.nodes = {{{"Instance-A", 1, "1"},
{0.0, 0.0, 0.0},
{"models/two-instances.inp", 10U}},
{{"Instance-A", 2, "2"},
{1.0, 0.0, 0.0},
{"models/two-instances.inp", 11U}},
{{"Instance-B", 1, "1"},
{0.0, 0.0, 0.0},
{"models/two-instances.inp", 10U}},
{{"Instance-B", 2, "2"},
{1.0, 0.0, 0.0},
{"models/two-instances.inp", 11U}}};
definition.elements = {{{"Instance-A", 1, "1"},
{0U, 1U},
0U,
0U,
{"models/two-instances.inp", 20U}},
{{"Instance-B", 1, "1"},
{2U, 3U},
0U,
0U,
{"models/two-instances.inp", 20U}}};
definition.materials = {
{"Steel", 210.0e9, 0.3, {"models/two-instances.inp", 30U}}};
definition.sections = {{"Section-1",
0.02,
1.0e-5,
0.0,
2.0e-5,
5.0e-6,
{0.0, 1.0, 0.0},
{},
{"models/two-instances.inp", 40U}}};
definition.parts = {
{"BeamPart", {1, 2}, {1}, {}, {}, {"models/two-instances.inp", 2U}}};
definition.instances = {{"Instance-A",
"BeamPart",
{{1, 0U}, {2, 1U}},
{{1, 0U}},
{"models/two-instances.inp", 50U}},
{"Instance-B",
"BeamPart",
{{1, 2U}, {2, 3U}},
{{1, 1U}},
{"models/two-instances.inp", 51U}}};
definition.steps = {{"Step-1",
{},
{},
0.1,
1.0,
1.0e-5,
1.0,
{"models/two-instances.inp", 60U}}};
auto result = fesa::Domain::create(std::move(definition));
ASSERT_TRUE(result.HasValue());
const fesa::Domain& domain = result.Value();
auto result = fesa::Domain::Create(std::move(definition));
ASSERT_TRUE(result.HasValue());
const fesa::Domain& domain = result.Value();
ASSERT_EQ(domain.nodes().size(), 4U);
EXPECT_EQ(domain.nodes()[0].sourceId.source_label, 1);
EXPECT_EQ(domain.nodes()[2].sourceId.source_label, 1);
EXPECT_EQ(domain.nodes()[0].sourceId.source_label_text, "1");
EXPECT_EQ(domain.nodes()[2].sourceId.source_label_text, "1");
EXPECT_NE(
domain.nodes()[0].sourceId.instance_name,
domain.nodes()[2].sourceId.instance_name);
ASSERT_EQ(domain.Nodes().size(), 4U);
EXPECT_EQ(domain.Nodes()[0].source_id.source_label, 1);
EXPECT_EQ(domain.Nodes()[2].source_id.source_label, 1);
EXPECT_EQ(domain.Nodes()[0].source_id.source_label_text, "1");
EXPECT_EQ(domain.Nodes()[2].source_id.source_label_text, "1");
EXPECT_NE(domain.Nodes()[0].source_id.instance_name,
domain.Nodes()[2].source_id.instance_name);
ASSERT_EQ(domain.elements().size(), 2U);
EXPECT_EQ(domain.elements()[0].sourceId.source_label, 1);
EXPECT_EQ(domain.elements()[1].sourceId.source_label, 1);
EXPECT_EQ(domain.elements()[0].sourceId.instance_name, "Instance-A");
EXPECT_EQ(domain.elements()[1].sourceId.instance_name, "Instance-B");
EXPECT_EQ(domain.elements()[0].nodeIndices[0], 0U);
EXPECT_EQ(domain.elements()[1].nodeIndices[0], 2U);
ASSERT_EQ(domain.Elements().size(), 2U);
EXPECT_EQ(domain.Elements()[0].source_id.source_label, 1);
EXPECT_EQ(domain.Elements()[1].source_id.source_label, 1);
EXPECT_EQ(domain.Elements()[0].source_id.instance_name, "Instance-A");
EXPECT_EQ(domain.Elements()[1].source_id.instance_name, "Instance-B");
EXPECT_EQ(domain.Elements()[0].node_indices[0], 0U);
EXPECT_EQ(domain.Elements()[1].node_indices[0], 2U);
}
// MITC4-MODEL-002
TEST(DomainModel, ShellOwnershipPreservesResolvedAssignmentsAndOptionalFrames) {
fesa::ModelDefinition definition{};
definition.sourcePath = "models/shell-owned.inp";
definition.sourceContentIdentity = "fnv1a64:1234567890abcdef";
definition.materials = {
{"Material-B", 70.0e9, 0.33, {"models/shell-owned.inp", 31U}},
{"Material-A", 210.0e9, 0.3, {"models/shell-owned.inp", 30U}}};
definition.shellSections = {
{"Section-B", 0.02, 0U, {"models/shell-owned.inp", 41U}},
{"Section-A", 0.01, 1U, {"models/shell-owned.inp", 40U}}};
definition.shellElements = {
{{"Shell-Instance", 20, "0020"},
fesa::ShellSourceElementType::s4r,
{4U, 5U, 6U, 7U},
0U,
0U,
{"models/shell-owned.inp", 21U}},
{{"Shell-Instance", 10, "0010"},
fesa::ShellSourceElementType::s4,
{0U, 1U, 2U, 3U},
1U,
1U,
{"models/shell-owned.inp", 20U}}};
definition.shellNodeInitialFrames = {
{4U, {0.0, 0.0, 1.0}, {1.0, 0.0, 0.0}, {0.0, 1.0, 0.0}},
{0U, {0.0, 1.0, 0.0}, {1.0, 0.0, 0.0}, {0.0, 0.0, -1.0}}};
fesa::ModelDefinition definition{};
definition.source_path = "models/shell-owned.inp";
definition.source_content_identity = "fnv1a64:1234567890abcdef";
definition.materials = {
{"Material-B", 70.0e9, 0.33, {"models/shell-owned.inp", 31U}},
{"Material-A", 210.0e9, 0.3, {"models/shell-owned.inp", 30U}}};
definition.shell_sections = {
{"Section-B", 0.02, 0U, {"models/shell-owned.inp", 41U}},
{"Section-A", 0.01, 1U, {"models/shell-owned.inp", 40U}}};
definition.shell_elements = {{{"Shell-Instance", 20, "0020"},
fesa::ShellSourceElementType::kS4r,
{4U, 5U, 6U, 7U},
0U,
0U,
{"models/shell-owned.inp", 21U}},
{{"Shell-Instance", 10, "0010"},
fesa::ShellSourceElementType::kS4,
{0U, 1U, 2U, 3U},
1U,
1U,
{"models/shell-owned.inp", 20U}}};
definition.shell_node_initial_frames = {
{4U, {0.0, 0.0, 1.0}, {1.0, 0.0, 0.0}, {0.0, 1.0, 0.0}},
{0U, {0.0, 1.0, 0.0}, {1.0, 0.0, 0.0}, {0.0, 0.0, -1.0}}};
auto result = fesa::Domain::create(definition);
ASSERT_TRUE(result.HasValue());
auto result = fesa::Domain::Create(definition);
ASSERT_TRUE(result.HasValue());
definition.shellSections[0].thickness = -1.0;
definition.shellElements[0].sourceId.source_label_text = "mutated";
definition.shellNodeInitialFrames[0].director[2] = -1.0;
definition.shell_sections[0].thickness = -1.0;
definition.shell_elements[0].source_id.source_label_text = "mutated";
definition.shell_node_initial_frames[0].director[2] = -1.0;
const fesa::Domain& domain = result.Value();
static_assert(std::is_same_v<
decltype(std::declval<const fesa::Domain&>().shellElements()),
const std::vector<fesa::Mitc4ShellDefinition>&>);
static_assert(std::is_same_v<
decltype(std::declval<const fesa::Domain&>().shellSections()),
const std::vector<fesa::ShellSection>&>);
static_assert(std::is_same_v<
decltype(std::declval<const fesa::Domain&>().shellNodeInitialFrames()),
const std::vector<fesa::ShellNodeInitialFrame>&>);
const fesa::Domain& domain = result.Value();
static_assert(std::is_same_v<
decltype(std::declval<const fesa::Domain&>().ShellElements()),
const std::vector<fesa::Mitc4ShellDefinition>&>);
static_assert(std::is_same_v<
decltype(std::declval<const fesa::Domain&>().ShellSections()),
const std::vector<fesa::ShellSection>&>);
static_assert(
std::is_same_v<decltype(std::declval<const fesa::Domain&>()
.ShellNodeInitialFrames()),
const std::vector<fesa::ShellNodeInitialFrame>&>);
ASSERT_EQ(domain.shellElements().size(), 2U);
EXPECT_EQ(domain.shellElements()[0].sourceId.source_label, 20);
EXPECT_EQ(domain.shellElements()[0].sourceId.source_label_text, "0020");
EXPECT_EQ(domain.shellElements()[1].sourceId.source_label, 10);
EXPECT_EQ(domain.shellElements()[0].sourceType, fesa::ShellSourceElementType::s4r);
EXPECT_EQ(domain.shellElements()[1].sourceType, fesa::ShellSourceElementType::s4);
EXPECT_EQ(domain.shellElements()[0].nodeIndices[3], 7U);
EXPECT_EQ(domain.shellElements()[0].materialIndex, 0U);
EXPECT_EQ(domain.shellElements()[0].sectionIndex, 0U);
ASSERT_EQ(domain.ShellElements().size(), 2U);
EXPECT_EQ(domain.ShellElements()[0].source_id.source_label, 20);
EXPECT_EQ(domain.ShellElements()[0].source_id.source_label_text, "0020");
EXPECT_EQ(domain.ShellElements()[1].source_id.source_label, 10);
EXPECT_EQ(domain.ShellElements()[0].source_type,
fesa::ShellSourceElementType::kS4r);
EXPECT_EQ(domain.ShellElements()[1].source_type,
fesa::ShellSourceElementType::kS4);
EXPECT_EQ(domain.ShellElements()[0].node_indices[3], 7U);
EXPECT_EQ(domain.ShellElements()[0].material_index, 0U);
EXPECT_EQ(domain.ShellElements()[0].section_index, 0U);
ASSERT_EQ(domain.shellSections().size(), 2U);
EXPECT_EQ(domain.shellSections()[0].name, "Section-B");
EXPECT_DOUBLE_EQ(domain.shellSections()[0].thickness, 0.02);
EXPECT_EQ(domain.shellSections()[0].materialIndex, 0U);
EXPECT_EQ(domain.shellSections()[1].name, "Section-A");
ASSERT_EQ(domain.ShellSections().size(), 2U);
EXPECT_EQ(domain.ShellSections()[0].name, "Section-B");
EXPECT_DOUBLE_EQ(domain.ShellSections()[0].thickness, 0.02);
EXPECT_EQ(domain.ShellSections()[0].material_index, 0U);
EXPECT_EQ(domain.ShellSections()[1].name, "Section-A");
ASSERT_EQ(domain.shellNodeInitialFrames().size(), 2U);
EXPECT_EQ(domain.shellNodeInitialFrames()[0].nodeIndex, 4U);
EXPECT_EQ(
domain.shellNodeInitialFrames()[0].director,
(std::array<double, 3>{0.0, 0.0, 1.0}));
EXPECT_EQ(
domain.shellNodeInitialFrames()[0].tangentA,
(std::array<double, 3>{1.0, 0.0, 0.0}));
EXPECT_EQ(
domain.shellNodeInitialFrames()[0].tangentB,
(std::array<double, 3>{0.0, 1.0, 0.0}));
ASSERT_EQ(domain.ShellNodeInitialFrames().size(), 2U);
EXPECT_EQ(domain.ShellNodeInitialFrames()[0].node_index, 4U);
EXPECT_EQ(domain.ShellNodeInitialFrames()[0].director,
(std::array<double, 3>{0.0, 0.0, 1.0}));
EXPECT_EQ(domain.ShellNodeInitialFrames()[0].tangent_a,
(std::array<double, 3>{1.0, 0.0, 0.0}));
EXPECT_EQ(domain.ShellNodeInitialFrames()[0].tangent_b,
(std::array<double, 3>{0.0, 1.0, 0.0}));
auto noFramesResult = fesa::Domain::create(fesa::ModelDefinition{});
ASSERT_TRUE(noFramesResult.HasValue());
EXPECT_TRUE(noFramesResult.Value().shellNodeInitialFrames().empty());
auto no_frames_result = fesa::Domain::Create(fesa::ModelDefinition{});
ASSERT_TRUE(no_frames_result.HasValue());
EXPECT_TRUE(no_frames_result.Value().ShellNodeInitialFrames().empty());
}
+124 -148
View File
@@ -1,4 +1,4 @@
#include "fesa/model/model_types.hpp"
#include "fesa/model/model_types.h"
#include <gtest/gtest.h>
@@ -14,174 +14,150 @@
namespace {
template<class T, class = void>
template <class T, class = void>
struct HasEquationId : std::false_type {};
template<class T>
template <class T>
struct HasEquationId<T, std::void_t<decltype(std::declval<T&>().equationId)>>
: std::true_type {};
template<class T, class = void>
template <class T, class = void>
struct HasEquationIds : std::false_type {};
template<class T>
template <class T>
struct HasEquationIds<T, std::void_t<decltype(std::declval<T&>().equationIds)>>
: std::true_type {};
} // namespace
} // namespace
TEST(DomainModel, SourceAndInternalIdentityRemainDistinct) {
const fesa::SourceLocation nodeLocation{
std::filesystem::path{"models/beam.inp"}, 11U};
const fesa::SourceEntityId firstIdentity{"Instance-A", 7, "0007"};
const fesa::SourceEntityId equalIdentity{"Instance-A", 7, "0007"};
const fesa::SourceEntityId laterIdentity{"Instance-B", 7, "0007"};
const fesa::SourceLocation node_location{
std::filesystem::path{"models/beam.inp"}, 11U};
const fesa::SourceEntityId first_identity{"Instance-A", 7, "0007"};
const fesa::SourceEntityId equal_identity{"Instance-A", 7, "0007"};
const fesa::SourceEntityId later_identity{"Instance-B", 7, "0007"};
EXPECT_EQ(
std::make_tuple(
firstIdentity.instance_name,
firstIdentity.source_label,
firstIdentity.source_label_text),
std::make_tuple(
equalIdentity.instance_name,
equalIdentity.source_label,
equalIdentity.source_label_text));
EXPECT_LT(
std::make_tuple(
firstIdentity.instance_name,
firstIdentity.source_label,
firstIdentity.source_label_text),
std::make_tuple(
laterIdentity.instance_name,
laterIdentity.source_label,
laterIdentity.source_label_text));
EXPECT_EQ(
std::make_tuple(first_identity.instance_name, first_identity.source_label,
first_identity.source_label_text),
std::make_tuple(equal_identity.instance_name, equal_identity.source_label,
equal_identity.source_label_text));
EXPECT_LT(
std::make_tuple(first_identity.instance_name, first_identity.source_label,
first_identity.source_label_text),
std::make_tuple(later_identity.instance_name, later_identity.source_label,
later_identity.source_label_text));
const fesa::Node node{firstIdentity, {1.0, 2.0, 3.0}, nodeLocation};
const fesa::LinearElasticMaterial material{
"Steel", 210.0e9, 0.3, {"models/beam.inp", 30U}};
const fesa::GeneralBeamSection section{
"Section-1",
0.02,
1.0e-5,
0.0,
2.0e-5,
5.0e-6,
{0.0, 1.0, 0.0},
{{-0.1, 0.0}, {0.1, 0.0}},
{"models/beam.inp", 40U}};
const fesa::EulerBeam3DDefinition element{
{"Instance-A", 3, "0003"},
{fesa::EntityIndex{5}, fesa::EntityIndex{9}},
fesa::EntityIndex{2},
fesa::EntityIndex{4},
{"models/beam.inp", 20U}};
const fesa::BoundaryCondition boundary{
"Root", 1, 6, 0.0, {"models/beam.inp", 60U}};
const fesa::NodalLoad load{
"Tip", 3, -1000.0, {"models/beam.inp", 70U}};
const fesa::StaticStepDefinition step{
"Step-1",
{boundary},
{load},
0.1,
1.0,
1.0e-5,
1.0,
{"models/beam.inp", 50U}};
const fesa::NodeSet nodeSet{
"Root", std::string{"Instance-A"}, {5U}, {"models/beam.inp", 25U}};
const fesa::ElementSet elementSet{
"Beam", std::string{"Instance-A"}, {4U}, {"models/beam.inp", 26U}};
const fesa::PartDefinition part{
"BeamPart", {7, 8}, {3}, {"Root", "Tip"}, {"Beam"},
{"models/beam.inp", 2U}};
const fesa::InstanceDefinition instance{
"Instance-A",
"BeamPart",
{{7, 5U}, {8, 9U}},
{{3, 4U}},
{"models/beam.inp", 80U}};
const fesa::Node node{first_identity, {1.0, 2.0, 3.0}, node_location};
const fesa::LinearElasticMaterial material{
"Steel", 210.0e9, 0.3, {"models/beam.inp", 30U}};
const fesa::GeneralBeamSection section{"Section-1",
0.02,
1.0e-5,
0.0,
2.0e-5,
5.0e-6,
{0.0, 1.0, 0.0},
{{-0.1, 0.0}, {0.1, 0.0}},
{"models/beam.inp", 40U}};
const fesa::EulerBeam3DDefinition element{
{"Instance-A", 3, "0003"},
{fesa::EntityIndex{5}, fesa::EntityIndex{9}},
fesa::EntityIndex{2},
fesa::EntityIndex{4},
{"models/beam.inp", 20U}};
const fesa::BoundaryCondition boundary{
"Root", 1, 6, 0.0, {"models/beam.inp", 60U}};
const fesa::NodalLoad load{"Tip", 3, -1000.0, {"models/beam.inp", 70U}};
const fesa::StaticStepDefinition step{
"Step-1", {boundary}, {load}, 0.1,
1.0, 1.0e-5, 1.0, {"models/beam.inp", 50U}};
const fesa::NodeSet node_set{
"Root", std::string{"Instance-A"}, {5U}, {"models/beam.inp", 25U}};
const fesa::ElementSet element_set{
"Beam", std::string{"Instance-A"}, {4U}, {"models/beam.inp", 26U}};
const fesa::PartDefinition part{"BeamPart", {7, 8},
{3}, {"Root", "Tip"},
{"Beam"}, {"models/beam.inp", 2U}};
const fesa::InstanceDefinition instance{"Instance-A",
"BeamPart",
{{7, 5U}, {8, 9U}},
{{3, 4U}},
{"models/beam.inp", 80U}};
fesa::ModelDefinition definition{};
definition.sourcePath = "models/beam.inp";
definition.sourceContentIdentity = "fnv1a64:0123456789abcdef";
definition.heading = "Identity mapping fixture";
definition.nodes = {node};
definition.elements = {element};
definition.materials = {material};
definition.sections = {section};
definition.nodeSets = {nodeSet};
definition.elementSets = {elementSet};
definition.parts = {part};
definition.instances = {instance};
definition.steps = {step};
definition.warnings = {{
fesa::Severity::kWarning,
"ignored-output-request",
{"models/beam.inp", 75U},
"*OUTPUT",
"",
"Output request does not alter mandatory FESA results."}};
fesa::ModelDefinition definition{};
definition.source_path = "models/beam.inp";
definition.source_content_identity = "fnv1a64:0123456789abcdef";
definition.heading = "Identity mapping fixture";
definition.nodes = {node};
definition.elements = {element};
definition.materials = {material};
definition.sections = {section};
definition.node_sets = {node_set};
definition.element_sets = {element_set};
definition.parts = {part};
definition.instances = {instance};
definition.steps = {step};
definition.warnings = {
{fesa::Severity::kWarning,
"ignored-output-request",
{"models/beam.inp", 75U},
"*OUTPUT",
"",
"Output request does not alter mandatory FESA results."}};
ASSERT_EQ(definition.nodes.size(), 1U);
EXPECT_EQ(definition.nodes[0].sourceId.source_label_text, "0007");
EXPECT_EQ(definition.nodes[0].location.line, 11U);
ASSERT_EQ(definition.instances.size(), 1U);
EXPECT_EQ(definition.instances[0].partName, "BeamPart");
EXPECT_EQ(definition.instances[0].nodeMappings[0].sourceLabel, 7);
EXPECT_EQ(definition.instances[0].nodeMappings[0].internalIndex, 5U);
EXPECT_EQ(definition.instances[0].elementMappings[0].internalIndex, 4U);
EXPECT_EQ(definition.steps[0].initialIncrement, 0.1);
EXPECT_EQ(definition.steps[0].timePeriod, 1.0);
EXPECT_EQ(definition.steps[0].minimumIncrement, 1.0e-5);
EXPECT_EQ(definition.steps[0].maximumIncrement, 1.0);
ASSERT_EQ(definition.warnings.size(), 1U);
EXPECT_EQ(definition.warnings[0].location.line, 75U);
ASSERT_EQ(definition.nodes.size(), 1U);
EXPECT_EQ(definition.nodes[0].source_id.source_label_text, "0007");
EXPECT_EQ(definition.nodes[0].location.line, 11U);
ASSERT_EQ(definition.instances.size(), 1U);
EXPECT_EQ(definition.instances[0].part_name, "BeamPart");
EXPECT_EQ(definition.instances[0].node_mappings[0].source_label, 7);
EXPECT_EQ(definition.instances[0].node_mappings[0].internal_index, 5U);
EXPECT_EQ(definition.instances[0].element_mappings[0].internal_index, 4U);
EXPECT_EQ(definition.steps[0].initial_increment, 0.1);
EXPECT_EQ(definition.steps[0].time_period, 1.0);
EXPECT_EQ(definition.steps[0].minimum_increment, 1.0e-5);
EXPECT_EQ(definition.steps[0].maximum_increment, 1.0);
ASSERT_EQ(definition.warnings.size(), 1U);
EXPECT_EQ(definition.warnings[0].location.line, 75U);
static_assert(!HasEquationId<fesa::Node>::value);
static_assert(!HasEquationIds<fesa::Node>::value);
static_assert(!HasEquationId<fesa::EulerBeam3DDefinition>::value);
static_assert(!HasEquationIds<fesa::EulerBeam3DDefinition>::value);
static_assert(!HasEquationId<fesa::Node>::value);
static_assert(!HasEquationIds<fesa::Node>::value);
static_assert(!HasEquationId<fesa::EulerBeam3DDefinition>::value);
static_assert(!HasEquationIds<fesa::EulerBeam3DDefinition>::value);
}
// MITC4-MODEL-001
TEST(DomainModel, Mitc4ShellRecordsPreserveSourceAndInternalIdentity) {
const fesa::Mitc4ShellDefinition s4Element{
{"Shell-Instance", 41, "0041"},
fesa::ShellSourceElementType::s4,
{fesa::EntityIndex{3},
fesa::EntityIndex{5},
fesa::EntityIndex{8},
fesa::EntityIndex{13}},
fesa::EntityIndex{2},
fesa::EntityIndex{7},
{"models/shell.inp", 20U}};
const fesa::Mitc4ShellDefinition s4rElement{
{"Shell-Instance", 42, "0042"},
fesa::ShellSourceElementType::s4r,
{fesa::EntityIndex{13},
fesa::EntityIndex{8},
fesa::EntityIndex{5},
fesa::EntityIndex{3}},
fesa::EntityIndex{2},
fesa::EntityIndex{7},
{"models/shell.inp", 21U}};
const fesa::Mitc4ShellDefinition s4_element{
{"Shell-Instance", 41, "0041"},
fesa::ShellSourceElementType::kS4,
{fesa::EntityIndex{3}, fesa::EntityIndex{5}, fesa::EntityIndex{8},
fesa::EntityIndex{13}},
fesa::EntityIndex{2},
fesa::EntityIndex{7},
{"models/shell.inp", 20U}};
const fesa::Mitc4ShellDefinition s4r_element{
{"Shell-Instance", 42, "0042"},
fesa::ShellSourceElementType::kS4r,
{fesa::EntityIndex{13}, fesa::EntityIndex{8}, fesa::EntityIndex{5},
fesa::EntityIndex{3}},
fesa::EntityIndex{2},
fesa::EntityIndex{7},
{"models/shell.inp", 21U}};
EXPECT_EQ(s4Element.sourceId.instance_name, "Shell-Instance");
EXPECT_EQ(s4Element.sourceId.source_label, 41);
EXPECT_EQ(s4Element.sourceId.source_label_text, "0041");
EXPECT_EQ(s4Element.sourceType, fesa::ShellSourceElementType::s4);
EXPECT_EQ(s4rElement.sourceType, fesa::ShellSourceElementType::s4r);
EXPECT_NE(s4Element.sourceType, s4rElement.sourceType);
EXPECT_EQ(
fesa::kMitc4InternalFormulation,
std::string_view{"FESA-MITC4"});
EXPECT_EQ(
s4Element.nodeIndices,
(std::array<fesa::EntityIndex, 4>{3U, 5U, 8U, 13U}));
EXPECT_EQ(s4Element.materialIndex, 2U);
EXPECT_EQ(s4Element.sectionIndex, 7U);
EXPECT_EQ(s4_element.source_id.instance_name, "Shell-Instance");
EXPECT_EQ(s4_element.source_id.source_label, 41);
EXPECT_EQ(s4_element.source_id.source_label_text, "0041");
EXPECT_EQ(s4_element.source_type, fesa::ShellSourceElementType::kS4);
EXPECT_EQ(s4r_element.source_type, fesa::ShellSourceElementType::kS4r);
EXPECT_NE(s4_element.source_type, s4r_element.source_type);
EXPECT_EQ(fesa::kMitc4InternalFormulation, std::string_view{"FESA-MITC4"});
EXPECT_EQ(s4_element.node_indices,
(std::array<fesa::EntityIndex, 4>{3U, 5U, 8U, 13U}));
EXPECT_EQ(s4_element.material_index, 2U);
EXPECT_EQ(s4_element.section_index, 7U);
static_assert(!HasEquationId<fesa::Mitc4ShellDefinition>::value);
static_assert(!HasEquationIds<fesa::Mitc4ShellDefinition>::value);
static_assert(!HasEquationId<fesa::Mitc4ShellDefinition>::value);
static_assert(!HasEquationIds<fesa::Mitc4ShellDefinition>::value);
}
+192 -213
View File
@@ -1,4 +1,4 @@
#include "fesa/model/shell_geometry.hpp"
#include "fesa/model/shell_geometry.h"
#include <gtest/gtest.h>
@@ -14,263 +14,242 @@ namespace {
using Vector3 = std::array<double, 3>;
fesa::Node node(fesa::EntityIndex label, Vector3 coordinates) {
return {
{"Shell-Instance", static_cast<std::int64_t>(label),
std::to_string(label)},
coordinates,
{"shell-geometry.inp", static_cast<std::size_t>(label + 1U)}};
fesa::Node Node(fesa::EntityIndex label, Vector3 coordinates) {
return {{"Shell-Instance", static_cast<std::int64_t>(label),
std::to_string(label)},
coordinates,
{"shell-geometry.inp", static_cast<std::size_t>(label + 1U)}};
}
fesa::Mitc4ShellDefinition element(
fesa::EntityIndex label,
std::array<fesa::EntityIndex, 4> nodeIndices) {
return {
{"Shell-Instance", static_cast<std::int64_t>(label),
std::to_string(label)},
fesa::ShellSourceElementType::s4,
nodeIndices,
0U,
0U,
{"shell-geometry.inp", static_cast<std::size_t>(100U + label)}};
fesa::Mitc4ShellDefinition Element(
fesa::EntityIndex label, std::array<fesa::EntityIndex, 4> node_indices) {
return {{"Shell-Instance", static_cast<std::int64_t>(label),
std::to_string(label)},
fesa::ShellSourceElementType::kS4,
node_indices,
0U,
0U,
{"shell-geometry.inp", static_cast<std::size_t>(100U + label)}};
}
std::vector<fesa::ShellSection> sections(double thickness = 0.2) {
return {{"Section", thickness, 0U, {"shell-geometry.inp", 90U}}};
std::vector<fesa::ShellSection> Sections(double thickness = 0.2) {
return {{"Section", thickness, 0U, {"shell-geometry.inp", 90U}}};
}
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::sqrt(dot(value, value));
double Norm(const Vector3& value) { return std::sqrt(Dot(value, value)); }
void ExpectVectorNear(const Vector3& actual, const Vector3& expected,
double tolerance = 1.0e-12) {
for (std::size_t component = 0U; component < actual.size(); ++component) {
EXPECT_NEAR(actual[component], expected[component], tolerance);
}
}
void expectVectorNear(
const Vector3& actual,
const Vector3& expected,
double tolerance = 1.0e-12) {
for (std::size_t component = 0U; component < actual.size(); ++component) {
EXPECT_NEAR(actual[component], expected[component], tolerance);
}
void ExpectRightHandedFrame(const fesa::ShellNodeInitialFrame& frame) {
EXPECT_NEAR(Norm(frame.director), 1.0, 1.0e-12);
EXPECT_NEAR(Norm(frame.tangent_a), 1.0, 1.0e-12);
EXPECT_NEAR(Norm(frame.tangent_b), 1.0, 1.0e-12);
EXPECT_NEAR(Dot(frame.director, frame.tangent_a), 0.0, 1.0e-12);
EXPECT_NEAR(Dot(frame.director, frame.tangent_b), 0.0, 1.0e-12);
EXPECT_NEAR(Dot(frame.tangent_a, frame.tangent_b), 0.0, 1.0e-12);
ExpectVectorNear(Cross(frame.tangent_a, frame.tangent_b), frame.director);
}
void expectRightHandedFrame(const fesa::ShellNodeInitialFrame& frame) {
EXPECT_NEAR(norm(frame.director), 1.0, 1.0e-12);
EXPECT_NEAR(norm(frame.tangentA), 1.0, 1.0e-12);
EXPECT_NEAR(norm(frame.tangentB), 1.0, 1.0e-12);
EXPECT_NEAR(dot(frame.director, frame.tangentA), 0.0, 1.0e-12);
EXPECT_NEAR(dot(frame.director, frame.tangentB), 0.0, 1.0e-12);
EXPECT_NEAR(dot(frame.tangentA, frame.tangentB), 0.0, 1.0e-12);
expectVectorNear(cross(frame.tangentA, frame.tangentB), frame.director);
const fesa::ShellNodeInitialFrame& FrameFor(const fesa::ShellGeometry& geometry,
fesa::EntityIndex node_index) {
const auto found =
std::find_if(geometry.nodal_frames.begin(), geometry.nodal_frames.end(),
[node_index](const fesa::ShellNodeInitialFrame& frame) {
return frame.node_index == node_index;
});
EXPECT_NE(found, geometry.nodal_frames.end());
return *found;
}
const fesa::ShellNodeInitialFrame& frameFor(
const fesa::ShellGeometry& geometry,
fesa::EntityIndex nodeIndex) {
const auto found = std::find_if(
geometry.nodalFrames.begin(),
geometry.nodalFrames.end(),
[nodeIndex](const fesa::ShellNodeInitialFrame& frame) {
return frame.nodeIndex == nodeIndex;
});
EXPECT_NE(found, geometry.nodalFrames.end());
return *found;
void ExpectFailureCode(const fesa::Result<fesa::ShellGeometry>& result,
const std::string& code) {
ASSERT_FALSE(result.HasValue());
EXPECT_EQ(result.GetStatus().Category(), fesa::FailureCategory::kModel);
ASSERT_EQ(result.GetStatus().Diagnostics().size(), 1U);
EXPECT_EQ(result.GetStatus().Diagnostics()[0].code, code);
}
void expectFailureCode(
const fesa::Result<fesa::ShellGeometry>& result,
const std::string& code) {
ASSERT_FALSE(result.HasValue());
EXPECT_EQ(result.GetStatus().Category(), fesa::FailureCategory::kModel);
ASSERT_EQ(result.GetStatus().Diagnostics().size(), 1U);
EXPECT_EQ(result.GetStatus().Diagnostics()[0].code, code);
}
} // namespace
} // namespace
// MITC4-GEO-001
TEST(Mitc4Geometry, BuildsDeterministicFramesForPlanarRotatedAndWarpedElements) {
const std::vector<fesa::Node> planarNodes{
node(0U, {0.0, 0.0, 0.0}),
node(1U, {1.0, 0.0, 0.0}),
node(2U, {1.0, 1.0, 0.0}),
node(3U, {0.0, 1.0, 0.0})};
auto planar = fesa::preprocessShellGeometry(
planarNodes, {element(10U, {0U, 1U, 2U, 3U})}, sections());
TEST(Mitc4Geometry,
BuildsDeterministicFramesForPlanarRotatedAndWarpedElements) {
const std::vector<fesa::Node> planar_nodes{
Node(0U, {0.0, 0.0, 0.0}), Node(1U, {1.0, 0.0, 0.0}),
Node(2U, {1.0, 1.0, 0.0}), Node(3U, {0.0, 1.0, 0.0})};
auto planar = fesa::PreprocessShellGeometry(
planar_nodes, {Element(10U, {0U, 1U, 2U, 3U})}, Sections());
ASSERT_TRUE(planar.HasValue());
ASSERT_EQ(planar.Value().elementData.size(), 1U);
expectVectorNear(planar.Value().elementData[0].normalCandidate, {0.0, 0.0, 1.0});
EXPECT_NEAR(planar.Value().elementData[0].surfaceAreaWeight, 1.0, 1.0e-12);
ASSERT_EQ(planar.Value().nodalFrames.size(), 4U);
for (const auto& frame : planar.Value().nodalFrames) {
expectVectorNear(frame.director, {0.0, 0.0, 1.0});
expectVectorNear(frame.tangentA, {1.0, 0.0, 0.0});
expectVectorNear(frame.tangentB, {0.0, 1.0, 0.0});
expectRightHandedFrame(frame);
}
ASSERT_TRUE(planar.HasValue());
ASSERT_EQ(planar.Value().element_data.size(), 1U);
ExpectVectorNear(planar.Value().element_data[0].normal_candidate,
{0.0, 0.0, 1.0});
EXPECT_NEAR(planar.Value().element_data[0].surface_area_weight, 1.0, 1.0e-12);
ASSERT_EQ(planar.Value().nodal_frames.size(), 4U);
for (const auto& frame : planar.Value().nodal_frames) {
ExpectVectorNear(frame.director, {0.0, 0.0, 1.0});
ExpectVectorNear(frame.tangent_a, {1.0, 0.0, 0.0});
ExpectVectorNear(frame.tangent_b, {0.0, 1.0, 0.0});
ExpectRightHandedFrame(frame);
}
const std::vector<fesa::Node> rotatedNodes{
node(0U, {0.0, 0.0, 0.0}),
node(1U, {0.0, 1.0, 0.0}),
node(2U, {0.0, 1.0, 1.0}),
node(3U, {0.0, 0.0, 1.0})};
auto rotated = fesa::preprocessShellGeometry(
rotatedNodes, {element(11U, {0U, 1U, 2U, 3U})}, sections());
const std::vector<fesa::Node> rotated_nodes{
Node(0U, {0.0, 0.0, 0.0}), Node(1U, {0.0, 1.0, 0.0}),
Node(2U, {0.0, 1.0, 1.0}), Node(3U, {0.0, 0.0, 1.0})};
auto rotated = fesa::PreprocessShellGeometry(
rotated_nodes, {Element(11U, {0U, 1U, 2U, 3U})}, Sections());
ASSERT_TRUE(rotated.HasValue());
const auto& rotatedFrame = frameFor(rotated.Value(), 0U);
expectVectorNear(rotatedFrame.director, {1.0, 0.0, 0.0});
expectVectorNear(rotatedFrame.tangentA, {0.0, 1.0, 0.0});
expectVectorNear(rotatedFrame.tangentB, {0.0, 0.0, 1.0});
expectRightHandedFrame(rotatedFrame);
ASSERT_TRUE(rotated.HasValue());
const auto& rotated_frame = FrameFor(rotated.Value(), 0U);
ExpectVectorNear(rotated_frame.director, {1.0, 0.0, 0.0});
ExpectVectorNear(rotated_frame.tangent_a, {0.0, 1.0, 0.0});
ExpectVectorNear(rotated_frame.tangent_b, {0.0, 0.0, 1.0});
ExpectRightHandedFrame(rotated_frame);
const std::vector<fesa::Node> warpedNodes{
node(0U, {0.0, 0.0, 0.0}),
node(1U, {2.0, 0.0, 0.0}),
node(2U, {2.0, 1.0, 0.2}),
node(3U, {0.0, 1.0, 0.0})};
auto warped = fesa::preprocessShellGeometry(
warpedNodes, {element(12U, {0U, 1U, 2U, 3U})}, sections());
const std::vector<fesa::Node> warped_nodes{
Node(0U, {0.0, 0.0, 0.0}), Node(1U, {2.0, 0.0, 0.0}),
Node(2U, {2.0, 1.0, 0.2}), Node(3U, {0.0, 1.0, 0.0})};
auto warped = fesa::PreprocessShellGeometry(
warped_nodes, {Element(12U, {0U, 1U, 2U, 3U})}, Sections());
ASSERT_TRUE(warped.HasValue());
EXPECT_GT(warped.Value().elementData[0].surfaceAreaWeight, 2.0);
for (const auto& frame : warped.Value().nodalFrames) {
expectRightHandedFrame(frame);
}
ASSERT_TRUE(warped.HasValue());
EXPECT_GT(warped.Value().element_data[0].surface_area_weight, 2.0);
for (const auto& frame : warped.Value().nodal_frames) {
ExpectRightHandedFrame(frame);
}
}
// MITC4-GEO-002
TEST(Mitc4Geometry, AreaWeightsSharedDirectorsInStableSourceIdentityOrder) {
const std::vector<fesa::Node> nodes{
node(0U, {0.0, 0.0, 0.0}),
node(1U, {1.0, 0.0, 0.0}),
node(2U, {1.0, 1.0, 0.0}),
node(3U, {0.0, 1.0, 0.0}),
node(4U, {2.0, 0.0, 1.0}),
node(5U, {2.0, 1.0, 1.0})};
const auto flat = element(10U, {0U, 1U, 2U, 3U});
const auto tilted = element(20U, {1U, 4U, 5U, 2U});
const std::vector<fesa::Node> nodes{
Node(0U, {0.0, 0.0, 0.0}), Node(1U, {1.0, 0.0, 0.0}),
Node(2U, {1.0, 1.0, 0.0}), Node(3U, {0.0, 1.0, 0.0}),
Node(4U, {2.0, 0.0, 1.0}), Node(5U, {2.0, 1.0, 1.0})};
const auto flat = Element(10U, {0U, 1U, 2U, 3U});
const auto tilted = Element(20U, {1U, 4U, 5U, 2U});
auto first = fesa::preprocessShellGeometry(
nodes, {tilted, flat}, sections());
auto second = fesa::preprocessShellGeometry(
nodes, {flat, tilted}, sections());
auto first = fesa::PreprocessShellGeometry(nodes, {tilted, flat}, Sections());
auto second =
fesa::PreprocessShellGeometry(nodes, {flat, tilted}, Sections());
ASSERT_TRUE(first.HasValue());
ASSERT_TRUE(second.HasValue());
const Vector3 expectedSharedDirector{
-1.0 / std::sqrt(5.0), 0.0, 2.0 / std::sqrt(5.0)};
for (const auto sharedNode : {1U, 2U}) {
const auto& firstFrame = frameFor(first.Value(), sharedNode);
const auto& secondFrame = frameFor(second.Value(), sharedNode);
expectVectorNear(firstFrame.director, expectedSharedDirector);
expectVectorNear(firstFrame.director, secondFrame.director, 0.0);
expectVectorNear(firstFrame.tangentA, {0.0, 1.0, 0.0});
expectRightHandedFrame(firstFrame);
}
ASSERT_TRUE(first.HasValue());
ASSERT_TRUE(second.HasValue());
const Vector3 expected_shared_director{-1.0 / std::sqrt(5.0), 0.0,
2.0 / std::sqrt(5.0)};
for (const auto sharedNode : {1U, 2U}) {
const auto& firstFrame = FrameFor(first.Value(), sharedNode);
const auto& secondFrame = FrameFor(second.Value(), sharedNode);
ExpectVectorNear(firstFrame.director, expected_shared_director);
ExpectVectorNear(firstFrame.director, secondFrame.director, 0.0);
ExpectVectorNear(firstFrame.tangent_a, {0.0, 1.0, 0.0});
ExpectRightHandedFrame(firstFrame);
}
}
// MITC4-GEO-003
TEST(Mitc4Geometry, RejectsInvalidSurfaceJacobianAndIncidentOrientationCases) {
const auto validElement = element(10U, {0U, 1U, 2U, 3U});
const auto valid_element = Element(10U, {0U, 1U, 2U, 3U});
expectFailureCode(
fesa::preprocessShellGeometry(
{node(0U, {0.0, 0.0, 0.0}), node(1U, {0.0, 0.0, 0.0}),
node(2U, {1.0, 1.0, 0.0}), node(3U, {0.0, 1.0, 0.0})},
{validElement}, sections()),
"invalid-shell-geometry");
ExpectFailureCode(fesa::PreprocessShellGeometry(
{Node(0U, {0.0, 0.0, 0.0}), Node(1U, {0.0, 0.0, 0.0}),
Node(2U, {1.0, 1.0, 0.0}), Node(3U, {0.0, 1.0, 0.0})},
{valid_element}, Sections()),
"invalid-shell-geometry");
expectFailureCode(
fesa::preprocessShellGeometry(
{node(0U, {0.0, 0.0, 0.0}), node(1U, {1.0, 1.0, 0.0}),
node(2U, {0.0, 1.0, 0.0}), node(3U, {1.0, 0.0, 0.0})},
{validElement}, sections()),
"invalid-shell-geometry");
ExpectFailureCode(fesa::PreprocessShellGeometry(
{Node(0U, {0.0, 0.0, 0.0}), Node(1U, {1.0, 1.0, 0.0}),
Node(2U, {0.0, 1.0, 0.0}), Node(3U, {1.0, 0.0, 0.0})},
{valid_element}, Sections()),
"invalid-shell-geometry");
expectFailureCode(
fesa::preprocessShellGeometry(
{node(0U, {0.0, 0.0, 0.0}), node(1U, {1.0, 0.0, 0.0}),
node(2U, {2.0, 0.0, 0.0}), node(3U, {3.0, 0.0, 0.0})},
{validElement}, sections()),
"invalid-shell-geometry");
ExpectFailureCode(fesa::PreprocessShellGeometry(
{Node(0U, {0.0, 0.0, 0.0}), Node(1U, {1.0, 0.0, 0.0}),
Node(2U, {2.0, 0.0, 0.0}), Node(3U, {3.0, 0.0, 0.0})},
{valid_element}, Sections()),
"invalid-shell-geometry");
expectFailureCode(
fesa::preprocessShellGeometry(
{node(0U, {0.0, 0.0, 0.0}), node(1U, {1.0, 0.0, 0.0}),
node(2U, {0.05, 0.05, 0.0}), node(3U, {0.0, 1.0, 0.0})},
{validElement}, sections()),
"invalid-shell-geometry");
ExpectFailureCode(
fesa::PreprocessShellGeometry(
{Node(0U, {0.0, 0.0, 0.0}), Node(1U, {1.0, 0.0, 0.0}),
Node(2U, {0.05, 0.05, 0.0}), Node(3U, {0.0, 1.0, 0.0})},
{valid_element}, Sections()),
"invalid-shell-geometry");
expectFailureCode(
fesa::preprocessShellGeometry(
{node(0U, {0.0, 0.0, 0.0}),
node(1U, {1.0, 0.0, 0.0}),
node(2U, {1.0, std::numeric_limits<double>::quiet_NaN(), 0.0}),
node(3U, {0.0, 1.0, 0.0})},
{validElement}, sections()),
"invalid-shell-geometry");
ExpectFailureCode(
fesa::PreprocessShellGeometry(
{Node(0U, {0.0, 0.0, 0.0}), Node(1U, {1.0, 0.0, 0.0}),
Node(2U, {1.0, std::numeric_limits<double>::quiet_NaN(), 0.0}),
Node(3U, {0.0, 1.0, 0.0})},
{valid_element}, Sections()),
"invalid-shell-geometry");
expectFailureCode(
fesa::preprocessShellGeometry(
{node(0U, {0.0, 0.0, 0.0}), node(1U, {1.0, 0.0, 0.0}),
node(2U, {1.0, 1.0, 0.0}), node(3U, {0.0, 1.0, 0.0})},
{validElement}, sections(0.0)),
"invalid-shell-jacobian");
ExpectFailureCode(fesa::PreprocessShellGeometry(
{Node(0U, {0.0, 0.0, 0.0}), Node(1U, {1.0, 0.0, 0.0}),
Node(2U, {1.0, 1.0, 0.0}), Node(3U, {0.0, 1.0, 0.0})},
{valid_element}, Sections(0.0)),
"invalid-shell-jacobian");
const std::vector<fesa::Node> opposedNodes{
node(0U, {0.0, 0.0, 0.0}), node(1U, {1.0, 0.0, 0.0}),
node(2U, {1.0, 1.0, 0.0}), node(3U, {0.0, 1.0, 0.0})};
expectFailureCode(
fesa::preprocessShellGeometry(
opposedNodes,
{element(10U, {0U, 1U, 2U, 3U}),
element(20U, {0U, 3U, 2U, 1U})},
sections()),
"opposed-incident-normal");
const std::vector<fesa::Node> opposed_nodes{
Node(0U, {0.0, 0.0, 0.0}), Node(1U, {1.0, 0.0, 0.0}),
Node(2U, {1.0, 1.0, 0.0}), Node(3U, {0.0, 1.0, 0.0})};
ExpectFailureCode(
fesa::PreprocessShellGeometry(
opposed_nodes,
{Element(10U, {0U, 1U, 2U, 3U}), Element(20U, {0U, 3U, 2U, 1U})},
Sections()),
"opposed-incident-normal");
}
// MITC4-GEO-004
TEST(Mitc4Geometry, ExposesTheCompleteRequiredValidationPointInventory) {
const auto& points = fesa::shellGeometryValidationPoints();
ASSERT_EQ(points.size(), 17U);
EXPECT_EQ(
std::count_if(points.begin(), points.end(), [](const auto& point) {
return point.kind == fesa::ShellGeometryPointKind::center;
}),
1);
EXPECT_EQ(
std::count_if(points.begin(), points.end(), [](const auto& point) {
return point.kind == fesa::ShellGeometryPointKind::stiffness;
}),
8);
EXPECT_EQ(
std::count_if(points.begin(), points.end(), [](const auto& point) {
return point.kind == fesa::ShellGeometryPointKind::tying;
}),
4);
EXPECT_EQ(
std::count_if(points.begin(), points.end(), [](const auto& point) {
return point.kind == fesa::ShellGeometryPointKind::recovery;
}),
4);
const auto& points = fesa::ShellGeometryValidationPoints();
ASSERT_EQ(points.size(), 17U);
EXPECT_EQ(std::count_if(points.begin(), points.end(),
[](const auto& point) {
return point.kind ==
fesa::ShellGeometryPointKind::kCenter;
}),
1);
EXPECT_EQ(std::count_if(points.begin(), points.end(),
[](const auto& point) {
return point.kind ==
fesa::ShellGeometryPointKind::kStiffness;
}),
8);
EXPECT_EQ(std::count_if(points.begin(), points.end(),
[](const auto& point) {
return point.kind ==
fesa::ShellGeometryPointKind::kTying;
}),
4);
EXPECT_EQ(std::count_if(points.begin(), points.end(),
[](const auto& point) {
return point.kind ==
fesa::ShellGeometryPointKind::kRecovery;
}),
4);
EXPECT_EQ(points.front().naturalCoordinates, (Vector3{0.0, 0.0, 0.0}));
const double gauss = 1.0 / std::sqrt(3.0);
EXPECT_EQ(points[1].naturalCoordinates, (Vector3{-gauss, -gauss, -gauss}));
EXPECT_EQ(points[8].naturalCoordinates, (Vector3{-gauss, gauss, gauss}));
EXPECT_EQ(points[9].naturalCoordinates, (Vector3{0.0, -1.0, 0.0}));
EXPECT_EQ(points[12].naturalCoordinates, (Vector3{1.0, 0.0, 0.0}));
EXPECT_EQ(points[13].naturalCoordinates, (Vector3{-gauss, -gauss, 0.0}));
EXPECT_EQ(points[16].naturalCoordinates, (Vector3{-gauss, gauss, 0.0}));
EXPECT_EQ(points.front().natural_coordinates, (Vector3{0.0, 0.0, 0.0}));
const double gauss = 1.0 / std::sqrt(3.0);
EXPECT_EQ(points[1].natural_coordinates, (Vector3{-gauss, -gauss, -gauss}));
EXPECT_EQ(points[8].natural_coordinates, (Vector3{-gauss, gauss, gauss}));
EXPECT_EQ(points[9].natural_coordinates, (Vector3{0.0, -1.0, 0.0}));
EXPECT_EQ(points[12].natural_coordinates, (Vector3{1.0, 0.0, 0.0}));
EXPECT_EQ(points[13].natural_coordinates, (Vector3{-gauss, -gauss, 0.0}));
EXPECT_EQ(points[16].natural_coordinates, (Vector3{-gauss, gauss, 0.0}));
}
+4 -4
View File
@@ -13,13 +13,13 @@ namespace {
fesa::DofManager makeEmptyDofs() {
fesa::ModelDefinition definition{};
definition.sourcePath = "models/result-records.inp";
definition.sourceContentIdentity = "fnv1a64:0123456789abcdef";
definition.source_path = "models/result-records.inp";
definition.source_content_identity = "fnv1a64:0123456789abcdef";
definition.steps = {{
"Step-1", {}, {}, 0.1, 1.0, 0.01, 1.0,
{definition.sourcePath, 10U}}};
{definition.source_path, 10U}}};
auto domain = fesa::Domain::create(std::move(definition));
auto domain = fesa::Domain::Create(std::move(definition));
EXPECT_TRUE(domain.HasValue());
auto model = fesa::AnalysisModel::create(domain.Value());
EXPECT_TRUE(model.HasValue());
+26 -26
View File
@@ -6,7 +6,7 @@
#include "fesa/assembly/parallel_for.hpp"
#include "fesa/assembly/sparse_assembler.hpp"
#include "fesa/fem/dof_manager.hpp"
#include "fesa/model/domain.hpp"
#include "fesa/model/domain.h"
#include <gtest/gtest.h>
@@ -51,8 +51,8 @@ fesa::ModelDefinition makeDefinition(
const bool nonzeroPrescription = true) {
const std::filesystem::path source{"models/result-recovery.inp"};
fesa::ModelDefinition definition{};
definition.sourcePath = source;
definition.sourceContentIdentity = "fnv1a64:0123456789abcdef";
definition.source_path = source;
definition.source_content_identity = "fnv1a64:0123456789abcdef";
definition.nodes = {
{{"Beam-1", 1, "1"}, {0.0, 0.0, 0.0}, {source, 10U}},
{{"Beam-1", 2, "2"}, {kLength, 0.0, 0.0}, {source, 11U}}};
@@ -110,7 +110,7 @@ RecoveryFixture makeFixture(
const bool reverseSecond = false,
const bool sectionJump = false,
const bool nonzeroPrescription = true) {
auto domainResult = fesa::Domain::create(makeDefinition(
auto domainResult = fesa::Domain::Create(makeDefinition(
twoElements,
std::move(sectionPoints),
std::move(loads),
@@ -158,8 +158,8 @@ fesa::ModelDefinition makeShellDefinition(
std::vector<fesa::NodalLoad> loads = {}) {
const std::filesystem::path source{"models/shell-result-recovery.inp"};
fesa::ModelDefinition definition{};
definition.sourcePath = source;
definition.sourceContentIdentity = "fnv1a64:fedcba9876543210";
definition.source_path = source;
definition.source_content_identity = "fnv1a64:fedcba9876543210";
definition.nodes = {
{{"Shell-1", 1, "1"}, {-1.0, -1.0, 0.0}, {source, 10U}},
{{"Shell-1", 2, "2"}, {1.0, -1.0, 0.0}, {source, 11U}},
@@ -175,26 +175,26 @@ fesa::ModelDefinition makeShellDefinition(
}
definition.materials = {
{"Material", 120.0, 0.25, {source, 20U}}};
definition.shellSections = {
definition.shell_sections = {
{"ShellSection", 2.0, 0U, {source, 30U}}};
definition.shellElements = {{
definition.shell_elements = {{
{"Shell-1", 10, "10"},
fesa::ShellSourceElementType::s4,
fesa::ShellSourceElementType::kS4,
{0U, 1U, twoElements ? 4U : 3U, twoElements ? 3U : 2U},
0U,
0U,
{source, 40U}}};
if (twoElements) {
definition.shellElements.push_back({
definition.shell_elements.push_back({
{"Shell-1", 20, "20"},
fesa::ShellSourceElementType::s4r,
fesa::ShellSourceElementType::kS4r,
{1U, 2U, 5U, 4U},
0U,
0U,
{source, 41U}});
}
for (std::size_t node = 0U; node < definition.nodes.size(); ++node) {
definition.shellNodeInitialFrames.push_back({
definition.shell_node_initial_frames.push_back({
static_cast<fesa::EntityIndex>(node),
{0.0, 0.0, 1.0},
{1.0, 0.0, 0.0},
@@ -206,7 +206,7 @@ fesa::ModelDefinition makeShellDefinition(
for (std::size_t node = 0U; node < definition.nodes.size(); ++node) {
allNodes.push_back(static_cast<fesa::EntityIndex>(node));
}
definition.nodeSets.push_back(
definition.node_sets.push_back(
{"All", {}, std::move(allNodes), {source, 50U}});
}
definition.steps = {{
@@ -225,7 +225,7 @@ fesa::ModelDefinition makeShellDefinition(
}
ShellRecoveryFixture makeShellFixture(fesa::ModelDefinition definition) {
auto domainResult = fesa::Domain::create(std::move(definition));
auto domainResult = fesa::Domain::Create(std::move(definition));
if (!domainResult.HasValue()) {
throw std::runtime_error{
"Shell recovery fixture Domain construction failed."};
@@ -272,10 +272,10 @@ fesa::AnalysisState makeShellPhysicalState(
auto state = fesa::AnalysisState::create(
*fixture.dofs, {"Step-1", 0U});
for (std::size_t node = 0U;
node < fixture.domain->nodes().size();
node < fixture.domain->Nodes().size();
++node) {
const double x = fixture.domain->nodes()[node].coordinates[0U];
const double y = fixture.domain->nodes()[node].coordinates[1U];
const double x = fixture.domain->Nodes()[node].coordinates[0U];
const double y = fixture.domain->Nodes()[node].coordinates[1U];
const std::size_t offset = 6U * node;
state.displacement()[offset] =
generalized[0U] * x + 0.5 * generalized[2U] * y;
@@ -345,19 +345,19 @@ void expectScaledNear(
std::vector<fesa::EndpointResultRow> makeStationRows(
const RecoveryFixture& fixture) {
const auto& nodes = fixture.domain->nodes();
const auto& elements = fixture.domain->elements();
const auto& nodes = fixture.domain->Nodes();
const auto& elements = fixture.domain->Elements();
return {
{0U, 0, nodes[elements[0U].nodeIndices[0U]].sourceId,
{0U, 0, nodes[elements[0U].node_indices[0U]].source_id,
{0.0, 0.0, 0.0, 0.0, 0.0, 0.0},
{1.0, 2.0, 3.0, 4.0}},
{0U, 1, nodes[elements[0U].nodeIndices[1U]].sourceId,
{0U, 1, nodes[elements[0U].node_indices[1U]].source_id,
{0.0, 0.0, 0.0, 0.0, 0.0, 0.0},
{5.0, 6.0, 7.0, 8.0}},
{1U, 0, nodes[elements[1U].nodeIndices[0U]].sourceId,
{1U, 0, nodes[elements[1U].node_indices[0U]].source_id,
{0.0, 0.0, 0.0, 0.0, 0.0, 0.0},
{5.0, 6.0, 7.0, 8.0}},
{1U, 1, nodes[elements[1U].nodeIndices[1U]].sourceId,
{1U, 1, nodes[elements[1U].node_indices[1U]].source_id,
{0.0, 0.0, 0.0, 0.0, 0.0, 0.0},
{9.0, 10.0, 11.0, 12.0}}};
}
@@ -639,10 +639,10 @@ TEST(ResultRecovery, RecoversShellRowsInStableElementAndGpOrder) {
auto state = fesa::AnalysisState::create(
*fixture.dofs, {"Step-1", 0U});
for (std::size_t node = 0U;
node < fixture.domain->nodes().size();
node < fixture.domain->Nodes().size();
++node) {
const double x = fixture.domain->nodes()[node].coordinates[0U];
const double y = fixture.domain->nodes()[node].coordinates[1U];
const double x = fixture.domain->Nodes()[node].coordinates[0U];
const double y = fixture.domain->Nodes()[node].coordinates[1U];
state.displacement()[node * 6U] = 0.1 * x + 0.1 * y;
state.displacement()[node * 6U + 1U] = -0.05 * y + 0.1 * x;
}
+1 -1
View File
@@ -3,7 +3,7 @@
#include "fesa/analysis/analysis_state.hpp"
#include "fesa/core/diagnostic.h"
#include "fesa/core/status.h"
#include "fesa/model/domain.hpp"
#include "fesa/model/domain.h"
#include <filesystem>
#include <type_traits>