diff --git a/include/fesa/analysis/analysis_model.hpp b/include/fesa/analysis/analysis_model.hpp index 3c1f4a2..b5e0f80 100644 --- a/include/fesa/analysis/analysis_model.hpp +++ b/include/fesa/analysis/analysis_model.hpp @@ -1,6 +1,6 @@ #pragma once -#include "fesa/model/domain.hpp" +#include "fesa/model/domain.h" #include diff --git a/include/fesa/analysis/linear_static_analysis.hpp b/include/fesa/analysis/linear_static_analysis.hpp index 4e32f9e..8b33014 100644 --- a/include/fesa/analysis/linear_static_analysis.hpp +++ b/include/fesa/analysis/linear_static_analysis.hpp @@ -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 #include diff --git a/include/fesa/elements/euler_beam_3d.h b/include/fesa/elements/euler_beam_3d.h new file mode 100644 index 0000000..5b6bec7 --- /dev/null +++ b/include/fesa/elements/euler_beam_3d.h @@ -0,0 +1,95 @@ +#ifndef FESA_ELEMENTS_EULER_BEAM_3D_H_ +#define FESA_ELEMENTS_EULER_BEAM_3D_H_ + +#include +#include +#include +#include + +#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, 2> equilibrium_end_actions; + std::array, 2> endpoint_section_resultants; + std::array, 2> gauss_generalized_strains; + std::array, 2> gauss_generalized_resultants; + std::vector 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 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 rotation, + std::vector> section_points); + + double length_; + double youngs_modulus_; + double shear_modulus_; + double area_; + double iy_; + double iz_; + double torsional_constant_; + std::array rotation_; + std::vector> section_points_; +}; + +} // namespace fesa + +#endif // FESA_ELEMENTS_EULER_BEAM_3D_H_ diff --git a/include/fesa/elements/euler_beam_3d.hpp b/include/fesa/elements/euler_beam_3d.hpp deleted file mode 100644 index 8cc5264..0000000 --- a/include/fesa/elements/euler_beam_3d.hpp +++ /dev/null @@ -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 -#include -#include -#include - -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, 2> equilibriumEndActions; - std::array, 2> endpointSectionResultants; - std::array, 2> gaussGeneralizedStrains; - std::array, 2> gaussGeneralizedResultants; - std::vector 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 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 rotation, - std::vector> sectionPoints); - - double length_; - double youngsModulus_; - double shearModulus_; - double area_; - double iy_; - double iz_; - double torsionalConstant_; - std::array rotation_; - std::vector> sectionPoints_; -}; - -} // namespace fesa diff --git a/include/fesa/elements/mitc4_shell.h b/include/fesa/elements/mitc4_shell.h new file mode 100644 index 0000000..9b49f38 --- /dev/null +++ b/include/fesa/elements/mitc4_shell.h @@ -0,0 +1,179 @@ +#ifndef FESA_ELEMENTS_MITC4_SHELL_H_ +#define FESA_ELEMENTS_MITC4_SHELL_H_ + +#include +#include + +#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 values; + std::array xi_derivatives; + std::array eta_derivatives; +}; + +/// @brief Stores a right-handed local shell frame at one location. +struct Mitc4LocalFrame { + std::array e1; + std::array e2; + std::array e3; +}; + +/// @brief Stores canonical MITC4 covariant shear interpolation weights. +struct Mitc4TyingWeights { + std::array xi_zeta; + std::array eta_zeta; +}; + +/// @brief Stores one fixed 2-by-2-by-2 integration point and weight. +struct Mitc4QuadraturePoint { + std::array 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 natural_coordinates; + Mitc4LocalFrame local_frame; + std::array generalized_strain; + std::array section_resultant; + std::array, 3> in_plane_stress; +}; + +/// @brief Stores physical-only recovery rows and strain energy. +struct Mitc4PhysicalRecovery { + std::array 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 Create( + std::array nodes, + std::array, 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& 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 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 RecoverPhysical( + const Vector& global_element_displacement24) const; + + private: + using Vector3 = std::array; + + /// @brief Stores covariant, reciprocal, frame, and Jacobian data at one + /// point. + struct GeometryData { + std::array covariant; + std::array reciprocal; + Mitc4LocalFrame frame; + double jacobian; + }; + + /// @brief Stores validated shell geometry and constitutive state. + Mitc4Shell(std::array coordinates, + std::array directors, std::array tangent_a, + std::array 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, 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 coordinates_; + std::array directors_; + std::array tangent_a_; + std::array 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_ diff --git a/include/fesa/elements/mitc4_shell.hpp b/include/fesa/elements/mitc4_shell.hpp deleted file mode 100644 index ac14971..0000000 --- a/include/fesa/elements/mitc4_shell.hpp +++ /dev/null @@ -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 -#include - -namespace fesa { - -struct Mitc4ShapeFunctions { - std::array values; - std::array xiDerivatives; - std::array etaDerivatives; -}; - -struct Mitc4LocalFrame { - std::array e1; - std::array e2; - std::array e3; -}; - -struct Mitc4TyingWeights { - std::array xiZeta; - std::array etaZeta; -}; - -struct Mitc4QuadraturePoint { - std::array naturalCoordinates; - double weight; -}; - -struct Mitc4Stiffness { - Matrix physicalLocal20; - Matrix physicalGlobal24; - Matrix drillingGlobal24; - Matrix stabilizedGlobal24; - double drillingStiffness; -}; - -struct Mitc4PhysicalRecoveryPoint { - std::array naturalCoordinates; - Mitc4LocalFrame localFrame; - std::array generalizedStrain; - std::array sectionResultant; - std::array, 3> inPlaneStress; -}; - -struct Mitc4PhysicalRecovery { - std::array 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 create( - std::array nodes, - std::array, 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& - 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 stiffness() const; - [[nodiscard]] Result recoverPhysical( - const Vector& globalElementDisplacement24) const; - -private: - using Vector3 = std::array; - - struct GeometryData { - std::array covariant; - std::array reciprocal; - Mitc4LocalFrame frame; - double jacobian; - }; - - Mitc4Shell( - std::array coordinates, - std::array directors, - std::array tangentA, - std::array 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, 20> basisDerivatives( - double xi, - double eta, - double zeta) const noexcept; - Matrix strainDisplacement( - double xi, - double eta, - double zeta, - const Matrix* tyingSamples) const; - - std::array coordinates_; - std::array directors_; - std::array tangentA_; - std::array tangentB_; - Vector3 normalCandidate_; - double thickness_; - double youngsModulus_; - double poissonRatio_; - SourceLocation sourceLocation_; - std::string identity_; -}; - -} // namespace fesa diff --git a/include/fesa/io/abaqus/domain_mapper.hpp b/include/fesa/io/abaqus/domain_mapper.hpp index dc66a71..cbfb03d 100644 --- a/include/fesa/io/abaqus/domain_mapper.hpp +++ b/include/fesa/io/abaqus/domain_mapper.hpp @@ -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 { diff --git a/include/fesa/model/domain.h b/include/fesa/model/domain.h new file mode 100644 index 0000000..bbc20cf --- /dev/null +++ b/include/fesa/model/domain.h @@ -0,0 +1,72 @@ +#ifndef FESA_MODEL_DOMAIN_H_ +#define FESA_MODEL_DOMAIN_H_ + +#include +#include +#include + +#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 Create(ModelDefinition definition); + + /// @brief Returns nodes in stable declaration order. + const std::vector& Nodes() const noexcept; + + /// @brief Returns Euler beam definitions in stable declaration order. + const std::vector& Elements() const noexcept; + + /// @brief Returns MITC4 shell definitions in stable declaration order. + const std::vector& ShellElements() const noexcept; + + /// @brief Returns materials in stable declaration order. + const std::vector& Materials() const noexcept; + + /// @brief Returns beam sections in stable declaration order. + const std::vector& Sections() const noexcept; + + /// @brief Returns shell sections in stable declaration order. + const std::vector& ShellSections() const noexcept; + + /// @brief Returns preprocessed shell-node frames in stable node order. + const std::vector& ShellNodeInitialFrames() + const noexcept; + + /// @brief Returns node sets in stable declaration order. + const std::vector& NodeSets() const noexcept; + + /// @brief Returns element sets in stable declaration order. + const std::vector& ElementSets() const noexcept; + + /// @brief Returns static steps in stable declaration order. + const std::vector& Steps() const noexcept; + + /// @brief Returns sorted nonfatal mapping diagnostics. + const std::vector& 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_ diff --git a/include/fesa/model/domain.hpp b/include/fesa/model/domain.hpp deleted file mode 100644 index 19d5cf8..0000000 --- a/include/fesa/model/domain.hpp +++ /dev/null @@ -1,38 +0,0 @@ -#pragma once - -#include "fesa/core/status.h" -#include "fesa/model/model_types.hpp" - -#include -#include -#include - -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 create(ModelDefinition definition); - - const std::vector& nodes() const noexcept; - const std::vector& elements() const noexcept; - const std::vector& shellElements() const noexcept; - const std::vector& materials() const noexcept; - const std::vector& sections() const noexcept; - const std::vector& shellSections() const noexcept; - const std::vector& shellNodeInitialFrames() const noexcept; - const std::vector& nodeSets() const noexcept; - const std::vector& elementSets() const noexcept; - const std::vector& steps() const noexcept; - const std::vector& 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 diff --git a/include/fesa/model/model_types.h b/include/fesa/model/model_types.h new file mode 100644 index 0000000..ad9e2e4 --- /dev/null +++ b/include/fesa/model/model_types.h @@ -0,0 +1,185 @@ +#ifndef FESA_MODEL_MODEL_TYPES_H_ +#define FESA_MODEL_MODEL_TYPES_H_ + +#include +#include +#include +#include +#include +#include +#include + +#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 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 first_axis; + std::vector> 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 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 director; + std::array tangent_a; + std::array tangent_b; +}; + +/// @brief Defines one two-node Euler beam with stable semantic references. +struct EulerBeam3DDefinition { + SourceEntityId source_id; + std::array 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 boundaries; + std::vector 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 instance_name; + std::vector node_indices; + SourceLocation location; +}; + +/// @brief Stores a stable resolved element-set membership list. +struct ElementSet { + std::string name; + std::optional instance_name; + std::vector element_indices; + SourceLocation location; +}; + +/// @brief Preserves source identities declared inside one part. +struct PartDefinition { + std::string name; + std::vector node_source_labels; + std::vector element_source_labels; + std::vector node_set_names; + std::vector 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 node_mappings; + std::vector 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 nodes; + std::vector elements; + std::vector shell_elements; + std::vector materials; + std::vector sections; + std::vector shell_sections; + std::vector shell_node_initial_frames; + std::vector node_sets; + std::vector element_sets; + std::vector parts; + std::vector instances; + std::vector steps; + std::vector warnings; +}; + +} // namespace fesa + +#endif // FESA_MODEL_MODEL_TYPES_H_ diff --git a/include/fesa/model/model_types.hpp b/include/fesa/model/model_types.hpp deleted file mode 100644 index c72db64..0000000 --- a/include/fesa/model/model_types.hpp +++ /dev/null @@ -1,165 +0,0 @@ -#pragma once - -#include "fesa/core/diagnostic.h" -#include "fesa/core/source_identity.h" - -#include -#include -#include -#include -#include -#include -#include - -namespace fesa { - -// Stable internal identities are vector positions assigned in declaration order. -using EntityIndex = std::uint32_t; - -struct Node { - SourceEntityId sourceId; - std::array 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 firstAxis; - std::vector> 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 nodeIndices; - EntityIndex materialIndex; - EntityIndex sectionIndex; - SourceLocation location; -}; - -struct ShellNodeInitialFrame { - EntityIndex nodeIndex; - std::array director; - std::array tangentA; - std::array tangentB; -}; - -struct EulerBeam3DDefinition { - SourceEntityId sourceId; - std::array 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 boundaries; - std::vector loads; - double initialIncrement; - double timePeriod; - double minimumIncrement; - double maximumIncrement; - SourceLocation location; -}; - -struct NodeSet { - std::string name; - std::optional instanceName; - std::vector nodeIndices; - SourceLocation location; -}; - -struct ElementSet { - std::string name; - std::optional instanceName; - std::vector elementIndices; - SourceLocation location; -}; - -struct PartDefinition { - std::string name; - std::vector nodeSourceLabels; - std::vector elementSourceLabels; - std::vector nodeSetNames; - std::vector elementSetNames; - SourceLocation location; -}; - -struct SourceIndexMapping { - std::int64_t sourceLabel; - EntityIndex internalIndex; -}; - -struct InstanceDefinition { - std::string name; - std::string partName; - std::vector nodeMappings; - std::vector 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 nodes; - std::vector elements; - std::vector shellElements; - std::vector materials; - std::vector sections; - std::vector shellSections; - std::vector shellNodeInitialFrames; - std::vector nodeSets; - std::vector elementSets; - std::vector parts; - std::vector instances; - std::vector steps; - std::vector warnings; -}; - -} // namespace fesa diff --git a/include/fesa/model/shell_geometry.h b/include/fesa/model/shell_geometry.h new file mode 100644 index 0000000..7e26b56 --- /dev/null +++ b/include/fesa/model/shell_geometry.h @@ -0,0 +1,53 @@ +#ifndef FESA_MODEL_SHELL_GEOMETRY_H_ +#define FESA_MODEL_SHELL_GEOMETRY_H_ + +#include +#include +#include + +#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 natural_coordinates; +}; + +/// @brief Stores deterministic preprocessing data for one shell element. +struct ShellElementGeometryData { + EntityIndex element_index; + std::array normal_candidate; + double surface_area_weight; +}; + +/// @brief Owns preprocessed shell node frames and element geometry data. +struct ShellGeometry { + std::vector nodal_frames; + std::vector element_data; +}; + +/// @brief Returns the complete fixed validation-point inventory. +/// @note Ordering is center, stiffness, tying, then recovery identity. +const std::array& +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 PreprocessShellGeometry( + const std::vector& nodes, + const std::vector& elements, + const std::vector& sections); + +} // namespace fesa + +#endif // FESA_MODEL_SHELL_GEOMETRY_H_ diff --git a/include/fesa/model/shell_geometry.hpp b/include/fesa/model/shell_geometry.hpp deleted file mode 100644 index 24b0c3b..0000000 --- a/include/fesa/model/shell_geometry.hpp +++ /dev/null @@ -1,44 +0,0 @@ -#pragma once - -#include "fesa/core/status.h" -#include "fesa/model/model_types.hpp" - -#include -#include -#include - -namespace fesa { - -enum class ShellGeometryPointKind { - center, - stiffness, - tying, - recovery -}; - -struct ShellGeometryValidationPoint { - ShellGeometryPointKind kind; - std::size_t locationIndex; - std::array naturalCoordinates; -}; - -struct ShellElementGeometryData { - EntityIndex elementIndex; - std::array normalCandidate; - double surfaceAreaWeight; -}; - -struct ShellGeometry { - std::vector nodalFrames; - std::vector elementData; -}; - -const std::array& -shellGeometryValidationPoints() noexcept; - -Result preprocessShellGeometry( - const std::vector& nodes, - const std::vector& elements, - const std::vector& sections); - -} // namespace fesa diff --git a/include/fesa/results/result_records.hpp b/include/fesa/results/result_records.hpp index 7fe932c..d8d0850 100644 --- a/include/fesa/results/result_records.hpp +++ b/include/fesa/results/result_records.hpp @@ -1,6 +1,6 @@ #pragma once -#include "fesa/model/model_types.hpp" +#include "fesa/model/model_types.h" #include #include diff --git a/include/fesa/results/results_writer.hpp b/include/fesa/results/results_writer.hpp index 7d212e6..40e4ef2 100644 --- a/include/fesa/results/results_writer.hpp +++ b/include/fesa/results/results_writer.hpp @@ -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 #include diff --git a/src/fesa/analysis/analysis_model.cpp b/src/fesa/analysis/analysis_model.cpp index ca8bbb2..8b13514 100644 --- a/src/fesa/analysis/analysis_model.cpp +++ b/src/fesa/analysis/analysis_model.cpp @@ -6,18 +6,18 @@ namespace fesa { Result AnalysisModel::create(const Domain& domain) { - if (domain.steps().empty()) { + if (domain.Steps().empty()) { return Result::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::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& AnalysisModel::activeElements() const noexcept { @@ -60,14 +60,14 @@ const std::vector& AnalysisModel::activeLoads() const noexcept { } AnalysisModel::AnalysisModel(const Domain& domain) : domain_{&domain} { - std::vector reachableMaterials(domain.materials().size(), false); - std::vector reachableSections(domain.sections().size(), false); + std::vector reachableMaterials(domain.Materials().size(), false); + std::vector 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(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 diff --git a/src/fesa/analysis/linear_static_analysis.cpp b/src/fesa/analysis/linear_static_analysis.cpp index 8b9158d..4e6a3f5 100644 --- a/src/fesa/analysis/linear_static_analysis.cpp +++ b/src/fesa/analysis/linear_static_analysis.cpp @@ -76,7 +76,7 @@ Status LinearStaticAnalysis::initialize(const AnalysisRequest& request) { } domain_ = std::make_unique(std::move(domain.Value())); - diagnostics_ = domain_->warnings(); + diagnostics_ = domain_->Warnings(); SortDiagnostics(diagnostics_); return Status::Ok(); } diff --git a/src/fesa/assembly/load_assembler.cpp b/src/fesa/assembly/load_assembler.cpp index 1b37034..f460c01 100644 --- a/src/fesa/assembly/load_assembler.cpp +++ b/src/fesa/assembly/load_assembler.cpp @@ -146,7 +146,7 @@ Result> resolveTarget( const Domain& domain, const NodalLoad& load) { std::vector 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> resolveTarget( std::vector 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(index)); } } @@ -172,10 +172,10 @@ Result> 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 seen(domain.nodes().size(), 0U); + const auto& nodes = matchingSets.front()->node_indices; + std::vector 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>::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 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 LoadAssembler::assembleFullNodalLoad( const AnalysisModel& model, const DofManager& dofs) { const Domain& domain = model.domain(); - if (domain.nodes().size() > + if (domain.Nodes().size() > (std::numeric_limits::max)() / dofsPerNode) { return Result::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::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 LoadAssembler::assembleFullNodalLoad( node * dofsPerNode + component) { return Result::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::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.")); } } diff --git a/src/fesa/assembly/sparse_assembler.cpp b/src/fesa/assembly/sparse_assembler.cpp index 46a43cd..409a8c6 100644 --- a/src/fesa/assembly/sparse_assembler.cpp +++ b/src/fesa/assembly/sparse_assembler.cpp @@ -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 @@ -52,46 +52,46 @@ Result SparseAssembler::assembleStiffness( const DofManager& dofs, const ParallelFor& parallelFor) { const Domain& domain = model.domain(); - if (domain.nodes().size() > + if (domain.Nodes().size() > (std::numeric_limits::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::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>> 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 SparseAssembler::assembleStiffness( std::array scatter; }; std::vector 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(elementOrder)); @@ -126,22 +126,22 @@ Result 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 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 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 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 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 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 SparseAssembler::assembleStiffness( std::vector> 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 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 SparseAssembler::assembleStiffness( ++component) { const std::size_t local = endpoint * kDofsPerNode + component; const std::size_t expected = - static_cast(element.nodeIndices[endpoint]) * + static_cast(element.node_indices[endpoint]) * kDofsPerNode + component; if (scatter[local] != expected || @@ -286,7 +286,7 @@ Result 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 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; diff --git a/src/fesa/elements/euler_beam_3d.cpp b/src/fesa/elements/euler_beam_3d.cpp index db2294f..8d0a2ca 100644 --- a/src/fesa/elements/euler_beam_3d.cpp +++ b/src/fesa/elements/euler_beam_3d.cpp @@ -1,4 +1,4 @@ -#include "fesa/elements/euler_beam_3d.hpp" +#include "fesa/elements/euler_beam_3d.h" #include #include @@ -19,497 +19,462 @@ constexpr double kStiffnessInvariantTolerance = 1.0e-12; using Vector3 = std::array; -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 modelFailure(const std::string& code, +Result ModelFailure(const std::string& code, const SourceLocation& location, const std::string& identity, const std::string& message) { - return Result::Failure(Status::Failure( - FailureCategory::kModel, - {{Severity::kError, code, location, "*ELEMENT", identity, message}})); + return Result::Failure(Status::Failure( + FailureCategory::kModel, + {{Severity::kError, code, location, "*ELEMENT", identity, message}})); } -Matrix transformation(const std::array& 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& 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 constitutiveDiagonal( - double youngsModulus, - double shearModulus, - double area, - double iy, - double iz, - double torsionalConstant) { - return { - youngsModulus * area, - shearModulus * torsionalConstant, - youngsModulus * iy, - youngsModulus * iz}; +std::array 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& diagonal) { - Matrix closed{kElementDofCount, kElementDofCount}; - const auto addBlock = [&closed](const std::array& 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& diagonal) { + Matrix closed{kElementDofCount, kElementDofCount}; + const auto add_block = [&closed](const std::array& 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& 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 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 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::infinity(); - } - const double difference = std::abs(lhsValue - rhsValue); - if (!std::isfinite(difference)) { - return std::numeric_limits::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::infinity(); + } + const double difference = std::abs(lhs_value - rhs_value); + if (!std::isfinite(difference)) { + return std::numeric_limits::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::infinity(); + } + if (!std::isfinite(maximum_difference) || !std::isfinite(scale_value) || + !(scale_value > 0.0)) { + return std::numeric_limits::infinity(); + } + const double normalized_error = maximum_difference / scale_value; + return std::isfinite(normalized_error) + ? normalized_error + : std::numeric_limits::infinity(); } -std::array generalizedStrain( - const Matrix& b, - const Vector& localDisplacement) { - std::array 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 GeneralizedStrain( + const Matrix& b, const Vector& local_displacement) { + std::array 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 generalizedResultant( +/// @brief Applies the diagonal section law without changing component order. +std::array GeneralizedResultant( const std::array& strain, const std::array& diagonal) { - std::array resultant{}; - for (std::size_t component = 0; component < resultant.size(); ++component) { - resultant[component] = diagonal[component] * strain[component]; - } - return resultant; + std::array 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::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::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 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 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 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 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 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 rotation = {ex[0], ex[1], ex[2], ey[0], ey[1], + ey[2], ez[0], ez[1], ez[2]}; - return Result::Success(EulerBeam3D{ - length, - material.youngsModulus, - shearModulus, - section.area, - section.i11, - section.i22, - section.torsionalConstant, - rotation, - section.sectionPoints}); + return Result::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 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 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 components = {load.px, load.py, load.pz, load.mx}; - Vector equivalent{kElementDofCount}; - const double inverseSqrtThree = 1.0 / std::sqrt(3.0); - const std::array 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 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 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 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(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(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 rotation, - std::vector> 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 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(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(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 rotation, + std::vector> 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 diff --git a/src/fesa/elements/mitc4_shell.cpp b/src/fesa/elements/mitc4_shell.cpp index 98d751f..d9634b4 100644 --- a/src/fesa/elements/mitc4_shell.cpp +++ b/src/fesa/elements/mitc4_shell.cpp @@ -1,4 +1,4 @@ -#include "fesa/elements/mitc4_shell.hpp" +#include "fesa/elements/mitc4_shell.h" #include #include @@ -20,963 +20,899 @@ constexpr std::size_t kGlobalDofCount = 24U; constexpr double kShearCorrection = 5.0 / 6.0; constexpr double kFrameTolerance = 1.0e-12; -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); }); } -Vector3 normalized(const Vector3& value) { - return scale(1.0 / norm(value), value); +Vector3 Normalized(const Vector3& value) { + return Scale(1.0 / Norm(value), value); } -Vector3 weightedSum( - const std::array& weights, - const std::array& values) { - Vector3 result{}; - for (std::size_t node = 0U; node < kNodeCount; ++node) { - result = add(result, scale(weights[node], values[node])); - } - return result; +Vector3 WeightedSum(const std::array& weights, + const std::array& values) { + Vector3 result{}; + for (std::size_t node = 0U; node < kNodeCount; ++node) { + result = Add(result, Scale(weights[node], values[node])); + } + return result; } -Vector3 derivativeSum( - const std::array& derivatives, - const std::array& values) { - std::array relative{}; - for (std::size_t node = 0U; node < kNodeCount; ++node) { - relative[node] = subtract(values[node], values[0]); - } - return weightedSum(derivatives, relative); +Vector3 DerivativeSum(const std::array& derivatives, + const std::array& values) { + std::array relative{}; + for (std::size_t node = 0U; node < kNodeCount; ++node) { + relative[node] = Subtract(values[node], values[0]); + } + return WeightedSum(derivatives, relative); } -bool sameCoordinates(const Vector3& left, const Vector3& right) { - return left == right; +bool SameCoordinates(const Vector3& left, const Vector3& right) { + return left == right; } -std::array nodalTangentsA( +/// @brief Selects each director's least-aligned global axis deterministically +/// and projects it into the tangent plane. +std::array NodalTangentsA( const std::array& directors) { - const std::array globalAxes{ - Vector3{1.0, 0.0, 0.0}, - Vector3{0.0, 1.0, 0.0}, - Vector3{0.0, 0.0, 1.0}}; - std::array tangents{}; - for (std::size_t node = 0U; node < kNodeCount; ++node) { - std::size_t selected = 0U; - double alignment = std::abs(dot(globalAxes[0], directors[node])); - for (std::size_t axis = 1U; axis < globalAxes.size(); ++axis) { - const double candidate = std::abs(dot(globalAxes[axis], directors[node])); - if (candidate < alignment) { - selected = axis; - alignment = candidate; - } - } - tangents[node] = normalized(subtract( - globalAxes[selected], - scale(dot(globalAxes[selected], directors[node]), directors[node]))); + const std::array global_axes{ + Vector3{1.0, 0.0, 0.0}, Vector3{0.0, 1.0, 0.0}, Vector3{0.0, 0.0, 1.0}}; + std::array tangents{}; + for (std::size_t node = 0U; node < kNodeCount; ++node) { + std::size_t selected = 0U; + double alignment = std::abs(Dot(global_axes[0], directors[node])); + for (std::size_t axis = 1U; axis < global_axes.size(); ++axis) { + const double candidate = + std::abs(Dot(global_axes[axis], directors[node])); + if (candidate < alignment) { + selected = axis; + alignment = candidate; + } } - return tangents; + tangents[node] = Normalized(Subtract( + global_axes[selected], + Scale(Dot(global_axes[selected], directors[node]), directors[node]))); + } + return tangents; } -std::array nodalTangentsB( +/// @brief Completes each right-handed nodal director frame. +std::array NodalTangentsB( const std::array& directors, - const std::array& tangentA) { - std::array tangents{}; - for (std::size_t node = 0U; node < kNodeCount; ++node) { - tangents[node] = cross(directors[node], tangentA[node]); + const std::array& tangent_a) { + std::array tangents{}; + for (std::size_t node = 0U; node < kNodeCount; ++node) { + tangents[node] = Cross(directors[node], tangent_a[node]); + } + return tangents; +} + +std::string ElementIdentity(const std::array& nodes) { + std::string identity; + for (const Node* node : nodes) { + if (node == nullptr) { + continue; } - return tangents; -} - -std::string elementIdentity(const std::array& nodes) { - std::string identity; - for (const Node* node : nodes) { - if (node == nullptr) { - continue; - } - if (!identity.empty()) { - identity += "-"; - } - identity += node->sourceId.source_label_text; + if (!identity.empty()) { + identity += "-"; } - return identity; + identity += node->source_id.source_label_text; + } + return identity; } -Result modelFailure( - std::string code, - const SourceLocation& location, - const std::string& identity, - std::string message) { - return Result::Failure(Status::Failure( - FailureCategory::kModel, - {{Severity::kError, - std::move(code), - location, - "*ELEMENT", - identity, - std::move(message)}})); +Result ModelFailure(std::string code, + const SourceLocation& location, + const std::string& identity, + std::string message) { + return Result::Failure(Status::Failure( + FailureCategory::kModel, {{Severity::kError, std::move(code), location, + "*ELEMENT", identity, std::move(message)}})); } -std::array, 3> covariantStrainColumn( +/// @brief Forms one physical covariant strain column with thickness stretch +/// excluded. +std::array, 3> CovariantStrainColumn( const std::array& covariant, const std::array& derivatives) { - std::array, 3> strain{}; - for (std::size_t first = 0U; first < 3U; ++first) { - for (std::size_t second = 0U; second < 3U; ++second) { - strain[first][second] = 0.5 * ( - dot(covariant[first], derivatives[second]) + - dot(covariant[second], derivatives[first])); - } + std::array, 3> strain{}; + for (std::size_t first = 0U; first < 3U; ++first) { + for (std::size_t second = 0U; second < 3U; ++second) { + strain[first][second] = + 0.5 * (Dot(covariant[first], derivatives[second]) + + Dot(covariant[second], derivatives[first])); } - // Thickness stretch is excluded from the five-component shell law. - strain[2U][2U] = 0.0; - return strain; + } + // Thickness stretch is excluded from the five-component shell law. + strain[2U][2U] = 0.0; + return strain; } -std::array, 3> reconstructCartesianStrain( - const std::array, 3>& covariantStrain, +/// @brief Reconstructs Cartesian strain in the approved reciprocal-basis +/// reduction order. +std::array, 3> ReconstructCartesianStrain( + const std::array, 3>& covariant_strain, const std::array& reciprocal) { - std::array, 3> tensor{}; - for (std::size_t row = 0U; row < 3U; ++row) { - for (std::size_t column = 0U; column < 3U; ++column) { - for (std::size_t first = 0U; first < 3U; ++first) { - for (std::size_t second = 0U; second < 3U; ++second) { - tensor[row][column] += - covariantStrain[first][second] * - reciprocal[first][row] * reciprocal[second][column]; - } - } + std::array, 3> tensor{}; + for (std::size_t row = 0U; row < 3U; ++row) { + for (std::size_t column = 0U; column < 3U; ++column) { + for (std::size_t first = 0U; first < 3U; ++first) { + for (std::size_t second = 0U; second < 3U; ++second) { + tensor[row][column] += covariant_strain[first][second] * + reciprocal[first][row] * + reciprocal[second][column]; } + } } - return tensor; + } + return tensor; } -double frameComponent( - const Vector3& left, - const std::array, 3>& tensor, - const Vector3& right) { - double value = 0.0; - for (std::size_t row = 0U; row < 3U; ++row) { - for (std::size_t column = 0U; column < 3U; ++column) { - value += left[row] * tensor[row][column] * right[column]; - } +double FrameComponent(const Vector3& left, + const std::array, 3>& tensor, + const Vector3& right) { + double value = 0.0; + for (std::size_t row = 0U; row < 3U; ++row) { + for (std::size_t column = 0U; column < 3U; ++column) { + value += left[row] * tensor[row][column] * right[column]; } - return value; + } + return value; } -std::array localEngineeringComponents( +/// @brief Projects tensor strain into signed local engineering components. +std::array LocalEngineeringComponents( const std::array, 3>& tensor, const Mitc4LocalFrame& frame) { - return { - frameComponent(frame.e1, tensor, frame.e1), - frameComponent(frame.e2, tensor, frame.e2), - 2.0 * frameComponent(frame.e1, tensor, frame.e2), - 2.0 * frameComponent(frame.e1, tensor, frame.e3), - 2.0 * frameComponent(frame.e2, tensor, frame.e3)}; + return {FrameComponent(frame.e1, tensor, frame.e1), + FrameComponent(frame.e2, tensor, frame.e2), + 2.0 * FrameComponent(frame.e1, tensor, frame.e2), + 2.0 * FrameComponent(frame.e1, tensor, frame.e3), + 2.0 * FrameComponent(frame.e2, tensor, frame.e3)}; } -Matrix scaledMatrix(const Matrix& source, double factor) { - Matrix result{source.Rows(), source.Columns()}; - for (std::size_t row = 0U; row < source.Rows(); ++row) { - for (std::size_t column = 0U; column < source.Columns(); ++column) { - result(row, column) = factor * source(row, column); +Matrix ScaledMatrix(const Matrix& source, double factor) { + Matrix result{source.Rows(), source.Columns()}; + for (std::size_t row = 0U; row < source.Rows(); ++row) { + for (std::size_t column = 0U; column < source.Columns(); ++column) { + result(row, column) = factor * source(row, column); + } + } + return result; +} + +/// @brief Lifts a local matrix by a deterministic serial congruence product. +/// @note The loop and accumulation order are part of reproducible stiffness. +Matrix Congruence(const Matrix& local, const Matrix& transformation) { + if (local.Rows() != local.Columns() || + local.Rows() != transformation.Rows()) { + throw std::invalid_argument{ + "MITC4 congruence dimensions are incompatible."}; + } + Matrix result{transformation.Columns(), transformation.Columns()}; + for (std::size_t row = 0U; row < result.Rows(); ++row) { + for (std::size_t column = row; column < result.Columns(); ++column) { + double value = 0.0; + for (std::size_t local_row = 0U; local_row < local.Rows(); ++local_row) { + for (std::size_t local_column = 0U; local_column < local.Columns(); + ++local_column) { + value += transformation(local_row, row) * + local(local_row, local_column) * + transformation(local_column, column); } + } + result(row, column) = value; + result(column, row) = value; } - return result; + } + return result; } -Matrix congruence(const Matrix& local, const Matrix& transformation) { - if (local.Rows() != local.Columns() || - local.Rows() != transformation.Rows()) { - throw std::invalid_argument{"MITC4 congruence dimensions are incompatible."}; +bool IsFinite(const Matrix& matrix) { + for (std::size_t row = 0U; row < matrix.Rows(); ++row) { + for (std::size_t column = 0U; column < matrix.Columns(); ++column) { + if (!std::isfinite(matrix(row, column))) { + return false; + } } - Matrix result{transformation.Columns(), transformation.Columns()}; - for (std::size_t row = 0U; row < result.Rows(); ++row) { - for (std::size_t column = row; column < result.Columns(); ++column) { - double value = 0.0; - for (std::size_t localRow = 0U; localRow < local.Rows(); ++localRow) { - for (std::size_t localColumn = 0U; - localColumn < local.Columns(); ++localColumn) { - value += transformation(localRow, row) * - local(localRow, localColumn) * - transformation(localColumn, column); - } - } - result(row, column) = value; - result(column, row) = value; - } + } + return true; +} + +bool IsFinite(const Vector& vector) { + for (std::size_t index = 0U; index < vector.Size(); ++index) { + if (!std::isfinite(vector[index])) { + return false; } - return result; + } + return true; } -bool isFinite(const Matrix& matrix) { - for (std::size_t row = 0U; row < matrix.Rows(); ++row) { - for (std::size_t column = 0U; column < matrix.Columns(); ++column) { - if (!std::isfinite(matrix(row, column))) { - return false; - } - } - } - return true; +Result StiffnessFailure(const SourceLocation& location, + const std::string& identity, + std::string message) { + return Result::Failure( + Status::Failure(FailureCategory::kModel, + {{Severity::kError, "invalid-shell-stiffness", location, + "*ELEMENT", identity, std::move(message)}})); } -bool isFinite(const Vector& vector) { - for (std::size_t index = 0U; index < vector.Size(); ++index) { - if (!std::isfinite(vector[index])) { - return false; - } - } - return true; +Result RecoveryFailure(const SourceLocation& location, + const std::string& identity, + std::string message) { + return Result::Failure( + Status::Failure(FailureCategory::kModel, + {{Severity::kError, "invalid-shell-recovery", location, + "*ELEMENT", identity, std::move(message)}})); } -Result stiffnessFailure( - const SourceLocation& location, - const std::string& identity, - std::string message) { - return Result::Failure(Status::Failure( - FailureCategory::kModel, - {{Severity::kError, - "invalid-shell-stiffness", - location, - "*ELEMENT", - identity, - std::move(message)}})); -} +} // namespace -Result recoveryFailure( - const SourceLocation& location, - const std::string& identity, - std::string message) { - return Result::Failure(Status::Failure( - FailureCategory::kModel, - {{Severity::kError, - "invalid-shell-recovery", - location, - "*ELEMENT", - identity, - std::move(message)}})); -} - -} // namespace - -Result Mitc4Shell::create( +Result Mitc4Shell::Create( std::array nodes, - std::array, 4> initialDirectors, - const ShellSection& section, - const LinearElasticMaterial& material) { - const std::string identity = elementIdentity(nodes); - if (std::any_of(nodes.begin(), nodes.end(), [](const Node* value) { - return value == nullptr; - })) { - return modelFailure( - "invalid-shell-geometry", section.location, identity, - "MITC4 creation requires four valid node references."); - } + std::array, 4> initial_directors, + const ShellSection& section, const LinearElasticMaterial& material) { + const std::string identity = ElementIdentity(nodes); + if (std::any_of(nodes.begin(), nodes.end(), + [](const Node* value) { return value == nullptr; })) { + return ModelFailure("invalid-shell-geometry", section.location, identity, + "MITC4 creation requires four valid node references."); + } - std::array coordinates{}; - for (std::size_t node = 0U; node < kNodeCount; ++node) { - coordinates[node] = nodes[node]->coordinates; - if (!isFinite(coordinates[node])) { - return modelFailure( - "invalid-shell-geometry", nodes[node]->location, identity, - "MITC4 node coordinates must be finite."); - } - for (std::size_t previous = 0U; previous < node; ++previous) { - if (sameCoordinates(coordinates[node], coordinates[previous])) { - return modelFailure( - "invalid-shell-geometry", nodes[node]->location, identity, - "MITC4 nodes must have distinct coordinates."); - } - } + std::array coordinates{}; + for (std::size_t node = 0U; node < kNodeCount; ++node) { + coordinates[node] = nodes[node]->coordinates; + if (!IsFinite(coordinates[node])) { + return ModelFailure("invalid-shell-geometry", nodes[node]->location, + identity, "MITC4 node coordinates must be finite."); } + for (std::size_t previous = 0U; previous < node; ++previous) { + if (SameCoordinates(coordinates[node], coordinates[previous])) { + return ModelFailure("invalid-shell-geometry", nodes[node]->location, + identity, + "MITC4 nodes must have distinct coordinates."); + } + } + } - for (std::size_t node = 0U; node < kNodeCount; ++node) { - const double directorNorm = norm(initialDirectors[node]); - if (!isFinite(initialDirectors[node]) || !std::isfinite(directorNorm) || - std::abs(directorNorm - 1.0) > kFrameTolerance) { - return modelFailure( - "invalid-shell-director", nodes[node]->location, identity, - "MITC4 initial directors must be finite unit vectors."); - } + for (std::size_t node = 0U; node < kNodeCount; ++node) { + const double director_norm = Norm(initial_directors[node]); + if (!IsFinite(initial_directors[node]) || !std::isfinite(director_norm) || + std::abs(director_norm - 1.0) > kFrameTolerance) { + return ModelFailure( + "invalid-shell-director", nodes[node]->location, identity, + "MITC4 initial directors must be finite unit vectors."); } + } - if (!std::isfinite(section.thickness) || !(section.thickness > 0.0)) { - return modelFailure( - "invalid-shell-section", section.location, identity, - "MITC4 shell thickness must be finite and positive."); - } - if (!std::isfinite(material.youngsModulus) || - !(material.youngsModulus > 0.0) || - !std::isfinite(material.poissonRatio) || - !(material.poissonRatio > -1.0) || !(material.poissonRatio < 0.5)) { - return modelFailure( - "invalid-shell-material", material.location, identity, - "MITC4 isotropic material requires finite E>0 and -1 0.0)) { + return ModelFailure("invalid-shell-section", section.location, identity, + "MITC4 shell thickness must be finite and positive."); + } + if (!std::isfinite(material.youngs_modulus) || + !(material.youngs_modulus > 0.0) || + !std::isfinite(material.poisson_ratio) || + !(material.poisson_ratio > -1.0) || !(material.poisson_ratio < 0.5)) { + return ModelFailure( + "invalid-shell-material", material.location, identity, + "MITC4 isotropic material requires finite E>0 and -1 0.0)) { - return modelFailure( - "invalid-shell-geometry", nodes[0]->location, identity, - "MITC4 center surface basis must be finite and nonzero."); - } - const Vector3 normalCandidate = scale(1.0 / centerMeasure, centerArea); - if (std::any_of( - initialDirectors.begin(), initialDirectors.end(), - [&normalCandidate](const Vector3& director) { - return !(dot(normalCandidate, director) > 0.0); - })) { - return modelFailure( - "invalid-shell-director", nodes[0]->location, identity, - "MITC4 directors must follow the source-order positive face."); - } + const auto center_shape = ShapeFunctions(0.0, 0.0); + const Vector3 center_xi = + DerivativeSum(center_shape.xi_derivatives, coordinates); + const Vector3 center_eta = + DerivativeSum(center_shape.eta_derivatives, coordinates); + const Vector3 center_area = Cross(center_xi, center_eta); + const double center_measure = Norm(center_area); + if (!IsFinite(center_area) || !std::isfinite(center_measure) || + !(center_measure > 0.0)) { + return ModelFailure( + "invalid-shell-geometry", nodes[0]->location, identity, + "MITC4 center surface basis must be finite and nonzero."); + } + const Vector3 normal_candidate = Scale(1.0 / center_measure, center_area); + if (std::any_of(initial_directors.begin(), initial_directors.end(), + [&normal_candidate](const Vector3& director) { + return !(Dot(normal_candidate, director) > 0.0); + })) { + return ModelFailure( + "invalid-shell-director", nodes[0]->location, identity, + "MITC4 directors must follow the source-order positive face."); + } - const auto tangentA = nodalTangentsA(initialDirectors); - const auto tangentB = nodalTangentsB(initialDirectors, tangentA); - Mitc4Shell shell{ - coordinates, - initialDirectors, - tangentA, - tangentB, - normalCandidate, - section.thickness, - material.youngsModulus, - material.poissonRatio, - nodes[0]->location, - identity}; + const auto tangent_a = NodalTangentsA(initial_directors); + const auto tangent_b = NodalTangentsB(initial_directors, tangent_a); + Mitc4Shell shell{coordinates, + initial_directors, + tangent_a, + tangent_b, + normal_candidate, + section.thickness, + material.youngs_modulus, + material.poisson_ratio, + nodes[0]->location, + identity}; - const double shearModulus = - material.youngsModulus / (2.0 * (1.0 + material.poissonRatio)); - const double planeStressFactor = material.youngsModulus / - (1.0 - material.poissonRatio * material.poissonRatio); - const double thicknessCubed = section.thickness * section.thickness * - section.thickness; - const std::array derived{ - shearModulus, - planeStressFactor, - kShearCorrection * shearModulus, - planeStressFactor * section.thickness, - planeStressFactor * thicknessCubed / 12.0, - kShearCorrection * shearModulus * section.thickness}; - if (std::any_of(derived.begin(), derived.end(), [](double value) { - return !std::isfinite(value) || !(value > 0.0); - })) { - return modelFailure( - "invalid-shell-material", material.location, identity, - "Derived MITC4 constitutive coefficients must be finite and positive."); - } + const double shear_modulus = + material.youngs_modulus / (2.0 * (1.0 + material.poisson_ratio)); + const double plane_stress_factor = + material.youngs_modulus / + (1.0 - material.poisson_ratio * material.poisson_ratio); + const double thickness_cubed = + section.thickness * section.thickness * section.thickness; + const std::array derived{ + shear_modulus, + plane_stress_factor, + kShearCorrection * shear_modulus, + plane_stress_factor * section.thickness, + plane_stress_factor * thickness_cubed / 12.0, + kShearCorrection * shear_modulus * section.thickness}; + if (std::any_of(derived.begin(), derived.end(), [](double value) { + return !std::isfinite(value) || !(value > 0.0); + })) { + return ModelFailure( + "invalid-shell-material", material.location, identity, + "Derived MITC4 constitutive coefficients must be finite and positive."); + } - GeometryData geometry{}; - if (!shell.evaluateGeometry(0.0, 0.0, 0.0, geometry)) { - return modelFailure( - "invalid-shell-jacobian", nodes[0]->location, identity, - "MITC4 center frame or Jacobian is invalid."); + GeometryData geometry{}; + if (!shell.EvaluateGeometry(0.0, 0.0, 0.0, geometry)) { + return ModelFailure("invalid-shell-jacobian", nodes[0]->location, identity, + "MITC4 center frame or Jacobian is invalid."); + } + for (const auto& point : VolumeQuadrature()) { + if (!shell.EvaluateGeometry(point.natural_coordinates[0], + point.natural_coordinates[1], + point.natural_coordinates[2], geometry)) { + return ModelFailure("invalid-shell-jacobian", nodes[0]->location, + identity, + "MITC4 quadrature frame or Jacobian is invalid."); } - for (const auto& point : volumeQuadrature()) { - if (!shell.evaluateGeometry( - point.naturalCoordinates[0], - point.naturalCoordinates[1], - point.naturalCoordinates[2], - geometry)) { - return modelFailure( - "invalid-shell-jacobian", nodes[0]->location, identity, - "MITC4 quadrature frame or Jacobian is invalid."); - } - } - constexpr std::array tyingPoints{ - Vector3{0.0, -1.0, 0.0}, - Vector3{0.0, 1.0, 0.0}, - Vector3{-1.0, 0.0, 0.0}, - Vector3{1.0, 0.0, 0.0}}; - for (const auto& point : tyingPoints) { - if (!shell.evaluateGeometry(point[0], point[1], point[2], geometry)) { - return modelFailure( - "invalid-shell-jacobian", nodes[0]->location, identity, - "MITC4 tying-point frame or Jacobian is invalid."); - } + } + constexpr std::array tying_points{ + Vector3{0.0, -1.0, 0.0}, Vector3{0.0, 1.0, 0.0}, Vector3{-1.0, 0.0, 0.0}, + Vector3{1.0, 0.0, 0.0}}; + for (const auto& point : tying_points) { + if (!shell.EvaluateGeometry(point[0], point[1], point[2], geometry)) { + return ModelFailure("invalid-shell-jacobian", nodes[0]->location, + identity, + "MITC4 tying-point frame or Jacobian is invalid."); } + } - return Result::Success(std::move(shell)); + return Result::Success(std::move(shell)); } -Mitc4ShapeFunctions Mitc4Shell::shapeFunctions( - double xi, - double eta) noexcept { - constexpr std::array xiSigns{-1.0, 1.0, 1.0, -1.0}; - constexpr std::array etaSigns{-1.0, -1.0, 1.0, 1.0}; - Mitc4ShapeFunctions shape{}; - for (std::size_t node = 0U; node < kNodeCount; ++node) { - shape.values[node] = 0.25 * (1.0 + xiSigns[node] * xi) * - (1.0 + etaSigns[node] * eta); - shape.xiDerivatives[node] = - 0.25 * xiSigns[node] * (1.0 + etaSigns[node] * eta); - shape.etaDerivatives[node] = - 0.25 * etaSigns[node] * (1.0 + xiSigns[node] * xi); - } - return shape; +Mitc4ShapeFunctions Mitc4Shell::ShapeFunctions(double xi, double eta) noexcept { + constexpr std::array xi_signs{-1.0, 1.0, 1.0, -1.0}; + constexpr std::array eta_signs{-1.0, -1.0, 1.0, 1.0}; + Mitc4ShapeFunctions shape{}; + for (std::size_t node = 0U; node < kNodeCount; ++node) { + shape.values[node] = + 0.25 * (1.0 + xi_signs[node] * xi) * (1.0 + eta_signs[node] * eta); + shape.xi_derivatives[node] = + 0.25 * xi_signs[node] * (1.0 + eta_signs[node] * eta); + shape.eta_derivatives[node] = + 0.25 * eta_signs[node] * (1.0 + xi_signs[node] * xi); + } + return shape; } -Mitc4TyingWeights Mitc4Shell::tyingWeights(double xi, double eta) noexcept { - return { - {(1.0 - eta) * 0.5, (1.0 + eta) * 0.5}, - {(1.0 - xi) * 0.5, (1.0 + xi) * 0.5}}; +Mitc4TyingWeights Mitc4Shell::TyingWeights(double xi, double eta) noexcept { + return {{(1.0 - eta) * 0.5, (1.0 + eta) * 0.5}, + {(1.0 - xi) * 0.5, (1.0 + xi) * 0.5}}; } const std::array& -Mitc4Shell::volumeQuadrature() noexcept { - static const std::array points = [] { - const double gauss = 1.0 / std::sqrt(3.0); - return std::array{ - Mitc4QuadraturePoint{{-gauss, -gauss, -gauss}, 1.0}, - {{-gauss, -gauss, gauss}, 1.0}, - {{gauss, -gauss, -gauss}, 1.0}, - {{gauss, -gauss, gauss}, 1.0}, - {{gauss, gauss, -gauss}, 1.0}, - {{gauss, gauss, gauss}, 1.0}, - {{-gauss, gauss, -gauss}, 1.0}, - {{-gauss, gauss, gauss}, 1.0}}; - }(); - return points; -} - -Mitc4LocalFrame Mitc4Shell::localFrame(double xi, double eta) const { - GeometryData geometry{}; - if (!evaluateGeometry(xi, eta, 0.0, geometry)) { - throw std::invalid_argument{"MITC4 local frame is invalid at the requested point."}; - } - return geometry.frame; -} - -Matrix Mitc4Shell::physicalTransformation20() const { - Matrix transformation{kPhysicalDofCount, kGlobalDofCount}; - for (std::size_t node = 0U; node < kNodeCount; ++node) { - const std::size_t physicalOffset = node * kPhysicalDofsPerNode; - const std::size_t globalOffset = node * kGlobalDofsPerNode; - for (std::size_t component = 0U; component < 3U; ++component) { - transformation(physicalOffset + component, globalOffset + component) = 1.0; - transformation(physicalOffset + 3U, globalOffset + 3U + component) = - tangentA_[node][component]; - transformation(physicalOffset + 4U, globalOffset + 3U + component) = - tangentB_[node][component]; - } - } - return transformation; -} - -Matrix Mitc4Shell::drillingTransformation4() const { - Matrix transformation{kNodeCount, kGlobalDofCount}; - for (std::size_t node = 0U; node < kNodeCount; ++node) { - const std::size_t globalOffset = node * kGlobalDofsPerNode; - for (std::size_t component = 0U; component < 3U; ++component) { - transformation(node, globalOffset + 3U + component) = - directors_[node][component]; - } - } - return transformation; -} - -Matrix Mitc4Shell::directStrainDisplacement20( - double xi, - double eta, - double zeta) const { - return strainDisplacement(xi, eta, zeta, nullptr); -} - -Matrix Mitc4Shell::covariantTyingShearSamples20() const { - constexpr std::array points{ - Vector3{0.0, -1.0, 0.0}, - Vector3{0.0, 1.0, 0.0}, - Vector3{-1.0, 0.0, 0.0}, - Vector3{1.0, 0.0, 0.0}}; - Matrix samples{4U, kPhysicalDofCount}; - for (std::size_t point = 0U; point < points.size(); ++point) { - GeometryData geometry{}; - if (!evaluateGeometry(points[point][0], points[point][1], 0.0, geometry)) { - throw std::logic_error{"Validated MITC4 tying geometry became invalid."}; - } - const auto derivatives = basisDerivatives( - points[point][0], points[point][1], 0.0); - const std::size_t first = point < 2U ? 0U : 1U; - for (std::size_t dof = 0U; dof < kPhysicalDofCount; ++dof) { - const auto strain = covariantStrainColumn( - geometry.covariant, derivatives[dof]); - samples(point, dof) = strain[first][2U]; - } - } - return samples; -} - -Matrix Mitc4Shell::strainDisplacement20( - double xi, - double eta, - double zeta) const { - const Matrix samples = covariantTyingShearSamples20(); - return strainDisplacement(xi, eta, zeta, &samples); -} - -Matrix Mitc4Shell::planeStressConstitutive() const { - const double factor = youngsModulus_ / - (1.0 - poissonRatio_ * poissonRatio_); - Matrix constitutive{3U, 3U}; - constitutive(0U, 0U) = factor; - constitutive(0U, 1U) = factor * poissonRatio_; - constitutive(1U, 0U) = factor * poissonRatio_; - constitutive(1U, 1U) = factor; - constitutive(2U, 2U) = factor * (1.0 - poissonRatio_) * 0.5; - return constitutive; -} - -Matrix Mitc4Shell::materialConstitutive5() const { - Matrix constitutive{5U, 5U}; - const Matrix planeStress = planeStressConstitutive(); - for (std::size_t row = 0U; row < 3U; ++row) { - for (std::size_t column = 0U; column < 3U; ++column) { - constitutive(row, column) = planeStress(row, column); - } - } - const double shearModulus = - youngsModulus_ / (2.0 * (1.0 + poissonRatio_)); - constitutive(3U, 3U) = kShearCorrection * shearModulus; - constitutive(4U, 4U) = kShearCorrection * shearModulus; - return constitutive; -} - -Matrix Mitc4Shell::membraneSectionMatrix() const { - return scaledMatrix(planeStressConstitutive(), thickness_); -} - -Matrix Mitc4Shell::bendingSectionMatrix() const { - return scaledMatrix( - planeStressConstitutive(), - thickness_ * thickness_ * thickness_ / 12.0); -} - -Matrix Mitc4Shell::transverseShearSectionMatrix() const { - const double shearModulus = - youngsModulus_ / (2.0 * (1.0 + poissonRatio_)); - Matrix result{2U, 2U}; - result(0U, 0U) = kShearCorrection * shearModulus * thickness_; - result(1U, 1U) = result(0U, 0U); - return result; -} - -Result Mitc4Shell::stiffness() const { - const Matrix constitutive = materialConstitutive5(); - const Matrix tyingSamples = covariantTyingShearSamples20(); - Matrix physicalLocal{kPhysicalDofCount, kPhysicalDofCount}; - for (const auto& point : volumeQuadrature()) { - GeometryData geometry{}; - if (!evaluateGeometry( - point.naturalCoordinates[0], - point.naturalCoordinates[1], - point.naturalCoordinates[2], - geometry)) { - return stiffnessFailure( - sourceLocation_, identity_, - "Validated MITC4 quadrature geometry became invalid."); - } - const Matrix strain = strainDisplacement( - point.naturalCoordinates[0], - point.naturalCoordinates[1], - point.naturalCoordinates[2], - &tyingSamples); - for (std::size_t row = 0U; row < kPhysicalDofCount; ++row) { - for (std::size_t column = row; - column < kPhysicalDofCount; ++column) { - double integrand = 0.0; - for (std::size_t first = 0U; first < 5U; ++first) { - for (std::size_t second = 0U; second < 5U; ++second) { - integrand += strain(first, row) * - constitutive(first, second) * - strain(second, column); - } - } - const double contribution = - integrand * geometry.jacobian * point.weight; - physicalLocal(row, column) += contribution; - if (row != column) { - physicalLocal(column, row) += contribution; - } - } - } - } - - double drillingReference = (std::numeric_limits::max)(); - bool hasDrillingReference = false; - for (std::size_t node = 0U; node < kNodeCount; ++node) { - const std::size_t offset = node * kPhysicalDofsPerNode; - for (std::size_t rotation = 3U; rotation < 5U; ++rotation) { - const double diagonal = physicalLocal(offset + rotation, offset + rotation); - if (std::isfinite(diagonal) && diagonal > 0.0) { - drillingReference = (std::min)(drillingReference, diagonal); - hasDrillingReference = true; - } - } - } - if (!hasDrillingReference) { - return stiffnessFailure( - sourceLocation_, identity_, - "MITC4 drilling stabilization requires a finite positive physical " - "tangent-rotation diagonal."); - } - if (!isFinite(physicalLocal)) { - return stiffnessFailure( - sourceLocation_, identity_, - "MITC4 physical stiffness must contain only finite values."); - } - - const double drillingStiffness = 1.0e-3 * drillingReference; - if (!std::isfinite(drillingStiffness) || !(drillingStiffness > 0.0)) { - return stiffnessFailure( - sourceLocation_, identity_, - "MITC4 drilling stiffness must be finite and positive."); - } - - Matrix physicalGlobal = congruence( - physicalLocal, physicalTransformation20()); - Matrix drillingLocal{kNodeCount, kNodeCount}; - for (std::size_t node = 0U; node < kNodeCount; ++node) { - drillingLocal(node, node) = drillingStiffness; - } - Matrix drillingGlobal = congruence( - drillingLocal, drillingTransformation4()); - Matrix stabilizedGlobal{kGlobalDofCount, kGlobalDofCount}; - for (std::size_t row = 0U; row < kGlobalDofCount; ++row) { - for (std::size_t column = 0U; column < kGlobalDofCount; ++column) { - stabilizedGlobal(row, column) = - physicalGlobal(row, column) + drillingGlobal(row, column); - } - } - if (!isFinite(physicalGlobal) || !isFinite(drillingGlobal) || - !isFinite(stabilizedGlobal)) { - return stiffnessFailure( - sourceLocation_, identity_, - "MITC4 transformed stiffness must contain only finite values."); - } - - return Result::Success(Mitc4Stiffness{ - std::move(physicalLocal), - std::move(physicalGlobal), - std::move(drillingGlobal), - std::move(stabilizedGlobal), - drillingStiffness}); -} - -Result Mitc4Shell::recoverPhysical( - const Vector& globalElementDisplacement24) const { - if (globalElementDisplacement24.Size() != kGlobalDofCount) { - return recoveryFailure( - sourceLocation_, identity_, - "MITC4 physical recovery requires exactly 24 global element DOFs."); - } - if (!isFinite(globalElementDisplacement24)) { - return recoveryFailure( - sourceLocation_, identity_, - "MITC4 physical recovery displacement must be finite."); - } - - const Vector physicalDisplacement = - physicalTransformation20().Multiply(globalElementDisplacement24); - const Matrix tyingSamples = covariantTyingShearSamples20(); - const Matrix constitutive = materialConstitutive5(); - const Matrix planeStress = planeStressConstitutive(); +Mitc4Shell::VolumeQuadrature() noexcept { + static const std::array points = [] { const double gauss = 1.0 / std::sqrt(3.0); - const std::array, 4> surfacePoints{ - std::array{-gauss, -gauss}, - std::array{gauss, -gauss}, - std::array{gauss, gauss}, - std::array{-gauss, gauss}}; - constexpr std::array thicknessPoints{-1.0, 1.0}; - constexpr std::array sectionPositions{-1.0, 0.0, 1.0}; - - Mitc4PhysicalRecovery recovery{}; - for (std::size_t surface = 0U; surface < surfacePoints.size(); ++surface) { - auto& point = recovery.points[surface]; - point.naturalCoordinates = surfacePoints[surface]; - GeometryData midsurfaceGeometry{}; - if (!evaluateGeometry( - point.naturalCoordinates[0], point.naturalCoordinates[1], 0.0, - midsurfaceGeometry)) { - return recoveryFailure( - sourceLocation_, identity_, - "MITC4 midsurface recovery geometry is invalid."); - } - point.localFrame = midsurfaceGeometry.frame; - - for (double thicknessSign : thicknessPoints) { - const double zeta = thicknessSign * gauss; - const Matrix strainMatrix = strainDisplacement( - point.naturalCoordinates[0], point.naturalCoordinates[1], zeta, - &tyingSamples); - const Vector strain = strainMatrix.Multiply(physicalDisplacement); - const Vector stress = constitutive.Multiply(strain); - GeometryData geometry{}; - if (!evaluateGeometry( - point.naturalCoordinates[0], point.naturalCoordinates[1], - zeta, geometry)) { - return recoveryFailure( - sourceLocation_, identity_, - "Validated MITC4 recovery geometry became invalid."); - } - - for (std::size_t component = 0U; component < 3U; ++component) { - point.generalizedStrain[component] += 0.5 * strain[component]; - point.generalizedStrain[3U + component] += - 3.0 * zeta * strain[component] / thickness_; - point.sectionResultant[component] += - 0.5 * thickness_ * stress[component]; - point.sectionResultant[3U + component] += - 0.25 * thickness_ * thickness_ * zeta * stress[component]; - } - for (std::size_t component = 0U; component < 2U; ++component) { - point.generalizedStrain[6U + component] += - 0.5 * strain[3U + component]; - point.sectionResultant[6U + component] += - 0.5 * thickness_ * stress[3U + component]; - } - recovery.strainEnergy += - 0.5 * strain.Dot(stress) * geometry.jacobian; - } - - for (std::size_t position = 0U; - position < sectionPositions.size(); ++position) { - GeometryData sectionGeometry{}; - if (!evaluateGeometry( - point.naturalCoordinates[0], point.naturalCoordinates[1], - sectionPositions[position], sectionGeometry)) { - return recoveryFailure( - sourceLocation_, identity_, - "MITC4 section-position recovery geometry is invalid."); - } - const Vector strain = strainDisplacement( - point.naturalCoordinates[0], point.naturalCoordinates[1], - sectionPositions[position], &tyingSamples) - .Multiply(physicalDisplacement); - Vector inPlaneStrain{3U}; - for (std::size_t component = 0U; component < 3U; ++component) { - inPlaneStrain[component] = strain[component]; - } - const Vector stress = planeStress.Multiply(inPlaneStrain); - for (std::size_t component = 0U; component < 3U; ++component) { - point.inPlaneStress[position][component] = stress[component]; - } - } - } - - if (!std::isfinite(recovery.strainEnergy)) { - return recoveryFailure( - sourceLocation_, identity_, - "MITC4 physical strain energy must be finite."); - } - for (const auto& point : recovery.points) { - const auto finite = [](const auto& values) { - return std::all_of(values.begin(), values.end(), [](double value) { - return std::isfinite(value); - }); - }; - if (!finite(point.generalizedStrain) || - !finite(point.sectionResultant) || - std::any_of( - point.inPlaneStress.begin(), point.inPlaneStress.end(), - [&finite](const auto& stress) { return !finite(stress); })) { - return recoveryFailure( - sourceLocation_, identity_, - "MITC4 physical recovery values must be finite."); - } - } - - return Result::Success(std::move(recovery)); + return std::array{ + Mitc4QuadraturePoint{{-gauss, -gauss, -gauss}, 1.0}, + {{-gauss, -gauss, gauss}, 1.0}, + {{gauss, -gauss, -gauss}, 1.0}, + {{gauss, -gauss, gauss}, 1.0}, + {{gauss, gauss, -gauss}, 1.0}, + {{gauss, gauss, gauss}, 1.0}, + {{-gauss, gauss, -gauss}, 1.0}, + {{-gauss, gauss, gauss}, 1.0}}; + }(); + return points; } -Mitc4Shell::Mitc4Shell( - std::array coordinates, - std::array directors, - std::array tangentA, - std::array tangentB, - Vector3 normalCandidate, - double thickness, - double youngsModulus, - double poissonRatio, - SourceLocation sourceLocation, - std::string identity) +Mitc4LocalFrame Mitc4Shell::LocalFrame(double xi, double eta) const { + GeometryData geometry{}; + if (!EvaluateGeometry(xi, eta, 0.0, geometry)) { + throw std::invalid_argument{ + "MITC4 local frame is invalid at the requested point."}; + } + return geometry.frame; +} + +Matrix Mitc4Shell::PhysicalTransformation20() const { + Matrix transformation{kPhysicalDofCount, kGlobalDofCount}; + for (std::size_t node = 0U; node < kNodeCount; ++node) { + const std::size_t physical_offset = node * kPhysicalDofsPerNode; + const std::size_t global_offset = node * kGlobalDofsPerNode; + for (std::size_t component = 0U; component < 3U; ++component) { + transformation(physical_offset + component, global_offset + component) = + 1.0; + transformation(physical_offset + 3U, global_offset + 3U + component) = + tangent_a_[node][component]; + transformation(physical_offset + 4U, global_offset + 3U + component) = + tangent_b_[node][component]; + } + } + return transformation; +} + +Matrix Mitc4Shell::DrillingTransformation4() const { + Matrix transformation{kNodeCount, kGlobalDofCount}; + for (std::size_t node = 0U; node < kNodeCount; ++node) { + const std::size_t global_offset = node * kGlobalDofsPerNode; + for (std::size_t component = 0U; component < 3U; ++component) { + transformation(node, global_offset + 3U + component) = + directors_[node][component]; + } + } + return transformation; +} + +Matrix Mitc4Shell::DirectStrainDisplacement20(double xi, double eta, + double zeta) const { + return StrainDisplacement(xi, eta, zeta, nullptr); +} + +Matrix Mitc4Shell::CovariantTyingShearSamples20() const { + constexpr std::array points{ + Vector3{0.0, -1.0, 0.0}, Vector3{0.0, 1.0, 0.0}, Vector3{-1.0, 0.0, 0.0}, + Vector3{1.0, 0.0, 0.0}}; + Matrix samples{4U, kPhysicalDofCount}; + for (std::size_t point = 0U; point < points.size(); ++point) { + GeometryData geometry{}; + if (!EvaluateGeometry(points[point][0], points[point][1], 0.0, geometry)) { + throw std::logic_error{"Validated MITC4 tying geometry became invalid."}; + } + const auto derivatives = + BasisDerivatives(points[point][0], points[point][1], 0.0); + const std::size_t first = point < 2U ? 0U : 1U; + for (std::size_t dof = 0U; dof < kPhysicalDofCount; ++dof) { + const auto strain = + CovariantStrainColumn(geometry.covariant, derivatives[dof]); + samples(point, dof) = strain[first][2U]; + } + } + return samples; +} + +Matrix Mitc4Shell::StrainDisplacement20(double xi, double eta, + double zeta) const { + const Matrix samples = CovariantTyingShearSamples20(); + return StrainDisplacement(xi, eta, zeta, &samples); +} + +Matrix Mitc4Shell::PlaneStressConstitutive() const { + const double factor = + youngs_modulus_ / (1.0 - poisson_ratio_ * poisson_ratio_); + Matrix constitutive{3U, 3U}; + constitutive(0U, 0U) = factor; + constitutive(0U, 1U) = factor * poisson_ratio_; + constitutive(1U, 0U) = factor * poisson_ratio_; + constitutive(1U, 1U) = factor; + constitutive(2U, 2U) = factor * (1.0 - poisson_ratio_) * 0.5; + return constitutive; +} + +Matrix Mitc4Shell::MaterialConstitutive5() const { + Matrix constitutive{5U, 5U}; + const Matrix plane_stress = PlaneStressConstitutive(); + for (std::size_t row = 0U; row < 3U; ++row) { + for (std::size_t column = 0U; column < 3U; ++column) { + constitutive(row, column) = plane_stress(row, column); + } + } + const double shear_modulus = youngs_modulus_ / (2.0 * (1.0 + poisson_ratio_)); + constitutive(3U, 3U) = kShearCorrection * shear_modulus; + constitutive(4U, 4U) = kShearCorrection * shear_modulus; + return constitutive; +} + +Matrix Mitc4Shell::MembraneSectionMatrix() const { + return ScaledMatrix(PlaneStressConstitutive(), thickness_); +} + +Matrix Mitc4Shell::BendingSectionMatrix() const { + return ScaledMatrix(PlaneStressConstitutive(), + thickness_ * thickness_ * thickness_ / 12.0); +} + +Matrix Mitc4Shell::TransverseShearSectionMatrix() const { + const double shear_modulus = youngs_modulus_ / (2.0 * (1.0 + poisson_ratio_)); + Matrix result{2U, 2U}; + result(0U, 0U) = kShearCorrection * shear_modulus * thickness_; + result(1U, 1U) = result(0U, 0U); + return result; +} + +Result Mitc4Shell::Stiffness() const { + const Matrix constitutive = MaterialConstitutive5(); + const Matrix tying_samples = CovariantTyingShearSamples20(); + Matrix physical_local{kPhysicalDofCount, kPhysicalDofCount}; + for (const auto& point : VolumeQuadrature()) { + GeometryData geometry{}; + if (!EvaluateGeometry(point.natural_coordinates[0], + point.natural_coordinates[1], + point.natural_coordinates[2], geometry)) { + return StiffnessFailure( + source_location_, identity_, + "Validated MITC4 quadrature geometry became invalid."); + } + const Matrix strain = StrainDisplacement( + point.natural_coordinates[0], point.natural_coordinates[1], + point.natural_coordinates[2], &tying_samples); + for (std::size_t row = 0U; row < kPhysicalDofCount; ++row) { + for (std::size_t column = row; column < kPhysicalDofCount; ++column) { + double integrand = 0.0; + for (std::size_t first = 0U; first < 5U; ++first) { + for (std::size_t second = 0U; second < 5U; ++second) { + integrand += strain(first, row) * constitutive(first, second) * + strain(second, column); + } + } + const double contribution = + integrand * geometry.jacobian * point.weight; + physical_local(row, column) += contribution; + if (row != column) { + physical_local(column, row) += contribution; + } + } + } + } + + double drilling_reference = (std::numeric_limits::max)(); + bool has_drilling_reference = false; + for (std::size_t node = 0U; node < kNodeCount; ++node) { + const std::size_t offset = node * kPhysicalDofsPerNode; + for (std::size_t rotation = 3U; rotation < 5U; ++rotation) { + const double diagonal = + physical_local(offset + rotation, offset + rotation); + if (std::isfinite(diagonal) && diagonal > 0.0) { + drilling_reference = (std::min)(drilling_reference, diagonal); + has_drilling_reference = true; + } + } + } + if (!has_drilling_reference) { + return StiffnessFailure( + source_location_, identity_, + "MITC4 drilling stabilization requires a finite positive physical " + "tangent-rotation diagonal."); + } + if (!IsFinite(physical_local)) { + return StiffnessFailure( + source_location_, identity_, + "MITC4 physical stiffness must contain only finite values."); + } + + const double drilling_stiffness = 1.0e-3 * drilling_reference; + if (!std::isfinite(drilling_stiffness) || !(drilling_stiffness > 0.0)) { + return StiffnessFailure( + source_location_, identity_, + "MITC4 drilling stiffness must be finite and positive."); + } + + Matrix physical_global = + Congruence(physical_local, PhysicalTransformation20()); + Matrix drilling_local{kNodeCount, kNodeCount}; + for (std::size_t node = 0U; node < kNodeCount; ++node) { + drilling_local(node, node) = drilling_stiffness; + } + Matrix drilling_global = + Congruence(drilling_local, DrillingTransformation4()); + Matrix stabilized_global{kGlobalDofCount, kGlobalDofCount}; + for (std::size_t row = 0U; row < kGlobalDofCount; ++row) { + for (std::size_t column = 0U; column < kGlobalDofCount; ++column) { + stabilized_global(row, column) = + physical_global(row, column) + drilling_global(row, column); + } + } + if (!IsFinite(physical_global) || !IsFinite(drilling_global) || + !IsFinite(stabilized_global)) { + return StiffnessFailure( + source_location_, identity_, + "MITC4 transformed stiffness must contain only finite values."); + } + + return Result::Success( + Mitc4Stiffness{std::move(physical_local), std::move(physical_global), + std::move(drilling_global), std::move(stabilized_global), + drilling_stiffness}); +} + +Result Mitc4Shell::RecoverPhysical( + const Vector& global_element_displacement24) const { + if (global_element_displacement24.Size() != kGlobalDofCount) { + return RecoveryFailure( + source_location_, identity_, + "MITC4 physical recovery requires exactly 24 global element DOFs."); + } + if (!IsFinite(global_element_displacement24)) { + return RecoveryFailure( + source_location_, identity_, + "MITC4 physical recovery displacement must be finite."); + } + + const Vector physical_displacement = + PhysicalTransformation20().Multiply(global_element_displacement24); + const Matrix tying_samples = CovariantTyingShearSamples20(); + const Matrix constitutive = MaterialConstitutive5(); + const Matrix plane_stress = PlaneStressConstitutive(); + const double gauss = 1.0 / std::sqrt(3.0); + const std::array, 4> surface_points{ + std::array{-gauss, -gauss}, + std::array{gauss, -gauss}, std::array{gauss, gauss}, + std::array{-gauss, gauss}}; + constexpr std::array thickness_points{-1.0, 1.0}; + constexpr std::array section_positions{-1.0, 0.0, 1.0}; + + Mitc4PhysicalRecovery recovery{}; + for (std::size_t surface = 0U; surface < surface_points.size(); ++surface) { + auto& point = recovery.points[surface]; + point.natural_coordinates = surface_points[surface]; + GeometryData midsurface_geometry{}; + if (!EvaluateGeometry(point.natural_coordinates[0], + point.natural_coordinates[1], 0.0, + midsurface_geometry)) { + return RecoveryFailure(source_location_, identity_, + "MITC4 midsurface recovery geometry is invalid."); + } + point.local_frame = midsurface_geometry.frame; + + for (double thickness_sign : thickness_points) { + const double zeta = thickness_sign * gauss; + const Matrix strain_matrix = StrainDisplacement( + point.natural_coordinates[0], point.natural_coordinates[1], zeta, + &tying_samples); + const Vector strain = strain_matrix.Multiply(physical_displacement); + const Vector stress = constitutive.Multiply(strain); + GeometryData geometry{}; + if (!EvaluateGeometry(point.natural_coordinates[0], + point.natural_coordinates[1], zeta, geometry)) { + return RecoveryFailure( + source_location_, identity_, + "Validated MITC4 recovery geometry became invalid."); + } + + for (std::size_t component = 0U; component < 3U; ++component) { + point.generalized_strain[component] += 0.5 * strain[component]; + point.generalized_strain[3U + component] += + 3.0 * zeta * strain[component] / thickness_; + point.section_resultant[component] += + 0.5 * thickness_ * stress[component]; + point.section_resultant[3U + component] += + 0.25 * thickness_ * thickness_ * zeta * stress[component]; + } + for (std::size_t component = 0U; component < 2U; ++component) { + point.generalized_strain[6U + component] += + 0.5 * strain[3U + component]; + point.section_resultant[6U + component] += + 0.5 * thickness_ * stress[3U + component]; + } + recovery.strain_energy += 0.5 * strain.Dot(stress) * geometry.jacobian; + } + + for (std::size_t position = 0U; position < section_positions.size(); + ++position) { + GeometryData section_geometry{}; + if (!EvaluateGeometry(point.natural_coordinates[0], + point.natural_coordinates[1], + section_positions[position], section_geometry)) { + return RecoveryFailure( + source_location_, identity_, + "MITC4 section-position recovery geometry is invalid."); + } + const Vector strain = + StrainDisplacement(point.natural_coordinates[0], + point.natural_coordinates[1], + section_positions[position], &tying_samples) + .Multiply(physical_displacement); + Vector in_plane_strain{3U}; + for (std::size_t component = 0U; component < 3U; ++component) { + in_plane_strain[component] = strain[component]; + } + const Vector stress = plane_stress.Multiply(in_plane_strain); + for (std::size_t component = 0U; component < 3U; ++component) { + point.in_plane_stress[position][component] = stress[component]; + } + } + } + + if (!std::isfinite(recovery.strain_energy)) { + return RecoveryFailure(source_location_, identity_, + "MITC4 physical strain energy must be finite."); + } + for (const auto& point : recovery.points) { + const auto finite = [](const auto& values) { + return std::all_of(values.begin(), values.end(), + [](double value) { return std::isfinite(value); }); + }; + if (!finite(point.generalized_strain) || !finite(point.section_resultant) || + std::any_of( + point.in_plane_stress.begin(), point.in_plane_stress.end(), + [&finite](const auto& stress) { return !finite(stress); })) { + return RecoveryFailure(source_location_, identity_, + "MITC4 physical recovery values must be finite."); + } + } + + return Result::Success(std::move(recovery)); +} + +Mitc4Shell::Mitc4Shell(std::array coordinates, + std::array directors, + std::array tangent_a, + std::array tangent_b, + Vector3 normal_candidate, double thickness, + double youngs_modulus, double poisson_ratio, + SourceLocation source_location, std::string identity) : coordinates_{std::move(coordinates)}, directors_{std::move(directors)}, - tangentA_{std::move(tangentA)}, - tangentB_{std::move(tangentB)}, - normalCandidate_{std::move(normalCandidate)}, + tangent_a_{std::move(tangent_a)}, + tangent_b_{std::move(tangent_b)}, + normal_candidate_{std::move(normal_candidate)}, thickness_{thickness}, - youngsModulus_{youngsModulus}, - poissonRatio_{poissonRatio}, - sourceLocation_{std::move(sourceLocation)}, + youngs_modulus_{youngs_modulus}, + poisson_ratio_{poisson_ratio}, + source_location_{std::move(source_location)}, identity_{std::move(identity)} {} -bool Mitc4Shell::evaluateGeometry( - double xi, - double eta, - double zeta, - GeometryData& result) const noexcept { - const auto shape = shapeFunctions(xi, eta); - const Vector3 midsurfaceXi = derivativeSum(shape.xiDerivatives, coordinates_); - const Vector3 midsurfaceEta = derivativeSum(shape.etaDerivatives, coordinates_); - const Vector3 directorXi = derivativeSum(shape.xiDerivatives, directors_); - const Vector3 directorEta = derivativeSum(shape.etaDerivatives, directors_); - const Vector3 directorValue = weightedSum(shape.values, directors_); - const double halfThickness = 0.5 * thickness_; +bool Mitc4Shell::EvaluateGeometry(double xi, double eta, double zeta, + GeometryData& result) const noexcept { + const auto shape = ShapeFunctions(xi, eta); + const Vector3 midsurface_xi = + DerivativeSum(shape.xi_derivatives, coordinates_); + const Vector3 midsurface_eta = + DerivativeSum(shape.eta_derivatives, coordinates_); + const Vector3 director_xi = DerivativeSum(shape.xi_derivatives, directors_); + const Vector3 director_eta = DerivativeSum(shape.eta_derivatives, directors_); + const Vector3 director_value = WeightedSum(shape.values, directors_); + const double half_thickness = 0.5 * thickness_; - result.covariant[0] = add( - midsurfaceXi, scale(halfThickness * zeta, directorXi)); - result.covariant[1] = add( - midsurfaceEta, scale(halfThickness * zeta, directorEta)); - result.covariant[2] = scale(halfThickness, directorValue); - result.jacobian = dot( - result.covariant[0], cross(result.covariant[1], result.covariant[2])); - if (!isFinite(result.covariant[0]) || !isFinite(result.covariant[1]) || - !isFinite(result.covariant[2]) || !std::isfinite(result.jacobian) || - !(result.jacobian > 0.0)) { - return false; - } + result.covariant[0] = + Add(midsurface_xi, Scale(half_thickness * zeta, director_xi)); + result.covariant[1] = + Add(midsurface_eta, Scale(half_thickness * zeta, director_eta)); + result.covariant[2] = Scale(half_thickness, director_value); + result.jacobian = + Dot(result.covariant[0], Cross(result.covariant[1], result.covariant[2])); + if (!IsFinite(result.covariant[0]) || !IsFinite(result.covariant[1]) || + !IsFinite(result.covariant[2]) || !std::isfinite(result.jacobian) || + !(result.jacobian > 0.0)) { + return false; + } - result.reciprocal[0] = scale( - 1.0 / result.jacobian, - cross(result.covariant[1], result.covariant[2])); - result.reciprocal[1] = scale( - 1.0 / result.jacobian, - cross(result.covariant[2], result.covariant[0])); - result.reciprocal[2] = scale( - 1.0 / result.jacobian, - cross(result.covariant[0], result.covariant[1])); + result.reciprocal[0] = Scale(1.0 / result.jacobian, + Cross(result.covariant[1], result.covariant[2])); + result.reciprocal[1] = Scale(1.0 / result.jacobian, + Cross(result.covariant[2], result.covariant[0])); + result.reciprocal[2] = Scale(1.0 / result.jacobian, + Cross(result.covariant[0], result.covariant[1])); - const Vector3 area = cross(midsurfaceXi, midsurfaceEta); - const double directorNorm = norm(directorValue); - if (!isFinite(area) || !isFinite(directorValue) || - !std::isfinite(directorNorm) || !(directorNorm > 0.0) || - !(dot(area, normalCandidate_) > 0.0)) { - return false; - } - result.frame.e3 = scale(1.0 / directorNorm, directorValue); - if (!(dot(area, result.frame.e3) > 0.0)) { - return false; - } - const Vector3 e1Candidate = subtract( - midsurfaceXi, - scale(dot(midsurfaceXi, result.frame.e3), result.frame.e3)); - const double e1Norm = norm(e1Candidate); - if (!isFinite(e1Candidate) || !std::isfinite(e1Norm) || !(e1Norm > 0.0)) { - return false; - } - result.frame.e1 = scale(1.0 / e1Norm, e1Candidate); - result.frame.e2 = cross(result.frame.e3, result.frame.e1); - return isFinite(result.reciprocal[0]) && isFinite(result.reciprocal[1]) && - isFinite(result.reciprocal[2]) && isFinite(result.frame.e2); + const Vector3 area = Cross(midsurface_xi, midsurface_eta); + const double director_norm = Norm(director_value); + if (!IsFinite(area) || !IsFinite(director_value) || + !std::isfinite(director_norm) || !(director_norm > 0.0) || + !(Dot(area, normal_candidate_) > 0.0)) { + return false; + } + result.frame.e3 = Scale(1.0 / director_norm, director_value); + if (!(Dot(area, result.frame.e3) > 0.0)) { + return false; + } + const Vector3 e1_candidate = + Subtract(midsurface_xi, + Scale(Dot(midsurface_xi, result.frame.e3), result.frame.e3)); + const double e1_norm = Norm(e1_candidate); + if (!IsFinite(e1_candidate) || !std::isfinite(e1_norm) || !(e1_norm > 0.0)) { + return false; + } + result.frame.e1 = Scale(1.0 / e1_norm, e1_candidate); + result.frame.e2 = Cross(result.frame.e3, result.frame.e1); + return IsFinite(result.reciprocal[0]) && IsFinite(result.reciprocal[1]) && + IsFinite(result.reciprocal[2]) && IsFinite(result.frame.e2); } -std::array, 20> Mitc4Shell::basisDerivatives( - double xi, - double eta, - double zeta) const noexcept { - const auto shape = shapeFunctions(xi, eta); - const double halfThickness = 0.5 * thickness_; - std::array, kPhysicalDofCount> derivatives{}; - constexpr std::array globalAxes{ - Vector3{1.0, 0.0, 0.0}, - Vector3{0.0, 1.0, 0.0}, - Vector3{0.0, 0.0, 1.0}}; +std::array, 20> Mitc4Shell::BasisDerivatives( + double xi, double eta, double zeta) const noexcept { + const auto shape = ShapeFunctions(xi, eta); + const double half_thickness = 0.5 * thickness_; + std::array, kPhysicalDofCount> derivatives{}; + constexpr std::array global_axes{ + Vector3{1.0, 0.0, 0.0}, Vector3{0.0, 1.0, 0.0}, Vector3{0.0, 0.0, 1.0}}; - for (std::size_t node = 0U; node < kNodeCount; ++node) { - const std::size_t offset = node * kPhysicalDofsPerNode; - for (std::size_t component = 0U; component < 3U; ++component) { - derivatives[offset + component][0] = - scale(shape.xiDerivatives[node], globalAxes[component]); - derivatives[offset + component][1] = - scale(shape.etaDerivatives[node], globalAxes[component]); - } - - const Vector3 alphaDirection = scale(-halfThickness, tangentB_[node]); - derivatives[offset + 3U][0] = - scale(zeta * shape.xiDerivatives[node], alphaDirection); - derivatives[offset + 3U][1] = - scale(zeta * shape.etaDerivatives[node], alphaDirection); - derivatives[offset + 3U][2] = - scale(shape.values[node], alphaDirection); - - const Vector3 betaDirection = scale(halfThickness, tangentA_[node]); - derivatives[offset + 4U][0] = - scale(zeta * shape.xiDerivatives[node], betaDirection); - derivatives[offset + 4U][1] = - scale(zeta * shape.etaDerivatives[node], betaDirection); - derivatives[offset + 4U][2] = - scale(shape.values[node], betaDirection); + for (std::size_t node = 0U; node < kNodeCount; ++node) { + const std::size_t offset = node * kPhysicalDofsPerNode; + for (std::size_t component = 0U; component < 3U; ++component) { + derivatives[offset + component][0] = + Scale(shape.xi_derivatives[node], global_axes[component]); + derivatives[offset + component][1] = + Scale(shape.eta_derivatives[node], global_axes[component]); } - return derivatives; + + const Vector3 alpha_direction = Scale(-half_thickness, tangent_b_[node]); + derivatives[offset + 3U][0] = + Scale(zeta * shape.xi_derivatives[node], alpha_direction); + derivatives[offset + 3U][1] = + Scale(zeta * shape.eta_derivatives[node], alpha_direction); + derivatives[offset + 3U][2] = Scale(shape.values[node], alpha_direction); + + const Vector3 beta_direction = Scale(half_thickness, tangent_a_[node]); + derivatives[offset + 4U][0] = + Scale(zeta * shape.xi_derivatives[node], beta_direction); + derivatives[offset + 4U][1] = + Scale(zeta * shape.eta_derivatives[node], beta_direction); + derivatives[offset + 4U][2] = Scale(shape.values[node], beta_direction); + } + return derivatives; } -Matrix Mitc4Shell::strainDisplacement( - double xi, - double eta, - double zeta, - const Matrix* tyingSamples) const { - GeometryData geometry{}; - if (!evaluateGeometry(xi, eta, zeta, geometry)) { - throw std::invalid_argument{"MITC4 strain geometry is invalid at the requested point."}; +Matrix Mitc4Shell::StrainDisplacement(double xi, double eta, double zeta, + const Matrix* tying_samples) const { + GeometryData geometry{}; + if (!EvaluateGeometry(xi, eta, zeta, geometry)) { + throw std::invalid_argument{ + "MITC4 strain geometry is invalid at the requested point."}; + } + const auto derivatives = BasisDerivatives(xi, eta, zeta); + const Mitc4TyingWeights weights = TyingWeights(xi, eta); + Matrix result{5U, kPhysicalDofCount}; + for (std::size_t dof = 0U; dof < kPhysicalDofCount; ++dof) { + auto covariant = + CovariantStrainColumn(geometry.covariant, derivatives[dof]); + if (tying_samples != nullptr) { + covariant[0U][2U] = weights.xi_zeta[0] * (*tying_samples)(0U, dof) + + weights.xi_zeta[1] * (*tying_samples)(1U, dof); + covariant[2U][0U] = covariant[0U][2U]; + covariant[1U][2U] = weights.eta_zeta[0] * (*tying_samples)(2U, dof) + + weights.eta_zeta[1] * (*tying_samples)(3U, dof); + covariant[2U][1U] = covariant[1U][2U]; } - const auto derivatives = basisDerivatives(xi, eta, zeta); - const Mitc4TyingWeights weights = tyingWeights(xi, eta); - Matrix result{5U, kPhysicalDofCount}; - for (std::size_t dof = 0U; dof < kPhysicalDofCount; ++dof) { - auto covariant = covariantStrainColumn( - geometry.covariant, derivatives[dof]); - if (tyingSamples != nullptr) { - covariant[0U][2U] = - weights.xiZeta[0] * (*tyingSamples)(0U, dof) + - weights.xiZeta[1] * (*tyingSamples)(1U, dof); - covariant[2U][0U] = covariant[0U][2U]; - covariant[1U][2U] = - weights.etaZeta[0] * (*tyingSamples)(2U, dof) + - weights.etaZeta[1] * (*tyingSamples)(3U, dof); - covariant[2U][1U] = covariant[1U][2U]; - } - const auto engineering = localEngineeringComponents( - reconstructCartesianStrain(covariant, geometry.reciprocal), - geometry.frame); - for (std::size_t component = 0U; component < engineering.size(); ++component) { - result(component, dof) = engineering[component]; - } + const auto engineering = LocalEngineeringComponents( + ReconstructCartesianStrain(covariant, geometry.reciprocal), + geometry.frame); + for (std::size_t component = 0U; component < engineering.size(); + ++component) { + result(component, dof) = engineering[component]; } - return result; + } + return result; } -} // namespace fesa +} // namespace fesa diff --git a/src/fesa/fem/dof_manager.cpp b/src/fesa/fem/dof_manager.cpp index 4a54a28..68a8814 100644 --- a/src/fesa/fem/dof_manager.cpp +++ b/src/fesa/fem/dof_manager.cpp @@ -37,16 +37,16 @@ bool tryPositiveInteger(const std::string& text, std::int64_t& value) { std::vector 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(node)}; } } @@ -96,15 +96,15 @@ SparsePattern buildSparsePattern( Result 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> 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(node) * dofsPerNode + @@ -150,12 +150,12 @@ Result DofManager::create(const AnalysisModel& model) { } std::vector> 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::create(const AnalysisModel& model) { } std::vector> 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) { diff --git a/src/fesa/io/abaqus/domain_mapper.cpp b/src/fesa/io/abaqus/domain_mapper.cpp index 54ed37b..b29b632 100644 --- a/src/fesa/io/abaqus/domain_mapper.cpp +++ b/src/fesa/io/abaqus/domain_mapper.cpp @@ -1,6 +1,6 @@ #include "fesa/io/abaqus/domain_mapper.hpp" -#include "fesa/model/shell_geometry.hpp" +#include "fesa/model/shell_geometry.h" #include #include @@ -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(definition_.shellSections.size()); - definition_.shellSections.push_back({ + static_cast(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 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::max()) { inputFailure( "entity-index-overflow", rawElement.location, @@ -2208,19 +2208,19 @@ private: return; } index = - static_cast(definition_.shellElements.size()); - definition_.shellElements.push_back({ + static_cast(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 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(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() && diff --git a/src/fesa/io/hdf5/hdf5_results_writer.cpp b/src/fesa/io/hdf5/hdf5_results_writer.cpp index 47b0c3d..ceebfec 100644 --- a/src/fesa/io/hdf5/hdf5_results_writer.cpp +++ b/src/fesa/io/hdf5/hdf5_results_writer.cpp @@ -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 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 sortedNodes = element.nodeIndices; + std::array 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, 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(rowIndex / kEndpointCount); const int expectedEndpoint = static_cast(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(expectedEndpoint)]]; + domain.Nodes()[element.node_indices[static_cast(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 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(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& axes) { std::vector 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(index), - element.sourceId.instance_name.c_str(), - element.sourceId.source_label_text.c_str(), - {static_cast(element.nodeIndices[0U]), - static_cast(element.nodeIndices[1U])}, + element.source_id.instance_name.c_str(), + element.source_id.source_label_text.c_str(), + {static_cast(element.node_indices[0U]), + static_cast(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 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(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(element.nodeIndices[0U]), - static_cast(element.nodeIndices[1U]), - static_cast(element.nodeIndices[2U]), - static_cast(element.nodeIndices[3U])}, - static_cast(element.sectionIndex), - static_cast(element.materialIndex)}); + {static_cast(element.node_indices[0U]), + static_cast(element.node_indices[1U]), + static_cast(element.node_indices[2U]), + static_cast(element.node_indices[3U])}, + static_cast(element.section_index), + static_cast(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 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(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 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 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(index), sourceFiles[index].c_str(), static_cast(section.location.line), section.name.c_str(), - static_cast(section.materialIndex), + static_cast(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 directors; - directors.reserve(domain.nodes().size() * 3U); + directors.reserve(domain.Nodes().size() * 3U); std::vector 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(domain.nodes().size()), 3U}, + {static_cast(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(domain.nodes().size()), 3U, 3U}, + {static_cast(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 nodalDimensions{ - static_cast(domain.nodes().size()), kDofsPerNode}; + static_cast(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(domain.shellElements().size()); + static_cast(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 nodalDimensions = { - static_cast(domain.nodes().size()), kDofsPerNode}; + static_cast(domain.Nodes().size()), kDofsPerNode}; writeDoubleDataset( file, std::string{kStepRoot} + "/nodal/displacement", @@ -1758,11 +1758,11 @@ void writeResultDatasets( } const std::vector endpointActionDimensions = { - static_cast(domain.elements().size()), + static_cast(domain.Elements().size()), kEndpointCount, kEndActionComponentCount}; const std::vector generalizedDimensions = { - static_cast(domain.elements().size()), + static_cast(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(domain.nodes().size()), + static_cast(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 nodalDimensions = { - static_cast(domain.nodes().size()), kDofsPerNode}; + static_cast(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(domain.shellElements().size()), + static_cast(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(domain.materials().size()), + static_cast(domain.Materials().size()), {"internal_material_id", "name", "E", "nu"}); requireCompoundDataset( file.get(), "/model/shell/sections", - static_cast(domain.shellSections().size()), + static_cast(domain.ShellSections().size()), {"internal_section_id", "source_file", "source_line", "source_elset", "material_internal_id", "thickness"}); std::vector directors; std::vector 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(domain.nodes().size()), 3U}, + {static_cast(domain.Nodes().size()), 3U}, "D1,D2,D3", "1,1,1", "global-cartesian", "nodal", &directors); requireModelDoubleDataset( file.get(), "/model/shell/nodal_frame", - {static_cast(domain.nodes().size()), 3U, 3U}, + {static_cast(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(domain.shellElements().size()); + static_cast(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(domain.elements().size()), + static_cast(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 endDimensions = { - static_cast(domain.elements().size()), + static_cast(domain.Elements().size()), kEndpointCount, kEndActionComponentCount}; const std::vector generalizedDimensions = { - static_cast(domain.elements().size()), + static_cast(domain.Elements().size()), kGaussPointCount, kGeneralizedComponentCount}; requireDoubleDataset( diff --git a/src/fesa/model/domain.cpp b/src/fesa/model/domain.cpp index d1a2ae1..ea86b15 100644 --- a/src/fesa/model/domain.cpp +++ b/src/fesa/model/domain.cpp @@ -1,66 +1,68 @@ -#include "fesa/model/domain.hpp" +#include "fesa/model/domain.h" #include namespace fesa { -Result Domain::create(ModelDefinition definition) { - return Result::Success(Domain{std::move(definition)}); +Result Domain::Create(ModelDefinition definition) { + return Result::Success(Domain{std::move(definition)}); } -const std::vector& Domain::nodes() const noexcept { - return definition_.nodes; +const std::vector& Domain::Nodes() const noexcept { + return definition_.nodes; } -const std::vector& Domain::elements() const noexcept { - return definition_.elements; +const std::vector& Domain::Elements() const noexcept { + return definition_.elements; } -const std::vector& Domain::shellElements() const noexcept { - return definition_.shellElements; +const std::vector& Domain::ShellElements() + const noexcept { + return definition_.shell_elements; } -const std::vector& Domain::materials() const noexcept { - return definition_.materials; +const std::vector& Domain::Materials() const noexcept { + return definition_.materials; } -const std::vector& Domain::sections() const noexcept { - return definition_.sections; +const std::vector& Domain::Sections() const noexcept { + return definition_.sections; } -const std::vector& Domain::shellSections() const noexcept { - return definition_.shellSections; +const std::vector& Domain::ShellSections() const noexcept { + return definition_.shell_sections; } -const std::vector& Domain::shellNodeInitialFrames() const noexcept { - return definition_.shellNodeInitialFrames; +const std::vector& Domain::ShellNodeInitialFrames() + const noexcept { + return definition_.shell_node_initial_frames; } -const std::vector& Domain::nodeSets() const noexcept { - return definition_.nodeSets; +const std::vector& Domain::NodeSets() const noexcept { + return definition_.node_sets; } -const std::vector& Domain::elementSets() const noexcept { - return definition_.elementSets; +const std::vector& Domain::ElementSets() const noexcept { + return definition_.element_sets; } -const std::vector& Domain::steps() const noexcept { - return definition_.steps; +const std::vector& Domain::Steps() const noexcept { + return definition_.steps; } -const std::vector& Domain::warnings() const noexcept { - return definition_.warnings; +const std::vector& 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 diff --git a/src/fesa/model/shell_geometry.cpp b/src/fesa/model/shell_geometry.cpp index b70b357..1277b41 100644 --- a/src/fesa/model/shell_geometry.cpp +++ b/src/fesa/model/shell_geometry.cpp @@ -1,4 +1,4 @@ -#include "fesa/model/shell_geometry.hpp" +#include "fesa/model/shell_geometry.h" #include #include @@ -14,478 +14,444 @@ constexpr std::array kXiSigns{-1.0, 1.0, 1.0, -1.0}; constexpr std::array kEtaSigns{-1.0, -1.0, 1.0, 1.0}; struct ShapeData { - std::array values; - std::array xiDerivatives; - std::array etaDerivatives; + std::array values; + std::array xi_derivatives; + std::array eta_derivatives; }; struct ElementWork { - std::array coordinates; - Vector3 normal; - double areaWeight; + std::array 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& weights, - const std::array& 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& weights, + const std::array& 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& derivatives, - const std::array& coordinates) { - // Shape derivatives sum to zero, so translating by node 1 improves the - // numerical cancellation without changing the covariant tangent. - std::array 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& derivatives, + const std::array& coordinates) { + // Shape derivatives sum to zero, so translating by node 1 improves the + // numerical cancellation without changing the covariant tangent. + std::array relative{}; + for (std::size_t node = 0U; node < coordinates.size(); ++node) { + relative[node] = Subtract(coordinates[node], coordinates[0]); + } + return WeightedSum(derivatives, relative); } -Result geometryFailure( - std::string code, - const SourceLocation& location, - std::string keyword, - std::string identity, - std::string message) { - return Result::Failure(Status::Failure( - FailureCategory::kModel, - {{Severity::kError, - std::move(code), - location, - std::move(keyword), - std::move(identity), - std::move(message)}})); +Result GeometryFailure(std::string code, + const SourceLocation& location, + std::string keyword, std::string identity, + std::string message) { + return Result::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& -shellGeometryValidationPoints() noexcept { - static const std::array points = [] { - const double g = 1.0 / std::sqrt(3.0); - return std::array{ - 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 points = [] { + const double g = 1.0 / std::sqrt(3.0); + return std::array{ + 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 preprocessShellGeometry( +Result PreprocessShellGeometry( const std::vector& nodes, const std::vector& elements, const std::vector& sections) { - ShellGeometry geometry; - geometry.elementData.reserve(elements.size()); - std::vector work; - work.reserve(elements.size()); + ShellGeometry geometry; + geometry.element_data.reserve(elements.size()); + std::vector work; + work.reserve(elements.size()); - const double g = 1.0 / std::sqrt(3.0); - const std::array 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 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(elementIndex), current.normal, areaWeight}); + } } - std::vector> 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 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(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> 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 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 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(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 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(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 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 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::Success(std::move(geometry)); + return Result::Success(std::move(geometry)); } -} // namespace fesa +} // namespace fesa diff --git a/src/fesa/results/result_recovery.cpp b/src/fesa/results/result_recovery.cpp index 424340e..67d92c5 100644 --- a/src/fesa/results/result_recovery.cpp +++ b/src/fesa/results/result_recovery.cpp @@ -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 #include @@ -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::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 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(definition.nodeIndices[endpoint]) * + static_cast(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::numeric_limits::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(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> resolveLoadTarget(const Domain& domain, const NodalLoad& load) { std::vector 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> resolveLoadTarget(const Domain& domain, std::vector 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(node)); } } @@ -404,9 +404,9 @@ Result> resolveLoadTarget(const Domain& domain, "A station-eligibility load target must resolve unambiguously."); } if (!sets.empty()) { - std::vector seen(domain.nodes().size(), 0U); - for (const EntityIndex node : sets.front()->nodeIndices) { - if (node >= domain.nodes().size() || seen[node] != 0U) { + std::vector seen(domain.Nodes().size(), 0U); + for (const EntityIndex node : sets.front()->node_indices) { + if (node >= domain.Nodes().size() || seen[node] != 0U) { return recoveryResultFailure>( "invalid-node-station-entity", load.location, @@ -416,7 +416,7 @@ Result> resolveLoadTarget(const Domain& domain, seen[node] = 1U; } return Result>::Success( - sets.front()->nodeIndices); + sets.front()->node_indices); } if (!nodes.empty()) { return Result>::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 nodalAppliedForce{}; std::array 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 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 delta = { second[0U] - first[0U], second[1U] - first[1U], @@ -598,7 +598,7 @@ std::optional localAxes(const Domain& domain, } const std::array 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 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(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(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 expectedShellElements; - if (!domain.shellElements().empty()) { - if (domain.shellElements().size() > + if (!domain.ShellElements().empty()) { + if (domain.ShellElements().size() > (std::numeric_limits::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>> 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 locations{ ShellMidsurfaceLocation::gp1, @@ -829,33 +829,33 @@ Status ResultRecovery::recover(const AnalysisModel& model, ShellSectionPosition::top}; constexpr std::array 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(elementOrder); - const auto& definition = domain.shellElements()[elementOrder]; + const auto& definition = domain.ShellElements()[elementOrder]; std::array nodes{}; std::array, 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>( "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>( "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> 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>( "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(endpoint) || - !sameSourceIdentity(row.node, domain.nodes()[nodeIndex].sourceId)) { + !sameSourceIdentity(row.node, domain.Nodes()[nodeIndex].source_id)) { return recoveryResultFailure>( "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>( "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 loadedNodes(domain.nodes().size(), 0U); + std::vector loadedNodes(domain.Nodes().size(), 0U); if (model.activeLoads().size() != model.step().loads.size()) { return recoveryResultFailure>( "invalid-node-station-entity", @@ -1042,7 +1042,7 @@ ResultRecovery::normalizeSectionResultantsToNodeStations( } std::vector 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>( "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>( "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>( "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>( "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}); } diff --git a/tests/integration/analysis/linear_static_analysis_test.cpp b/tests/integration/analysis/linear_static_analysis_test.cpp index 06d9431..22ec87a 100644 --- a/tests/integration/analysis/linear_static_analysis_test.cpp +++ b/tests/integration/analysis/linear_static_analysis_test.cpp @@ -307,8 +307,8 @@ public: const fesa::AnalysisState& state, const std::vector& diagnostics) override { outputPath_ = outputPath; - nodeCount_ = domain.nodes().size(); - shellElementCount_ = domain.shellElements().size(); + nodeCount_ = domain.Nodes().size(); + shellElementCount_ = domain.ShellElements().size(); state_ = std::make_unique(state); diagnostics_ = diagnostics; return fesa::Status::Ok(); diff --git a/tests/reference/reference_comparison.cpp b/tests/reference/reference_comparison.cpp index 8dc6379..e37e444 100644 --- a/tests/reference/reference_comparison.cpp +++ b/tests/reference/reference_comparison.cpp @@ -785,9 +785,9 @@ void requireFiniteStress(const hid_t file) { std::array 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 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 normalizeStations( endpoints.push_back({ static_cast(element), static_cast(endpoint), - domain.nodes()[node].sourceId, + domain.Nodes()[node].source_id, {}, values}); } @@ -1192,8 +1192,8 @@ PhysicsEvidence makePhysicsEvidence( } evidence.freeResidualNorm = std::sqrt(static_cast(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 applied = { load[node * 6U + 0U], load[node * 6U + 1U], diff --git a/tests/reference/reference_comparison_test.cpp b/tests/reference/reference_comparison_test.cpp index 9361eb1..252a335 100644 --- a/tests/reference/reference_comparison_test.cpp +++ b/tests/reference/reference_comparison_test.cpp @@ -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 @@ -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(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(element), static_cast(endpoint), - domain.nodes()[node].sourceId, + domain.Nodes()[node].source_id, {}, values.sectionResultants[element][endpoint]}); } diff --git a/tests/unit/analysis/analysis_model_test.cpp b/tests/unit/analysis/analysis_model_test.cpp index 8145cde..54c4d52 100644 --- a/tests/unit/analysis/analysis_model_test.cpp +++ b/tests/unit/analysis/analysis_model_test.cpp @@ -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()); diff --git a/tests/unit/analysis/analysis_state_test.cpp b/tests/unit/analysis/analysis_state_test.cpp index 3cf1d40..b338179 100644 --- a/tests/unit/analysis/analysis_state_test.cpp +++ b/tests/unit/analysis/analysis_state_test.cpp @@ -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()); diff --git a/tests/unit/assembly/load_assembler_test.cpp b/tests/unit/assembly/load_assembler_test.cpp index f88da39..0e86edd 100644 --- a/tests/unit/assembly/load_assembler_test.cpp +++ b/tests/unit/assembly/load_assembler_test.cpp @@ -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 @@ -33,8 +33,8 @@ LoadFixture makeFixture( std::vector 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((index + 1U) * 10U); definition.nodes.push_back({ @@ -42,7 +42,7 @@ LoadFixture makeFixture( {static_cast(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 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(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."}; } diff --git a/tests/unit/assembly/sparse_assembler_test.cpp b/tests/unit/assembly/sparse_assembler_test.cpp index ad79de4..b21cb3c 100644 --- a/tests/unit/assembly/sparse_assembler_test.cpp +++ b/tests/unit/assembly/sparse_assembler_test.cpp @@ -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 @@ -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(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(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 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 nodes{}; std::array, 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::Failure(shell.GetStatus()); } - return shell.Value().stiffness(); + return shell.Value().Stiffness(); } fesa::Result 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::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( diff --git a/tests/unit/constraints/essential_constraints_test.cpp b/tests/unit/constraints/essential_constraints_test.cpp index 4730aa6..d7fcb8f 100644 --- a/tests/unit/constraints/essential_constraints_test.cpp +++ b/tests/unit/constraints/essential_constraints_test.cpp @@ -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 @@ -19,8 +19,8 @@ fesa::DofManager makeDofs( std::vector 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 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()); diff --git a/tests/unit/elements/euler_beam_3d_test.cpp b/tests/unit/elements/euler_beam_3d_test.cpp index d827a6a..65eb965 100644 --- a/tests/unit/elements/euler_beam_3d_test.cpp +++ b/tests/unit/elements/euler_beam_3d_test.cpp @@ -1,4 +1,4 @@ -#include "fesa/elements/euler_beam_3d.hpp" +#include "fesa/elements/euler_beam_3d.h" #include @@ -20,1160 +20,1083 @@ constexpr double kMatrixTolerance = 1.0e-12; constexpr double kRigidTolerance = 1.0e-10; constexpr double kAnalyticalTolerance = 1.0e-9; -Node makeNode(std::array coordinates, std::size_t line) { - return {{"Beam-1", static_cast(line), std::to_string(line)}, - coordinates, - {"beam-test.inp", line}}; +Node MakeNode(std::array coordinates, std::size_t line) { + return {{"Beam-1", static_cast(line), std::to_string(line)}, + coordinates, + {"beam-test.inp", line}}; } -LinearElasticMaterial makeMaterial(double youngsModulus = 210.0e9, - double poissonRatio = 0.3) { - return {"Steel", youngsModulus, poissonRatio, {"beam-test.inp", 20U}}; +LinearElasticMaterial MakeMaterial(double youngs_modulus = 210.0e9, + double poisson_ratio = 0.3) { + return {"Steel", youngs_modulus, poisson_ratio, {"beam-test.inp", 20U}}; } -GeneralBeamSection makeSection( - std::array firstAxis = {0.0, 1.0, 0.0}, - std::vector> sectionPoints = {}) { - return {"Section-1", - 0.012, - 2.5e-5, - 0.0, - 4.0e-5, - 1.5e-5, - firstAxis, - std::move(sectionPoints), - {"beam-test.inp", 30U}}; +GeneralBeamSection MakeSection( + std::array first_axis = {0.0, 1.0, 0.0}, + std::vector> section_points = {}) { + return {"Section-1", + 0.012, + 2.5e-5, + 0.0, + 4.0e-5, + 1.5e-5, + first_axis, + std::move(section_points), + {"beam-test.inp", 30U}}; } -EulerBeam3D requireBeam(const Node& firstNode, - const Node& secondNode, +EulerBeam3D RequireBeam(const Node& first_node, const Node& second_node, const GeneralBeamSection& section, const LinearElasticMaterial& material) { - auto result = EulerBeam3D::create(firstNode, secondNode, section, material); - if (!result.HasValue()) { - throw std::runtime_error{"Expected a valid EulerBeam3D fixture."}; + auto result = EulerBeam3D::Create(first_node, second_node, section, material); + if (!result.HasValue()) { + throw std::runtime_error{"Expected a valid EulerBeam3D fixture."}; + } + return std::move(result.Value()); +} + +EulerBeam3D AlignedBeam( + double length, const GeneralBeamSection& section = MakeSection(), + const LinearElasticMaterial& material = MakeMaterial()) { + return RequireBeam(MakeNode({0.0, 0.0, 0.0}, 1U), + MakeNode({length, 0.0, 0.0}, 2U), section, material); +} + +double MaximumAbsoluteEntry(const Matrix& matrix) { + double maximum = 0.0; + for (std::size_t row = 0; row < matrix.Rows(); ++row) { + for (std::size_t column = 0; column < matrix.Columns(); ++column) { + maximum = (std::max)(maximum, std::abs(matrix(row, column))); } - return std::move(result.Value()); + } + return maximum; } -EulerBeam3D alignedBeam(double length, - const GeneralBeamSection& section = makeSection(), - const LinearElasticMaterial& material = makeMaterial()) { - return requireBeam( - makeNode({0.0, 0.0, 0.0}, 1U), - makeNode({length, 0.0, 0.0}, 2U), - section, - material); -} - -double maximumAbsoluteEntry(const Matrix& matrix) { - double maximum = 0.0; - for (std::size_t row = 0; row < matrix.Rows(); ++row) { - for (std::size_t column = 0; column < matrix.Columns(); ++column) { - maximum = (std::max)(maximum, std::abs(matrix(row, column))); - } +bool MatrixIsFinite(const Matrix& matrix) { + for (std::size_t row = 0; row < matrix.Rows(); ++row) { + for (std::size_t column = 0; column < matrix.Columns(); ++column) { + if (!std::isfinite(matrix(row, column))) { + return false; + } } - return maximum; + } + return true; } -bool matrixIsFinite(const Matrix& matrix) { - for (std::size_t row = 0; row < matrix.Rows(); ++row) { - for (std::size_t column = 0; column < matrix.Columns(); ++column) { - if (!std::isfinite(matrix(row, column))) { - return false; - } - } +double NormalizedMatrixError(const Matrix& actual, const Matrix& expected) { + if (actual.Rows() != expected.Rows() || + actual.Columns() != expected.Columns()) { + throw std::invalid_argument{"Matrix comparison requires equal shapes."}; + } + + double maximum_difference = 0.0; + for (std::size_t row = 0; row < actual.Rows(); ++row) { + for (std::size_t column = 0; column < actual.Columns(); ++column) { + maximum_difference = + (std::max)(maximum_difference, + std::abs(actual(row, column) - expected(row, column))); } - return true; + } + + const double scale = + (std::max)(1.0, (std::max)(MaximumAbsoluteEntry(actual), + MaximumAbsoluteEntry(expected))); + return maximum_difference / scale; } -double normalizedMatrixError(const Matrix& actual, const Matrix& expected) { - if (actual.Rows() != expected.Rows() || actual.Columns() != expected.Columns()) { - throw std::invalid_argument{"Matrix comparison requires equal shapes."}; - } - - double maximumDifference = 0.0; - for (std::size_t row = 0; row < actual.Rows(); ++row) { - for (std::size_t column = 0; column < actual.Columns(); ++column) { - maximumDifference = (std::max)( - maximumDifference, - std::abs(actual(row, column) - expected(row, column))); - } - } - - const double scale = (std::max)( - 1.0, - (std::max)(maximumAbsoluteEntry(actual), maximumAbsoluteEntry(expected))); - return maximumDifference / scale; +double VectorNorm(const Vector& vector) { + double sum = 0.0; + for (std::size_t index = 0; index < vector.Size(); ++index) { + sum += vector[index] * vector[index]; + } + return std::sqrt(sum); } -double vectorNorm(const Vector& vector) { - double sum = 0.0; - for (std::size_t index = 0; index < vector.Size(); ++index) { - sum += vector[index] * vector[index]; - } - return std::sqrt(sum); +double QuadraticEnergy(const Matrix& matrix, const Vector& vector) { + const Vector product = matrix.Multiply(vector); + double value = 0.0; + for (std::size_t index = 0; index < vector.Size(); ++index) { + value += vector[index] * product[index]; + } + return value; } -double quadraticEnergy(const Matrix& matrix, const Vector& vector) { - const Vector product = matrix.Multiply(vector); - double value = 0.0; - for (std::size_t index = 0; index < vector.Size(); ++index) { - value += vector[index] * product[index]; - } - return value; +void ExpectScaledNear(double actual, double expected, + double relative_tolerance) { + const double scale = (std::max)(1.0, std::abs(expected)); + EXPECT_LE(std::abs(actual - expected), relative_tolerance * scale); } -void expectScaledNear(double actual, double expected, double relativeTolerance) { - const double scale = (std::max)(1.0, std::abs(expected)); - EXPECT_LE(std::abs(actual - expected), relativeTolerance * scale); +void ExpectRelativeNear(double actual, double expected, + double relative_tolerance) { + ASSERT_NE(expected, 0.0); + EXPECT_LE(std::abs(actual - expected) / std::abs(expected), + relative_tolerance); } -void expectRelativeNear(double actual, double expected, double relativeTolerance) { - ASSERT_NE(expected, 0.0); - EXPECT_LE(std::abs(actual - expected) / std::abs(expected), relativeTolerance); -} - -Matrix expectedClosedStiffness(double length, - const GeneralBeamSection& section, +Matrix ExpectedClosedStiffness(double length, const GeneralBeamSection& section, const LinearElasticMaterial& material) { - Matrix expected{kElementDofCount, kElementDofCount}; - const double shearModulus = - material.youngsModulus / (2.0 * (1.0 + material.poissonRatio)); + Matrix expected{kElementDofCount, kElementDofCount}; + const double shear_modulus = + material.youngs_modulus / (2.0 * (1.0 + material.poisson_ratio)); - const auto addBlock = [&expected](const std::vector& indices, - const std::vector& values) { - const std::size_t width = indices.size(); - for (std::size_t row = 0; row < width; ++row) { - for (std::size_t column = 0; column < width; ++column) { - expected(indices[row], indices[column]) = values[row * width + column]; - } - } - }; + const auto add_block = [&expected](const std::vector& indices, + const std::vector& values) { + const std::size_t width = indices.size(); + for (std::size_t row = 0; row < width; ++row) { + for (std::size_t column = 0; column < width; ++column) { + expected(indices[row], indices[column]) = values[row * width + column]; + } + } + }; - const double axial = material.youngsModulus * section.area / length; - addBlock({0U, 6U}, {axial, -axial, -axial, axial}); + const double axial = material.youngs_modulus * section.area / length; + add_block({0U, 6U}, {axial, -axial, -axial, axial}); - const double torsion = shearModulus * section.torsionalConstant / length; - addBlock({3U, 9U}, {torsion, -torsion, -torsion, torsion}); + const double torsion = shear_modulus * section.torsional_constant / length; + add_block({3U, 9U}, {torsion, -torsion, -torsion, torsion}); - const auto bendingBlock = [length](double flexuralRigidity, double rotationSign) { - const double v = 12.0 * flexuralRigidity / (length * length * length); - const double c = rotationSign * 6.0 * flexuralRigidity / (length * length); - const double d = 4.0 * flexuralRigidity / length; - const double e = 2.0 * flexuralRigidity / length; - return std::vector{ - v, c, -v, c, - c, d, -c, e, - -v, -c, v, -c, - c, e, -c, d}; - }; + const auto bending_block = [length](double flexural_rigidity, + double rotation_sign) { + const double v = 12.0 * flexural_rigidity / (length * length * length); + const double c = + rotation_sign * 6.0 * flexural_rigidity / (length * length); + const double d = 4.0 * flexural_rigidity / length; + const double e = 2.0 * flexural_rigidity / length; + return std::vector{v, c, -v, c, c, d, -c, e, + -v, -c, v, -c, c, e, -c, d}; + }; - addBlock( - {1U, 5U, 7U, 11U}, - bendingBlock(material.youngsModulus * section.i22, 1.0)); - addBlock( - {2U, 4U, 8U, 10U}, - bendingBlock(material.youngsModulus * section.i11, -1.0)); - return expected; + add_block({1U, 5U, 7U, 11U}, + bending_block(material.youngs_modulus * section.i22, 1.0)); + add_block({2U, 4U, 8U, 10U}, + bending_block(material.youngs_modulus * section.i11, -1.0)); + return expected; } -std::array symmetricEigenvalues(Matrix matrix) { - for (std::size_t iteration = 0; iteration < 100U * kElementDofCount; ++iteration) { - std::size_t p = 0U; - std::size_t q = 1U; - double maximumOffDiagonal = 0.0; - for (std::size_t row = 0; row < kElementDofCount; ++row) { - for (std::size_t column = row + 1U; column < kElementDofCount; ++column) { - const double candidate = std::abs(matrix(row, column)); - if (candidate > maximumOffDiagonal) { - maximumOffDiagonal = candidate; - p = row; - q = column; - } - } +std::array SymmetricEigenvalues(Matrix matrix) { + for (std::size_t iteration = 0; iteration < 100U * kElementDofCount; + ++iteration) { + std::size_t p = 0U; + std::size_t q = 1U; + double maximum_off_diagonal = 0.0; + for (std::size_t row = 0; row < kElementDofCount; ++row) { + for (std::size_t column = row + 1U; column < kElementDofCount; ++column) { + const double candidate = std::abs(matrix(row, column)); + if (candidate > maximum_off_diagonal) { + maximum_off_diagonal = candidate; + p = row; + q = column; } - if (maximumOffDiagonal <= - 1.0e-14 * (std::max)(1.0, maximumAbsoluteEntry(matrix))) { - break; - } - - const double app = matrix(p, p); - const double aqq = matrix(q, q); - const double apq = matrix(p, q); - const double angle = 0.5 * std::atan2(2.0 * apq, aqq - app); - const double cosine = std::cos(angle); - const double sine = std::sin(angle); - - for (std::size_t index = 0; index < kElementDofCount; ++index) { - if (index == p || index == q) { - continue; - } - const double aip = matrix(index, p); - const double aiq = matrix(index, q); - matrix(index, p) = cosine * aip - sine * aiq; - matrix(p, index) = matrix(index, p); - matrix(index, q) = sine * aip + cosine * aiq; - matrix(q, index) = matrix(index, q); - } - - matrix(p, p) = cosine * cosine * app - 2.0 * sine * cosine * apq + - sine * sine * aqq; - matrix(q, q) = sine * sine * app + 2.0 * sine * cosine * apq + - cosine * cosine * aqq; - matrix(p, q) = 0.0; - matrix(q, p) = 0.0; + } + } + if (maximum_off_diagonal <= + 1.0e-14 * (std::max)(1.0, MaximumAbsoluteEntry(matrix))) { + break; } - std::array eigenvalues{}; + const double app = matrix(p, p); + const double aqq = matrix(q, q); + const double apq = matrix(p, q); + const double angle = 0.5 * std::atan2(2.0 * apq, aqq - app); + const double cosine = std::cos(angle); + const double sine = std::sin(angle); + for (std::size_t index = 0; index < kElementDofCount; ++index) { - eigenvalues[index] = matrix(index, index); + if (index == p || index == q) { + continue; + } + const double aip = matrix(index, p); + const double aiq = matrix(index, q); + matrix(index, p) = cosine * aip - sine * aiq; + matrix(p, index) = matrix(index, p); + matrix(index, q) = sine * aip + cosine * aiq; + matrix(q, index) = matrix(index, q); } - return eigenvalues; + + matrix(p, p) = + cosine * cosine * app - 2.0 * sine * cosine * apq + sine * sine * aqq; + matrix(q, q) = + sine * sine * app + 2.0 * sine * cosine * apq + cosine * cosine * aqq; + matrix(p, q) = 0.0; + matrix(q, p) = 0.0; + } + + std::array eigenvalues{}; + for (std::size_t index = 0; index < kElementDofCount; ++index) { + eigenvalues[index] = matrix(index, index); + } + return eigenvalues; } -std::size_t symmetricRank(const Matrix& matrix, double relativeTolerance) { - const auto eigenvalues = symmetricEigenvalues(matrix); - double maximum = 0.0; - for (const double value : eigenvalues) { - maximum = (std::max)(maximum, std::abs(value)); - } - return static_cast(std::count_if( - eigenvalues.begin(), - eigenvalues.end(), - [maximum, relativeTolerance](double value) { - return std::abs(value) > relativeTolerance * maximum; - })); +std::size_t SymmetricRank(const Matrix& matrix, double relative_tolerance) { + const auto eigenvalues = SymmetricEigenvalues(matrix); + double maximum = 0.0; + for (const double value : eigenvalues) { + maximum = (std::max)(maximum, std::abs(value)); + } + return static_cast( + std::count_if(eigenvalues.begin(), eigenvalues.end(), + [maximum, relative_tolerance](double value) { + return std::abs(value) > relative_tolerance * maximum; + })); } -Matrix testOnlyOnePointStiffness(double length, +Matrix TestOnlyOnePointStiffness(double length, const GeneralBeamSection& section, const LinearElasticMaterial& material) { - // At xi=0 the two bending curvature rows retain only the nodal rotations. - // This deliberately under-integrated negative control is independent of production. - Matrix b{4U, kElementDofCount}; - b(0U, 0U) = -1.0 / length; - b(0U, 6U) = 1.0 / length; - b(1U, 3U) = -1.0 / length; - b(1U, 9U) = 1.0 / length; - b(2U, 4U) = -1.0 / length; - b(2U, 10U) = 1.0 / length; - b(3U, 5U) = -1.0 / length; - b(3U, 11U) = 1.0 / length; + // At xi=0 the two bending curvature rows retain only the nodal rotations. + // This deliberately under-integrated negative control is independent of + // production. + Matrix b{4U, kElementDofCount}; + b(0U, 0U) = -1.0 / length; + b(0U, 6U) = 1.0 / length; + b(1U, 3U) = -1.0 / length; + b(1U, 9U) = 1.0 / length; + b(2U, 4U) = -1.0 / length; + b(2U, 10U) = 1.0 / length; + b(3U, 5U) = -1.0 / length; + b(3U, 11U) = 1.0 / length; - const double shearModulus = - material.youngsModulus / (2.0 * (1.0 + material.poissonRatio)); - const std::array diagonal = { - material.youngsModulus * section.area, - shearModulus * section.torsionalConstant, - material.youngsModulus * section.i11, - material.youngsModulus * section.i22}; + const double shear_modulus = + material.youngs_modulus / (2.0 * (1.0 + material.poisson_ratio)); + const std::array diagonal = { + material.youngs_modulus * section.area, + shear_modulus * section.torsional_constant, + material.youngs_modulus * section.i11, + material.youngs_modulus * section.i22}; - Matrix stiffness{kElementDofCount, kElementDofCount}; - 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) * length; - } - } + Matrix stiffness{kElementDofCount, kElementDofCount}; + 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) * length; + } } - return stiffness; + } + return stiffness; } -Vector solveFixedFirstNode(const Matrix& stiffness, - const std::array& freeEndLoad) { - std::array, 6> augmented{}; +Vector SolveFixedFirstNode(const Matrix& stiffness, + const std::array& free_end_load) { + std::array, 6> augmented{}; + for (std::size_t row = 0; row < 6U; ++row) { + for (std::size_t column = 0; column < 6U; ++column) { + augmented[row][column] = stiffness(row + 6U, column + 6U); + } + augmented[row][6U] = free_end_load[row]; + } + + for (std::size_t pivot = 0; pivot < 6U; ++pivot) { + std::size_t pivot_row = pivot; + for (std::size_t row = pivot + 1U; row < 6U; ++row) { + if (std::abs(augmented[row][pivot]) > + std::abs(augmented[pivot_row][pivot])) { + pivot_row = row; + } + } + if (std::abs(augmented[pivot_row][pivot]) <= + std::numeric_limits::min()) { + throw std::runtime_error{"Cantilever fixture is singular."}; + } + std::swap(augmented[pivot], augmented[pivot_row]); + + const double pivot_value = augmented[pivot][pivot]; + for (std::size_t column = pivot; column < 7U; ++column) { + augmented[pivot][column] /= pivot_value; + } for (std::size_t row = 0; row < 6U; ++row) { - for (std::size_t column = 0; column < 6U; ++column) { - augmented[row][column] = stiffness(row + 6U, column + 6U); - } - augmented[row][6U] = freeEndLoad[row]; + if (row == pivot) { + continue; + } + const double factor = augmented[row][pivot]; + for (std::size_t column = pivot; column < 7U; ++column) { + augmented[row][column] -= factor * augmented[pivot][column]; + } } + } - for (std::size_t pivot = 0; pivot < 6U; ++pivot) { - std::size_t pivotRow = pivot; - for (std::size_t row = pivot + 1U; row < 6U; ++row) { - if (std::abs(augmented[row][pivot]) > - std::abs(augmented[pivotRow][pivot])) { - pivotRow = row; - } - } - if (std::abs(augmented[pivotRow][pivot]) <= - std::numeric_limits::min()) { - throw std::runtime_error{"Cantilever fixture is singular."}; - } - std::swap(augmented[pivot], augmented[pivotRow]); - - const double pivotValue = augmented[pivot][pivot]; - for (std::size_t column = pivot; column < 7U; ++column) { - augmented[pivot][column] /= pivotValue; - } - for (std::size_t row = 0; row < 6U; ++row) { - if (row == pivot) { - continue; - } - const double factor = augmented[row][pivot]; - for (std::size_t column = pivot; column < 7U; ++column) { - augmented[row][column] -= factor * augmented[pivot][column]; - } - } - } - - Vector displacement{kElementDofCount}; - for (std::size_t component = 0; component < 6U; ++component) { - displacement[component + 6U] = augmented[component][6U]; - } - return displacement; + Vector displacement{kElementDofCount}; + for (std::size_t component = 0; component < 6U; ++component) { + displacement[component + 6U] = augmented[component][6U]; + } + return displacement; } -Vector solveDenseSystem(Matrix matrix, Vector rightHandSide) { - if (matrix.Rows() != matrix.Columns() || - matrix.Rows() != rightHandSide.Size()) { - throw std::invalid_argument{"Dense test solve requires a square system."}; - } +Vector SolveDenseSystem(Matrix matrix, Vector right_hand_side) { + if (matrix.Rows() != matrix.Columns() || + matrix.Rows() != right_hand_side.Size()) { + throw std::invalid_argument{"Dense test solve requires a square system."}; + } - for (std::size_t pivot = 0; pivot < matrix.Rows(); ++pivot) { - std::size_t pivotRow = pivot; - for (std::size_t row = pivot + 1U; row < matrix.Rows(); ++row) { - if (std::abs(matrix(row, pivot)) > - std::abs(matrix(pivotRow, pivot))) { - pivotRow = row; - } - } - if (!(std::abs(matrix(pivotRow, pivot)) > 0.0) || - !std::isfinite(matrix(pivotRow, pivot))) { - throw std::runtime_error{"Uniform-load test fixture is singular."}; - } - for (std::size_t column = pivot; column < matrix.Columns(); ++column) { - std::swap(matrix(pivot, column), matrix(pivotRow, column)); - } - std::swap(rightHandSide[pivot], rightHandSide[pivotRow]); - - const double pivotValue = matrix(pivot, pivot); - for (std::size_t column = pivot; column < matrix.Columns(); ++column) { - matrix(pivot, column) /= pivotValue; - } - rightHandSide[pivot] /= pivotValue; - for (std::size_t row = 0; row < matrix.Rows(); ++row) { - if (row == pivot) { - continue; - } - const double factor = matrix(row, pivot); - for (std::size_t column = pivot; column < matrix.Columns(); ++column) { - matrix(row, column) -= factor * matrix(pivot, column); - } - rightHandSide[row] -= factor * rightHandSide[pivot]; - } + for (std::size_t pivot = 0; pivot < matrix.Rows(); ++pivot) { + std::size_t pivot_row = pivot; + for (std::size_t row = pivot + 1U; row < matrix.Rows(); ++row) { + if (std::abs(matrix(row, pivot)) > std::abs(matrix(pivot_row, pivot))) { + pivot_row = row; + } } - return rightHandSide; + if (!(std::abs(matrix(pivot_row, pivot)) > 0.0) || + !std::isfinite(matrix(pivot_row, pivot))) { + throw std::runtime_error{"Uniform-load test fixture is singular."}; + } + for (std::size_t column = pivot; column < matrix.Columns(); ++column) { + std::swap(matrix(pivot, column), matrix(pivot_row, column)); + } + std::swap(right_hand_side[pivot], right_hand_side[pivot_row]); + + const double pivot_value = matrix(pivot, pivot); + for (std::size_t column = pivot; column < matrix.Columns(); ++column) { + matrix(pivot, column) /= pivot_value; + } + right_hand_side[pivot] /= pivot_value; + for (std::size_t row = 0; row < matrix.Rows(); ++row) { + if (row == pivot) { + continue; + } + const double factor = matrix(row, pivot); + for (std::size_t column = pivot; column < matrix.Columns(); ++column) { + matrix(row, column) -= factor * matrix(pivot, column); + } + right_hand_side[row] -= factor * right_hand_side[pivot]; + } + } + return right_hand_side; } -Vector solveUniformTransverseCantilever(std::size_t elementCount, - double length, - double lineLoad, +Vector SolveUniformTransverseCantilever(std::size_t element_count, + double length, double line_load, const GeneralBeamSection& section, const LinearElasticMaterial& material) { - const double elementLength = length / static_cast(elementCount); - const std::size_t systemSize = 2U * (elementCount + 1U); - Matrix assembledStiffness{systemSize, systemSize}; - Vector assembledLoad{systemSize}; - const std::array bendingDofs = {1U, 5U, 7U, 11U}; + const double element_length = length / static_cast(element_count); + const std::size_t system_size = 2U * (element_count + 1U); + Matrix assembled_stiffness{system_size, system_size}; + Vector assembled_load{system_size}; + const std::array bending_dofs = {1U, 5U, 7U, 11U}; - // Test-only direct assembly keeps this evidence at the formulation boundary: - // two [v,rz] DOFs per node, with no Domain, parser, or DLOAD path. - for (std::size_t element = 0; element < elementCount; ++element) { - const EulerBeam3D beam = alignedBeam(elementLength, section, material); - const Matrix elementStiffness = beam.localStiffness(); - const Vector elementLoad = beam.localEquivalentLoad( - {0.0, lineLoad, 0.0, 0.0}); - const std::array assembledDofs = { - 2U * element, - 2U * element + 1U, - 2U * (element + 1U), - 2U * (element + 1U) + 1U}; - for (std::size_t row = 0; row < bendingDofs.size(); ++row) { - assembledLoad[assembledDofs[row]] += elementLoad[bendingDofs[row]]; - for (std::size_t column = 0; column < bendingDofs.size(); ++column) { - assembledStiffness(assembledDofs[row], assembledDofs[column]) += - elementStiffness(bendingDofs[row], bendingDofs[column]); - } - } + // Test-only direct assembly keeps this evidence at the formulation boundary: + // two [v,rz] DOFs per node, with no Domain, parser, or DLOAD path. + for (std::size_t element = 0; element < element_count; ++element) { + const EulerBeam3D beam = AlignedBeam(element_length, section, material); + const Matrix element_stiffness = beam.LocalStiffness(); + const Vector element_load = + beam.LocalEquivalentLoad({0.0, line_load, 0.0, 0.0}); + const std::array assembled_dofs = { + 2U * element, 2U * element + 1U, 2U * (element + 1U), + 2U * (element + 1U) + 1U}; + for (std::size_t row = 0; row < bending_dofs.size(); ++row) { + assembled_load[assembled_dofs[row]] += element_load[bending_dofs[row]]; + for (std::size_t column = 0; column < bending_dofs.size(); ++column) { + assembled_stiffness(assembled_dofs[row], assembled_dofs[column]) += + element_stiffness(bending_dofs[row], bending_dofs[column]); + } } + } - const std::size_t freeSize = systemSize - 2U; - Matrix freeStiffness{freeSize, freeSize}; - Vector freeLoad{freeSize}; - for (std::size_t row = 0; row < freeSize; ++row) { - freeLoad[row] = assembledLoad[row + 2U]; - for (std::size_t column = 0; column < freeSize; ++column) { - freeStiffness(row, column) = - assembledStiffness(row + 2U, column + 2U); - } + const std::size_t free_size = system_size - 2U; + Matrix free_stiffness{free_size, free_size}; + Vector free_load{free_size}; + for (std::size_t row = 0; row < free_size; ++row) { + free_load[row] = assembled_load[row + 2U]; + for (std::size_t column = 0; column < free_size; ++column) { + free_stiffness(row, column) = assembled_stiffness(row + 2U, column + 2U); } + } - const Vector freeDisplacement = - solveDenseSystem(std::move(freeStiffness), std::move(freeLoad)); - Vector nodalDisplacement{systemSize}; - for (std::size_t dof = 0; dof < freeSize; ++dof) { - nodalDisplacement[dof + 2U] = freeDisplacement[dof]; - } - return nodalDisplacement; + const Vector free_displacement = + SolveDenseSystem(std::move(free_stiffness), std::move(free_load)); + Vector nodal_displacement{system_size}; + for (std::size_t dof = 0; dof < free_size; ++dof) { + nodal_displacement[dof + 2U] = free_displacement[dof]; + } + return nodal_displacement; } -double uniformLoadInteriorDisplacementError( - const Vector& nodalDisplacement, - std::size_t elementCount, - double length, - double lineLoad, - double flexuralRigidity) { - const double elementLength = length / static_cast(elementCount); - const std::array gaussPoints = { - -0.9061798459386640, - -0.5384693101056831, - 0.0, - 0.5384693101056831, - 0.9061798459386640}; - const std::array gaussWeights = { - 0.2369268850561891, - 0.4786286704993665, - 0.5688888888888889, - 0.4786286704993665, - 0.2369268850561891}; - double squaredError = 0.0; - double squaredReference = 0.0; +double UniformLoadInteriorDisplacementError(const Vector& nodal_displacement, + std::size_t element_count, + double length, double line_load, + double flexural_rigidity) { + const double element_length = length / static_cast(element_count); + const std::array gauss_points = { + -0.9061798459386640, -0.5384693101056831, 0.0, 0.5384693101056831, + 0.9061798459386640}; + const std::array gauss_weights = { + 0.2369268850561891, 0.4786286704993665, 0.5688888888888889, + 0.4786286704993665, 0.2369268850561891}; + double squared_error = 0.0; + double squared_reference = 0.0; - // Five-point integration is independent of production and exactly integrates - // the squared error between cubic Hermite interpolation and the quartic beam solution. - for (std::size_t element = 0; element < elementCount; ++element) { - for (std::size_t point = 0; point < gaussPoints.size(); ++point) { - const double r = 0.5 * (1.0 + gaussPoints[point]); - const double rSquared = r * r; - const double rCubed = rSquared * r; - const double h1 = 1.0 - 3.0 * rSquared + 2.0 * rCubed; - const double h2 = elementLength * (r - 2.0 * rSquared + rCubed); - const double h3 = 3.0 * rSquared - 2.0 * rCubed; - const double h4 = elementLength * (-rSquared + rCubed); - const double interpolated = - h1 * nodalDisplacement[2U * element] + - h2 * nodalDisplacement[2U * element + 1U] + - h3 * nodalDisplacement[2U * (element + 1U)] + - h4 * nodalDisplacement[2U * (element + 1U) + 1U]; - const double x = elementLength * - (static_cast(element) + r); - const double analytical = - lineLoad * x * x * - (6.0 * length * length - 4.0 * length * x + x * x) / - (24.0 * flexuralRigidity); - const double weight = 0.5 * elementLength * gaussWeights[point]; - const double difference = interpolated - analytical; - squaredError += weight * difference * difference; - squaredReference += weight * analytical * analytical; - } + // Five-point integration is independent of production and exactly integrates + // the squared error between cubic Hermite interpolation and the quartic beam + // solution. + for (std::size_t element = 0; element < element_count; ++element) { + for (std::size_t point = 0; point < gauss_points.size(); ++point) { + const double r = 0.5 * (1.0 + gauss_points[point]); + const double r_squared = r * r; + const double r_cubed = r_squared * r; + const double h1 = 1.0 - 3.0 * r_squared + 2.0 * r_cubed; + const double h2 = element_length * (r - 2.0 * r_squared + r_cubed); + const double h3 = 3.0 * r_squared - 2.0 * r_cubed; + const double h4 = element_length * (-r_squared + r_cubed); + const double interpolated = + h1 * nodal_displacement[2U * element] + + h2 * nodal_displacement[2U * element + 1U] + + h3 * nodal_displacement[2U * (element + 1U)] + + h4 * nodal_displacement[2U * (element + 1U) + 1U]; + const double x = element_length * (static_cast(element) + r); + const double analytical = + line_load * x * x * + (6.0 * length * length - 4.0 * length * x + x * x) / + (24.0 * flexural_rigidity); + const double weight = 0.5 * element_length * gauss_weights[point]; + const double difference = interpolated - analytical; + squared_error += weight * difference * difference; + squared_reference += weight * analytical * analytical; } - return std::sqrt(squaredError / squaredReference); + } + return std::sqrt(squared_error / squared_reference); } -Matrix transformationFromKnownRows( +Matrix TransformationFromKnownRows( const std::array, 3>& rotation) { - Matrix transformation{kElementDofCount, kElementDofCount}; - 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) { - transformation(block * 3U + row, block * 3U + column) = - rotation[row][column]; - } - } + Matrix transformation{kElementDofCount, kElementDofCount}; + 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) { + transformation(block * 3U + row, block * 3U + column) = + rotation[row][column]; + } } - return transformation; + } + return transformation; } -Vector transposeMultiply(const Matrix& matrix, const Vector& vector) { - if (matrix.Rows() != vector.Size()) { - throw std::invalid_argument{"Transpose multiply dimension mismatch."}; +Vector TransposeMultiply(const Matrix& matrix, const Vector& vector) { + if (matrix.Rows() != vector.Size()) { + throw std::invalid_argument{"Transpose multiply dimension mismatch."}; + } + Vector result{matrix.Columns()}; + for (std::size_t column = 0; column < matrix.Columns(); ++column) { + for (std::size_t row = 0; row < matrix.Rows(); ++row) { + result[column] += matrix(row, column) * vector[row]; } - Vector result{matrix.Columns()}; - for (std::size_t column = 0; column < matrix.Columns(); ++column) { - for (std::size_t row = 0; row < matrix.Rows(); ++row) { - result[column] += matrix(row, column) * vector[row]; - } - } - return result; + } + return result; } -double determinant(const std::array, 3>& matrix) { - return matrix[0][0] * - (matrix[1][1] * matrix[2][2] - matrix[1][2] * matrix[2][1]) - - matrix[0][1] * - (matrix[1][0] * matrix[2][2] - matrix[1][2] * matrix[2][0]) + - matrix[0][2] * - (matrix[1][0] * matrix[2][1] - matrix[1][1] * matrix[2][0]); +double Determinant(const std::array, 3>& matrix) { + return matrix[0][0] * + (matrix[1][1] * matrix[2][2] - matrix[1][2] * matrix[2][1]) - + matrix[0][1] * + (matrix[1][0] * matrix[2][2] - matrix[1][2] * matrix[2][0]) + + matrix[0][2] * + (matrix[1][0] * matrix[2][1] - matrix[1][1] * matrix[2][0]); } TEST(EulerBeam3D, HermiteAndBMatrixMatchReviewedSigns) { - const double length = 2.5; - const auto section = makeSection(); - const auto material = makeMaterial(); - const auto beam = alignedBeam(length, section, material); + const double length = 2.5; + const auto section = MakeSection(); + const auto material = MakeMaterial(); + const auto beam = AlignedBeam(length, section, material); - const double axialStrain = 0.012; - const double twist = -0.021; - const std::array v = {1.2, -0.4, 0.3, -0.07}; - const std::array w = {-0.8, 0.6, -0.2, 0.05}; - const auto value = [](const std::array& coefficients, double x) { - return coefficients[0] + coefficients[1] * x + coefficients[2] * x * x + - coefficients[3] * x * x * x; - }; - const auto slope = [](const std::array& coefficients, double x) { - return coefficients[1] + 2.0 * coefficients[2] * x + - 3.0 * coefficients[3] * x * x; - }; - const auto curvature = [](const std::array& coefficients, double x) { - return 2.0 * coefficients[2] + 6.0 * coefficients[3] * x; - }; + const double axial_strain = 0.012; + const double twist = -0.021; + const std::array v = {1.2, -0.4, 0.3, -0.07}; + const std::array w = {-0.8, 0.6, -0.2, 0.05}; + const auto value = [](const std::array& coefficients, double x) { + return coefficients[0] + coefficients[1] * x + coefficients[2] * x * x + + coefficients[3] * x * x * x; + }; + const auto slope = [](const std::array& coefficients, double x) { + return coefficients[1] + 2.0 * coefficients[2] * x + + 3.0 * coefficients[3] * x * x; + }; + const auto curvature = [](const std::array& coefficients, + double x) { + return 2.0 * coefficients[2] + 6.0 * coefficients[3] * x; + }; - Vector displacement{kElementDofCount}; - displacement[0U] = 0.2; - displacement[1U] = value(v, 0.0); - displacement[2U] = value(w, 0.0); - displacement[3U] = -0.1; - displacement[4U] = -slope(w, 0.0); - displacement[5U] = slope(v, 0.0); - displacement[6U] = displacement[0U] + axialStrain * length; - displacement[7U] = value(v, length); - displacement[8U] = value(w, length); - displacement[9U] = displacement[3U] + twist * length; - displacement[10U] = -slope(w, length); - displacement[11U] = slope(v, length); + Vector displacement{kElementDofCount}; + displacement[0U] = 0.2; + displacement[1U] = value(v, 0.0); + displacement[2U] = value(w, 0.0); + displacement[3U] = -0.1; + displacement[4U] = -slope(w, 0.0); + displacement[5U] = slope(v, 0.0); + displacement[6U] = displacement[0U] + axial_strain * length; + displacement[7U] = value(v, length); + displacement[8U] = value(w, length); + displacement[9U] = displacement[3U] + twist * length; + displacement[10U] = -slope(w, length); + displacement[11U] = slope(v, length); - const BeamRecovery recovery = beam.recover(displacement); - const double inverseSqrtThree = 1.0 / std::sqrt(3.0); - const std::array gaussXi = {-inverseSqrtThree, inverseSqrtThree}; - for (std::size_t point = 0; point < gaussXi.size(); ++point) { - const double x = 0.5 * length * (1.0 + gaussXi[point]); - EXPECT_NEAR(recovery.gaussGeneralizedStrains[point][0U], axialStrain, 1.0e-14); - EXPECT_NEAR(recovery.gaussGeneralizedStrains[point][1U], twist, 1.0e-14); - EXPECT_NEAR( - recovery.gaussGeneralizedStrains[point][2U], - -curvature(w, x), - 1.0e-13); - EXPECT_NEAR( - recovery.gaussGeneralizedStrains[point][3U], - curvature(v, x), - 1.0e-13); - } + const BeamRecovery recovery = beam.Recover(displacement); + const double inverse_sqrt_three = 1.0 / std::sqrt(3.0); + const std::array gauss_xi = {-inverse_sqrt_three, + inverse_sqrt_three}; + for (std::size_t point = 0; point < gauss_xi.size(); ++point) { + const double x = 0.5 * length * (1.0 + gauss_xi[point]); + EXPECT_NEAR(recovery.gauss_generalized_strains[point][0U], axial_strain, + 1.0e-14); + EXPECT_NEAR(recovery.gauss_generalized_strains[point][1U], twist, 1.0e-14); + EXPECT_NEAR(recovery.gauss_generalized_strains[point][2U], -curvature(w, x), + 1.0e-13); + EXPECT_NEAR(recovery.gauss_generalized_strains[point][3U], curvature(v, x), + 1.0e-13); + } - EXPECT_NEAR( - recovery.endpointSectionResultants[0U][2U], - material.youngsModulus * section.i11 * -curvature(w, 0.0), - 1.0e-5); - EXPECT_NEAR( - recovery.endpointSectionResultants[1U][2U], - material.youngsModulus * section.i11 * -curvature(w, length), - 1.0e-5); - EXPECT_NEAR( - recovery.endpointSectionResultants[0U][3U], - material.youngsModulus * section.i22 * curvature(v, 0.0), - 1.0e-5); - EXPECT_NEAR( - recovery.endpointSectionResultants[1U][3U], - material.youngsModulus * section.i22 * curvature(v, length), - 1.0e-5); + EXPECT_NEAR(recovery.endpoint_section_resultants[0U][2U], + material.youngs_modulus * section.i11 * -curvature(w, 0.0), + 1.0e-5); + EXPECT_NEAR(recovery.endpoint_section_resultants[1U][2U], + material.youngs_modulus * section.i11 * -curvature(w, length), + 1.0e-5); + EXPECT_NEAR(recovery.endpoint_section_resultants[0U][3U], + material.youngs_modulus * section.i22 * curvature(v, 0.0), + 1.0e-5); + EXPECT_NEAR(recovery.endpoint_section_resultants[1U][3U], + material.youngs_modulus * section.i22 * curvature(v, length), + 1.0e-5); } TEST(EulerBeam3D, TwoPointGaussMatchesClosedStiffness) { - const double length = 3.7; - auto section = makeSection(); - section.area = 0.019; - section.i11 = 3.1e-5; - section.i22 = 7.4e-5; - section.torsionalConstant = 2.2e-5; - const auto material = makeMaterial(73.0e9, 0.27); - const auto beam = alignedBeam(length, section, material); + const double length = 3.7; + auto section = MakeSection(); + section.area = 0.019; + section.i11 = 3.1e-5; + section.i22 = 7.4e-5; + section.torsional_constant = 2.2e-5; + const auto material = MakeMaterial(73.0e9, 0.27); + const auto beam = AlignedBeam(length, section, material); - const Matrix actual = beam.localStiffness(); - const Matrix closed = expectedClosedStiffness(length, section, material); - EXPECT_LE(normalizedMatrixError(actual, closed), kMatrixTolerance); + const Matrix actual = beam.LocalStiffness(); + const Matrix closed = ExpectedClosedStiffness(length, section, material); + EXPECT_LE(NormalizedMatrixError(actual, closed), kMatrixTolerance); - Matrix transpose{actual.Rows(), actual.Columns()}; - for (std::size_t row = 0; row < actual.Rows(); ++row) { - for (std::size_t column = 0; column < actual.Columns(); ++column) { - transpose(row, column) = actual(column, row); - } + Matrix transpose{actual.Rows(), actual.Columns()}; + for (std::size_t row = 0; row < actual.Rows(); ++row) { + for (std::size_t column = 0; column < actual.Columns(); ++column) { + transpose(row, column) = actual(column, row); } - EXPECT_LE(normalizedMatrixError(actual, transpose), kMatrixTolerance); + } + EXPECT_LE(NormalizedMatrixError(actual, transpose), kMatrixTolerance); } TEST(EulerBeam3D, HasSixRigidModesRankSixAndPositiveDeformationEnergy) { - const double length = 2.0; - auto section = makeSection(); - section.area = 1.4; - section.i11 = 0.8; - section.i22 = 1.1; - section.torsionalConstant = 0.6; - const auto material = makeMaterial(5.0, 0.25); - const Matrix stiffness = alignedBeam(length, section, material).localStiffness(); + const double length = 2.0; + auto section = MakeSection(); + section.area = 1.4; + section.i11 = 0.8; + section.i22 = 1.1; + section.torsional_constant = 0.6; + const auto material = MakeMaterial(5.0, 0.25); + const Matrix stiffness = + AlignedBeam(length, section, material).LocalStiffness(); - std::array rigidModes = { - Vector{kElementDofCount}, Vector{kElementDofCount}, Vector{kElementDofCount}, - Vector{kElementDofCount}, Vector{kElementDofCount}, Vector{kElementDofCount}}; - rigidModes[0U][0U] = rigidModes[0U][6U] = 1.0; - rigidModes[1U][1U] = rigidModes[1U][7U] = 1.0; - rigidModes[2U][2U] = rigidModes[2U][8U] = 1.0; - rigidModes[3U][3U] = rigidModes[3U][9U] = 1.0; - rigidModes[4U][4U] = rigidModes[4U][10U] = 1.0; - rigidModes[4U][8U] = -length; - rigidModes[5U][5U] = rigidModes[5U][11U] = 1.0; - rigidModes[5U][7U] = length; + std::array rigid_modes = { + Vector{kElementDofCount}, Vector{kElementDofCount}, + Vector{kElementDofCount}, Vector{kElementDofCount}, + Vector{kElementDofCount}, Vector{kElementDofCount}}; + rigid_modes[0U][0U] = rigid_modes[0U][6U] = 1.0; + rigid_modes[1U][1U] = rigid_modes[1U][7U] = 1.0; + rigid_modes[2U][2U] = rigid_modes[2U][8U] = 1.0; + rigid_modes[3U][3U] = rigid_modes[3U][9U] = 1.0; + rigid_modes[4U][4U] = rigid_modes[4U][10U] = 1.0; + rigid_modes[4U][8U] = -length; + rigid_modes[5U][5U] = rigid_modes[5U][11U] = 1.0; + rigid_modes[5U][7U] = length; - const double stiffnessScale = (std::max)(1.0, maximumAbsoluteEntry(stiffness)); - for (const Vector& mode : rigidModes) { - const double normalizedResidual = - vectorNorm(stiffness.Multiply(mode)) / - (stiffnessScale * (std::max)(1.0, vectorNorm(mode))); - EXPECT_LE(normalizedResidual, kRigidTolerance); - } + const double stiffness_scale = + (std::max)(1.0, MaximumAbsoluteEntry(stiffness)); + for (const Vector& mode : rigid_modes) { + const double normalizedResidual = + VectorNorm(stiffness.Multiply(mode)) / + (stiffness_scale * (std::max)(1.0, VectorNorm(mode))); + EXPECT_LE(normalizedResidual, kRigidTolerance); + } - const std::array q = { - 1.0, 1.0, 1.0, length, length, length, - 1.0, 1.0, 1.0, length, length, length}; - Matrix scaled{kElementDofCount, kElementDofCount}; - for (std::size_t row = 0; row < kElementDofCount; ++row) { - for (std::size_t column = 0; column < kElementDofCount; ++column) { - scaled(row, column) = stiffness(row, column) / (q[row] * q[column]); - } - } - const auto eigenvalues = symmetricEigenvalues(scaled); - double maximumSingularValue = 0.0; - for (const double value : eigenvalues) { - maximumSingularValue = (std::max)(maximumSingularValue, std::abs(value)); - } - const auto positiveCount = std::count_if( - eigenvalues.begin(), eigenvalues.end(), [maximumSingularValue](double value) { - return std::abs(value) > kRigidTolerance * maximumSingularValue; - }); - EXPECT_EQ(positiveCount, 6); - for (const double value : eigenvalues) { - EXPECT_GE(value, -kRigidTolerance * maximumSingularValue); + const std::array q = { + 1.0, 1.0, 1.0, length, length, length, + 1.0, 1.0, 1.0, length, length, length}; + Matrix scaled{kElementDofCount, kElementDofCount}; + for (std::size_t row = 0; row < kElementDofCount; ++row) { + for (std::size_t column = 0; column < kElementDofCount; ++column) { + scaled(row, column) = stiffness(row, column) / (q[row] * q[column]); } + } + const auto eigenvalues = SymmetricEigenvalues(scaled); + double maximum_singular_value = 0.0; + for (const double value : eigenvalues) { + maximum_singular_value = + (std::max)(maximum_singular_value, std::abs(value)); + } + const auto positive_count = std::count_if( + eigenvalues.begin(), eigenvalues.end(), + [maximum_singular_value](double value) { + return std::abs(value) > kRigidTolerance * maximum_singular_value; + }); + EXPECT_EQ(positive_count, 6); + for (const double value : eigenvalues) { + EXPECT_GE(value, -kRigidTolerance * maximum_singular_value); + } - for (std::size_t component = 0; component < 6U; ++component) { - Vector deformation{kElementDofCount}; - deformation[6U + component] = 1.0; - EXPECT_GT(quadraticEnergy(stiffness, deformation), 0.0); - } + for (std::size_t component = 0; component < 6U; ++component) { + Vector deformation{kElementDofCount}; + deformation[6U + component] = 1.0; + EXPECT_GT(QuadraticEnergy(stiffness, deformation), 0.0); + } } TEST(EulerBeam3D, RotatedTransformPreservesWorkAndEnergy) { - const double inverseSqrtTwo = 1.0 / std::sqrt(2.0); - const std::array, 3> rotation = {{ - {{2.0 / 3.0, 2.0 / 3.0, 1.0 / 3.0}}, - {{-inverseSqrtTwo, inverseSqrtTwo, 0.0}}, - {{-inverseSqrtTwo / 3.0, -inverseSqrtTwo / 3.0, - 4.0 * inverseSqrtTwo / 3.0}}}}; - const Matrix transformation = transformationFromKnownRows(rotation); + const double inverse_sqrt_two = 1.0 / std::sqrt(2.0); + const std::array, 3> rotation = { + {{{2.0 / 3.0, 2.0 / 3.0, 1.0 / 3.0}}, + {{-inverse_sqrt_two, inverse_sqrt_two, 0.0}}, + {{-inverse_sqrt_two / 3.0, -inverse_sqrt_two / 3.0, + 4.0 * inverse_sqrt_two / 3.0}}}}; + const Matrix transformation = TransformationFromKnownRows(rotation); - for (std::size_t row = 0; row < 3U; ++row) { - for (std::size_t column = 0; column < 3U; ++column) { - double dot = 0.0; - for (std::size_t component = 0; component < 3U; ++component) { - dot += rotation[row][component] * rotation[column][component]; - } - EXPECT_NEAR(dot, row == column ? 1.0 : 0.0, 1.0e-14); - } + for (std::size_t row = 0; row < 3U; ++row) { + for (std::size_t column = 0; column < 3U; ++column) { + double dot = 0.0; + for (std::size_t component = 0; component < 3U; ++component) { + dot += rotation[row][component] * rotation[column][component]; + } + EXPECT_NEAR(dot, row == column ? 1.0 : 0.0, 1.0e-14); } - EXPECT_NEAR(determinant(rotation), 1.0, 1.0e-14); + } + EXPECT_NEAR(Determinant(rotation), 1.0, 1.0e-14); - const auto section = makeSection({-2.0, 2.0, 0.0}); - const auto material = makeMaterial(); - const auto beam = requireBeam( - makeNode({1.0, -2.0, 0.5}, 1U), - makeNode({3.0, 0.0, 1.5}, 2U), - section, - material); - const Matrix local = beam.localStiffness(); - const Matrix global = beam.globalStiffness(); + const auto section = MakeSection({-2.0, 2.0, 0.0}); + const auto material = MakeMaterial(); + const auto beam = + RequireBeam(MakeNode({1.0, -2.0, 0.5}, 1U), MakeNode({3.0, 0.0, 1.5}, 2U), + section, material); + const Matrix local = beam.LocalStiffness(); + const Matrix global = beam.GlobalStiffness(); - Matrix expectedGlobal{kElementDofCount, kElementDofCount}; - const Matrix localTimesTransform = local.Multiply(transformation); - 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) { - expectedGlobal(row, column) += - transformation(inner, row) * localTimesTransform(inner, column); - } - } + Matrix expected_global{kElementDofCount, kElementDofCount}; + const Matrix local_times_transform = local.Multiply(transformation); + 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) { + expected_global(row, column) += + transformation(inner, row) * local_times_transform(inner, column); + } } - EXPECT_LE(normalizedMatrixError(global, expectedGlobal), kMatrixTolerance); + } + EXPECT_LE(NormalizedMatrixError(global, expected_global), kMatrixTolerance); - Vector localDisplacement{kElementDofCount}; - for (std::size_t index = 0; index < localDisplacement.Size(); ++index) { - localDisplacement[index] = 0.01 * static_cast(index + 1U) - 0.04; - } - const Vector globalDisplacement = transposeMultiply(transformation, localDisplacement); - const Vector localForce = local.Multiply(localDisplacement); - const Vector globalForce = global.Multiply(globalDisplacement); - const Vector expectedGlobalForce = transposeMultiply(transformation, localForce); - for (std::size_t index = 0; index < kElementDofCount; ++index) { - expectScaledNear(globalForce[index], expectedGlobalForce[index], kMatrixTolerance); - } - expectScaledNear( - quadraticEnergy(global, globalDisplacement), - quadraticEnergy(local, localDisplacement), - kMatrixTolerance); + Vector local_displacement{kElementDofCount}; + for (std::size_t index = 0; index < local_displacement.Size(); ++index) { + local_displacement[index] = 0.01 * static_cast(index + 1U) - 0.04; + } + const Vector global_displacement = + TransposeMultiply(transformation, local_displacement); + const Vector local_force = local.Multiply(local_displacement); + const Vector global_force = global.Multiply(global_displacement); + const Vector expected_global_force = + TransposeMultiply(transformation, local_force); + for (std::size_t index = 0; index < kElementDofCount; ++index) { + ExpectScaledNear(global_force[index], expected_global_force[index], + kMatrixTolerance); + } + ExpectScaledNear(QuadraticEnergy(global, global_displacement), + QuadraticEnergy(local, local_displacement), + kMatrixTolerance); - Vector globalVariation{kElementDofCount}; - for (std::size_t index = 0; index < globalVariation.Size(); ++index) { - globalVariation[index] = 0.03 - 0.002 * static_cast(index); - } - const Vector localVariation = transformation.Multiply(globalVariation); - expectScaledNear( - globalVariation.Dot(globalForce), - localVariation.Dot(localForce), - kMatrixTolerance); + Vector global_variation{kElementDofCount}; + for (std::size_t index = 0; index < global_variation.Size(); ++index) { + global_variation[index] = 0.03 - 0.002 * static_cast(index); + } + const Vector local_variation = transformation.Multiply(global_variation); + ExpectScaledNear(global_variation.Dot(global_force), + local_variation.Dot(local_force), kMatrixTolerance); - const BeamRecovery recovery = beam.recover(globalDisplacement); - EXPECT_NEAR( - recovery.gaussGeneralizedStrains[0U][0U], - (localDisplacement[6U] - localDisplacement[0U]) / 3.0, - 1.0e-14); + const BeamRecovery recovery = beam.Recover(global_displacement); + EXPECT_NEAR(recovery.gauss_generalized_strains[0U][0U], + (local_displacement[6U] - local_displacement[0U]) / 3.0, 1.0e-14); } TEST(EulerBeam3D, ConstantLineLoadMatchesAllSignedComponents) { - const double length = 4.0; - const ConstantLocalLineLoad load{2.5, -3.0, 5.5, -7.0}; - const Vector equivalent = alignedBeam(length).localEquivalentLoad(load); - const std::array expected = { - 5.0, -6.0, 11.0, -14.0, -22.0 / 3.0, -4.0, - 5.0, -6.0, 11.0, -14.0, 22.0 / 3.0, 4.0}; - ASSERT_EQ(equivalent.Size(), expected.size()); - for (std::size_t index = 0; index < expected.size(); ++index) { - expectScaledNear(equivalent[index], expected[index], kMatrixTolerance); - } + const double length = 4.0; + const ConstantLocalLineLoad load{2.5, -3.0, 5.5, -7.0}; + const Vector equivalent = AlignedBeam(length).LocalEquivalentLoad(load); + const std::array expected = { + 5.0, -6.0, 11.0, -14.0, -22.0 / 3.0, -4.0, + 5.0, -6.0, 11.0, -14.0, 22.0 / 3.0, 4.0}; + ASSERT_EQ(equivalent.Size(), expected.size()); + for (std::size_t index = 0; index < expected.size(); ++index) { + ExpectScaledNear(equivalent[index], expected[index], kMatrixTolerance); + } - auto convergenceSection = makeSection(); - convergenceSection.area = 1.0; - convergenceSection.i11 = 1.0; - convergenceSection.i22 = 1.0; - convergenceSection.torsionalConstant = 1.0; - const auto convergenceMaterial = makeMaterial(5.0, 0.25); - const double transverseLoad = -3.0; - const std::array elementCounts = {1U, 2U, 4U}; - std::array relativeErrors{}; - for (std::size_t mesh = 0; mesh < elementCounts.size(); ++mesh) { - const double elementLength = - length / static_cast(elementCounts[mesh]); - const Vector elementLoad = alignedBeam( - elementLength, - convergenceSection, - convergenceMaterial) - .localEquivalentLoad( - {0.0, transverseLoad, 0.0, 0.0}); - expectRelativeNear( - elementLoad[1U], transverseLoad * elementLength / 2.0, kMatrixTolerance); - expectRelativeNear( - elementLoad[5U], - transverseLoad * elementLength * elementLength / 12.0, - kMatrixTolerance); - expectRelativeNear( - elementLoad[7U], transverseLoad * elementLength / 2.0, kMatrixTolerance); - expectRelativeNear( - elementLoad[11U], - -transverseLoad * elementLength * elementLength / 12.0, - kMatrixTolerance); + auto convergence_section = MakeSection(); + convergence_section.area = 1.0; + convergence_section.i11 = 1.0; + convergence_section.i22 = 1.0; + convergence_section.torsional_constant = 1.0; + const auto convergence_material = MakeMaterial(5.0, 0.25); + const double transverse_load = -3.0; + const std::array element_counts = {1U, 2U, 4U}; + std::array relative_errors{}; + for (std::size_t mesh = 0; mesh < element_counts.size(); ++mesh) { + const double element_length = + length / static_cast(element_counts[mesh]); + const Vector element_load = + AlignedBeam(element_length, convergence_section, convergence_material) + .LocalEquivalentLoad({0.0, transverse_load, 0.0, 0.0}); + ExpectRelativeNear(element_load[1U], transverse_load * element_length / 2.0, + kMatrixTolerance); + ExpectRelativeNear(element_load[5U], + transverse_load * element_length * element_length / 12.0, + kMatrixTolerance); + ExpectRelativeNear(element_load[7U], transverse_load * element_length / 2.0, + kMatrixTolerance); + ExpectRelativeNear( + element_load[11U], + -transverse_load * element_length * element_length / 12.0, + kMatrixTolerance); - const Vector nodalDisplacement = solveUniformTransverseCantilever( - elementCounts[mesh], - length, - transverseLoad, - convergenceSection, - convergenceMaterial); - relativeErrors[mesh] = uniformLoadInteriorDisplacementError( - nodalDisplacement, - elementCounts[mesh], - length, - transverseLoad, - convergenceMaterial.youngsModulus * convergenceSection.i22); - } - EXPECT_GT(relativeErrors[0U], relativeErrors[1U]); - EXPECT_GT(relativeErrors[1U], relativeErrors[2U]); - EXPECT_NEAR( - std::log(relativeErrors[0U] / relativeErrors[1U]) / std::log(2.0), - 4.0, - 1.0e-8); - EXPECT_NEAR( - std::log(relativeErrors[1U] / relativeErrors[2U]) / std::log(2.0), - 4.0, - 1.0e-8); + const Vector nodal_displacement = SolveUniformTransverseCantilever( + element_counts[mesh], length, transverse_load, convergence_section, + convergence_material); + relative_errors[mesh] = UniformLoadInteriorDisplacementError( + nodal_displacement, element_counts[mesh], length, transverse_load, + convergence_material.youngs_modulus * convergence_section.i22); + } + EXPECT_GT(relative_errors[0U], relative_errors[1U]); + EXPECT_GT(relative_errors[1U], relative_errors[2U]); + EXPECT_NEAR( + std::log(relative_errors[0U] / relative_errors[1U]) / std::log(2.0), 4.0, + 1.0e-8); + EXPECT_NEAR( + std::log(relative_errors[1U] / relative_errors[2U]) / std::log(2.0), 4.0, + 1.0e-8); } TEST(EulerBeam3D, AnalyticalAxialTorsionAndTwoPlaneBendingRecover) { - const double length = 3.0; - const auto section = makeSection(); - const auto material = makeMaterial(); - const auto beam = alignedBeam(length, section, material); - const Matrix stiffness = beam.localStiffness(); - const double shearModulus = - material.youngsModulus / (2.0 * (1.0 + material.poissonRatio)); + const double length = 3.0; + const auto section = MakeSection(); + const auto material = MakeMaterial(); + const auto beam = AlignedBeam(length, section, material); + const Matrix stiffness = beam.LocalStiffness(); + const double shear_modulus = + material.youngs_modulus / (2.0 * (1.0 + material.poisson_ratio)); - const double axialForce = 1250.0; - const Vector axial = solveFixedFirstNode(stiffness, {axialForce, 0.0, 0.0, 0.0, 0.0, 0.0}); - expectRelativeNear( - axial[6U], - axialForce * length / (material.youngsModulus * section.area), - kAnalyticalTolerance); - const BeamRecovery axialRecovery = beam.recover(axial); - expectRelativeNear( - axialRecovery.equilibriumEndActions[0U][0U], - -axialForce, - kAnalyticalTolerance); - expectRelativeNear( - axialRecovery.equilibriumEndActions[1U][0U], - axialForce, - kAnalyticalTolerance); - expectRelativeNear( - axialRecovery.endpointSectionResultants[0U][0U], - axialForce, - kAnalyticalTolerance); - expectRelativeNear( - axialRecovery.endpointSectionResultants[1U][0U], - axialForce, - kAnalyticalTolerance); + const double axial_force = 1250.0; + const Vector axial = + SolveFixedFirstNode(stiffness, {axial_force, 0.0, 0.0, 0.0, 0.0, 0.0}); + ExpectRelativeNear( + axial[6U], + axial_force * length / (material.youngs_modulus * section.area), + kAnalyticalTolerance); + const BeamRecovery axial_recovery = beam.Recover(axial); + ExpectRelativeNear(axial_recovery.equilibrium_end_actions[0U][0U], + -axial_force, kAnalyticalTolerance); + ExpectRelativeNear(axial_recovery.equilibrium_end_actions[1U][0U], + axial_force, kAnalyticalTolerance); + ExpectRelativeNear(axial_recovery.endpoint_section_resultants[0U][0U], + axial_force, kAnalyticalTolerance); + ExpectRelativeNear(axial_recovery.endpoint_section_resultants[1U][0U], + axial_force, kAnalyticalTolerance); - const double torque = -870.0; - const Vector torsion = solveFixedFirstNode(stiffness, {0.0, 0.0, 0.0, torque, 0.0, 0.0}); - expectRelativeNear( - torsion[9U], - torque * length / (shearModulus * section.torsionalConstant), - kAnalyticalTolerance); - const BeamRecovery torsionRecovery = beam.recover(torsion); - expectRelativeNear( - torsionRecovery.equilibriumEndActions[0U][3U], - -torque, - kAnalyticalTolerance); - expectRelativeNear( - torsionRecovery.equilibriumEndActions[1U][3U], - torque, - kAnalyticalTolerance); - expectRelativeNear( - torsionRecovery.endpointSectionResultants[0U][1U], - torque, - kAnalyticalTolerance); + const double torque = -870.0; + const Vector torsion = + SolveFixedFirstNode(stiffness, {0.0, 0.0, 0.0, torque, 0.0, 0.0}); + ExpectRelativeNear( + torsion[9U], + torque * length / (shear_modulus * section.torsional_constant), + kAnalyticalTolerance); + const BeamRecovery torsion_recovery = beam.Recover(torsion); + ExpectRelativeNear(torsion_recovery.equilibrium_end_actions[0U][3U], -torque, + kAnalyticalTolerance); + ExpectRelativeNear(torsion_recovery.equilibrium_end_actions[1U][3U], torque, + kAnalyticalTolerance); + ExpectRelativeNear(torsion_recovery.endpoint_section_resultants[0U][1U], + torque, kAnalyticalTolerance); - const double localYForce = 640.0; - const Vector localY = solveFixedFirstNode(stiffness, {0.0, localYForce, 0.0, 0.0, 0.0, 0.0}); - expectRelativeNear( - localY[7U], - localYForce * length * length * length / - (3.0 * material.youngsModulus * section.i22), - kAnalyticalTolerance); - expectRelativeNear( - localY[11U], - localYForce * length * length / - (2.0 * material.youngsModulus * section.i22), - kAnalyticalTolerance); - const BeamRecovery localYRecovery = beam.recover(localY); - expectRelativeNear( - localYRecovery.equilibriumEndActions[0U][1U], - -localYForce, - kAnalyticalTolerance); - expectRelativeNear( - localYRecovery.equilibriumEndActions[1U][1U], - localYForce, - kAnalyticalTolerance); - expectRelativeNear( - localYRecovery.equilibriumEndActions[0U][5U], - -localYForce * length, - kAnalyticalTolerance); - expectRelativeNear( - localYRecovery.endpointSectionResultants[0U][3U], - localYForce * length, - kAnalyticalTolerance); - EXPECT_NEAR(localYRecovery.endpointSectionResultants[1U][3U], 0.0, 1.0e-8); + const double local_yforce = 640.0; + const Vector local_y = + SolveFixedFirstNode(stiffness, {0.0, local_yforce, 0.0, 0.0, 0.0, 0.0}); + ExpectRelativeNear(local_y[7U], + local_yforce * length * length * length / + (3.0 * material.youngs_modulus * section.i22), + kAnalyticalTolerance); + ExpectRelativeNear(local_y[11U], + local_yforce * length * length / + (2.0 * material.youngs_modulus * section.i22), + kAnalyticalTolerance); + const BeamRecovery local_yrecovery = beam.Recover(local_y); + ExpectRelativeNear(local_yrecovery.equilibrium_end_actions[0U][1U], + -local_yforce, kAnalyticalTolerance); + ExpectRelativeNear(local_yrecovery.equilibrium_end_actions[1U][1U], + local_yforce, kAnalyticalTolerance); + ExpectRelativeNear(local_yrecovery.equilibrium_end_actions[0U][5U], + -local_yforce * length, kAnalyticalTolerance); + ExpectRelativeNear(local_yrecovery.endpoint_section_resultants[0U][3U], + local_yforce * length, kAnalyticalTolerance); + EXPECT_NEAR(local_yrecovery.endpoint_section_resultants[1U][3U], 0.0, 1.0e-8); - const double localZForce = -510.0; - const Vector localZ = solveFixedFirstNode(stiffness, {0.0, 0.0, localZForce, 0.0, 0.0, 0.0}); - expectRelativeNear( - localZ[8U], - localZForce * length * length * length / - (3.0 * material.youngsModulus * section.i11), - kAnalyticalTolerance); - expectRelativeNear( - localZ[10U], - -localZForce * length * length / - (2.0 * material.youngsModulus * section.i11), - kAnalyticalTolerance); - const BeamRecovery localZRecovery = beam.recover(localZ); - expectRelativeNear( - localZRecovery.equilibriumEndActions[0U][2U], - -localZForce, - kAnalyticalTolerance); - expectRelativeNear( - localZRecovery.equilibriumEndActions[1U][2U], - localZForce, - kAnalyticalTolerance); - expectRelativeNear( - localZRecovery.equilibriumEndActions[0U][4U], - localZForce * length, - kAnalyticalTolerance); - expectRelativeNear( - localZRecovery.endpointSectionResultants[0U][2U], - -localZForce * length, - kAnalyticalTolerance); - EXPECT_NEAR(localZRecovery.endpointSectionResultants[1U][2U], 0.0, 1.0e-8); + const double local_zforce = -510.0; + const Vector local_z = + SolveFixedFirstNode(stiffness, {0.0, 0.0, local_zforce, 0.0, 0.0, 0.0}); + ExpectRelativeNear(local_z[8U], + local_zforce * length * length * length / + (3.0 * material.youngs_modulus * section.i11), + kAnalyticalTolerance); + ExpectRelativeNear(local_z[10U], + -local_zforce * length * length / + (2.0 * material.youngs_modulus * section.i11), + kAnalyticalTolerance); + const BeamRecovery local_zrecovery = beam.Recover(local_z); + ExpectRelativeNear(local_zrecovery.equilibrium_end_actions[0U][2U], + -local_zforce, kAnalyticalTolerance); + ExpectRelativeNear(local_zrecovery.equilibrium_end_actions[1U][2U], + local_zforce, kAnalyticalTolerance); + ExpectRelativeNear(local_zrecovery.equilibrium_end_actions[0U][4U], + local_zforce * length, kAnalyticalTolerance); + ExpectRelativeNear(local_zrecovery.endpoint_section_resultants[0U][2U], + -local_zforce * length, kAnalyticalTolerance); + EXPECT_NEAR(local_zrecovery.endpoint_section_resultants[1U][2U], 0.0, 1.0e-8); } TEST(EulerBeam3D, RejectsInvalidGeometryAndProperties) { - const Node origin = makeNode({0.0, 0.0, 0.0}, 1U); - const Node unitX = makeNode({1.0, 0.0, 0.0}, 2U); - const auto validSection = makeSection(); - const auto validMaterial = makeMaterial(); + const Node origin = MakeNode({0.0, 0.0, 0.0}, 1U); + const Node unit_x = MakeNode({1.0, 0.0, 0.0}, 2U); + const auto valid_section = MakeSection(); + const auto valid_material = MakeMaterial(); - const auto expectFailure = [](const Result& result, - const std::string& code) { - if (result.HasValue()) { - const Matrix stiffness = result.Value().localStiffness(); - ADD_FAILURE() - << "Invalid fixture was accepted; local stiffness finite=" - << matrixIsFinite(stiffness) - << ", maximum absolute entry=" << maximumAbsoluteEntry(stiffness); - return; - } - EXPECT_EQ(result.GetStatus().Category(), FailureCategory::kModel); - ASSERT_EQ(result.GetStatus().Diagnostics().size(), 1U); - EXPECT_EQ(result.GetStatus().Diagnostics()[0U].code, code); - }; - - expectFailure( - EulerBeam3D::create(origin, origin, validSection, validMaterial), - "invalid-beam-length"); - expectFailure( - EulerBeam3D::create( - origin, - makeNode({1.0e-12, 0.0, 0.0}, 2U), - validSection, - validMaterial), - "invalid-beam-length"); - - const double coordinate = 1048576.0; - const Node scaledFirst = makeNode({coordinate, 0.0, 0.0}, 1U); - const Node belowThreshold = makeNode({coordinate + coordinate * 0.5e-12, 0.0, 0.0}, 2U); - const Node aboveThreshold = makeNode({coordinate + coordinate * 2.0e-12, 0.0, 0.0}, 2U); - expectFailure( - EulerBeam3D::create(scaledFirst, belowThreshold, validSection, validMaterial), - "invalid-beam-length"); - EXPECT_TRUE(EulerBeam3D::create( - scaledFirst, aboveThreshold, validSection, validMaterial) - .HasValue()); - - auto parallelGuide = validSection; - parallelGuide.firstAxis = {1.0, 0.0, 0.0}; - expectFailure( - EulerBeam3D::create(origin, unitX, parallelGuide, validMaterial), - "invalid-beam-guide-vector"); - auto guideAtThreshold = validSection; - guideAtThreshold.firstAxis = {1.0, 1.0e-12, 0.0}; - expectFailure( - EulerBeam3D::create(origin, unitX, guideAtThreshold, validMaterial), - "invalid-beam-guide-vector"); - auto guideAboveThreshold = validSection; - guideAboveThreshold.firstAxis = {1.0, 2.0e-12, 0.0}; - EXPECT_TRUE(EulerBeam3D::create(origin, unitX, guideAboveThreshold, validMaterial).HasValue()); - - auto invalidMaterial = validMaterial; - invalidMaterial.youngsModulus = 0.0; - expectFailure( - EulerBeam3D::create(origin, unitX, validSection, invalidMaterial), - "invalid-beam-property"); - invalidMaterial = validMaterial; - invalidMaterial.poissonRatio = -2.0; - expectFailure( - EulerBeam3D::create(origin, unitX, validSection, invalidMaterial), - "invalid-beam-property"); - - for (std::size_t property = 0; property < 4U; ++property) { - auto invalidSection = validSection; - double* properties[] = { - &invalidSection.area, - &invalidSection.i11, - &invalidSection.i22, - &invalidSection.torsionalConstant}; - *properties[property] = 0.0; - expectFailure( - EulerBeam3D::create(origin, unitX, invalidSection, validMaterial), - "invalid-beam-property"); + const auto expect_failure = [](const Result& result, + const std::string& code) { + if (result.HasValue()) { + const Matrix stiffness = result.Value().LocalStiffness(); + ADD_FAILURE() << "Invalid fixture was accepted; local stiffness finite=" + << MatrixIsFinite(stiffness) << ", maximum absolute entry=" + << MaximumAbsoluteEntry(stiffness); + return; } + EXPECT_EQ(result.GetStatus().Category(), FailureCategory::kModel); + ASSERT_EQ(result.GetStatus().Diagnostics().size(), 1U); + EXPECT_EQ(result.GetStatus().Diagnostics()[0U].code, code); + }; - auto overflowMaterial = makeMaterial( - std::numeric_limits::max() / 4.0, - 0.25); - auto overflowSection = validSection; - overflowSection.area = 8.0; - expectFailure( - EulerBeam3D::create(origin, unitX, overflowSection, overflowMaterial), + expect_failure( + EulerBeam3D::Create(origin, origin, valid_section, valid_material), + "invalid-beam-length"); + expect_failure(EulerBeam3D::Create(origin, MakeNode({1.0e-12, 0.0, 0.0}, 2U), + valid_section, valid_material), + "invalid-beam-length"); + + const double coordinate = 1048576.0; + const Node scaled_first = MakeNode({coordinate, 0.0, 0.0}, 1U); + const Node below_threshold = + MakeNode({coordinate + coordinate * 0.5e-12, 0.0, 0.0}, 2U); + const Node above_threshold = + MakeNode({coordinate + coordinate * 2.0e-12, 0.0, 0.0}, 2U); + expect_failure(EulerBeam3D::Create(scaled_first, below_threshold, + valid_section, valid_material), + "invalid-beam-length"); + EXPECT_TRUE(EulerBeam3D::Create(scaled_first, above_threshold, valid_section, + valid_material) + .HasValue()); + + auto parallel_guide = valid_section; + parallel_guide.first_axis = {1.0, 0.0, 0.0}; + expect_failure( + EulerBeam3D::Create(origin, unit_x, parallel_guide, valid_material), + "invalid-beam-guide-vector"); + auto guide_at_threshold = valid_section; + guide_at_threshold.first_axis = {1.0, 1.0e-12, 0.0}; + expect_failure( + EulerBeam3D::Create(origin, unit_x, guide_at_threshold, valid_material), + "invalid-beam-guide-vector"); + auto guide_above_threshold = valid_section; + guide_above_threshold.first_axis = {1.0, 2.0e-12, 0.0}; + EXPECT_TRUE( + EulerBeam3D::Create(origin, unit_x, guide_above_threshold, valid_material) + .HasValue()); + + auto invalid_material = valid_material; + invalid_material.youngs_modulus = 0.0; + expect_failure( + EulerBeam3D::Create(origin, unit_x, valid_section, invalid_material), + "invalid-beam-property"); + invalid_material = valid_material; + invalid_material.poisson_ratio = -2.0; + expect_failure( + EulerBeam3D::Create(origin, unit_x, valid_section, invalid_material), + "invalid-beam-property"); + + for (std::size_t property = 0; property < 4U; ++property) { + auto invalid_section = valid_section; + double* properties[] = {&invalid_section.area, &invalid_section.i11, + &invalid_section.i22, + &invalid_section.torsional_constant}; + *properties[property] = 0.0; + expect_failure( + EulerBeam3D::Create(origin, unit_x, invalid_section, valid_material), "invalid-beam-property"); + } - auto underflowSection = validSection; - underflowSection.area = std::numeric_limits::denorm_min(); - underflowSection.i11 = std::numeric_limits::denorm_min(); - underflowSection.i22 = std::numeric_limits::denorm_min(); - underflowSection.torsionalConstant = - std::numeric_limits::denorm_min(); - expectFailure( - EulerBeam3D::create( - origin, - unitX, - underflowSection, - makeMaterial(0.5, 0.25)), - "invalid-beam-property"); + auto overflow_material = + MakeMaterial(std::numeric_limits::max() / 4.0, 0.25); + auto overflow_section = valid_section; + overflow_section.area = 8.0; + expect_failure( + EulerBeam3D::Create(origin, unit_x, overflow_section, overflow_material), + "invalid-beam-property"); - auto lengthScaledSection = validSection; - lengthScaledSection.area = 1.0; - lengthScaledSection.i11 = 1.0; - lengthScaledSection.i22 = 1.0; - lengthScaledSection.torsionalConstant = 1.0; - expectFailure( - EulerBeam3D::create( - origin, - makeNode({1.0e103, 0.0, 0.0}, 2U), - lengthScaledSection, - makeMaterial(1.0, 0.25)), - "invalid-beam-property"); + auto underflow_section = valid_section; + underflow_section.area = std::numeric_limits::denorm_min(); + underflow_section.i11 = std::numeric_limits::denorm_min(); + underflow_section.i22 = std::numeric_limits::denorm_min(); + underflow_section.torsional_constant = + std::numeric_limits::denorm_min(); + expect_failure(EulerBeam3D::Create(origin, unit_x, underflow_section, + MakeMaterial(0.5, 0.25)), + "invalid-beam-property"); - auto coupledSection = validSection; - coupledSection.i12 = 1.0e-9; - expectFailure( - EulerBeam3D::create(origin, unitX, coupledSection, validMaterial), - "unsupported-coupled-section"); + auto length_scaled_section = valid_section; + length_scaled_section.area = 1.0; + length_scaled_section.i11 = 1.0; + length_scaled_section.i22 = 1.0; + length_scaled_section.torsional_constant = 1.0; + expect_failure( + EulerBeam3D::Create(origin, MakeNode({1.0e103, 0.0, 0.0}, 2U), + length_scaled_section, MakeMaterial(1.0, 0.25)), + "invalid-beam-property"); + + auto coupled_section = valid_section; + coupled_section.i12 = 1.0e-9; + expect_failure( + EulerBeam3D::Create(origin, unit_x, coupled_section, valid_material), + "unsupported-coupled-section"); } TEST(EulerBeam3D, RecoversSectionPointAndDefaultCentroidS11) { - const double length = 2.0; - const double epsilon = 0.01; - const double kappaY = 0.02; - const double kappaZ = -0.03; - const auto material = makeMaterial(); - auto section = makeSection({0.0, 1.0, 0.0}, {{0.25, -0.5}, {-0.4, 0.3}}); - const auto beam = alignedBeam(length, section, material); + const double length = 2.0; + const double epsilon = 0.01; + const double kappa_y = 0.02; + const double kappa_z = -0.03; + const auto material = MakeMaterial(); + auto section = MakeSection({0.0, 1.0, 0.0}, {{0.25, -0.5}, {-0.4, 0.3}}); + const auto beam = AlignedBeam(length, section, material); - Vector displacement{kElementDofCount}; - displacement[6U] = epsilon * length; - displacement[7U] = 0.5 * kappaZ * length * length; - displacement[8U] = -0.5 * kappaY * length * length; - displacement[10U] = kappaY * length; - displacement[11U] = kappaZ * length; + Vector displacement{kElementDofCount}; + displacement[6U] = epsilon * length; + displacement[7U] = 0.5 * kappa_z * length * length; + displacement[8U] = -0.5 * kappa_y * length * length; + displacement[10U] = kappa_y * length; + displacement[11U] = kappa_z * length; - const BeamRecovery recovery = beam.recover(displacement); - ASSERT_EQ(recovery.stressPoints.size(), 4U); - for (std::size_t gaussPoint = 0; gaussPoint < 2U; ++gaussPoint) { - for (std::size_t point = 0; point < section.sectionPoints.size(); ++point) { - const BeamStressPoint& stress = - recovery.stressPoints[gaussPoint * section.sectionPoints.size() + point]; - const double x1 = section.sectionPoints[point][0U]; - const double x2 = section.sectionPoints[point][1U]; - EXPECT_EQ(stress.gaussPoint, static_cast(gaussPoint + 1U)); - EXPECT_EQ(stress.sectionPoint, point + 1U); - EXPECT_DOUBLE_EQ(stress.x1, x1); - EXPECT_DOUBLE_EQ(stress.x2, x2); - expectScaledNear( - stress.s11, - material.youngsModulus * (epsilon + x2 * kappaY - x1 * kappaZ), - kMatrixTolerance); - } + const BeamRecovery recovery = beam.Recover(displacement); + ASSERT_EQ(recovery.stress_points.size(), 4U); + for (std::size_t gauss_point = 0; gauss_point < 2U; ++gauss_point) { + for (std::size_t point = 0; point < section.section_points.size(); + ++point) { + const BeamStressPoint& stress = + recovery.stress_points[gauss_point * section.section_points.size() + + point]; + const double x1 = section.section_points[point][0U]; + const double x2 = section.section_points[point][1U]; + EXPECT_EQ(stress.gauss_point, static_cast(gauss_point + 1U)); + EXPECT_EQ(stress.section_point, point + 1U); + EXPECT_DOUBLE_EQ(stress.x1, x1); + EXPECT_DOUBLE_EQ(stress.x2, x2); + ExpectScaledNear( + stress.s11, + material.youngs_modulus * (epsilon + x2 * kappa_y - x1 * kappa_z), + kMatrixTolerance); } + } - const auto defaultBeam = alignedBeam(length, makeSection(), material); - const BeamRecovery defaultRecovery = defaultBeam.recover(displacement); - ASSERT_EQ(defaultRecovery.stressPoints.size(), 2U); - for (std::size_t gaussPoint = 0; gaussPoint < 2U; ++gaussPoint) { - const BeamStressPoint& stress = defaultRecovery.stressPoints[gaussPoint]; - EXPECT_EQ(stress.gaussPoint, static_cast(gaussPoint + 1U)); - EXPECT_EQ(stress.sectionPoint, 0U); - EXPECT_DOUBLE_EQ(stress.x1, 0.0); - EXPECT_DOUBLE_EQ(stress.x2, 0.0); - EXPECT_EQ(stress.source, "fesa-default"); - expectScaledNear( - stress.s11, - material.youngsModulus * epsilon, - kMatrixTolerance); - } + const auto default_beam = AlignedBeam(length, MakeSection(), material); + const BeamRecovery default_recovery = default_beam.Recover(displacement); + ASSERT_EQ(default_recovery.stress_points.size(), 2U); + for (std::size_t gauss_point = 0; gauss_point < 2U; ++gauss_point) { + const BeamStressPoint& stress = default_recovery.stress_points[gauss_point]; + EXPECT_EQ(stress.gauss_point, static_cast(gauss_point + 1U)); + EXPECT_EQ(stress.section_point, 0U); + EXPECT_DOUBLE_EQ(stress.x1, 0.0); + EXPECT_DOUBLE_EQ(stress.x2, 0.0); + EXPECT_EQ(stress.source, "fesa-default"); + ExpectScaledNear(stress.s11, material.youngs_modulus * epsilon, + kMatrixTolerance); + } } TEST(EulerBeam3D, ReproducesConstantStrainTwistAndCurvaturePatches) { - const double length = 2.8; - const double epsilon = -0.014; - const double twist = 0.023; - const double kappaY = -0.031; - const double kappaZ = 0.047; - const auto section = makeSection(); - const auto material = makeMaterial(); - const double shearModulus = - material.youngsModulus / (2.0 * (1.0 + material.poissonRatio)); - const auto beam = alignedBeam(length, section, material); + const double length = 2.8; + const double epsilon = -0.014; + const double twist = 0.023; + const double kappa_y = -0.031; + const double kappa_z = 0.047; + const auto section = MakeSection(); + const auto material = MakeMaterial(); + const double shear_modulus = + material.youngs_modulus / (2.0 * (1.0 + material.poisson_ratio)); + const auto beam = AlignedBeam(length, section, material); - Vector displacement{kElementDofCount}; - displacement[6U] = epsilon * length; - displacement[7U] = 0.5 * kappaZ * length * length; - displacement[8U] = -0.5 * kappaY * length * length; - displacement[9U] = twist * length; - displacement[10U] = kappaY * length; - displacement[11U] = kappaZ * length; + Vector displacement{kElementDofCount}; + displacement[6U] = epsilon * length; + displacement[7U] = 0.5 * kappa_z * length * length; + displacement[8U] = -0.5 * kappa_y * length * length; + displacement[9U] = twist * length; + displacement[10U] = kappa_y * length; + displacement[11U] = kappa_z * length; - const std::array expectedStrain = {epsilon, twist, kappaY, kappaZ}; - const std::array expectedResultant = { - material.youngsModulus * section.area * epsilon, - shearModulus * section.torsionalConstant * twist, - material.youngsModulus * section.i11 * kappaY, - material.youngsModulus * section.i22 * kappaZ}; - const BeamRecovery recovery = beam.recover(displacement); - for (std::size_t point = 0; point < 2U; ++point) { - for (std::size_t component = 0; component < 4U; ++component) { - expectScaledNear( - recovery.gaussGeneralizedStrains[point][component], - expectedStrain[component], - kMatrixTolerance); - expectScaledNear( - recovery.gaussGeneralizedResultants[point][component], - expectedResultant[component], - kMatrixTolerance); - expectScaledNear( - recovery.endpointSectionResultants[point][component], - expectedResultant[component], - kMatrixTolerance); - } + const std::array expected_strain = {epsilon, twist, kappa_y, + kappa_z}; + const std::array expected_resultant = { + material.youngs_modulus * section.area * epsilon, + shear_modulus * section.torsional_constant * twist, + material.youngs_modulus * section.i11 * kappa_y, + material.youngs_modulus * section.i22 * kappa_z}; + const BeamRecovery recovery = beam.Recover(displacement); + for (std::size_t point = 0; point < 2U; ++point) { + for (std::size_t component = 0; component < 4U; ++component) { + ExpectScaledNear(recovery.gauss_generalized_strains[point][component], + expected_strain[component], kMatrixTolerance); + ExpectScaledNear(recovery.gauss_generalized_resultants[point][component], + expected_resultant[component], kMatrixTolerance); + ExpectScaledNear(recovery.endpoint_section_resultants[point][component], + expected_resultant[component], kMatrixTolerance); } + } - const std::array endActionComponents = {0U, 3U, 4U, 5U}; - for (std::size_t component = 0; component < expectedResultant.size(); ++component) { - expectScaledNear( - recovery.equilibriumEndActions[0U][endActionComponents[component]], - -expectedResultant[component], - kMatrixTolerance); - expectScaledNear( - recovery.equilibriumEndActions[1U][endActionComponents[component]], - expectedResultant[component], - kMatrixTolerance); - } - EXPECT_NEAR(recovery.equilibriumEndActions[0U][1U], 0.0, 1.0e-8); - EXPECT_NEAR(recovery.equilibriumEndActions[0U][2U], 0.0, 1.0e-8); - EXPECT_NEAR(recovery.equilibriumEndActions[1U][1U], 0.0, 1.0e-8); - EXPECT_NEAR(recovery.equilibriumEndActions[1U][2U], 0.0, 1.0e-8); + const std::array end_action_components = {0U, 3U, 4U, 5U}; + for (std::size_t component = 0; component < expected_resultant.size(); + ++component) { + ExpectScaledNear( + recovery.equilibrium_end_actions[0U][end_action_components[component]], + -expected_resultant[component], kMatrixTolerance); + ExpectScaledNear( + recovery.equilibrium_end_actions[1U][end_action_components[component]], + expected_resultant[component], kMatrixTolerance); + } + EXPECT_NEAR(recovery.equilibrium_end_actions[0U][1U], 0.0, 1.0e-8); + EXPECT_NEAR(recovery.equilibrium_end_actions[0U][2U], 0.0, 1.0e-8); + EXPECT_NEAR(recovery.equilibrium_end_actions[1U][1U], 0.0, 1.0e-8); + EXPECT_NEAR(recovery.equilibrium_end_actions[1U][2U], 0.0, 1.0e-8); } TEST(EulerBeam3D, OnePointNegativeControlHasRankFour) { - const double length = 3.7; - auto section = makeSection(); - section.area = 1.0; - section.i11 = 0.7; - section.i22 = 1.2; - section.torsionalConstant = 0.9; - const auto material = makeMaterial(4.0, 0.25); + const double length = 3.7; + auto section = MakeSection(); + section.area = 1.0; + section.i11 = 0.7; + section.i22 = 1.2; + section.torsional_constant = 0.9; + const auto material = MakeMaterial(4.0, 0.25); - const Matrix onePoint = testOnlyOnePointStiffness(length, section, material); - const Matrix production = alignedBeam(length, section, material).localStiffness(); - EXPECT_EQ(symmetricRank(onePoint, kRigidTolerance), 4U); - EXPECT_EQ(symmetricRank(production, kRigidTolerance), 6U); + const Matrix one_point = TestOnlyOnePointStiffness(length, section, material); + const Matrix production = + AlignedBeam(length, section, material).LocalStiffness(); + EXPECT_EQ(SymmetricRank(one_point, kRigidTolerance), 4U); + EXPECT_EQ(SymmetricRank(production, kRigidTolerance), 6U); } -} // namespace -} // namespace fesa +} // namespace +} // namespace fesa diff --git a/tests/unit/elements/mitc4_shell_test.cpp b/tests/unit/elements/mitc4_shell_test.cpp index d9f543f..05b3955 100644 --- a/tests/unit/elements/mitc4_shell_test.cpp +++ b/tests/unit/elements/mitc4_shell_test.cpp @@ -1,4 +1,4 @@ -#include "fesa/elements/mitc4_shell.hpp" +#include "fesa/elements/mitc4_shell.h" #include @@ -16,921 +16,900 @@ namespace { using Vector3 = std::array; -fesa::Node node(std::int64_t label, Vector3 coordinates) { - return { - {"Shell-Instance", label, std::to_string(label)}, - coordinates, - {"mitc4-shell.inp", static_cast(label + 1)}}; +fesa::Node Node(std::int64_t label, Vector3 coordinates) { + return {{"Shell-Instance", label, std::to_string(label)}, + coordinates, + {"mitc4-shell.inp", static_cast(label + 1)}}; } -std::array nodePointers( +std::array NodePointers( const std::array& nodes) { - return {&nodes[0], &nodes[1], &nodes[2], &nodes[3]}; + return {&nodes[0], &nodes[1], &nodes[2], &nodes[3]}; } -fesa::ShellSection section(double thickness = 2.0) { - return {"Section", thickness, 0U, {"mitc4-shell.inp", 20U}}; +fesa::ShellSection Section(double thickness = 2.0) { + return {"Section", thickness, 0U, {"mitc4-shell.inp", 20U}}; } -fesa::LinearElasticMaterial material( - double youngsModulus = 120.0, - double poissonRatio = 0.25) { - return { - "Material", - youngsModulus, - poissonRatio, - {"mitc4-shell.inp", 21U}}; +fesa::LinearElasticMaterial Material(double youngs_modulus = 120.0, + double poisson_ratio = 0.25) { + return {"Material", youngs_modulus, poisson_ratio, {"mitc4-shell.inp", 21U}}; } -std::array directors(Vector3 director = {0.0, 0.0, 1.0}) { - return {director, director, director, director}; +std::array Directors(Vector3 director = {0.0, 0.0, 1.0}) { + return {director, director, director, director}; } -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 ExpectOrthonormalRightHanded(const fesa::Mitc4LocalFrame& frame) { + EXPECT_NEAR(Norm(frame.e1), 1.0, 1.0e-12); + EXPECT_NEAR(Norm(frame.e2), 1.0, 1.0e-12); + EXPECT_NEAR(Norm(frame.e3), 1.0, 1.0e-12); + EXPECT_NEAR(Dot(frame.e1, frame.e2), 0.0, 1.0e-12); + EXPECT_NEAR(Dot(frame.e1, frame.e3), 0.0, 1.0e-12); + EXPECT_NEAR(Dot(frame.e2, frame.e3), 0.0, 1.0e-12); + ExpectVectorNear(Cross(frame.e1, frame.e2), frame.e3); +} + +void ExpectMatrixNear(const fesa::Matrix& actual, const fesa::Matrix& expected, + double tolerance = 1.0e-12) { + ASSERT_EQ(actual.Rows(), expected.Rows()); + ASSERT_EQ(actual.Columns(), expected.Columns()); + for (std::size_t row = 0U; row < actual.Rows(); ++row) { + for (std::size_t column = 0U; column < actual.Columns(); ++column) { + EXPECT_NEAR(actual(row, column), expected(row, column), tolerance) + << "at (" << row << ", " << column << ")"; } + } } -void expectOrthonormalRightHanded(const fesa::Mitc4LocalFrame& frame) { - EXPECT_NEAR(norm(frame.e1), 1.0, 1.0e-12); - EXPECT_NEAR(norm(frame.e2), 1.0, 1.0e-12); - EXPECT_NEAR(norm(frame.e3), 1.0, 1.0e-12); - EXPECT_NEAR(dot(frame.e1, frame.e2), 0.0, 1.0e-12); - EXPECT_NEAR(dot(frame.e1, frame.e3), 0.0, 1.0e-12); - EXPECT_NEAR(dot(frame.e2, frame.e3), 0.0, 1.0e-12); - expectVectorNear(cross(frame.e1, frame.e2), frame.e3); +void ExpectSymmetric(const fesa::Matrix& matrix) { + ASSERT_EQ(matrix.Rows(), matrix.Columns()); + for (std::size_t row = 0U; row < matrix.Rows(); ++row) { + for (std::size_t column = 0U; column < matrix.Columns(); ++column) { + EXPECT_NEAR(matrix(row, column), matrix(column, row), 1.0e-12); + } + } } -void expectMatrixNear( - const fesa::Matrix& actual, - const fesa::Matrix& expected, - double tolerance = 1.0e-12) { - ASSERT_EQ(actual.Rows(), expected.Rows()); - ASSERT_EQ(actual.Columns(), expected.Columns()); - for (std::size_t row = 0U; row < actual.Rows(); ++row) { - for (std::size_t column = 0U; column < actual.Columns(); ++column) { - EXPECT_NEAR(actual(row, column), expected(row, column), tolerance) - << "at (" << row << ", " << column << ")"; +bool HasPositiveCholeskyPivots(const fesa::Matrix& matrix) { + if (matrix.Rows() != matrix.Columns()) { + return false; + } + fesa::Matrix lower{matrix.Rows(), matrix.Columns()}; + for (std::size_t row = 0U; row < matrix.Rows(); ++row) { + for (std::size_t column = 0U; column <= row; ++column) { + double value = matrix(row, column); + for (std::size_t inner = 0U; inner < column; ++inner) { + value -= lower(row, inner) * lower(column, inner); + } + if (row == column) { + if (!std::isfinite(value) || !(value > 0.0)) { + return false; } + lower(row, column) = std::sqrt(value); + } else { + lower(row, column) = value / lower(column, column); + } } + } + return true; } -void expectSymmetric(const fesa::Matrix& matrix) { - ASSERT_EQ(matrix.Rows(), matrix.Columns()); - for (std::size_t row = 0U; row < matrix.Rows(); ++row) { - for (std::size_t column = 0U; column < matrix.Columns(); ++column) { - EXPECT_NEAR(matrix(row, column), matrix(column, row), 1.0e-12); +double FrobeniusNorm(const fesa::Matrix& matrix) { + double squared_norm = 0.0; + for (std::size_t row = 0U; row < matrix.Rows(); ++row) { + for (std::size_t column = 0U; column < matrix.Columns(); ++column) { + squared_norm += matrix(row, column) * matrix(row, column); + } + } + return std::sqrt(squared_norm); +} + +double ScaledSymmetryError(const fesa::Matrix& matrix, + std::size_t dofs_per_node, double element_length) { + fesa::Matrix difference{matrix.Rows(), matrix.Columns()}; + fesa::Matrix scaled{matrix.Rows(), matrix.Columns()}; + for (std::size_t row = 0U; row < matrix.Rows(); ++row) { + const double row_scale = row % dofs_per_node < 3U ? element_length : 1.0; + for (std::size_t column = 0U; column < matrix.Columns(); ++column) { + const double column_scale = + column % dofs_per_node < 3U ? element_length : 1.0; + scaled(row, column) = row_scale * matrix(row, column) * column_scale; + difference(row, column) = row_scale * + (matrix(row, column) - matrix(column, row)) * + column_scale; + } + } + return FrobeniusNorm(difference) / FrobeniusNorm(scaled); +} + +fesa::Matrix ScaledStiffness(const fesa::Matrix& matrix, + std::size_t dofs_per_node, double element_length) { + fesa::Matrix scaled{matrix.Rows(), matrix.Columns()}; + for (std::size_t row = 0U; row < matrix.Rows(); ++row) { + const double row_scale = row % dofs_per_node < 3U ? element_length : 1.0; + for (std::size_t column = 0U; column < matrix.Columns(); ++column) { + const double column_scale = + column % dofs_per_node < 3U ? element_length : 1.0; + scaled(row, column) = row_scale * matrix(row, column) * column_scale; + } + } + return scaled; +} + +std::vector SymmetricEigenvalues(fesa::Matrix matrix) { + if (matrix.Rows() != matrix.Columns()) { + throw std::invalid_argument{ + "Symmetric eigensolve requires a square matrix."}; + } + const std::size_t size = matrix.Rows(); + double matrix_scale = 0.0; + for (std::size_t row = 0U; row < size; ++row) { + for (std::size_t column = 0U; column < size; ++column) { + matrix_scale = (std::max)(matrix_scale, std::abs(matrix(row, column))); + } + } + if (matrix_scale != 0.0) { + const double convergence_tolerance = 1.0e-14 * matrix_scale; + const std::size_t iteration_limit = 100U * size * size; + for (std::size_t iteration = 0U; iteration < iteration_limit; ++iteration) { + std::size_t pivot_row = 0U; + std::size_t pivot_column = 0U; + double largest_off_diagonal = 0.0; + for (std::size_t row = 0U; row < size; ++row) { + for (std::size_t column = row + 1U; column < size; ++column) { + const double candidate = std::abs(matrix(row, column)); + if (candidate > largest_off_diagonal) { + largest_off_diagonal = candidate; + pivot_row = row; + pivot_column = column; + } } - } -} + } + if (largest_off_diagonal <= convergence_tolerance) { + break; + } -bool hasPositiveCholeskyPivots(const fesa::Matrix& matrix) { - if (matrix.Rows() != matrix.Columns()) { - return false; - } - fesa::Matrix lower{matrix.Rows(), matrix.Columns()}; - for (std::size_t row = 0U; row < matrix.Rows(); ++row) { - for (std::size_t column = 0U; column <= row; ++column) { - double value = matrix(row, column); - for (std::size_t inner = 0U; inner < column; ++inner) { - value -= lower(row, inner) * lower(column, inner); - } - if (row == column) { - if (!std::isfinite(value) || !(value > 0.0)) { - return false; - } - lower(row, column) = std::sqrt(value); - } else { - lower(row, column) = value / lower(column, column); - } + const double pivot = matrix(pivot_row, pivot_column); + const double tau = + (matrix(pivot_column, pivot_column) - matrix(pivot_row, pivot_row)) / + (2.0 * pivot); + const double tangent = tau >= 0.0 + ? 1.0 / (tau + std::sqrt(1.0 + tau * tau)) + : -1.0 / (-tau + std::sqrt(1.0 + tau * tau)); + const double cosine = 1.0 / std::sqrt(1.0 + tangent * tangent); + const double sine = tangent * cosine; + const double row_diagonal = matrix(pivot_row, pivot_row); + const double column_diagonal = matrix(pivot_column, pivot_column); + matrix(pivot_row, pivot_row) = row_diagonal - tangent * pivot; + matrix(pivot_column, pivot_column) = column_diagonal + tangent * pivot; + matrix(pivot_row, pivot_column) = 0.0; + matrix(pivot_column, pivot_row) = 0.0; + for (std::size_t index = 0U; index < size; ++index) { + if (index == pivot_row || index == pivot_column) { + continue; } + const double row_value = matrix(index, pivot_row); + const double column_value = matrix(index, pivot_column); + const double rotated_row = cosine * row_value - sine * column_value; + const double rotated_column = sine * row_value + cosine * column_value; + matrix(index, pivot_row) = rotated_row; + matrix(pivot_row, index) = rotated_row; + matrix(index, pivot_column) = rotated_column; + matrix(pivot_column, index) = rotated_column; + } } - return true; + } + + std::vector eigenvalues(size); + for (std::size_t index = 0U; index < size; ++index) { + eigenvalues[index] = matrix(index, index); + } + return eigenvalues; } -double frobeniusNorm(const fesa::Matrix& matrix) { - double squaredNorm = 0.0; - for (std::size_t row = 0U; row < matrix.Rows(); ++row) { - for (std::size_t column = 0U; column < matrix.Columns(); ++column) { - squaredNorm += matrix(row, column) * matrix(row, column); - } - } - return std::sqrt(squaredNorm); +std::size_t NumericalRank(const fesa::Matrix& scaled_matrix) { + const auto eigenvalues = SymmetricEigenvalues(scaled_matrix); + double spectral_scale = 0.0; + for (double eigenvalue : eigenvalues) { + spectral_scale = (std::max)(spectral_scale, std::abs(eigenvalue)); + } + return static_cast( + std::count_if(eigenvalues.begin(), eigenvalues.end(), + [spectral_scale](double eigenvalue) { + return std::abs(eigenvalue) > 1.0e-9 * spectral_scale; + })); } -double scaledSymmetryError( - const fesa::Matrix& matrix, - std::size_t dofsPerNode, - double elementLength) { - fesa::Matrix difference{matrix.Rows(), matrix.Columns()}; - fesa::Matrix scaled{matrix.Rows(), matrix.Columns()}; - for (std::size_t row = 0U; row < matrix.Rows(); ++row) { - const double rowScale = row % dofsPerNode < 3U ? elementLength : 1.0; - for (std::size_t column = 0U; column < matrix.Columns(); ++column) { - const double columnScale = - column % dofsPerNode < 3U ? elementLength : 1.0; - scaled(row, column) = - rowScale * matrix(row, column) * columnScale; - difference(row, column) = rowScale * - (matrix(row, column) - matrix(column, row)) * columnScale; - } - } - return frobeniusNorm(difference) / frobeniusNorm(scaled); +double SymmetricOperatorNorm(const fesa::Matrix& matrix) { + const auto eigenvalues = SymmetricEigenvalues(matrix); + double result = 0.0; + for (double eigenvalue : eigenvalues) { + result = (std::max)(result, std::abs(eigenvalue)); + } + return result; } -fesa::Matrix scaledStiffness( - const fesa::Matrix& matrix, - std::size_t dofsPerNode, - double elementLength) { - fesa::Matrix scaled{matrix.Rows(), matrix.Columns()}; - for (std::size_t row = 0U; row < matrix.Rows(); ++row) { - const double rowScale = row % dofsPerNode < 3U ? elementLength : 1.0; - for (std::size_t column = 0U; column < matrix.Columns(); ++column) { - const double columnScale = - column % dofsPerNode < 3U ? elementLength : 1.0; - scaled(row, column) = - rowScale * matrix(row, column) * columnScale; - } - } - return scaled; +double QuadraticEnergy(const fesa::Matrix& stiffness, + const fesa::Vector& vector) { + return 0.5 * vector.Dot(stiffness.Multiply(vector)); } -std::vector symmetricEigenvalues(fesa::Matrix matrix) { - if (matrix.Rows() != matrix.Columns()) { - throw std::invalid_argument{"Symmetric eigensolve requires a square matrix."}; +fesa::Vector PhysicalField(const std::array, 4>& values) { + fesa::Vector result{20U}; + for (std::size_t node_index = 0U; node_index < 4U; ++node_index) { + for (std::size_t component = 0U; component < 5U; ++component) { + result[5U * node_index + component] = values[node_index][component]; } - const std::size_t size = matrix.Rows(); - double matrixScale = 0.0; - for (std::size_t row = 0U; row < size; ++row) { - for (std::size_t column = 0U; column < size; ++column) { - matrixScale = (std::max)(matrixScale, std::abs(matrix(row, column))); - } - } - if (matrixScale != 0.0) { - const double convergenceTolerance = 1.0e-14 * matrixScale; - const std::size_t iterationLimit = 100U * size * size; - for (std::size_t iteration = 0U; iteration < iterationLimit; ++iteration) { - std::size_t pivotRow = 0U; - std::size_t pivotColumn = 0U; - double largestOffDiagonal = 0.0; - for (std::size_t row = 0U; row < size; ++row) { - for (std::size_t column = row + 1U; column < size; ++column) { - const double candidate = std::abs(matrix(row, column)); - if (candidate > largestOffDiagonal) { - largestOffDiagonal = candidate; - pivotRow = row; - pivotColumn = column; - } - } - } - if (largestOffDiagonal <= convergenceTolerance) { - break; - } - - const double pivot = matrix(pivotRow, pivotColumn); - const double tau = - (matrix(pivotColumn, pivotColumn) - matrix(pivotRow, pivotRow)) / - (2.0 * pivot); - const double tangent = tau >= 0.0 - ? 1.0 / (tau + std::sqrt(1.0 + tau * tau)) - : -1.0 / (-tau + std::sqrt(1.0 + tau * tau)); - const double cosine = 1.0 / std::sqrt(1.0 + tangent * tangent); - const double sine = tangent * cosine; - const double rowDiagonal = matrix(pivotRow, pivotRow); - const double columnDiagonal = matrix(pivotColumn, pivotColumn); - matrix(pivotRow, pivotRow) = rowDiagonal - tangent * pivot; - matrix(pivotColumn, pivotColumn) = columnDiagonal + tangent * pivot; - matrix(pivotRow, pivotColumn) = 0.0; - matrix(pivotColumn, pivotRow) = 0.0; - for (std::size_t index = 0U; index < size; ++index) { - if (index == pivotRow || index == pivotColumn) { - continue; - } - const double rowValue = matrix(index, pivotRow); - const double columnValue = matrix(index, pivotColumn); - const double rotatedRow = cosine * rowValue - sine * columnValue; - const double rotatedColumn = sine * rowValue + cosine * columnValue; - matrix(index, pivotRow) = rotatedRow; - matrix(pivotRow, index) = rotatedRow; - matrix(index, pivotColumn) = rotatedColumn; - matrix(pivotColumn, index) = rotatedColumn; - } - } - } - - std::vector eigenvalues(size); - for (std::size_t index = 0U; index < size; ++index) { - eigenvalues[index] = matrix(index, index); - } - return eigenvalues; + } + return result; } -std::size_t numericalRank(const fesa::Matrix& scaledMatrix) { - const auto eigenvalues = symmetricEigenvalues(scaledMatrix); - double spectralScale = 0.0; - for (double eigenvalue : eigenvalues) { - spectralScale = (std::max)(spectralScale, std::abs(eigenvalue)); +void ExpectStrain(const fesa::Mitc4Shell& shell, const fesa::Vector& field, + double xi, double eta, double zeta, + const std::array& expected) { + const auto actual = shell.StrainDisplacement20(xi, eta, zeta).Multiply(field); + for (std::size_t component = 0U; component < expected.size(); ++component) { + EXPECT_NEAR(actual[component], expected[component], 1.0e-12) + << "component " << component; + } +} + +fesa::Vector PhysicalRigidMode(const std::array& nodes, + const Vector3& translation, + const Vector3& rotation) { + fesa::Vector mode{24U}; + for (std::size_t node_index = 0U; node_index < nodes.size(); ++node_index) { + const Vector3 rotational_translation = + Cross(rotation, nodes[node_index].coordinates); + const std::size_t offset = 6U * node_index; + for (std::size_t component = 0U; component < 3U; ++component) { + mode[offset + component] = + translation[component] + rotational_translation[component]; } - return static_cast(std::count_if( - eigenvalues.begin(), eigenvalues.end(), [spectralScale](double eigenvalue) { - return std::abs(eigenvalue) > 1.0e-9 * spectralScale; - })); + // Remove the director-parallel component: it is numerical drilling, + // not part of the five-DOF physical rigid motion. + mode[offset + 3U] = rotation[0]; + mode[offset + 4U] = rotation[1]; + mode[offset + 5U] = 0.0; + } + return mode; } -double symmetricOperatorNorm(const fesa::Matrix& matrix) { - const auto eigenvalues = symmetricEigenvalues(matrix); - double result = 0.0; - for (double eigenvalue : eigenvalues) { - result = (std::max)(result, std::abs(eigenvalue)); - } - return result; +std::array PlanarNodes() { + return {Node(1, {-1.0, -1.0, 0.0}), Node(2, {1.0, -1.0, 0.0}), + Node(3, {1.0, 1.0, 0.0}), Node(4, {-1.0, 1.0, 0.0})}; } -double quadraticEnergy(const fesa::Matrix& stiffness, const fesa::Vector& vector) { - return 0.5 * vector.Dot(stiffness.Multiply(vector)); -} - -fesa::Vector physicalField(const std::array, 4>& values) { - fesa::Vector result{20U}; - for (std::size_t nodeIndex = 0U; nodeIndex < 4U; ++nodeIndex) { - for (std::size_t component = 0U; component < 5U; ++component) { - result[5U * nodeIndex + component] = values[nodeIndex][component]; - } - } - return result; -} - -void expectStrain( - const fesa::Mitc4Shell& shell, - const fesa::Vector& field, - double xi, - double eta, - double zeta, - const std::array& expected) { - const auto actual = shell.strainDisplacement20(xi, eta, zeta).Multiply(field); - for (std::size_t component = 0U; component < expected.size(); ++component) { - EXPECT_NEAR(actual[component], expected[component], 1.0e-12) - << "component " << component; - } -} - -fesa::Vector physicalRigidMode( - const std::array& nodes, - const Vector3& translation, - const Vector3& rotation) { - fesa::Vector mode{24U}; - for (std::size_t nodeIndex = 0U; nodeIndex < nodes.size(); ++nodeIndex) { - const Vector3 rotationalTranslation = cross( - rotation, nodes[nodeIndex].coordinates); - const std::size_t offset = 6U * nodeIndex; - for (std::size_t component = 0U; component < 3U; ++component) { - mode[offset + component] = - translation[component] + rotationalTranslation[component]; - } - // Remove the director-parallel component: it is numerical drilling, - // not part of the five-DOF physical rigid motion. - mode[offset + 3U] = rotation[0]; - mode[offset + 4U] = rotation[1]; - mode[offset + 5U] = 0.0; - } - return mode; -} - -std::array planarNodes() { - return { - node(1, {-1.0, -1.0, 0.0}), - node(2, {1.0, -1.0, 0.0}), - node(3, {1.0, 1.0, 0.0}), - node(4, {-1.0, 1.0, 0.0})}; -} - -} // namespace +} // namespace // MITC4-KIN-001 TEST(Mitc4ShellKinematics, ShapeFunctionsSatisfyNodalAndDerivativeIdentities) { - constexpr std::array naturalNodes{ - Vector3{-1.0, -1.0, 0.0}, - Vector3{1.0, -1.0, 0.0}, - Vector3{1.0, 1.0, 0.0}, - Vector3{-1.0, 1.0, 0.0}}; + constexpr std::array kNaturalNodes{ + Vector3{-1.0, -1.0, 0.0}, Vector3{1.0, -1.0, 0.0}, Vector3{1.0, 1.0, 0.0}, + Vector3{-1.0, 1.0, 0.0}}; - for (std::size_t point = 0U; point < naturalNodes.size(); ++point) { - const auto shape = fesa::Mitc4Shell::shapeFunctions( - naturalNodes[point][0], naturalNodes[point][1]); - for (std::size_t nodeIndex = 0U; nodeIndex < naturalNodes.size(); ++nodeIndex) { - EXPECT_DOUBLE_EQ(shape.values[nodeIndex], point == nodeIndex ? 1.0 : 0.0); - } + for (std::size_t point = 0U; point < kNaturalNodes.size(); ++point) { + const auto shape = fesa::Mitc4Shell::ShapeFunctions( + kNaturalNodes[point][0], kNaturalNodes[point][1]); + for (std::size_t node_index = 0U; node_index < kNaturalNodes.size(); + ++node_index) { + EXPECT_DOUBLE_EQ(shape.values[node_index], + point == node_index ? 1.0 : 0.0); } + } - const auto shape = fesa::Mitc4Shell::shapeFunctions(0.25, -0.5); - double valueSum = 0.0; - double xiDerivativeSum = 0.0; - double etaDerivativeSum = 0.0; - for (std::size_t nodeIndex = 0U; nodeIndex < 4U; ++nodeIndex) { - valueSum += shape.values[nodeIndex]; - xiDerivativeSum += shape.xiDerivatives[nodeIndex]; - etaDerivativeSum += shape.etaDerivatives[nodeIndex]; - } - EXPECT_DOUBLE_EQ(valueSum, 1.0); - EXPECT_DOUBLE_EQ(xiDerivativeSum, 0.0); - EXPECT_DOUBLE_EQ(etaDerivativeSum, 0.0); - EXPECT_EQ( - shape.values, - (std::array{0.28125, 0.46875, 0.15625, 0.09375})); + const auto shape = fesa::Mitc4Shell::ShapeFunctions(0.25, -0.5); + double value_sum = 0.0; + double xi_derivative_sum = 0.0; + double eta_derivative_sum = 0.0; + for (std::size_t node_index = 0U; node_index < 4U; ++node_index) { + value_sum += shape.values[node_index]; + xi_derivative_sum += shape.xi_derivatives[node_index]; + eta_derivative_sum += shape.eta_derivatives[node_index]; + } + EXPECT_DOUBLE_EQ(value_sum, 1.0); + EXPECT_DOUBLE_EQ(xi_derivative_sum, 0.0); + EXPECT_DOUBLE_EQ(eta_derivative_sum, 0.0); + EXPECT_EQ(shape.values, + (std::array{0.28125, 0.46875, 0.15625, 0.09375})); } // MITC4-KIN-002 -TEST(Mitc4ShellKinematics, BuildsRightHandedFramesAndSeparatePhysicalDrillingMaps) { - const std::array nodes{ - node(1, {0.0, -1.0, -1.0}), - node(2, {0.0, 1.0, -1.0}), - node(3, {0.0, 1.0, 1.0}), - node(4, {0.0, -1.0, 1.0})}; - const auto candidate = fesa::Mitc4Shell::create( - nodePointers(nodes), directors({1.0, 0.0, 0.0}), section(), material()); - ASSERT_TRUE(candidate.HasValue()); - const auto& shell = candidate.Value(); +TEST(Mitc4ShellKinematics, + BuildsRightHandedFramesAndSeparatePhysicalDrillingMaps) { + const std::array nodes{ + Node(1, {0.0, -1.0, -1.0}), Node(2, {0.0, 1.0, -1.0}), + Node(3, {0.0, 1.0, 1.0}), Node(4, {0.0, -1.0, 1.0})}; + const auto candidate = fesa::Mitc4Shell::Create( + NodePointers(nodes), Directors({1.0, 0.0, 0.0}), Section(), Material()); + ASSERT_TRUE(candidate.HasValue()); + const auto& shell = candidate.Value(); - const auto frame = shell.localFrame(0.0, 0.0); - expectVectorNear(frame.e1, {0.0, 1.0, 0.0}); - expectVectorNear(frame.e2, {0.0, 0.0, 1.0}); - expectVectorNear(frame.e3, {1.0, 0.0, 0.0}); - expectOrthonormalRightHanded(frame); + const auto frame = shell.LocalFrame(0.0, 0.0); + ExpectVectorNear(frame.e1, {0.0, 1.0, 0.0}); + ExpectVectorNear(frame.e2, {0.0, 0.0, 1.0}); + ExpectVectorNear(frame.e3, {1.0, 0.0, 0.0}); + ExpectOrthonormalRightHanded(frame); - const auto physical = shell.physicalTransformation20(); - const auto drilling = shell.drillingTransformation4(); - ASSERT_EQ(physical.Rows(), 20U); - ASSERT_EQ(physical.Columns(), 24U); - ASSERT_EQ(drilling.Rows(), 4U); - ASSERT_EQ(drilling.Columns(), 24U); - for (std::size_t nodeIndex = 0U; nodeIndex < 4U; ++nodeIndex) { - const std::size_t physicalOffset = 5U * nodeIndex; - const std::size_t globalOffset = 6U * nodeIndex; - for (std::size_t component = 0U; component < 3U; ++component) { - EXPECT_DOUBLE_EQ( - physical(physicalOffset + component, globalOffset + component), - 1.0); - } - EXPECT_DOUBLE_EQ(physical(physicalOffset + 3U, globalOffset + 4U), 1.0); - EXPECT_DOUBLE_EQ(physical(physicalOffset + 4U, globalOffset + 5U), 1.0); - EXPECT_DOUBLE_EQ(drilling(nodeIndex, globalOffset + 3U), 1.0); - - for (std::size_t globalDof = 0U; globalDof < 24U; ++globalDof) { - if (globalDof != globalOffset + 4U) { - EXPECT_DOUBLE_EQ(physical(physicalOffset + 3U, globalDof), 0.0); - } - if (globalDof != globalOffset + 5U) { - EXPECT_DOUBLE_EQ(physical(physicalOffset + 4U, globalDof), 0.0); - } - if (globalDof != globalOffset + 3U) { - EXPECT_DOUBLE_EQ(drilling(nodeIndex, globalDof), 0.0); - } - } + const auto physical = shell.PhysicalTransformation20(); + const auto drilling = shell.DrillingTransformation4(); + ASSERT_EQ(physical.Rows(), 20U); + ASSERT_EQ(physical.Columns(), 24U); + ASSERT_EQ(drilling.Rows(), 4U); + ASSERT_EQ(drilling.Columns(), 24U); + for (std::size_t node_index = 0U; node_index < 4U; ++node_index) { + const std::size_t physical_offset = 5U * node_index; + const std::size_t global_offset = 6U * node_index; + for (std::size_t component = 0U; component < 3U; ++component) { + EXPECT_DOUBLE_EQ( + physical(physical_offset + component, global_offset + component), + 1.0); } + EXPECT_DOUBLE_EQ(physical(physical_offset + 3U, global_offset + 4U), 1.0); + EXPECT_DOUBLE_EQ(physical(physical_offset + 4U, global_offset + 5U), 1.0); + EXPECT_DOUBLE_EQ(drilling(node_index, global_offset + 3U), 1.0); - auto invalidDirectors = directors({1.0, 0.0, 0.0}); - invalidDirectors[2] = {0.0, 0.0, 0.0}; - EXPECT_FALSE(fesa::Mitc4Shell::create( - nodePointers(nodes), invalidDirectors, section(), material()) - .HasValue()); + for (std::size_t global_dof = 0U; global_dof < 24U; ++global_dof) { + if (global_dof != global_offset + 4U) { + EXPECT_DOUBLE_EQ(physical(physical_offset + 3U, global_dof), 0.0); + } + if (global_dof != global_offset + 5U) { + EXPECT_DOUBLE_EQ(physical(physical_offset + 4U, global_dof), 0.0); + } + if (global_dof != global_offset + 3U) { + EXPECT_DOUBLE_EQ(drilling(node_index, global_dof), 0.0); + } + } + } + + auto invalid_directors = Directors({1.0, 0.0, 0.0}); + invalid_directors[2] = {0.0, 0.0, 0.0}; + EXPECT_FALSE(fesa::Mitc4Shell::Create(NodePointers(nodes), invalid_directors, + Section(), Material()) + .HasValue()); } // MITC4-KIN-003 TEST(Mitc4ShellKinematics, FormsDirectColumnsAndAllCovariantTyingSamples) { - const auto nodes = planarNodes(); - const auto candidate = fesa::Mitc4Shell::create( - nodePointers(nodes), directors(), section(), material()); - ASSERT_TRUE(candidate.HasValue()); - const auto& shell = candidate.Value(); + const auto nodes = PlanarNodes(); + const auto candidate = fesa::Mitc4Shell::Create( + NodePointers(nodes), Directors(), Section(), Material()); + ASSERT_TRUE(candidate.HasValue()); + const auto& shell = candidate.Value(); - const auto direct = shell.directStrainDisplacement20(0.0, 0.0, 0.5); - ASSERT_EQ(direct.Rows(), 5U); - ASSERT_EQ(direct.Columns(), 20U); - EXPECT_DOUBLE_EQ(direct(0U, 0U), -0.25); - EXPECT_DOUBLE_EQ(direct(0U, 4U), -0.125); - EXPECT_DOUBLE_EQ(direct(1U, 1U), -0.25); - EXPECT_DOUBLE_EQ(direct(1U, 3U), 0.125); - EXPECT_DOUBLE_EQ(direct(2U, 0U), -0.25); - EXPECT_DOUBLE_EQ(direct(2U, 1U), -0.25); - EXPECT_DOUBLE_EQ(direct(2U, 3U), 0.125); - EXPECT_DOUBLE_EQ(direct(2U, 4U), -0.125); - EXPECT_DOUBLE_EQ(direct(3U, 2U), -0.25); - EXPECT_DOUBLE_EQ(direct(3U, 4U), 0.25); - EXPECT_DOUBLE_EQ(direct(4U, 2U), -0.25); - EXPECT_DOUBLE_EQ(direct(4U, 3U), -0.25); + const auto direct = shell.DirectStrainDisplacement20(0.0, 0.0, 0.5); + ASSERT_EQ(direct.Rows(), 5U); + ASSERT_EQ(direct.Columns(), 20U); + EXPECT_DOUBLE_EQ(direct(0U, 0U), -0.25); + EXPECT_DOUBLE_EQ(direct(0U, 4U), -0.125); + EXPECT_DOUBLE_EQ(direct(1U, 1U), -0.25); + EXPECT_DOUBLE_EQ(direct(1U, 3U), 0.125); + EXPECT_DOUBLE_EQ(direct(2U, 0U), -0.25); + EXPECT_DOUBLE_EQ(direct(2U, 1U), -0.25); + EXPECT_DOUBLE_EQ(direct(2U, 3U), 0.125); + EXPECT_DOUBLE_EQ(direct(2U, 4U), -0.125); + EXPECT_DOUBLE_EQ(direct(3U, 2U), -0.25); + EXPECT_DOUBLE_EQ(direct(3U, 4U), 0.25); + EXPECT_DOUBLE_EQ(direct(4U, 2U), -0.25); + EXPECT_DOUBLE_EQ(direct(4U, 3U), -0.25); - const auto samples = shell.covariantTyingShearSamples20(); - ASSERT_EQ(samples.Rows(), 4U); - ASSERT_EQ(samples.Columns(), 20U); - EXPECT_DOUBLE_EQ(samples(0U, 2U), -0.25); - EXPECT_DOUBLE_EQ(samples(0U, 4U), 0.25); - EXPECT_DOUBLE_EQ(samples(0U, 7U), 0.25); - EXPECT_DOUBLE_EQ(samples(0U, 9U), 0.25); - EXPECT_DOUBLE_EQ(samples(1U, 12U), 0.25); - EXPECT_DOUBLE_EQ(samples(1U, 14U), 0.25); - EXPECT_DOUBLE_EQ(samples(1U, 17U), -0.25); - EXPECT_DOUBLE_EQ(samples(1U, 19U), 0.25); - EXPECT_DOUBLE_EQ(samples(2U, 2U), -0.25); - EXPECT_DOUBLE_EQ(samples(2U, 3U), -0.25); - EXPECT_DOUBLE_EQ(samples(2U, 17U), 0.25); - EXPECT_DOUBLE_EQ(samples(2U, 18U), -0.25); - EXPECT_DOUBLE_EQ(samples(3U, 7U), -0.25); - EXPECT_DOUBLE_EQ(samples(3U, 8U), -0.25); - EXPECT_DOUBLE_EQ(samples(3U, 12U), 0.25); - EXPECT_DOUBLE_EQ(samples(3U, 13U), -0.25); + const auto samples = shell.CovariantTyingShearSamples20(); + ASSERT_EQ(samples.Rows(), 4U); + ASSERT_EQ(samples.Columns(), 20U); + EXPECT_DOUBLE_EQ(samples(0U, 2U), -0.25); + EXPECT_DOUBLE_EQ(samples(0U, 4U), 0.25); + EXPECT_DOUBLE_EQ(samples(0U, 7U), 0.25); + EXPECT_DOUBLE_EQ(samples(0U, 9U), 0.25); + EXPECT_DOUBLE_EQ(samples(1U, 12U), 0.25); + EXPECT_DOUBLE_EQ(samples(1U, 14U), 0.25); + EXPECT_DOUBLE_EQ(samples(1U, 17U), -0.25); + EXPECT_DOUBLE_EQ(samples(1U, 19U), 0.25); + EXPECT_DOUBLE_EQ(samples(2U, 2U), -0.25); + EXPECT_DOUBLE_EQ(samples(2U, 3U), -0.25); + EXPECT_DOUBLE_EQ(samples(2U, 17U), 0.25); + EXPECT_DOUBLE_EQ(samples(2U, 18U), -0.25); + EXPECT_DOUBLE_EQ(samples(3U, 7U), -0.25); + EXPECT_DOUBLE_EQ(samples(3U, 8U), -0.25); + EXPECT_DOUBLE_EQ(samples(3U, 12U), 0.25); + EXPECT_DOUBLE_EQ(samples(3U, 13U), -0.25); - const auto weights = fesa::Mitc4Shell::tyingWeights(0.25, -0.5); - EXPECT_EQ(weights.xiZeta, (std::array{0.75, 0.25})); - EXPECT_EQ(weights.etaZeta, (std::array{0.375, 0.625})); + const auto weights = fesa::Mitc4Shell::TyingWeights(0.25, -0.5); + EXPECT_EQ(weights.xi_zeta, (std::array{0.75, 0.25})); + EXPECT_EQ(weights.eta_zeta, (std::array{0.375, 0.625})); - const auto tied = shell.strainDisplacement20(0.0, 0.0, 0.0); - EXPECT_DOUBLE_EQ( - tied(3U, 4U), - 2.0 * (0.5 * samples(0U, 4U) + 0.5 * samples(1U, 4U))); - EXPECT_DOUBLE_EQ( - tied(4U, 3U), - 2.0 * (0.5 * samples(2U, 3U) + 0.5 * samples(3U, 3U))); + const auto tied = shell.StrainDisplacement20(0.0, 0.0, 0.0); + EXPECT_DOUBLE_EQ(tied(3U, 4U), + 2.0 * (0.5 * samples(0U, 4U) + 0.5 * samples(1U, 4U))); + EXPECT_DOUBLE_EQ(tied(4U, 3U), + 2.0 * (0.5 * samples(2U, 3U) + 0.5 * samples(3U, 3U))); } // MITC4-KIN-004 -TEST(Mitc4ShellConstitutive, BuildsExactPositiveDefiniteSectionMatricesAndRescalesUnits) { - const auto nodes = planarNodes(); - const auto candidate = fesa::Mitc4Shell::create( - nodePointers(nodes), directors(), section(), material()); - ASSERT_TRUE(candidate.HasValue()); - const auto& shell = candidate.Value(); +TEST(Mitc4ShellConstitutive, + BuildsExactPositiveDefiniteSectionMatricesAndRescalesUnits) { + const auto nodes = PlanarNodes(); + const auto candidate = fesa::Mitc4Shell::Create( + NodePointers(nodes), Directors(), Section(), Material()); + ASSERT_TRUE(candidate.HasValue()); + const auto& shell = candidate.Value(); - const auto cps = shell.planeStressConstitutive(); - const auto c5 = shell.materialConstitutive5(); - const auto a = shell.membraneSectionMatrix(); - const auto d = shell.bendingSectionMatrix(); - const auto as = shell.transverseShearSectionMatrix(); - EXPECT_EQ(cps.Rows(), 3U); - EXPECT_EQ(cps.Columns(), 3U); - EXPECT_EQ(c5.Rows(), 5U); - EXPECT_EQ(c5.Columns(), 5U); - EXPECT_EQ(a.Rows(), 3U); - EXPECT_EQ(d.Rows(), 3U); - EXPECT_EQ(as.Rows(), 2U); - EXPECT_DOUBLE_EQ(cps(0U, 0U), 128.0); - EXPECT_DOUBLE_EQ(cps(0U, 1U), 32.0); - EXPECT_DOUBLE_EQ(cps(2U, 2U), 48.0); - EXPECT_DOUBLE_EQ(c5(3U, 3U), 40.0); - EXPECT_DOUBLE_EQ(c5(4U, 4U), 40.0); - EXPECT_DOUBLE_EQ(a(0U, 0U), 256.0); - EXPECT_NEAR(d(0U, 0U), 256.0 / 3.0, 1.0e-12); - EXPECT_DOUBLE_EQ(as(0U, 0U), 80.0); - expectSymmetric(cps); - expectSymmetric(c5); - EXPECT_TRUE(hasPositiveCholeskyPivots(cps)); - EXPECT_TRUE(hasPositiveCholeskyPivots(c5)); - EXPECT_TRUE(hasPositiveCholeskyPivots(a)); - EXPECT_TRUE(hasPositiveCholeskyPivots(d)); - EXPECT_TRUE(hasPositiveCholeskyPivots(as)); + const auto cps = shell.PlaneStressConstitutive(); + const auto c5 = shell.MaterialConstitutive5(); + const auto a = shell.MembraneSectionMatrix(); + const auto d = shell.BendingSectionMatrix(); + const auto as = shell.TransverseShearSectionMatrix(); + EXPECT_EQ(cps.Rows(), 3U); + EXPECT_EQ(cps.Columns(), 3U); + EXPECT_EQ(c5.Rows(), 5U); + EXPECT_EQ(c5.Columns(), 5U); + EXPECT_EQ(a.Rows(), 3U); + EXPECT_EQ(d.Rows(), 3U); + EXPECT_EQ(as.Rows(), 2U); + EXPECT_DOUBLE_EQ(cps(0U, 0U), 128.0); + EXPECT_DOUBLE_EQ(cps(0U, 1U), 32.0); + EXPECT_DOUBLE_EQ(cps(2U, 2U), 48.0); + EXPECT_DOUBLE_EQ(c5(3U, 3U), 40.0); + EXPECT_DOUBLE_EQ(c5(4U, 4U), 40.0); + EXPECT_DOUBLE_EQ(a(0U, 0U), 256.0); + EXPECT_NEAR(d(0U, 0U), 256.0 / 3.0, 1.0e-12); + EXPECT_DOUBLE_EQ(as(0U, 0U), 80.0); + ExpectSymmetric(cps); + ExpectSymmetric(c5); + EXPECT_TRUE(HasPositiveCholeskyPivots(cps)); + EXPECT_TRUE(HasPositiveCholeskyPivots(c5)); + EXPECT_TRUE(HasPositiveCholeskyPivots(a)); + EXPECT_TRUE(HasPositiveCholeskyPivots(d)); + EXPECT_TRUE(HasPositiveCholeskyPivots(as)); - constexpr double forceScale = 7.0; - constexpr double lengthScale = 3.0; - const auto scaledCandidate = fesa::Mitc4Shell::create( - nodePointers(nodes), - directors(), - section(2.0 * lengthScale), - material(120.0 * forceScale / (lengthScale * lengthScale), 0.25)); - ASSERT_TRUE(scaledCandidate.HasValue()); - const auto& scaled = scaledCandidate.Value(); - fesa::Matrix expectedCps{3U, 3U}; - fesa::Matrix expectedC5{5U, 5U}; - fesa::Matrix expectedA{3U, 3U}; - fesa::Matrix expectedD{3U, 3U}; - fesa::Matrix expectedAs{2U, 2U}; - for (std::size_t row = 0U; row < 3U; ++row) { - for (std::size_t column = 0U; column < 3U; ++column) { - expectedCps(row, column) = - cps(row, column) * forceScale / (lengthScale * lengthScale); - expectedA(row, column) = a(row, column) * forceScale / lengthScale; - expectedD(row, column) = d(row, column) * forceScale * lengthScale; - } + constexpr double kForceScale = 7.0; + constexpr double kLengthScale = 3.0; + const auto scaled_candidate = fesa::Mitc4Shell::Create( + NodePointers(nodes), Directors(), Section(2.0 * kLengthScale), + Material(120.0 * kForceScale / (kLengthScale * kLengthScale), 0.25)); + ASSERT_TRUE(scaled_candidate.HasValue()); + const auto& scaled = scaled_candidate.Value(); + fesa::Matrix expected_cps{3U, 3U}; + fesa::Matrix expected_c5{5U, 5U}; + fesa::Matrix expected_a{3U, 3U}; + fesa::Matrix expected_d{3U, 3U}; + fesa::Matrix expected_as{2U, 2U}; + for (std::size_t row = 0U; row < 3U; ++row) { + for (std::size_t column = 0U; column < 3U; ++column) { + expected_cps(row, column) = + cps(row, column) * kForceScale / (kLengthScale * kLengthScale); + expected_a(row, column) = a(row, column) * kForceScale / kLengthScale; + expected_d(row, column) = d(row, column) * kForceScale * kLengthScale; } - for (std::size_t row = 0U; row < 2U; ++row) { - for (std::size_t column = 0U; column < 2U; ++column) { - expectedAs(row, column) = as(row, column) * forceScale / lengthScale; - } + } + for (std::size_t row = 0U; row < 2U; ++row) { + for (std::size_t column = 0U; column < 2U; ++column) { + expected_as(row, column) = as(row, column) * kForceScale / kLengthScale; } - for (std::size_t row = 0U; row < 5U; ++row) { - for (std::size_t column = 0U; column < 5U; ++column) { - expectedC5(row, column) = - c5(row, column) * forceScale / (lengthScale * lengthScale); - } + } + for (std::size_t row = 0U; row < 5U; ++row) { + for (std::size_t column = 0U; column < 5U; ++column) { + expected_c5(row, column) = + c5(row, column) * kForceScale / (kLengthScale * kLengthScale); } - expectMatrixNear(scaled.planeStressConstitutive(), expectedCps); - expectMatrixNear(scaled.materialConstitutive5(), expectedC5); - expectMatrixNear(scaled.membraneSectionMatrix(), expectedA); - expectMatrixNear(scaled.bendingSectionMatrix(), expectedD); - expectMatrixNear(scaled.transverseShearSectionMatrix(), expectedAs); + } + ExpectMatrixNear(scaled.PlaneStressConstitutive(), expected_cps); + ExpectMatrixNear(scaled.MaterialConstitutive5(), expected_c5); + ExpectMatrixNear(scaled.MembraneSectionMatrix(), expected_a); + ExpectMatrixNear(scaled.BendingSectionMatrix(), expected_d); + ExpectMatrixNear(scaled.TransverseShearSectionMatrix(), expected_as); - EXPECT_FALSE(fesa::Mitc4Shell::create( - nodePointers(nodes), directors(), section(0.0), material()) - .HasValue()); - EXPECT_FALSE(fesa::Mitc4Shell::create( - nodePointers(nodes), directors(), section(), material(0.0, 0.25)) - .HasValue()); - EXPECT_FALSE(fesa::Mitc4Shell::create( - nodePointers(nodes), directors(), section(), material(120.0, 0.5)) - .HasValue()); + EXPECT_FALSE(fesa::Mitc4Shell::Create(NodePointers(nodes), Directors(), + Section(0.0), Material()) + .HasValue()); + EXPECT_FALSE(fesa::Mitc4Shell::Create(NodePointers(nodes), Directors(), + Section(), Material(0.0, 0.25)) + .HasValue()); + EXPECT_FALSE(fesa::Mitc4Shell::Create(NodePointers(nodes), Directors(), + Section(), Material(120.0, 0.5)) + .HasValue()); } // MITC4-KIN-005 TEST(Mitc4ShellKinematics, UsesOneFixedTwoByTwoByTwoQuadratureOrder) { - const auto& points = fesa::Mitc4Shell::volumeQuadrature(); - ASSERT_EQ(points.size(), 8U); - const double g = 1.0 / std::sqrt(3.0); - const std::array expected{ - Vector3{-g, -g, -g}, Vector3{-g, -g, g}, - Vector3{g, -g, -g}, Vector3{g, -g, g}, - Vector3{g, g, -g}, Vector3{g, g, g}, - Vector3{-g, g, -g}, Vector3{-g, g, g}}; - for (std::size_t point = 0U; point < points.size(); ++point) { - EXPECT_EQ(points[point].naturalCoordinates, expected[point]); - EXPECT_DOUBLE_EQ(points[point].weight, 1.0); - } + const auto& points = fesa::Mitc4Shell::VolumeQuadrature(); + ASSERT_EQ(points.size(), 8U); + const double g = 1.0 / std::sqrt(3.0); + const std::array expected{Vector3{-g, -g, -g}, Vector3{-g, -g, g}, + Vector3{g, -g, -g}, Vector3{g, -g, g}, + Vector3{g, g, -g}, Vector3{g, g, g}, + Vector3{-g, g, -g}, Vector3{-g, g, g}}; + for (std::size_t point = 0U; point < points.size(); ++point) { + EXPECT_EQ(points[point].natural_coordinates, expected[point]); + EXPECT_DOUBLE_EQ(points[point].weight, 1.0); + } } // MITC4-KERNEL-001 -TEST(Mitc4ShellKernel, FormsFiniteScaledSymmetricPhysicalAndStabilizedStiffness) { - const auto nodes = planarNodes(); - const auto shellCandidate = fesa::Mitc4Shell::create( - nodePointers(nodes), directors(), section(), material()); - ASSERT_TRUE(shellCandidate.HasValue()); +TEST(Mitc4ShellKernel, + FormsFiniteScaledSymmetricPhysicalAndStabilizedStiffness) { + const auto nodes = PlanarNodes(); + const auto shell_candidate = fesa::Mitc4Shell::Create( + NodePointers(nodes), Directors(), Section(), Material()); + ASSERT_TRUE(shell_candidate.HasValue()); - const auto stiffnessCandidate = shellCandidate.Value().stiffness(); - ASSERT_TRUE(stiffnessCandidate.HasValue()); - const auto& stiffness = stiffnessCandidate.Value(); - EXPECT_EQ(stiffness.physicalLocal20.Rows(), 20U); - EXPECT_EQ(stiffness.physicalLocal20.Columns(), 20U); - EXPECT_EQ(stiffness.physicalGlobal24.Rows(), 24U); - EXPECT_EQ(stiffness.drillingGlobal24.Rows(), 24U); - EXPECT_EQ(stiffness.stabilizedGlobal24.Rows(), 24U); - for (const fesa::Matrix* matrix : { - &stiffness.physicalLocal20, - &stiffness.physicalGlobal24, - &stiffness.drillingGlobal24, - &stiffness.stabilizedGlobal24}) { - for (std::size_t row = 0U; row < matrix->Rows(); ++row) { - for (std::size_t column = 0U; column < matrix->Columns(); ++column) { - EXPECT_TRUE(std::isfinite((*matrix)(row, column))); - } - } + const auto stiffness_candidate = shell_candidate.Value().Stiffness(); + ASSERT_TRUE(stiffness_candidate.HasValue()); + const auto& stiffness = stiffness_candidate.Value(); + EXPECT_EQ(stiffness.physical_local20.Rows(), 20U); + EXPECT_EQ(stiffness.physical_local20.Columns(), 20U); + EXPECT_EQ(stiffness.physical_global24.Rows(), 24U); + EXPECT_EQ(stiffness.drilling_global24.Rows(), 24U); + EXPECT_EQ(stiffness.stabilized_global24.Rows(), 24U); + for (const fesa::Matrix* matrix : + {&stiffness.physical_local20, &stiffness.physical_global24, + &stiffness.drilling_global24, &stiffness.stabilized_global24}) { + for (std::size_t row = 0U; row < matrix->Rows(); ++row) { + for (std::size_t column = 0U; column < matrix->Columns(); ++column) { + EXPECT_TRUE(std::isfinite((*matrix)(row, column))); + } } - EXPECT_LE(scaledSymmetryError(stiffness.physicalLocal20, 5U, 2.0), 1.0e-12); - EXPECT_LE(scaledSymmetryError(stiffness.physicalGlobal24, 6U, 2.0), 1.0e-12); - EXPECT_LE(scaledSymmetryError(stiffness.drillingGlobal24, 6U, 2.0), 1.0e-12); - EXPECT_LE(scaledSymmetryError(stiffness.stabilizedGlobal24, 6U, 2.0), 1.0e-12); + } + EXPECT_LE(ScaledSymmetryError(stiffness.physical_local20, 5U, 2.0), 1.0e-12); + EXPECT_LE(ScaledSymmetryError(stiffness.physical_global24, 6U, 2.0), 1.0e-12); + EXPECT_LE(ScaledSymmetryError(stiffness.drilling_global24, 6U, 2.0), 1.0e-12); + EXPECT_LE(ScaledSymmetryError(stiffness.stabilized_global24, 6U, 2.0), + 1.0e-12); - const auto repeatedCandidate = shellCandidate.Value().stiffness(); - ASSERT_TRUE(repeatedCandidate.HasValue()); - const auto& repeated = repeatedCandidate.Value(); - expectMatrixNear(repeated.physicalLocal20, stiffness.physicalLocal20, 0.0); - expectMatrixNear(repeated.physicalGlobal24, stiffness.physicalGlobal24, 0.0); - expectMatrixNear(repeated.drillingGlobal24, stiffness.drillingGlobal24, 0.0); - expectMatrixNear(repeated.stabilizedGlobal24, stiffness.stabilizedGlobal24, 0.0); - EXPECT_DOUBLE_EQ(repeated.drillingStiffness, stiffness.drillingStiffness); + const auto repeated_candidate = shell_candidate.Value().Stiffness(); + ASSERT_TRUE(repeated_candidate.HasValue()); + const auto& repeated = repeated_candidate.Value(); + ExpectMatrixNear(repeated.physical_local20, stiffness.physical_local20, 0.0); + ExpectMatrixNear(repeated.physical_global24, stiffness.physical_global24, + 0.0); + ExpectMatrixNear(repeated.drilling_global24, stiffness.drilling_global24, + 0.0); + ExpectMatrixNear(repeated.stabilized_global24, stiffness.stabilized_global24, + 0.0); + EXPECT_DOUBLE_EQ(repeated.drilling_stiffness, stiffness.drilling_stiffness); } // MITC4-KERNEL-002 -TEST(Mitc4ShellKernel, PreservesPhysicalEnergyUnderTwentyToTwentyFourCongruence) { - const auto nodes = planarNodes(); - const auto shellCandidate = fesa::Mitc4Shell::create( - nodePointers(nodes), directors(), section(), material()); - ASSERT_TRUE(shellCandidate.HasValue()); - const auto stiffnessCandidate = shellCandidate.Value().stiffness(); - ASSERT_TRUE(stiffnessCandidate.HasValue()); - const auto& stiffness = stiffnessCandidate.Value(); +TEST(Mitc4ShellKernel, + PreservesPhysicalEnergyUnderTwentyToTwentyFourCongruence) { + const auto nodes = PlanarNodes(); + const auto shell_candidate = fesa::Mitc4Shell::Create( + NodePointers(nodes), Directors(), Section(), Material()); + ASSERT_TRUE(shell_candidate.HasValue()); + const auto stiffness_candidate = shell_candidate.Value().Stiffness(); + ASSERT_TRUE(stiffness_candidate.HasValue()); + const auto& stiffness = stiffness_candidate.Value(); - fesa::Vector globalField{24U}; - for (std::size_t index = 0U; index < globalField.Size(); ++index) { - globalField[index] = 0.125 * static_cast( - static_cast(index % 7U) - 3); - } - const auto physicalField20 = - shellCandidate.Value().physicalTransformation20().Multiply(globalField); - const double localEnergy = quadraticEnergy( - stiffness.physicalLocal20, physicalField20); - const double globalEnergy = quadraticEnergy( - stiffness.physicalGlobal24, globalField); - ASSERT_NE(localEnergy, 0.0); - ASSERT_NE(globalEnergy, 0.0); - EXPECT_LE( - std::abs(globalEnergy - localEnergy) / - (std::abs(globalEnergy) + std::abs(localEnergy)), - 1.0e-12); + fesa::Vector global_field{24U}; + for (std::size_t index = 0U; index < global_field.Size(); ++index) { + global_field[index] = + 0.125 * static_cast(static_cast(index % 7U) - 3); + } + const auto physical_field20 = + shell_candidate.Value().PhysicalTransformation20().Multiply(global_field); + const double local_energy = + QuadraticEnergy(stiffness.physical_local20, physical_field20); + const double global_energy = + QuadraticEnergy(stiffness.physical_global24, global_field); + ASSERT_NE(local_energy, 0.0); + ASSERT_NE(global_energy, 0.0); + EXPECT_LE(std::abs(global_energy - local_energy) / + (std::abs(global_energy) + std::abs(local_energy)), + 1.0e-12); } // MITC4-KERNEL-003 -TEST(Mitc4ShellKernel, RetainsSixRigidModesAndHasExpectedPhysicalAndStabilizedRank) { - const auto nodes = planarNodes(); - const auto shellCandidate = fesa::Mitc4Shell::create( - nodePointers(nodes), directors(), section(), material()); - ASSERT_TRUE(shellCandidate.HasValue()); - const auto stiffnessCandidate = shellCandidate.Value().stiffness(); - ASSERT_TRUE(stiffnessCandidate.HasValue()); - const auto& stiffness = stiffnessCandidate.Value(); +TEST(Mitc4ShellKernel, + RetainsSixRigidModesAndHasExpectedPhysicalAndStabilizedRank) { + const auto nodes = PlanarNodes(); + const auto shell_candidate = fesa::Mitc4Shell::Create( + NodePointers(nodes), Directors(), Section(), Material()); + ASSERT_TRUE(shell_candidate.HasValue()); + const auto stiffness_candidate = shell_candidate.Value().Stiffness(); + ASSERT_TRUE(stiffness_candidate.HasValue()); + const auto& stiffness = stiffness_candidate.Value(); - const auto scaledPhysical20 = - scaledStiffness(stiffness.physicalLocal20, 5U, 2.0); - const auto scaledPhysical24 = - scaledStiffness(stiffness.physicalGlobal24, 6U, 2.0); - const auto scaledStabilized24 = - scaledStiffness(stiffness.stabilizedGlobal24, 6U, 2.0); - EXPECT_EQ(numericalRank(scaledPhysical20), 14U); - EXPECT_EQ(numericalRank(scaledStabilized24), 18U); + const auto scaled_physical20 = + ScaledStiffness(stiffness.physical_local20, 5U, 2.0); + const auto scaled_physical24 = + ScaledStiffness(stiffness.physical_global24, 6U, 2.0); + const auto scaled_stabilized24 = + ScaledStiffness(stiffness.stabilized_global24, 6U, 2.0); + EXPECT_EQ(NumericalRank(scaled_physical20), 14U); + EXPECT_EQ(NumericalRank(scaled_stabilized24), 18U); - const std::array rigidModes{ - physicalRigidMode(nodes, {1.0, 0.0, 0.0}, {}), - physicalRigidMode(nodes, {0.0, 1.0, 0.0}, {}), - physicalRigidMode(nodes, {0.0, 0.0, 1.0}, {}), - physicalRigidMode(nodes, {}, {1.0, 0.0, 0.0}), - physicalRigidMode(nodes, {}, {0.0, 1.0, 0.0}), - physicalRigidMode(nodes, {}, {0.0, 0.0, 1.0})}; - const double physicalNorm = symmetricOperatorNorm(scaledPhysical24); - const double stabilizedNorm = symmetricOperatorNorm(scaledStabilized24); - ASSERT_GT(physicalNorm, 0.0); - ASSERT_GT(stabilizedNorm, 0.0); - for (const auto& rigidMode : rigidModes) { - fesa::Vector scaledMode = rigidMode; - for (std::size_t nodeIndex = 0U; nodeIndex < 4U; ++nodeIndex) { - for (std::size_t component = 0U; component < 3U; ++component) { - scaledMode[6U * nodeIndex + component] /= 2.0; - } - } - const double modeNorm = scaledMode.Norm(); - ASSERT_GT(modeNorm, 0.0); - EXPECT_LE( - scaledPhysical24.Multiply(scaledMode).Norm() / - (physicalNorm * modeNorm), - 1.0e-10); - EXPECT_LE( - scaledStabilized24.Multiply(scaledMode).Norm() / - (stabilizedNorm * modeNorm), - 1.0e-10); + const std::array rigid_modes{ + PhysicalRigidMode(nodes, {1.0, 0.0, 0.0}, {}), + PhysicalRigidMode(nodes, {0.0, 1.0, 0.0}, {}), + PhysicalRigidMode(nodes, {0.0, 0.0, 1.0}, {}), + PhysicalRigidMode(nodes, {}, {1.0, 0.0, 0.0}), + PhysicalRigidMode(nodes, {}, {0.0, 1.0, 0.0}), + PhysicalRigidMode(nodes, {}, {0.0, 0.0, 1.0})}; + const double physical_norm = SymmetricOperatorNorm(scaled_physical24); + const double stabilized_norm = SymmetricOperatorNorm(scaled_stabilized24); + ASSERT_GT(physical_norm, 0.0); + ASSERT_GT(stabilized_norm, 0.0); + for (const auto& rigidMode : rigid_modes) { + fesa::Vector scaledMode = rigidMode; + for (std::size_t node_index = 0U; node_index < 4U; ++node_index) { + for (std::size_t component = 0U; component < 3U; ++component) { + scaledMode[6U * node_index + component] /= 2.0; + } } + const double modeNorm = scaledMode.Norm(); + ASSERT_GT(modeNorm, 0.0); + EXPECT_LE(scaled_physical24.Multiply(scaledMode).Norm() / + (physical_norm * modeNorm), + 1.0e-10); + EXPECT_LE(scaled_stabilized24.Multiply(scaledMode).Norm() / + (stabilized_norm * modeNorm), + 1.0e-10); + } } // MITC4-KERNEL-004 TEST(Mitc4ShellPatch, ReproducesIndependentMembraneBendingShearAndTwistFields) { - const auto nodes = planarNodes(); - const auto shellCandidate = fesa::Mitc4Shell::create( - nodePointers(nodes), directors(), section(), material()); - ASSERT_TRUE(shellCandidate.HasValue()); - const auto& shell = shellCandidate.Value(); - const auto stiffnessCandidate = shell.stiffness(); - ASSERT_TRUE(stiffnessCandidate.HasValue()); - const auto& stiffness = stiffnessCandidate.Value().physicalLocal20; - constexpr double magnitude = 0.2; - const double gauss = 1.0 / std::sqrt(3.0); + const auto nodes = PlanarNodes(); + const auto shell_candidate = fesa::Mitc4Shell::Create( + NodePointers(nodes), Directors(), Section(), Material()); + ASSERT_TRUE(shell_candidate.HasValue()); + const auto& shell = shell_candidate.Value(); + const auto stiffness_candidate = shell.Stiffness(); + ASSERT_TRUE(stiffness_candidate.HasValue()); + const auto& stiffness = stiffness_candidate.Value().physical_local20; + constexpr double kMagnitude = 0.2; + const double gauss = 1.0 / std::sqrt(3.0); - std::array, 4> e11Values{}; - std::array, 4> e22Values{}; - std::array, 4> g12Values{}; - std::array, 4> k11Values{}; - std::array, 4> k22Values{}; - std::array, 4> g13Values{}; - std::array, 4> g23Values{}; - std::array, 4> k12Values{}; - for (std::size_t nodeIndex = 0U; nodeIndex < nodes.size(); ++nodeIndex) { - const double x = nodes[nodeIndex].coordinates[0]; - const double y = nodes[nodeIndex].coordinates[1]; - e11Values[nodeIndex][0] = magnitude * x; - e22Values[nodeIndex][1] = magnitude * y; - g12Values[nodeIndex][0] = 0.5 * magnitude * y; - g12Values[nodeIndex][1] = 0.5 * magnitude * x; - k11Values[nodeIndex][4] = magnitude * x; - k22Values[nodeIndex][3] = -magnitude * y; - g13Values[nodeIndex][2] = magnitude * x; - g23Values[nodeIndex][2] = magnitude * y; - k12Values[nodeIndex][2] = -0.5 * magnitude * x * y; - k12Values[nodeIndex][3] = -0.5 * magnitude * x; - k12Values[nodeIndex][4] = 0.5 * magnitude * y; - } + std::array, 4> e11_values{}; + std::array, 4> e22_values{}; + std::array, 4> g12_values{}; + std::array, 4> k11_values{}; + std::array, 4> k22_values{}; + std::array, 4> g13_values{}; + std::array, 4> g23_values{}; + std::array, 4> k12_values{}; + for (std::size_t node_index = 0U; node_index < nodes.size(); ++node_index) { + const double x = nodes[node_index].coordinates[0]; + const double y = nodes[node_index].coordinates[1]; + e11_values[node_index][0] = kMagnitude * x; + e22_values[node_index][1] = kMagnitude * y; + g12_values[node_index][0] = 0.5 * kMagnitude * y; + g12_values[node_index][1] = 0.5 * kMagnitude * x; + k11_values[node_index][4] = kMagnitude * x; + k22_values[node_index][3] = -kMagnitude * y; + g13_values[node_index][2] = kMagnitude * x; + g23_values[node_index][2] = kMagnitude * y; + k12_values[node_index][2] = -0.5 * kMagnitude * x * y; + k12_values[node_index][3] = -0.5 * kMagnitude * x; + k12_values[node_index][4] = 0.5 * kMagnitude * y; + } - const std::array fields{ - physicalField(e11Values), physicalField(e22Values), - physicalField(g12Values), physicalField(k11Values), - physicalField(k22Values), physicalField(g13Values), - physicalField(g23Values), physicalField(k12Values)}; - expectStrain(shell, fields[0], gauss, -gauss, gauss, {magnitude, 0.0, 0.0, 0.0, 0.0}); - expectStrain(shell, fields[1], gauss, -gauss, gauss, {0.0, magnitude, 0.0, 0.0, 0.0}); - expectStrain(shell, fields[2], gauss, -gauss, gauss, {0.0, 0.0, magnitude, 0.0, 0.0}); - expectStrain(shell, fields[3], gauss, -gauss, gauss, {gauss * magnitude, 0.0, 0.0, 0.0, 0.0}); - expectStrain(shell, fields[4], gauss, -gauss, gauss, {0.0, gauss * magnitude, 0.0, 0.0, 0.0}); - expectStrain(shell, fields[5], gauss, -gauss, gauss, {0.0, 0.0, 0.0, magnitude, 0.0}); - expectStrain(shell, fields[6], gauss, -gauss, gauss, {0.0, 0.0, 0.0, 0.0, magnitude}); - expectStrain(shell, fields[7], gauss, -gauss, gauss, {0.0, 0.0, gauss * magnitude, 0.0, 0.0}); - for (const auto& field : fields) { - EXPECT_GT(quadraticEnergy(stiffness, field), 0.0); - } + const std::array fields{ + PhysicalField(e11_values), PhysicalField(e22_values), + PhysicalField(g12_values), PhysicalField(k11_values), + PhysicalField(k22_values), PhysicalField(g13_values), + PhysicalField(g23_values), PhysicalField(k12_values)}; + ExpectStrain(shell, fields[0], gauss, -gauss, gauss, + {kMagnitude, 0.0, 0.0, 0.0, 0.0}); + ExpectStrain(shell, fields[1], gauss, -gauss, gauss, + {0.0, kMagnitude, 0.0, 0.0, 0.0}); + ExpectStrain(shell, fields[2], gauss, -gauss, gauss, + {0.0, 0.0, kMagnitude, 0.0, 0.0}); + ExpectStrain(shell, fields[3], gauss, -gauss, gauss, + {gauss * kMagnitude, 0.0, 0.0, 0.0, 0.0}); + ExpectStrain(shell, fields[4], gauss, -gauss, gauss, + {0.0, gauss * kMagnitude, 0.0, 0.0, 0.0}); + ExpectStrain(shell, fields[5], gauss, -gauss, gauss, + {0.0, 0.0, 0.0, kMagnitude, 0.0}); + ExpectStrain(shell, fields[6], gauss, -gauss, gauss, + {0.0, 0.0, 0.0, 0.0, kMagnitude}); + ExpectStrain(shell, fields[7], gauss, -gauss, gauss, + {0.0, 0.0, gauss * kMagnitude, 0.0, 0.0}); + for (const auto& field : fields) { + EXPECT_GT(QuadraticEnergy(stiffness, field), 0.0); + } } // MITC4-KERNEL-005 -TEST(Mitc4ShellDrilling, UsesOnlyEightPositivePhysicalRotationDiagonalsAndFixedFactor) { - const std::array nodes{ - node(1, {-50.0, -50.0, 0.0}), node(2, {50.0, -50.0, 0.0}), - node(3, {50.0, 50.0, 0.0}), node(4, {-50.0, 50.0, 0.0})}; - const auto shellCandidate = fesa::Mitc4Shell::create( - nodePointers(nodes), directors(), section(0.1), material()); - ASSERT_TRUE(shellCandidate.HasValue()); - const auto stiffnessCandidate = shellCandidate.Value().stiffness(); - ASSERT_TRUE(stiffnessCandidate.HasValue()); - const auto& stiffness = stiffnessCandidate.Value(); +TEST(Mitc4ShellDrilling, + UsesOnlyEightPositivePhysicalRotationDiagonalsAndFixedFactor) { + const std::array nodes{ + Node(1, {-50.0, -50.0, 0.0}), Node(2, {50.0, -50.0, 0.0}), + Node(3, {50.0, 50.0, 0.0}), Node(4, {-50.0, 50.0, 0.0})}; + const auto shell_candidate = fesa::Mitc4Shell::Create( + NodePointers(nodes), Directors(), Section(0.1), Material()); + ASSERT_TRUE(shell_candidate.HasValue()); + const auto stiffness_candidate = shell_candidate.Value().Stiffness(); + ASSERT_TRUE(stiffness_candidate.HasValue()); + const auto& stiffness = stiffness_candidate.Value(); - double expectedReference = (std::numeric_limits::max)(); - double allDiagonalMinimum = (std::numeric_limits::max)(); - for (std::size_t nodeIndex = 0U; nodeIndex < 4U; ++nodeIndex) { - for (std::size_t rotation = 3U; rotation < 5U; ++rotation) { - const std::size_t index = 5U * nodeIndex + rotation; - const double diagonal = stiffness.physicalLocal20(index, index); - ASSERT_TRUE(std::isfinite(diagonal)); - ASSERT_GT(diagonal, 0.0); - expectedReference = (std::min)(expectedReference, diagonal); - } - for (std::size_t component = 0U; component < 5U; ++component) { - const std::size_t index = 5U * nodeIndex + component; - const double diagonal = stiffness.physicalLocal20(index, index); - if (std::isfinite(diagonal) && diagonal > 0.0) { - allDiagonalMinimum = (std::min)(allDiagonalMinimum, diagonal); - } - } + double expected_reference = (std::numeric_limits::max)(); + double all_diagonal_minimum = (std::numeric_limits::max)(); + for (std::size_t node_index = 0U; node_index < 4U; ++node_index) { + for (std::size_t rotation = 3U; rotation < 5U; ++rotation) { + const std::size_t index = 5U * node_index + rotation; + const double diagonal = stiffness.physical_local20(index, index); + ASSERT_TRUE(std::isfinite(diagonal)); + ASSERT_GT(diagonal, 0.0); + expected_reference = (std::min)(expected_reference, diagonal); } - EXPECT_DOUBLE_EQ(stiffness.drillingStiffness, 1.0e-3 * expectedReference); - EXPECT_LT(allDiagonalMinimum, expectedReference); - EXPECT_NE(stiffness.drillingStiffness, 1.0e-3 * allDiagonalMinimum); + for (std::size_t component = 0U; component < 5U; ++component) { + const std::size_t index = 5U * node_index + component; + const double diagonal = stiffness.physical_local20(index, index); + if (std::isfinite(diagonal) && diagonal > 0.0) { + all_diagonal_minimum = (std::min)(all_diagonal_minimum, diagonal); + } + } + } + EXPECT_DOUBLE_EQ(stiffness.drilling_stiffness, 1.0e-3 * expected_reference); + EXPECT_LT(all_diagonal_minimum, expected_reference); + EXPECT_NE(stiffness.drilling_stiffness, 1.0e-3 * all_diagonal_minimum); } // MITC4-KERNEL-006 -TEST(Mitc4ShellDrilling, FailsNonfiniteReferenceAndStabilizesEachPureDrillCoordinate) { - const auto nodes = planarNodes(); - const auto shellCandidate = fesa::Mitc4Shell::create( - nodePointers(nodes), directors(), section(), material()); - ASSERT_TRUE(shellCandidate.HasValue()); - const auto stiffnessCandidate = shellCandidate.Value().stiffness(); - ASSERT_TRUE(stiffnessCandidate.HasValue()); - const auto& stiffness = stiffnessCandidate.Value(); +TEST(Mitc4ShellDrilling, + FailsNonfiniteReferenceAndStabilizesEachPureDrillCoordinate) { + const auto nodes = PlanarNodes(); + const auto shell_candidate = fesa::Mitc4Shell::Create( + NodePointers(nodes), Directors(), Section(), Material()); + ASSERT_TRUE(shell_candidate.HasValue()); + const auto stiffness_candidate = shell_candidate.Value().Stiffness(); + ASSERT_TRUE(stiffness_candidate.HasValue()); + const auto& stiffness = stiffness_candidate.Value(); - for (std::size_t nodeIndex = 0U; nodeIndex < 4U; ++nodeIndex) { - fesa::Vector pureDrill{24U}; - pureDrill[6U * nodeIndex + 5U] = 1.0; - EXPECT_DOUBLE_EQ( - stiffness.physicalGlobal24.Multiply(pureDrill).Norm(), 0.0); - const auto drillAction = stiffness.drillingGlobal24.Multiply(pureDrill); - EXPECT_DOUBLE_EQ(drillAction[6U * nodeIndex + 5U], stiffness.drillingStiffness); - EXPECT_GT(quadraticEnergy(stiffness.drillingGlobal24, pureDrill), 0.0); - } + for (std::size_t node_index = 0U; node_index < 4U; ++node_index) { + fesa::Vector pure_drill{24U}; + pure_drill[6U * node_index + 5U] = 1.0; + EXPECT_DOUBLE_EQ(stiffness.physical_global24.Multiply(pure_drill).Norm(), + 0.0); + const auto drill_action = stiffness.drilling_global24.Multiply(pure_drill); + EXPECT_DOUBLE_EQ(drill_action[6U * node_index + 5U], + stiffness.drilling_stiffness); + EXPECT_GT(QuadraticEnergy(stiffness.drilling_global24, pure_drill), 0.0); + } - const std::array extremeNodes{ - node(1, {-5.0e9, -5.0e9, 0.0}), node(2, {5.0e9, -5.0e9, 0.0}), - node(3, {5.0e9, 5.0e9, 0.0}), node(4, {-5.0e9, 5.0e9, 0.0})}; - const auto extremeShell = fesa::Mitc4Shell::create( - nodePointers(extremeNodes), directors(), section(1.0), material(1.0e300)); - ASSERT_TRUE(extremeShell.HasValue()); - const auto failure = extremeShell.Value().stiffness(); - ASSERT_FALSE(failure.HasValue()); - ASSERT_EQ(failure.GetStatus().Diagnostics().size(), 1U); - EXPECT_EQ(failure.GetStatus().Diagnostics()[0].code, "invalid-shell-stiffness"); - const auto repeatedFailure = extremeShell.Value().stiffness(); - ASSERT_FALSE(repeatedFailure.HasValue()); - ASSERT_EQ(repeatedFailure.GetStatus().Diagnostics().size(), 1U); - EXPECT_EQ( - repeatedFailure.GetStatus().Diagnostics()[0].code, - failure.GetStatus().Diagnostics()[0].code); - EXPECT_EQ( - repeatedFailure.GetStatus().Diagnostics()[0].message, - failure.GetStatus().Diagnostics()[0].message); + const std::array extreme_nodes{ + Node(1, {-5.0e9, -5.0e9, 0.0}), Node(2, {5.0e9, -5.0e9, 0.0}), + Node(3, {5.0e9, 5.0e9, 0.0}), Node(4, {-5.0e9, 5.0e9, 0.0})}; + const auto extreme_shell = + fesa::Mitc4Shell::Create(NodePointers(extreme_nodes), Directors(), + Section(1.0), Material(1.0e300)); + ASSERT_TRUE(extreme_shell.HasValue()); + const auto failure = extreme_shell.Value().Stiffness(); + ASSERT_FALSE(failure.HasValue()); + ASSERT_EQ(failure.GetStatus().Diagnostics().size(), 1U); + EXPECT_EQ(failure.GetStatus().Diagnostics()[0].code, + "invalid-shell-stiffness"); + const auto repeated_failure = extreme_shell.Value().Stiffness(); + ASSERT_FALSE(repeated_failure.HasValue()); + ASSERT_EQ(repeated_failure.GetStatus().Diagnostics().size(), 1U); + EXPECT_EQ(repeated_failure.GetStatus().Diagnostics()[0].code, + failure.GetStatus().Diagnostics()[0].code); + EXPECT_EQ(repeated_failure.GetStatus().Diagnostics()[0].message, + failure.GetStatus().Diagnostics()[0].message); } // MITC4-KERNEL-007 TEST(Mitc4ShellDrilling, ExcludesPureDrillFromPhysicalRecoveryAndEnergy) { - const auto nodes = planarNodes(); - const auto shellCandidate = fesa::Mitc4Shell::create( - nodePointers(nodes), directors(), section(), material()); - ASSERT_TRUE(shellCandidate.HasValue()); - const auto& shell = shellCandidate.Value(); - const auto stiffnessCandidate = shell.stiffness(); - ASSERT_TRUE(stiffnessCandidate.HasValue()); + const auto nodes = PlanarNodes(); + const auto shell_candidate = fesa::Mitc4Shell::Create( + NodePointers(nodes), Directors(), Section(), Material()); + ASSERT_TRUE(shell_candidate.HasValue()); + const auto& shell = shell_candidate.Value(); + const auto stiffness_candidate = shell.Stiffness(); + ASSERT_TRUE(stiffness_candidate.HasValue()); - for (std::size_t nodeIndex = 0U; nodeIndex < nodes.size(); ++nodeIndex) { - fesa::Vector pureDrill{24U}; - pureDrill[6U * nodeIndex + 5U] = 1.0; - EXPECT_GT( - stiffnessCandidate.Value().stabilizedGlobal24.Multiply(pureDrill).Norm(), - 0.0); + for (std::size_t node_index = 0U; node_index < nodes.size(); ++node_index) { + fesa::Vector pure_drill{24U}; + pure_drill[6U * node_index + 5U] = 1.0; + EXPECT_GT(stiffness_candidate.Value() + .stabilized_global24.Multiply(pure_drill) + .Norm(), + 0.0); - const auto recoveryCandidate = shell.recoverPhysical(pureDrill); - ASSERT_TRUE(recoveryCandidate.HasValue()); - const auto& recovery = recoveryCandidate.Value(); - EXPECT_DOUBLE_EQ(recovery.strainEnergy, 0.0); - for (const auto& point : recovery.points) { - for (double value : point.generalizedStrain) { - EXPECT_DOUBLE_EQ(value, 0.0); - } - for (double value : point.sectionResultant) { - EXPECT_DOUBLE_EQ(value, 0.0); - } - for (const auto& stress : point.inPlaneStress) { - for (double value : stress) { - EXPECT_DOUBLE_EQ(value, 0.0); - } - } + const auto recovery_candidate = shell.RecoverPhysical(pure_drill); + ASSERT_TRUE(recovery_candidate.HasValue()); + const auto& recovery = recovery_candidate.Value(); + EXPECT_DOUBLE_EQ(recovery.strain_energy, 0.0); + for (const auto& point : recovery.points) { + for (double value : point.generalized_strain) { + EXPECT_DOUBLE_EQ(value, 0.0); + } + for (double value : point.section_resultant) { + EXPECT_DOUBLE_EQ(value, 0.0); + } + for (const auto& stress : point.in_plane_stress) { + for (double value : stress) { + EXPECT_DOUBLE_EQ(value, 0.0); } + } } + } } // MITC4-PHYSREC-001 -TEST(Mitc4ShellPhysicalRecovery, RecoversHandFieldAtFixedLocationsAndSectionPositions) { - const auto nodes = planarNodes(); - const auto shellCandidate = fesa::Mitc4Shell::create( - nodePointers(nodes), directors(), section(), material()); - ASSERT_TRUE(shellCandidate.HasValue()); - const auto& shell = shellCandidate.Value(); +TEST(Mitc4ShellPhysicalRecovery, + RecoversHandFieldAtFixedLocationsAndSectionPositions) { + const auto nodes = PlanarNodes(); + const auto shell_candidate = fesa::Mitc4Shell::Create( + NodePointers(nodes), Directors(), Section(), Material()); + ASSERT_TRUE(shell_candidate.HasValue()); + const auto& shell = shell_candidate.Value(); - constexpr std::array generalized{ - 0.1, -0.05, 0.2, 0.3, -0.15, 0.25, 0.4, -0.3}; - fesa::Vector globalField{24U}; - for (std::size_t nodeIndex = 0U; nodeIndex < nodes.size(); ++nodeIndex) { - const double x = nodes[nodeIndex].coordinates[0]; - const double y = nodes[nodeIndex].coordinates[1]; - const std::size_t offset = 6U * nodeIndex; - globalField[offset] = generalized[0] * x + 0.5 * generalized[2] * y; - globalField[offset + 1U] = - generalized[1] * y + 0.5 * generalized[2] * x; - globalField[offset + 2U] = - generalized[6] * x + generalized[7] * y - - 0.5 * generalized[5] * x * y; - globalField[offset + 3U] = - -generalized[4] * y - 0.5 * generalized[5] * x; - globalField[offset + 4U] = - generalized[3] * x + 0.5 * generalized[5] * y; + constexpr std::array kGeneralized{0.1, -0.05, 0.2, 0.3, + -0.15, 0.25, 0.4, -0.3}; + fesa::Vector global_field{24U}; + for (std::size_t node_index = 0U; node_index < nodes.size(); ++node_index) { + const double x = nodes[node_index].coordinates[0]; + const double y = nodes[node_index].coordinates[1]; + const std::size_t offset = 6U * node_index; + global_field[offset] = kGeneralized[0] * x + 0.5 * kGeneralized[2] * y; + global_field[offset + 1U] = kGeneralized[1] * y + 0.5 * kGeneralized[2] * x; + global_field[offset + 2U] = kGeneralized[6] * x + kGeneralized[7] * y - + 0.5 * kGeneralized[5] * x * y; + global_field[offset + 3U] = + -kGeneralized[4] * y - 0.5 * kGeneralized[5] * x; + global_field[offset + 4U] = kGeneralized[3] * x + 0.5 * kGeneralized[5] * y; + } + + const auto recovery_candidate = shell.RecoverPhysical(global_field); + ASSERT_TRUE(recovery_candidate.HasValue()); + const auto& recovery = recovery_candidate.Value(); + const double gauss = 1.0 / std::sqrt(3.0); + const std::array, 4> expected_coordinates{ + std::array{-gauss, -gauss}, + std::array{gauss, -gauss}, std::array{gauss, gauss}, + std::array{-gauss, gauss}}; + constexpr std::array kExpectedResultant{22.4, -6.4, 19.2, 22.4, + -6.4, 8.0, 32.0, -24.0}; + constexpr std::array, 3> kExpectedStress{ + std::array{-22.4, 6.4, -2.4}, + std::array{11.2, -3.2, 9.6}, + std::array{44.8, -12.8, 21.6}}; + + ASSERT_EQ(recovery.points.size(), expected_coordinates.size()); + for (std::size_t point_index = 0U; point_index < recovery.points.size(); + ++point_index) { + const auto& point = recovery.points[point_index]; + EXPECT_EQ(point.natural_coordinates, expected_coordinates[point_index]); + ExpectOrthonormalRightHanded(point.local_frame); + ExpectVectorNear(point.local_frame.e1, {1.0, 0.0, 0.0}); + ExpectVectorNear(point.local_frame.e2, {0.0, 1.0, 0.0}); + ExpectVectorNear(point.local_frame.e3, {0.0, 0.0, 1.0}); + for (std::size_t component = 0U; component < kGeneralized.size(); + ++component) { + EXPECT_NEAR(point.generalized_strain[component], kGeneralized[component], + 1.0e-12); + EXPECT_NEAR(point.section_resultant[component], + kExpectedResultant[component], 1.0e-12); } - - const auto recoveryCandidate = shell.recoverPhysical(globalField); - ASSERT_TRUE(recoveryCandidate.HasValue()); - const auto& recovery = recoveryCandidate.Value(); - const double gauss = 1.0 / std::sqrt(3.0); - const std::array, 4> expectedCoordinates{ - std::array{-gauss, -gauss}, - std::array{gauss, -gauss}, - std::array{gauss, gauss}, - std::array{-gauss, gauss}}; - constexpr std::array expectedResultant{ - 22.4, -6.4, 19.2, 22.4, -6.4, 8.0, 32.0, -24.0}; - constexpr std::array, 3> expectedStress{ - std::array{-22.4, 6.4, -2.4}, - std::array{11.2, -3.2, 9.6}, - std::array{44.8, -12.8, 21.6}}; - - ASSERT_EQ(recovery.points.size(), expectedCoordinates.size()); - for (std::size_t pointIndex = 0U; - pointIndex < recovery.points.size(); ++pointIndex) { - const auto& point = recovery.points[pointIndex]; - EXPECT_EQ(point.naturalCoordinates, expectedCoordinates[pointIndex]); - expectOrthonormalRightHanded(point.localFrame); - expectVectorNear(point.localFrame.e1, {1.0, 0.0, 0.0}); - expectVectorNear(point.localFrame.e2, {0.0, 1.0, 0.0}); - expectVectorNear(point.localFrame.e3, {0.0, 0.0, 1.0}); - for (std::size_t component = 0U; - component < generalized.size(); ++component) { - EXPECT_NEAR( - point.generalizedStrain[component], generalized[component], - 1.0e-12); - EXPECT_NEAR( - point.sectionResultant[component], expectedResultant[component], - 1.0e-12); - } - for (std::size_t position = 0U; - position < expectedStress.size(); ++position) { - for (std::size_t component = 0U; - component < expectedStress[position].size(); ++component) { - EXPECT_NEAR( - point.inPlaneStress[position][component], - expectedStress[position][component], 1.0e-12); - } - } + for (std::size_t position = 0U; position < kExpectedStress.size(); + ++position) { + for (std::size_t component = 0U; + component < kExpectedStress[position].size(); ++component) { + EXPECT_NEAR(point.in_plane_stress[position][component], + kExpectedStress[position][component], 1.0e-12); + } } - EXPECT_NEAR(recovery.strainEnergy, 72.16, 1.0e-12); + } + EXPECT_NEAR(recovery.strain_energy, 72.16, 1.0e-12); } diff --git a/tests/unit/fem/dof_manager_test.cpp b/tests/unit/fem/dof_manager_test.cpp index 75185ab..c1aca0d 100644 --- a/tests/unit/fem/dof_manager_test.cpp +++ b/tests/unit/fem/dof_manager_test.cpp @@ -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{"Beam-1"}, {0U}, {source, 50U}}, {"Ends", std::optional{"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()); diff --git a/tests/unit/io/abaqus/domain_mapper_test.cpp b/tests/unit/io/abaqus/domain_mapper_test.cpp index 4e0f205..9dab454 100644 --- a/tests/unit/io/abaqus/domain_mapper_test.cpp +++ b/tests/unit/io/abaqus/domain_mapper_test.cpp @@ -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{0.0, 1.0, 0.0})); + EXPECT_DOUBLE_EQ(section.torsional_constant, 5.0); + EXPECT_EQ(section.first_axis, (std::array{0.0, 1.0, 0.0})); EXPECT_EQ( - section.sectionPoints, + section.section_points, (std::vector>{{-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{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{2U, 3U})); - ASSERT_EQ(domain.nodeSets().size(), 3U); - EXPECT_EQ(domain.nodeSets()[0].name, "Ends"); - EXPECT_EQ(domain.nodeSets()[0].instanceName, std::optional{"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{"First"}); + EXPECT_EQ(domain.NodeSets()[0].node_indices, (std::vector{0U, 1U})); - EXPECT_EQ(domain.nodeSets()[1].instanceName, std::optional{"Second"}); - EXPECT_EQ(domain.nodeSets()[1].nodeIndices, + EXPECT_EQ(domain.NodeSets()[1].instance_name, std::optional{"Second"}); + EXPECT_EQ(domain.NodeSets()[1].node_indices, (std::vector{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{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{"First"}); - EXPECT_EQ(domain.elementSets()[0].elementIndices, + EXPECT_EQ(domain.ElementSets()[0].element_indices, (std::vector{0U})); - EXPECT_EQ(domain.elementSets()[1].instanceName, + EXPECT_EQ(domain.ElementSets()[1].instance_name, std::optional{"Second"}); - EXPECT_EQ(domain.elementSets()[1].elementIndices, + EXPECT_EQ(domain.ElementSets()[1].element_indices, (std::vector{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{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{0U})); + ASSERT_NE(rootNodeSet, assemblyDomain.NodeSets().end()); + EXPECT_EQ(rootNodeSet->node_indices, (std::vector{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{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{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{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{0.0, 0.0, 1.0})); EXPECT_EQ( - domain.shellNodeInitialFrames()[0].tangentA, + domain.ShellNodeInitialFrames()[0].tangent_a, (std::array{1.0, 0.0, 0.0})); EXPECT_EQ( - domain.shellNodeInitialFrames()[0].tangentB, + domain.ShellNodeInitialFrames()[0].tangent_b, (std::array{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; diff --git a/tests/unit/io/hdf5/hdf5_results_writer_test.cpp b/tests/unit/io/hdf5/hdf5_results_writer_test.cpp index ba6e989..78cc04a 100644 --- a/tests/unit/io/hdf5/hdf5_results_writer_test.cpp +++ b/tests/unit/io/hdf5/hdf5_results_writer_test.cpp @@ -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 @@ -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(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(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 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."}; diff --git a/tests/unit/model/domain_test.cpp b/tests/unit/model/domain_test.cpp index 456551d..2aa6f1b 100644 --- a/tests/unit/model/domain_test.cpp +++ b/tests/unit/model/domain_test.cpp @@ -1,4 +1,4 @@ -#include "fesa/model/domain.hpp" +#include "fesa/model/domain.h" #include @@ -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().nodes()), - const std::vector&>); - static_assert(std::is_same_v< - decltype(std::declval().elements()), - const std::vector&>); + const fesa::Domain& domain = result.Value(); + const fesa::Node* const first_node_address = domain.Nodes().data(); + static_assert( + std::is_same_v().Nodes()), + const std::vector&>); + static_assert( + std::is_same_v().Elements()), + const std::vector&>); - 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().shellElements()), - const std::vector&>); - static_assert(std::is_same_v< - decltype(std::declval().shellSections()), - const std::vector&>); - static_assert(std::is_same_v< - decltype(std::declval().shellNodeInitialFrames()), - const std::vector&>); + const fesa::Domain& domain = result.Value(); + static_assert(std::is_same_v< + decltype(std::declval().ShellElements()), + const std::vector&>); + static_assert(std::is_same_v< + decltype(std::declval().ShellSections()), + const std::vector&>); + static_assert( + std::is_same_v() + .ShellNodeInitialFrames()), + const std::vector&>); - 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{0.0, 0.0, 1.0})); - EXPECT_EQ( - domain.shellNodeInitialFrames()[0].tangentA, - (std::array{1.0, 0.0, 0.0})); - EXPECT_EQ( - domain.shellNodeInitialFrames()[0].tangentB, - (std::array{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{0.0, 0.0, 1.0})); + EXPECT_EQ(domain.ShellNodeInitialFrames()[0].tangent_a, + (std::array{1.0, 0.0, 0.0})); + EXPECT_EQ(domain.ShellNodeInitialFrames()[0].tangent_b, + (std::array{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()); } diff --git a/tests/unit/model/model_types_test.cpp b/tests/unit/model/model_types_test.cpp index 9d3480d..81c2430 100644 --- a/tests/unit/model/model_types_test.cpp +++ b/tests/unit/model/model_types_test.cpp @@ -1,4 +1,4 @@ -#include "fesa/model/model_types.hpp" +#include "fesa/model/model_types.h" #include @@ -14,174 +14,150 @@ namespace { -template +template struct HasEquationId : std::false_type {}; -template +template struct HasEquationId().equationId)>> : std::true_type {}; -template +template struct HasEquationIds : std::false_type {}; -template +template struct HasEquationIds().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::value); - static_assert(!HasEquationIds::value); - static_assert(!HasEquationId::value); - static_assert(!HasEquationIds::value); + static_assert(!HasEquationId::value); + static_assert(!HasEquationIds::value); + static_assert(!HasEquationId::value); + static_assert(!HasEquationIds::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{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{3U, 5U, 8U, 13U})); + EXPECT_EQ(s4_element.material_index, 2U); + EXPECT_EQ(s4_element.section_index, 7U); - static_assert(!HasEquationId::value); - static_assert(!HasEquationIds::value); + static_assert(!HasEquationId::value); + static_assert(!HasEquationIds::value); } diff --git a/tests/unit/model/shell_geometry_test.cpp b/tests/unit/model/shell_geometry_test.cpp index 3839666..bd563b6 100644 --- a/tests/unit/model/shell_geometry_test.cpp +++ b/tests/unit/model/shell_geometry_test.cpp @@ -1,4 +1,4 @@ -#include "fesa/model/shell_geometry.hpp" +#include "fesa/model/shell_geometry.h" #include @@ -14,263 +14,242 @@ namespace { using Vector3 = std::array; -fesa::Node node(fesa::EntityIndex label, Vector3 coordinates) { - return { - {"Shell-Instance", static_cast(label), - std::to_string(label)}, - coordinates, - {"shell-geometry.inp", static_cast(label + 1U)}}; +fesa::Node Node(fesa::EntityIndex label, Vector3 coordinates) { + return {{"Shell-Instance", static_cast(label), + std::to_string(label)}, + coordinates, + {"shell-geometry.inp", static_cast(label + 1U)}}; } -fesa::Mitc4ShellDefinition element( - fesa::EntityIndex label, - std::array nodeIndices) { - return { - {"Shell-Instance", static_cast(label), - std::to_string(label)}, - fesa::ShellSourceElementType::s4, - nodeIndices, - 0U, - 0U, - {"shell-geometry.inp", static_cast(100U + label)}}; +fesa::Mitc4ShellDefinition Element( + fesa::EntityIndex label, std::array node_indices) { + return {{"Shell-Instance", static_cast(label), + std::to_string(label)}, + fesa::ShellSourceElementType::kS4, + node_indices, + 0U, + 0U, + {"shell-geometry.inp", static_cast(100U + label)}}; } -std::vector sections(double thickness = 0.2) { - return {{"Section", thickness, 0U, {"shell-geometry.inp", 90U}}}; +std::vector 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& 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& 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 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 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 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 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 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 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 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 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::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::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 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 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})); } diff --git a/tests/unit/results/result_records_test.cpp b/tests/unit/results/result_records_test.cpp index 31e1d08..b200580 100644 --- a/tests/unit/results/result_records_test.cpp +++ b/tests/unit/results/result_records_test.cpp @@ -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()); diff --git a/tests/unit/results/result_recovery_test.cpp b/tests/unit/results/result_recovery_test.cpp index be14400..6e55c9f 100644 --- a/tests/unit/results/result_recovery_test.cpp +++ b/tests/unit/results/result_recovery_test.cpp @@ -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 @@ -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 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(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(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 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; } diff --git a/tests/unit/results/results_writer_test.cpp b/tests/unit/results/results_writer_test.cpp index e89422e..ffb3e13 100644 --- a/tests/unit/results/results_writer_test.cpp +++ b/tests/unit/results/results_writer_test.cpp @@ -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 #include